test: rework how udev-test is invoked

As part of the build, we would populate build/test/sys/ using
sys-script.py, and then udev-test.p[ly] would create a tmpfs instance
on build/test/tmpfs and copy the sys tree to build/test/tmpfs/sys.

Also, we had udev-test.p[ly] which called test-udev. test-udev was
marked as a manual test and installed, but neither udev-test.p[ly] or
sys-script.py were.

test-udev is renamed to udev-rule-runner, which reduces confusion and
frees up the test-udev name. udev-test.py is renamed to test-udev.py.
All three files are now installed.

test-udev.py is modified to internally call sys-script.py to set up the
sys tree. Copying and creating it from scratch should take the same
amount of time. We avoid having a magic directory, everything is now
done underneath a temporary directory.

test-udev.py is now a normal installed test, and run-unit-tests.py will
pick it up. When test-udev.py is invoked from meson, the path to
udev-rule-runner is passed via envvar; when it is invoked via
run-unit-tests.py or directly, it looks for udev-rule-runner in a relative
path.

The goal of this whole change is to let Debian drop the 'udev' test.
It called sys-script.py and udev-test.pl from the source directory and
had to recreate a bunch of the logic. Now test-udev.py will now be called
via 'upstream'.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2023-05-05 14:46:34 +02:00
parent 09ea351b6f
commit 0454cf05d3
5 changed files with 71 additions and 37 deletions

View file

@ -4415,6 +4415,7 @@ foreach test : simple_tests
tests += { 'sources' : [test] }
endforeach
TESTS = {}
foreach test : tests
sources = test.get('sources')
condition = test.get('condition', '')
@ -4466,6 +4467,8 @@ foreach test : tests
suite : suite,
is_parallel : test.get('parallel', true))
endif
TESTS += { name : exe }
endforeach
exe = executable(
@ -4527,6 +4530,16 @@ if want_tests != 'false' and static_libudev_pic
test('test-libudev-static-sym', exe)
endif
if want_tests != 'false'
udev_rule_runner = TESTS['udev-rule-runner'].full_path()
test('test-udev',
test_udev_py,
args : ['-v'],
env : ['UDEV_RULE_RUNNER=@0@'.format(udev_rule_runner)],
timeout : 180)
endif
############################################################
foreach fuzzer : simple_fuzzers

View file

@ -409,7 +409,7 @@ tests += [
'timeout' : 120,
},
{
'sources' : files('test-udev.c'),
'sources' : files('udev-rule-runner.c'),
'link_with' : [
libshared,
libudevd_core,

View file

@ -62,11 +62,11 @@ static int fake_filesystems(void) {
const char *error;
bool ignore_mount_error;
} fakefss[] = {
{ "test/tmpfs/sys", "/sys", "Failed to mount test /sys", false },
{ "test/tmpfs/dev", "/dev", "Failed to mount test /dev", false },
{ "test/run", "/run", "Failed to mount test /run", false },
{ "test/run", "/etc/udev/rules.d", "Failed to mount empty /etc/udev/rules.d", true },
{ "test/run", UDEVLIBEXECDIR "/rules.d", "Failed to mount empty " UDEVLIBEXECDIR "/rules.d", true },
{ "tmpfs/sys", "/sys", "Failed to mount test /sys", false },
{ "tmpfs/dev", "/dev", "Failed to mount test /dev", false },
{ "run", "/run", "Failed to mount test /run", false },
{ "run", "/etc/udev/rules.d", "Failed to mount empty /etc/udev/rules.d", true },
{ "run", UDEVLIBEXECDIR "/rules.d", "Failed to mount empty " UDEVLIBEXECDIR "/rules.d", true },
};
int r;

View file

@ -123,19 +123,14 @@ endif
############################################################
# prepare test/sys tree
sys_script_py = find_program('sys-script.py')
custom_target(
'sys',
command : [sys_script_py, meson.current_build_dir()],
output : 'sys',
build_by_default : want_tests != 'false')
sys_script_py = files('sys-script.py')
test_udev_py = files('test-udev.py')
if want_tests != 'false'
test('udev-test',
files('udev-test.py'),
args : ['-v'],
timeout : 180)
if install_tests
install_data(
sys_script_py,
test_udev_py,
install_dir : unittestsdir)
endif
############################################################

View file

@ -4,6 +4,7 @@
# pylint: disable=missing-docstring,redefined-outer-name,invalid-name
# pylint: disable=unspecified-encoding,no-else-return,line-too-long,too-many-lines
# pylint: disable=multiple-imports,too-many-instance-attributes,consider-using-with
# pylint: disable=global-statement
# udev test
#
@ -26,9 +27,10 @@ import re
import stat
import subprocess
import sys
import tempfile
import textwrap
from pathlib import Path
from typing import Optional
from typing import Callable, Optional
try:
import pytest
@ -37,16 +39,15 @@ except ImportError as e:
sys.exit(77)
# TODO: pass path to test-udev from outside
UDEV_BIN = './test-udev'
UDEV_RUN = Path('test/run')
UDEV_RULES_DIR = UDEV_RUN / 'udev/rules.d'
UDEV_RULES = UDEV_RULES_DIR / 'udev-test.rules'
SYS_SCRIPT = Path(__file__).with_name('sys-script.py')
try:
UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER'])
except KeyError:
UDEV_BIN = Path(__file__).parent / 'manual/udev-rule-runner'
UDEV_BIN = UDEV_BIN.absolute()
UDEV_RUN = Path('test/run')
UDEV_TMPFS = Path('test/tmpfs')
UDEV_DEV = UDEV_TMPFS / 'dev'
UDEV_SYS = UDEV_TMPFS / 'sys'
# Those will be set by the udev_setup() fixture
UDEV_RUN = UDEV_RULES = UDEV_DEV = UDEV_SYS = None
# Relax sd-device's sysfs verification, since we want to provide a fake sysfs
# here that actually is a tmpfs.
@ -178,14 +179,23 @@ class Rules:
desc: str
devices: list[Device]
rules: str
device_generator: Callable = None
repeat: int = 1
delay: Optional[int] = None
@classmethod
def new(cls, desc: str, *devices, rules = None, **kwargs):
def new(cls, desc: str, *devices, rules=None, device_generator=None, **kwargs):
assert rules.startswith('\n')
rules = textwrap.dedent(rules[1:]) if rules else ''
return cls(desc, devices, rules, **kwargs)
assert bool(devices) ^ bool(device_generator)
return cls(desc, devices, rules, device_generator=device_generator, **kwargs)
def generate_devices(self) -> None:
# We can't do this when the class is created, because setup is done later.
if self.device_generator:
self.devices = self.device_generator()
def create_rules_file(self) -> None:
# create temporary rules
@ -2298,7 +2308,8 @@ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \"printf %%s 'foo1 foo2' | grep 'foo1 f
Rules.new(
'all_block_devs',
*all_block_devs(lambda name: (["blockdev"], None) if name.endswith('/sda6') else (None, None)),
device_generator = lambda: \
all_block_devs(lambda name: (["blockdev"], None) if name.endswith('/sda6') else (None, None)),
repeat = 10,
rules = r"""
SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sd*", SYMLINK+="blockdev"
@ -2344,15 +2355,27 @@ def udev_setup():
if issue:
pytest.skip(issue)
subprocess.run(['umount', UDEV_TMPFS],
global UDEV_RUN, UDEV_RULES, UDEV_DEV, UDEV_SYS
_tmpdir = tempfile.TemporaryDirectory()
tmpdir = Path(_tmpdir.name)
UDEV_RUN = tmpdir / 'run'
UDEV_RULES = UDEV_RUN / 'udev-test.rules'
udev_tmpfs = tmpdir / 'tmpfs'
UDEV_DEV = udev_tmpfs / 'dev'
UDEV_SYS = udev_tmpfs / 'sys'
subprocess.run(['umount', udev_tmpfs],
stderr=subprocess.DEVNULL,
check=False)
UDEV_TMPFS.mkdir(exist_ok=True, parents=True)
udev_tmpfs.mkdir(exist_ok=True, parents=True)
subprocess.check_call(['mount', '-v',
'-t', 'tmpfs',
'-o', 'rw,mode=0755,nosuid,noexec',
'tmpfs', UDEV_TMPFS])
'tmpfs', udev_tmpfs])
UDEV_DEV.mkdir(exist_ok=True)
# setting group and mode of udev_dev ensures the tests work
@ -2367,10 +2390,12 @@ def udev_setup():
os.mknod(sda, 0o600 | stat.S_IFBLK, os.makedev(8, 0))
sda.unlink()
subprocess.check_call(['cp', '-r', 'test/sys/', UDEV_SYS])
subprocess.check_call([SYS_SCRIPT, UDEV_SYS.parent])
subprocess.check_call(['rm', '-rf', UDEV_RUN])
UDEV_RUN.mkdir(parents=True)
os.chdir(tmpdir)
if subprocess.run([UDEV_BIN, 'check'],
check=False).returncode != 0:
pytest.skip(f'{UDEV_BIN} failed to set up the environment, skipping the test',
@ -2379,8 +2404,8 @@ def udev_setup():
yield
subprocess.check_call(['rm', '-rf', UDEV_RUN])
subprocess.check_call(['umount', '-v', UDEV_TMPFS])
UDEV_TMPFS.rmdir()
subprocess.check_call(['umount', '-v', udev_tmpfs])
udev_tmpfs.rmdir()
@pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES))
@ -2388,6 +2413,7 @@ def test_udev(rules: Rules, udev_setup):
assert udev_setup is None
rules.create_rules_file()
rules.generate_devices()
for _ in range(rules.repeat):
fork_and_run_udev('add', rules)