From 0454cf05d38d289474ca65c1917d414b2958f6b5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 14:46:34 +0200 Subject: [PATCH] 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'. --- meson.build | 13 ++++ src/test/meson.build | 2 +- src/test/{test-udev.c => udev-rule-runner.c} | 10 +-- test/meson.build | 19 +++--- test/{udev-test.py => test-udev.py} | 64 ++++++++++++++------ 5 files changed, 71 insertions(+), 37 deletions(-) rename src/test/{test-udev.c => udev-rule-runner.c} (89%) rename test/{udev-test.py => test-udev.py} (98%) diff --git a/meson.build b/meson.build index 224e6251d2..f6c7279fd5 100644 --- a/meson.build +++ b/meson.build @@ -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 diff --git a/src/test/meson.build b/src/test/meson.build index 8a5e47f004..8e76df624d 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -409,7 +409,7 @@ tests += [ 'timeout' : 120, }, { - 'sources' : files('test-udev.c'), + 'sources' : files('udev-rule-runner.c'), 'link_with' : [ libshared, libudevd_core, diff --git a/src/test/test-udev.c b/src/test/udev-rule-runner.c similarity index 89% rename from src/test/test-udev.c rename to src/test/udev-rule-runner.c index 00ca79d0eb..0b5938802a 100644 --- a/src/test/test-udev.c +++ b/src/test/udev-rule-runner.c @@ -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; diff --git a/test/meson.build b/test/meson.build index 8d6667b405..f53971416e 100644 --- a/test/meson.build +++ b/test/meson.build @@ -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 ############################################################ diff --git a/test/udev-test.py b/test/test-udev.py similarity index 98% rename from test/udev-test.py rename to test/test-udev.py index 5ddda3865f..86937205de 100755 --- a/test/udev-test.py +++ b/test/test-udev.py @@ -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) -- 2.25.1