]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test: Add mkosi-based integration test runner 32121/head
authorRichard Maw <richard.maw@codethink.co.uk>
Fri, 5 Apr 2024 16:19:59 +0000 (17:19 +0100)
committerRichard Maw <richard.maw@codethink.co.uk>
Thu, 18 Apr 2024 15:26:38 +0000 (16:26 +0100)
The first two tests are included to ensure parallel test execution is
demonstrable.

meson.build
meson_options.txt
mkosi.conf
mkosi.images/system/mkosi.conf
mkosi.images/system/mkosi.extra/usr/lib/systemd/system/user@.service.d/99-SYSTEMD_UNIT_PATH.conf [new file with mode: 0644]
test/README.testsuite
test/TEST-02-UNITTESTS/meson.build [new file with mode: 0644]
test/integration_test_wrapper.py [new file with mode: 0755]
test/meson.build

index 1a160960cc6f4d2c5d1cb00908fc0bbd7a7bcc0f..e2de14809521a4f6db30ce3b5669d6e0b1754f51 100644 (file)
@@ -2573,6 +2573,18 @@ endif
 
 #####################################################################
 
+if get_option('integration-tests') != false
+        system_mkosi = custom_target('system_mkosi',
+                build_always_stale : true,
+                output : 'system',
+                console : true,
+                command : ['mkosi', '-C', meson.project_source_root(), '--image=system', '--format=disk', '--output-dir', meson.project_build_root() / '@OUTPUT@', '--without-tests', '-fi', 'build'],
+                depends : [executables_by_name['bootctl'], executables_by_name['systemd-measure'], executables_by_name['systemd-repart'], ukify],
+        )
+endif
+
+############################################################
+
 subdir('rules.d')
 subdir('test')
 
index 41a524b0dcb5fd2552d0e555fb93a6ee6eed07b4..d52ca4e4b5629c617eff89d98dcc2196cc4d58fb 100644 (file)
@@ -498,6 +498,8 @@ option('install-tests', type : 'boolean', value : false,
        description : 'install test executables')
 option('log-message-verification', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
        description : 'do fake printf() calls to verify format strings')
+option('integration-tests', type : 'boolean', value : false,
+       description : 'run the integration tests')
 
 option('ok-color', type : 'combo',
        choices : ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
index 1cc7a51d961364816c9e1279871efbe6e02cdb73..b9c928b027b8d22c2c5995febf02ef0f2d32ebc7 100644 (file)
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
 [Config]
-Images=system
+@Images=system
 MinimumVersion=23~devel
 
 [Output]
@@ -21,6 +21,7 @@ BuildSourcesEphemeral=yes
 @Incremental=yes
 @RuntimeSize=8G
 @RuntimeBuildSources=yes
+@QemuSmp=2
 ToolsTreePackages=virtiofsd
 KernelCommandLineExtra=systemd.crash_shell
                        systemd.log_level=debug,console:info
index ed09d841b843bf4af049571d6a3b9dee24c35253..90f302e44d0e4375db7c63972d8b0800d87ad31c 100644 (file)
@@ -5,6 +5,9 @@
 
 [Content]
 Autologin=yes
+ExtraTrees=
+        %D/mkosi.crt:/usr/lib/verity.d/mkosi.crt # sysext verification key
+
 Packages=
         acl
         bash-completion
diff --git a/mkosi.images/system/mkosi.extra/usr/lib/systemd/system/user@.service.d/99-SYSTEMD_UNIT_PATH.conf b/mkosi.images/system/mkosi.extra/usr/lib/systemd/system/user@.service.d/99-SYSTEMD_UNIT_PATH.conf
new file mode 100644 (file)
index 0000000..fa63493
--- /dev/null
@@ -0,0 +1,2 @@
+[Service]
+PassEnvironment=SYSTEMD_UNIT_PATH
index 0c04e2d4a6a40549c5d0d8dc2975f267438c2f57..44e59ea951dbfc398087caef6e32b510ef3b7b0a 100644 (file)
@@ -28,6 +28,22 @@ To run just one of the cases:
 
 $ sudo make -C test/TEST-01-BASIC clean setup run
 
+To run the meson-based integration test config
+enable integration tests and options for required commands with the following:
+
+$ meson configure build -Dintegration-tests=true -Dremote=enabled -Dopenssl=enabled -Dblkid=enabled -Dtpm2=enabled
+
+Once enabled the integration tests can be run with:
+
+$ sudo meson test -C build/ --suite integration-tests --num-processes "$((nproc / 2))"
+
+As usual, specific tests can be run in meson by appending the name of the test
+which is usually the name of the directory e.g.
+
+$ sudo meson test -C build/ --suite integration-tests --num-processes "$((nproc / 2))" TEST-01-BASIC
+
+See `meson introspect build --tests` for a list of tests.
+
 Specifying the build directory
 ==============================
 
diff --git a/test/TEST-02-UNITTESTS/meson.build b/test/TEST-02-UNITTESTS/meson.build
new file mode 100644 (file)
index 0000000..6bc0483
--- /dev/null
@@ -0,0 +1,13 @@
+test_params += {
+        'mkosi_args': test_params['mkosi_args'] + [
+                '--kernel-command-line-extra=' + ' '.join([
+                        '''
+frobnicate!
+
+systemd.setenv=TEST_CMDLINE_NEWLINE=foo
+systemd.setenv=TEST_CMDLINE_NEWLINE=bar
+
+''',
+                ]),
+        ],
+}
diff --git a/test/integration_test_wrapper.py b/test/integration_test_wrapper.py
new file mode 100755 (executable)
index 0000000..138b6af
--- /dev/null
@@ -0,0 +1,134 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+'''Test wrapper command for driving integration tests.
+
+Note: This is deliberately rough and only intended to drive existing tests
+with the expectation that as part of formally defining the API it will be tidy.
+
+'''
+
+import argparse
+import logging
+import os
+from pathlib import Path
+import shlex
+import subprocess
+
+
+TEST_EXIT_DROPIN = """\
+[Unit]
+SuccessAction=exit
+FailureAction=exit
+"""
+
+
+EMERGENCY_EXIT_DROPIN = """\
+[Unit]
+Wants=emergency-exit.service
+"""
+
+
+EMERGENCY_EXIT_SERVICE = """\
+[Unit]
+DefaultDependencies=no
+Conflicts=shutdown.target
+Conflicts=rescue.service
+Before=shutdown.target
+Before=rescue.service
+FailureAction=exit
+
+[Service]
+ExecStart=false
+"""
+
+
+parser = argparse.ArgumentParser(description=__doc__)
+parser.add_argument('--test-name', required=True)
+parser.add_argument('--mkosi-image-name', required=True)
+parser.add_argument('--mkosi-output-path', required=True, type=Path)
+parser.add_argument('--test-number', required=True)
+parser.add_argument('--no-emergency-exit',
+                    dest='emergency_exit', default=True, action='store_false',
+                    help="Disable emergency exit drop-ins for interactive debugging")
+parser.add_argument('mkosi_args', nargs="*")
+
+def main():
+    logging.basicConfig(level=logging.DEBUG)
+    args = parser.parse_args()
+
+    test_unit_name = f"testsuite-{args.test_number}.service"
+    # Machine names shouldn't have / since it's used as a file name
+    # and it must be a valid hostname so 64 chars max
+    machine_name = args.test_name.replace('/', '_')[:64]
+
+    logging.debug(f"test name: {args.test_name}\n"
+                  f"test number: {args.test_number}\n"
+                  f"image: {args.mkosi_image_name}\n"
+                  f"mkosi output path: {args.mkosi_output_path}\n"
+                  f"mkosi args: {args.mkosi_args}\n"
+                  f"emergency exit: {args.emergency_exit}")
+
+    journal_file = Path(f"{machine_name}.journal").absolute()
+    logging.info(f"Capturing journal to {journal_file}")
+
+    mkosi_args = [
+        'mkosi',
+        '--directory', Path('..').resolve(),
+        '--output-dir', args.mkosi_output_path.absolute(),
+        '--machine', machine_name,
+        '--image', args.mkosi_image_name,
+        '--format=disk',
+        '--runtime-build-sources=no',
+        '--ephemeral',
+        '--forward-journal', journal_file,
+        *(
+            [
+                '--credential',
+                f"systemd.extra-unit.emergency-exit.service={shlex.quote(EMERGENCY_EXIT_SERVICE)} "
+                f"systemd.unit-dropin.emergency.target={shlex.quote(EMERGENCY_EXIT_DROPIN)}",
+            ]
+            if args.emergency_exit
+            else []
+        ),
+        f"--credential=systemd.unit-dropin.{test_unit_name}={shlex.quote(TEST_EXIT_DROPIN)}",
+        '--append',
+        '--kernel-command-line-extra',
+        ' '.join([
+            'systemd.hostname=H',
+            f"SYSTEMD_UNIT_PATH=/usr/lib/systemd/tests/testdata/testsuite-{args.test_number}.units:/usr/lib/systemd/tests/testdata/units:",
+            'systemd.unit=testsuite.target',
+            f"systemd.wants={test_unit_name}",
+        ]),
+        *args.mkosi_args,
+    ]
+
+    mkosi_args += ['qemu']
+
+    logging.debug(f"Running {shlex.join(os.fspath(a) for a in mkosi_args)}")
+
+    try:
+        subprocess.run(mkosi_args, check=True)
+    except subprocess.CalledProcessError as e:
+        if e.returncode not in (0, 77):
+            suggested_command = [
+                'journalctl',
+                '--all',
+                '--no-hostname',
+                '-o', 'short-monotonic',
+                '--file', journal_file,
+                f"_SYSTEMD_UNIT={test_unit_name}",
+                '+', f"SYSLOG_IDENTIFIER=testsuite-{args.test_number}.sh",
+                '+', 'PRIORITY=4',
+                '+', 'PRIORITY=3',
+                '+', 'PRIORITY=2',
+                '+', 'PRIORITY=1',
+                '+', 'PRIORITY=0',
+            ]
+            logging.info("Test failed, relevant logs can be viewed with: "
+                         f"{shlex.join(os.fspath(a) for a in suggested_command)}")
+        exit(e.returncode)
+
+
+if __name__ == '__main__':
+    main()
index aadf37a139ea610050bdb9dc1b437782d5c513cf..4009eeece2caed383020f22d09343eb9d3430a1b 100644 (file)
@@ -331,3 +331,44 @@ if want_tests != 'false' and conf.get('ENABLE_KERNEL_INSTALL') == 1
              depends : deps,
              suite : 'kernel-install')
 endif
+
+############################################################
+
+if get_option('integration-tests') != false
+        integration_test_wrapper = find_program('integration_test_wrapper.py')
+        integration_tests = {
+                '01': 'TEST-01-BASIC',
+                '02': 'TEST-02-UNITTESTS',
+        }
+        foreach test_number, dirname : integration_tests
+                test_unit_name = f'testsuite-@test_number@.service'
+                test_params = {
+                        'test_name' : dirname,
+                        'mkosi_image_name' : 'system',
+                        'mkosi_output_path' : system_mkosi,
+                        'test_number' : test_number,
+                        'mkosi_args' : [],
+                        'depends' : [system_mkosi],
+                        'timeout' : 600,
+                }
+
+                # TODO: This fs.exists call isn't included in rebuild logic
+                # so if you add a new meson.build in a subdir
+                # you need to touch another build file to get it to reparse.
+                if fs.exists(dirname / 'meson.build')
+                        subdir(dirname)
+                endif
+                args = ['--test-name', test_params['test_name'],
+                        '--mkosi-image-name', test_params['mkosi_image_name'],
+                        '--mkosi-output-path', test_params['mkosi_output_path'],
+                        '--test-number', test_params['test_number']]
+                args += ['--'] + test_params['mkosi_args']
+                test(test_params['test_name'],
+                     integration_test_wrapper,
+                     env: test_env,
+                     args : args,
+                     depends : test_params['depends'],
+                     timeout : test_params['timeout'],
+                     suite : 'integration-tests')
+        endforeach
+endif