2024-04-19 10:17:58 +03:00
#!/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
2024-05-07 13:24:51 +03:00
import json
2024-04-19 10:17:58 +03:00
import os
import shlex
import subprocess
import sys
import textwrap
from pathlib import Path
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
"""
def main ( ) :
2024-04-24 22:18:27 +03:00
if not bool ( int ( os . getenv ( " SYSTEMD_INTEGRATION_TESTS " , " 0 " ) ) ) :
print ( " SYSTEMD_INTEGRATION_TESTS=1 not found in environment, skipping " , file = sys . stderr )
exit ( 77 )
2024-04-19 10:17:58 +03:00
parser = argparse . ArgumentParser ( description = __doc__ )
parser . add_argument ( ' --meson-source-dir ' , required = True , type = Path )
parser . add_argument ( ' --meson-build-dir ' , required = True , type = Path )
parser . add_argument ( ' --test-name ' , required = True )
parser . add_argument ( ' --test-number ' , required = True )
2024-04-30 20:06:00 +03:00
parser . add_argument ( ' --storage ' , required = True )
2024-05-05 19:14:44 +03:00
parser . add_argument ( ' --firmware ' , required = True )
2024-04-19 10:17:58 +03:00
parser . add_argument ( ' mkosi_args ' , nargs = " * " )
args = parser . parse_args ( )
2024-05-03 11:57:50 +03:00
name = args . test_name + ( f " - { i } " if ( i := os . getenv ( " MESON_TEST_ITERATION " ) ) else " " )
2024-04-19 10:17:58 +03:00
test_unit = f " testsuite- { args . test_number } .service "
dropin = textwrap . dedent (
""" \
[ Unit ]
After = multi - user . target network . target
Requires = multi - user . target
2024-05-05 12:38:09 +03:00
SuccessAction = exit
SuccessActionExitStatus = 123
2024-04-19 10:17:58 +03:00
[ Service ]
StandardOutput = journal + console
"""
)
2024-05-03 11:27:58 +03:00
if os . getenv ( " TEST_MATCH_SUBTEST " ) :
dropin + = textwrap . dedent (
f """
[ Service ]
Environment = TEST_MATCH_SUBTEST = { os . environ [ " TEST_MATCH_SUBTEST " ] }
"""
)
if os . getenv ( " TEST_MATCH_TESTCASE " ) :
dropin + = textwrap . dedent (
f """
[ Service ]
Environment = TEST_MATCH_TESTCASE = { os . environ [ " TEST_MATCH_TESTCASE " ] }
"""
)
2024-04-19 10:17:58 +03:00
if not sys . stderr . isatty ( ) :
dropin + = textwrap . dedent (
"""
[ Unit ]
FailureAction = exit
"""
)
2024-05-03 11:57:50 +03:00
journal_file = ( args . meson_build_dir / ( f " test/journal/ { name } .journal " ) ) . absolute ( )
2024-04-19 10:17:58 +03:00
journal_file . unlink ( missing_ok = True )
else :
journal_file = None
cmd = [
' mkosi ' ,
' --directory ' , os . fspath ( args . meson_source_dir ) ,
' --output-dir ' , os . fspath ( args . meson_build_dir / ' mkosi.output ' ) ,
' --extra-search-path ' , os . fspath ( args . meson_build_dir ) ,
2024-05-03 11:57:50 +03:00
' --machine ' , name ,
2024-04-19 10:17:58 +03:00
' --ephemeral ' ,
* ( [ ' --forward-journal ' , journal_file ] if journal_file else [ ] ) ,
* (
[
' --credential ' ,
f " systemd.extra-unit.emergency-exit.service= { shlex . quote ( EMERGENCY_EXIT_SERVICE ) } " ,
' --credential ' ,
f " systemd.unit-dropin.emergency.target= { shlex . quote ( EMERGENCY_EXIT_DROPIN ) } " ,
]
if not sys . stderr . isatty ( )
else [ ]
) ,
' --credential ' ,
f " systemd.unit-dropin. { test_unit } = { shlex . quote ( dropin ) } " ,
2024-04-25 12:00:15 +03:00
' --runtime-network=none ' ,
2024-04-28 21:46:14 +03:00
' --runtime-scratch=no ' ,
2024-04-19 10:17:58 +03:00
' --append ' ,
2024-05-05 19:14:44 +03:00
' --qemu-firmware ' , args . firmware ,
2024-04-19 10:17:58 +03:00
' --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: " ,
f " systemd.unit= { test_unit } " ,
2024-04-28 23:51:41 +03:00
' systemd.mask=systemd-networkd-wait-online.service ' ,
2024-04-28 20:28:37 +03:00
* (
[
" systemd.mask=serial-getty@.service " ,
" systemd.show_status=no " ,
" systemd.crash_shell=0 " ,
2024-04-29 11:47:25 +03:00
" systemd.crash_action=poweroff " ,
2024-04-28 20:28:37 +03:00
]
if not sys . stderr . isatty ( )
else [ ]
) ,
2024-04-19 10:17:58 +03:00
] ) ,
2024-05-02 09:52:50 +03:00
' --credential ' , f " journal.storage= { ' persistent ' if sys . stderr . isatty ( ) else args . storage } " ,
2024-04-19 10:17:58 +03:00
* args . mkosi_args ,
' qemu ' ,
]
2024-04-23 16:13:22 +03:00
result = subprocess . run ( cmd )
2024-05-07 12:50:11 +03:00
2024-04-23 16:13:22 +03:00
# Return code 123 is the expected success code
2024-05-07 12:50:11 +03:00
if result . returncode in ( 123 , 77 ) :
# Do not keep journal files for tests that don't fail.
if journal_file :
journal_file . unlink ( missing_ok = True )
exit ( 0 if result . returncode == 123 else 77 )
2024-04-19 10:17:58 +03:00
if journal_file :
2024-05-07 13:24:51 +03:00
ops = [ ]
if os . getenv ( " GITHUB_ACTIONS " ) :
id = os . environ [ " GITHUB_RUN_ID " ]
iteration = os . environ [ " GITHUB_RUN_ATTEMPT " ]
j = json . loads (
subprocess . run (
[
" mkosi " ,
" --directory " , os . fspath ( args . meson_source_dir ) ,
" --json " ,
" summary " ,
] ,
stdout = subprocess . PIPE ,
text = True ,
) . stdout
)
images = { image [ " Image " ] : image for image in j [ " Images " ] }
distribution = images [ " system " ] [ " Distribution " ]
release = images [ " system " ] [ " Release " ]
artifact = f " ci-mkosi- { id } - { iteration } - { distribution } - { release } -failed-test-journals "
ops + = [ f " gh run download { id } --name { artifact } -D ci/ { artifact } " ]
journal_file = Path ( f " ci/ { artifact } /test/journal/ { name } .journal " )
ops + = [ f " journalctl --file { journal_file } --no-hostname -o short-monotonic -u { test_unit } -p info " ]
2024-05-07 12:50:11 +03:00
print ( " Test failed, relevant logs can be viewed with: \n \n "
2024-05-07 13:24:51 +03:00
f " { ( ' && ' . join ( ops ) ) } \n " , file = sys . stderr )
2024-05-07 12:50:11 +03:00
# 0 also means we failed so translate that to a non-zero exit code to mark the test as failed.
exit ( result . returncode or 1 )
2024-04-19 10:17:58 +03:00
if __name__ == ' __main__ ' :
main ( )