diff options
Diffstat (limited to '')
-rw-r--r-- | debian/tests/assert.sh | 34 | ||||
-rwxr-xr-x | debian/tests/boot-and-services | 574 | ||||
-rwxr-xr-x | debian/tests/boot-smoke | 95 | ||||
-rwxr-xr-x | debian/tests/build-login | 38 | ||||
-rwxr-xr-x | debian/tests/build-with-static-libsystemd | 38 | ||||
-rw-r--r-- | debian/tests/control | 224 | ||||
-rwxr-xr-x | debian/tests/hostnamed | 22 | ||||
-rw-r--r-- | debian/tests/lidswitch.evemu | 34 | ||||
-rwxr-xr-x | debian/tests/localed-locale | 59 | ||||
-rwxr-xr-x | debian/tests/localed-x11-keymap | 63 | ||||
-rwxr-xr-x | debian/tests/logind | 210 | ||||
-rwxr-xr-x | debian/tests/process-killer | 9 | ||||
-rwxr-xr-x | debian/tests/storage | 271 | ||||
-rwxr-xr-x | debian/tests/timedated | 186 | ||||
-rwxr-xr-x | debian/tests/unit-config | 370 | ||||
-rwxr-xr-x | debian/tests/unit-tests | 6 | ||||
-rwxr-xr-x | debian/tests/upstream | 31 |
17 files changed, 2264 insertions, 0 deletions
diff --git a/debian/tests/assert.sh b/debian/tests/assert.sh new file mode 100644 index 0000000..1d47bf4 --- /dev/null +++ b/debian/tests/assert.sh @@ -0,0 +1,34 @@ +# utility functions for shell tests + +assert_true() { + if ! $1; then + echo "FAIL: command '$1' failed with exit code $?" >&2 + exit 1 + fi +} + + +assert_eq() { + if [ "$1" != "$2" ]; then + echo "FAIL: expected: '$2' actual: '$1'" >&2 + exit 1 + fi +} + +assert_in() { + if ! echo "$2" | grep -q "$1"; then + echo "FAIL: '$1' not found in:" >&2 + echo "$2" >&2 + exit 1 + fi +} + +assert_rc() { + local exp=$1 + shift + set +e + $@ + RC=$? + set -e + assert_eq $RC $exp +} diff --git a/debian/tests/boot-and-services b/debian/tests/boot-and-services new file mode 100755 index 0000000..dc35840 --- /dev/null +++ b/debian/tests/boot-and-services @@ -0,0 +1,574 @@ +#!/usr/bin/python3 +# autopkgtest check: Boot with systemd and check critical desktop services +# (C) 2014 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> + +import sys +import os +import unittest +import subprocess +import tempfile +import shutil +import time +import re +from glob import glob + +is_container = subprocess.call(['systemd-detect-virt', '--container']) == 0 + + +def wait_unit_stop(unit, timeout=10): + '''Wait until given unit is not running any more + + Raise RuntimeError on timeout. + ''' + for i in range(timeout): + if subprocess.call(['systemctl', 'is-active', '--quiet', unit]) != 0: + return + time.sleep(1) + + raise RuntimeError('Timed out waiting for %s to stop' % unit) + + +class ServicesTest(unittest.TestCase): + '''Check that expected services are running''' + + def test_0_init(self): + '''Verify that init is systemd''' + + self.assertIn('systemd', os.readlink('/proc/1/exe')) + + def test_no_failed(self): + '''No failed units''' + + out = subprocess.check_output( + ['systemctl', '--state=failed', '--no-legend', '--plain'], + universal_newlines=True) + failed = out.splitlines() + # ignore /etc/modules failure as stuff that we put there by default + # often fails + failed = [f for f in failed if 'systemd-modules-load' not in f] + # apparmor fails if not enabled in the kernel + if not os.path.exists('/sys/kernel/security/apparmor'): + failed = [f for f in failed if 'apparmor.service' not in f] + # ignore thermald as it doesn't start in most virtual envs + failed = [f for f in failed if 'thermald' not in f] + # console-setup.service fails on devices without keyboard (LP: #1516591) + failed = [f for f in failed if 'console-setup' not in f] + # cpi.service fails on s390x + failed = [f for f in failed if 'cpi.service' not in f] + # https://bugs.debian.org/969568 + failed = [f for f in failed if 'rng-tools-debian.service' not in f] + # https://bugs.debian.org/926138 + if is_container: + failed = [f for f in failed if 'e2scrub_reap.service' not in f] + if failed: + for f in failed: + f = f.split()[0] + print('-------- journal for failed service %s -----------' % f) + sys.stdout.flush() + subprocess.call(['journalctl', '-b', '-u', f]) + self.assertEqual(failed, []) + + @unittest.skipUnless(shutil.which('gdm3') is not None, 'gdm3 not found') + def test_gdm3(self): + subprocess.check_call(['pgrep', '-af', '/gdm[-3]']) + self.active_unit('gdm') + + def test_dbus(self): + out = subprocess.check_output( + ['dbus-send', '--print-reply', '--system', + '--dest=org.freedesktop.DBus', '/', 'org.freedesktop.DBus.GetId']) + self.assertIn(b'string "', out) + self.active_unit('dbus') + + def test_network_manager(self): + # 0.9.10 changed the command name + _help = subprocess.check_output(['nmcli', '--help'], + stderr=subprocess.STDOUT) + if b' g[eneral]' in _help: + out = subprocess.check_output(['nmcli', 'general']) + else: + out = subprocess.check_output(['nmcli', 'nm']) + self.assertIn(b'enabled', out) + self.active_unit('NetworkManager') + + def test_cron(self): + out = subprocess.check_output(['ps', 'u', '-C', 'cron']) + self.assertIn(b'root', out) + self.active_unit('cron') + + def test_logind(self): + out = subprocess.check_output(['loginctl']) + self.assertNotEqual(b'', out) + self.active_unit('systemd-logind') + + @unittest.skipIf('pkg.systemd.upstream' in os.environ.get('DEB_BUILD_PROFILES', ''), + 'Forwarding to rsyslog is a Debian patch') + def test_rsyslog(self): + out = subprocess.check_output(['ps', 'u', '-C', 'rsyslogd']) + self.assertIn(b'bin/rsyslogd', out) + self.active_unit('rsyslog') + with open('/var/log/syslog') as f: + log = f.read() + if not is_container: + # has kernel messages + self.assertRegex(log, 'kernel:.*') + # has init messages + self.assertRegex(log, 'systemd.*Reached target(?: graphical.target -)? Graphical Interface') + # has other services + self.assertRegex(log, 'NetworkManager.*:') + + @unittest.skipIf(is_container, 'udev does not work in containers') + def test_udev(self): + out = subprocess.check_output(['udevadm', 'info', '--export-db']) + self.assertIn(b'\nP: /devices/', out) + self.active_unit('systemd-udevd') + + def test_tmp_mount(self): + # check if we want to mount /tmp in fstab + want_tmp_mount = False + try: + with open('/etc/fstab') as f: + for l in f: + try: + if not l.startswith('#') and l.split()[1] in ('/tmp', '/tmp/'): + want_tmp_mount = True + break + except IndexError: + pass + except FileNotFoundError: + pass + + # ensure that we actually do/don't have a /tmp mount + (status, status_out) = subprocess.getstatusoutput('systemctl status tmp.mount') + findmnt = subprocess.call(['findmnt', '-n', '/tmp'], stdout=subprocess.PIPE) + if want_tmp_mount: + self.assertEqual(status, 0, status_out) + self.assertEqual(findmnt, 0) + else: + # 4 is correct (since upstream commit ca473d57), accept 3 for systemd <= 230 + self.assertIn(status, [3, 4], status_out) + self.assertNotEqual(findmnt, 0) + + @unittest.skipIf('pkg.systemd.upstream' in os.environ.get('DEB_BUILD_PROFILES', ''), + 'Debian specific configuration, N/A for upstream') + def test_tmp_cleanup(self): + # systemd-tmpfiles-clean.timer only runs 15 mins after boot, shortcut + # it + self.assertEqual(subprocess.call( + ['systemctl', 'status', 'systemd-tmpfiles-clean.timer'], + stdout=subprocess.PIPE), 0) + subprocess.check_call(['systemctl', 'start', 'systemd-tmpfiles-clean']) + if not is_container: + # all files in /tmp/ should get cleaned up on boot + self.assertFalse(os.path.exists('/tmp/oldfile.test')) + self.assertFalse(os.path.exists('/tmp/newfile.test')) + # files in /var/tmp/ older than 30d should get cleaned up + # XXX FIXME: /var/tmp/ cleanup was disabled in #675422 + # if not is_container: + # self.assertFalse(os.path.exists('/var/tmp/oldfile.test')) + self.assertTrue(os.path.exists('/var/tmp/newfile.test')) + + # next run should leave the recent ones + os.close(os.open('/tmp/newfile.test', + os.O_CREAT | os.O_EXCL | os.O_WRONLY)) + subprocess.check_call(['systemctl', 'start', 'systemd-tmpfiles-clean']) + wait_unit_stop('systemd-tmpfiles-clean') + self.assertTrue(os.path.exists('/tmp/newfile.test')) + + # Helper methods + + def active_unit(self, unit): + '''Check that given unit is active''' + + out = subprocess.check_output(['systemctl', 'status', unit]) + self.assertIn(b'active (running)', out) + + +class JournalTest(unittest.TestCase): + '''Check journal functionality''' + + def test_no_options(self): + out = subprocess.check_output(['journalctl']) + if not is_container: + # has kernel messages + self.assertRegex(out, b'kernel:.*') + # has init messages + self.assertRegex(out, b'systemd.*Reached target(?: graphical.target -)? Graphical Interface') + # has other services + self.assertRegex(out, b'NetworkManager.*:.*starting') + + def test_log_for_service(self): + out = subprocess.check_output( + ['journalctl', '_SYSTEMD_UNIT=NetworkManager.service']) + self.assertRegex(out, b'NetworkManager.*:.*starting') + self.assertNotIn(b'kernel:', out) + self.assertNotIn(b'systemd:', out) + + +@unittest.skipIf(is_container, 'nspawn does not work in most containers') +class NspawnTest(unittest.TestCase): + '''Check nspawn''' + + @classmethod + def setUpClass(kls): + '''Build a bootable busybox mini-container''' + + kls.td_c_busybox = tempfile.TemporaryDirectory(prefix='c_busybox.') + kls.c_busybox = kls.td_c_busybox.name + for d in ['etc/init.d', 'bin', 'sbin']: + os.makedirs(os.path.join(kls.c_busybox, d)) + shutil.copy('/bin/busybox', os.path.join(kls.c_busybox, 'bin')) + shutil.copy('/etc/os-release', os.path.join(kls.c_busybox, 'etc')) + os.symlink('busybox', os.path.join(kls.c_busybox, 'bin', 'sh')) + os.symlink('../bin/busybox', os.path.join(kls.c_busybox, 'sbin/init')) + with open(os.path.join(kls.c_busybox, 'etc/init.d/rcS'), 'w') as f: + f.write('''#!/bin/sh +echo fake container started +ps aux +poweroff\n''') + os.fchmod(f.fileno(), 0o755) + subprocess.check_call(['systemd-machine-id-setup', '--root', + kls.c_busybox], stderr=subprocess.PIPE) + + def setUp(self): + self.workdir = tempfile.TemporaryDirectory() + + def test_boot(self): + cont = os.path.join(self.workdir.name, 'c1') + shutil.copytree(self.c_busybox, cont, symlinks=True) + os.sync() + nspawn = subprocess.Popen(['systemd-nspawn', '-D', cont, '-b'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out = nspawn.communicate(timeout=60)[0] + self.assertIn(b'Spawning container c1', out) + self.assertIn(b'fake container started', out) + self.assertRegex(out, b'\n\s+1\s+0\s+init[\r\n]') + self.assertRegex(out, b'\n\s+2+\s+0\s.*rcS[\r\n]') + self.assertRegex(out, b'Container c1.*shut down') + self.assertEqual(nspawn.returncode, 0) + + def test_service(self): + self.assertTrue(os.path.isdir('/var/lib/machines')) + cont = '/var/lib/machines/c1' + shutil.copytree(self.c_busybox, cont, symlinks=True) + self.addCleanup(shutil.rmtree, cont) + os.sync() + subprocess.check_call(['systemctl', 'start', 'systemd-nspawn@c1']) + wait_unit_stop('systemd-nspawn@c1') + + subprocess.call(['journalctl', '--sync']) + systemctl = subprocess.Popen( + ['systemctl', 'status', '-overbose', '-l', 'systemd-nspawn@c1'], + stdout=subprocess.PIPE) + out = systemctl.communicate()[0].decode('UTF-8', 'replace') + self.assertEqual(systemctl.returncode, 3, out) + self.assertNotIn('failed', out) + + +@unittest.skipUnless(os.path.exists('/sys/kernel/security/apparmor'), + 'AppArmor not enabled') +class AppArmorTest(unittest.TestCase): + def test_profile(self): + '''AppArmor confined unit''' + + # create AppArmor profile + aa_profile = tempfile.NamedTemporaryFile(prefix='aa_violator.') + aa_profile.write(b'''#include <tunables/global> + +profile "violator-test" { + #include <abstractions/base> + + /{usr/,}bin/** rix, + /etc/machine-id r, +} +''') + aa_profile.flush() + subprocess.check_call(['apparmor_parser', '-r', '-v', aa_profile.name]) + + # create confined unit + with open('/run/systemd/system/violator.service', 'w') as f: + f.write('''[Unit] +Description=AppArmor test + +[Service] +ExecStart=/bin/sh -euc 'echo CP1; cat /etc/machine-id; echo CP2; if cat /etc/passwd; then exit 1; fi; echo CP3' +AppArmorProfile=violator-test +''') + self.addCleanup(os.unlink, '/run/systemd/system/violator.service') + + # launch + subprocess.check_call(['systemctl', 'daemon-reload']) + subprocess.check_call(['systemctl', 'start', 'violator.service']) + wait_unit_stop('violator.service') + + # check status + st = subprocess.Popen(['systemctl', 'status', '-l', + 'violator.service'], stdout=subprocess.PIPE, + universal_newlines=True) + out = st.communicate()[0] + # unit should be stopped + self.assertEqual(st.returncode, 3) + + self.assertIn('inactive', out) + self.assertIn('CP1', out) + self.assertIn('CP2', out) + self.assertIn('CP3', out) + with open('/etc/machine-id') as f: + self.assertIn(f.read().strip(), out) + self.assertNotIn('root:x', out, 'unit can read /etc/passwd') + + +@unittest.skipIf(os.path.exists('/sys/fs/cgroup/cgroup.controllers'), + 'test needs to be reworked on unified cgroup hierarchy') +class CgroupsTest(unittest.TestCase): + '''Check cgroup setup''' + + @classmethod + def setUpClass(kls): + kls.controllers = [] + for controller in glob('/sys/fs/cgroup/*'): + if not os.path.islink(controller): + kls.controllers.append(controller) + + def setUp(self): + self.service = 'testsrv.service' + self.service_file = '/run/systemd/system/' + self.service + + def tearDown(self): + subprocess.call(['systemctl', 'stop', self.service], + stderr=subprocess.PIPE) + try: + os.unlink(self.service_file) + except OSError: + pass + subprocess.check_call(['systemctl', 'daemon-reload']) + + def create_service(self, extra_service=''): + '''Create test service unit''' + + with open(self.service_file, 'w') as f: + f.write('''[Unit] +Description=test service +[Service] +ExecStart=/bin/sleep 500 +%s +''' % extra_service) + subprocess.check_call(['systemctl', 'daemon-reload']) + + def assertNoControllers(self): + '''Assert that no cgroup controllers exist for test service''' + + cs = glob('/sys/fs/cgroup/*/system.slice/%s' % self.service) + self.assertEqual(cs, []) + + def assertController(self, name): + '''Assert that cgroup controller exists for test service''' + + c = '/sys/fs/cgroup/%s/system.slice/%s' % (name, self.service) + self.assertTrue(os.path.isdir(c)) + + def assertNoController(self, name): + '''Assert that cgroup controller does not exist for test service''' + + c = '/sys/fs/cgroup/%s/system.slice/%s' % (name, self.service) + self.assertFalse(os.path.isdir(c)) + + def test_simple(self): + '''simple service''' + + self.create_service() + self.assertNoControllers() + subprocess.check_call(['systemctl', 'start', self.service]) + self.assertController('systemd') + subprocess.check_call(['systemctl', 'stop', self.service]) + self.assertNoControllers() + + def test_cpushares(self): + '''service with CPUShares''' + + self.create_service('CPUShares=1000') + self.assertNoControllers() + subprocess.check_call(['systemctl', 'start', self.service]) + self.assertController('systemd') + self.assertController('cpu,cpuacct') + subprocess.check_call(['systemctl', 'stop', self.service]) + self.assertNoControllers() + + +class SeccompTest(unittest.TestCase): + '''Check seccomp syscall filtering''' + + def test_failing(self): + with open('/run/systemd/system/scfail.service', 'w') as f: + f.write('''[Unit] +Description=seccomp test +[Service] +ExecStart=/bin/cat /etc/machine-id +SystemCallFilter=access +''') + self.addCleanup(os.unlink, '/run/systemd/system/scfail.service') + + # launch + subprocess.check_call(['systemctl', 'daemon-reload']) + subprocess.check_call(['systemctl', 'start', 'scfail.service']) + wait_unit_stop('scfail.service') + + # check status + st = subprocess.Popen(['systemctl', 'status', '-l', + 'scfail.service'], stdout=subprocess.PIPE) + out = st.communicate()[0] + # unit should be stopped + self.assertEqual(st.returncode, 3) + + subprocess.check_call(['systemctl', 'reset-failed', 'scfail.service']) + + self.assertIn(b'failed', out) + self.assertRegex(out, b'code=(killed|dumped), signal=SYS') + with open('/etc/machine-id') as f: + self.assertNotIn(f.read().strip().encode('ASCII'), out) + + +@unittest.skipIf(is_container, 'systemd-coredump does not work in containers') +class CoredumpTest(unittest.TestCase): + '''Check systemd-coredump''' + + def test_bash_crash(self): + subprocess.call("ulimit -c unlimited; bash -c 'kill -SEGV $$'", shell=True, + cwd='/tmp', stderr=subprocess.DEVNULL) + + # with systemd-coredump installed we should get the core dumps in + # systemd's dir + for timeout in range(50): + cores = glob('/var/lib/systemd/coredump/core.bash.*') + if cores: + break + time.sleep(1) + self.assertNotEqual(cores, []) + self.assertEqual(glob('/tmp/core*'), []) + + # we should also get a message and stack trace in journal + for timeout in range(10): + subprocess.call(['journalctl', '--sync']) + journal = subprocess.check_output(['journalctl', '-t', 'systemd-coredump']) + if re.search(b'Process.*bash.*dumped core', journal) and \ + re.search(b'#[0-9] .*bash', journal): + break + time.sleep(1) + self.assertRegex(journal, b'Process.*bash.*dumped core') + self.assertIn(b'Stack trace', journal) + self.assertRegex(journal, b'#[0-9] .*bash') + + +class CLITest(unittest.TestCase): + def setUp(self): + self.programs = [] + for line in subprocess.check_output(['dpkg', '-L', 'systemd', 'systemd-container', 'systemd-coredump', 'udev'], + universal_newlines=True).splitlines(): + if '/bin/' in line: + self.programs.append(line.strip()) + + def test_help(self): + '--help works and succeeds''' + + for program in self.programs: + p = subprocess.Popen([program, '--help'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + (out, err) = p.communicate() + try: + self.assertEqual(err, '') + self.assertEqual(p.returncode, 0) + self.assertIn(os.path.basename(program), out) + self.assertTrue('--help' in out or 'Usage' in out, out) + except AssertionError: + print('Failed program: %s' % program) + raise + + def test_version(self): + '--version works and succeeds''' + + version = subprocess.check_output(['pkg-config', '--modversion', 'systemd'], + universal_newlines=True).strip() + + for program in self.programs: + # known to not respond to --version + if os.path.basename(program) in ['kernel-install', 'systemd-ask-password', 'systemd-stdio-bridge']: + continue + p = subprocess.Popen([program, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + (out, err) = p.communicate() + try: + self.assertEqual(err, '') + self.assertEqual(p.returncode, 0) + self.assertIn(version, out) + except AssertionError: + print('Failed program: %s' % program) + raise + + def test_invalid_option(self): + '''Calling with invalid option fails''' + + for program in self.programs: + p = subprocess.Popen([program, '--invalid-option'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + (out, err) = p.communicate() + try: + # kernel-install is an unique snowflake + if not program.endswith('/kernel-install'): + self.assertIn('--invalid-option', err) + self.assertNotEqual(p.returncode, 0) + except AssertionError: + print('Failed program: %s' % program) + raise + + +def pre_boot_setup(): + '''Test setup before rebooting testbed''' + + subprocess.check_call(['systemctl', 'set-default', 'graphical.target'], + stderr=subprocess.STDOUT) + + # This test installs network-manager, which seems to cause + # systemd-networkd-wait-online to be stuck as they conflict, + # so systemctl start network-online.target ran by autopkgtest + # gets stuck, at least in Debian Bullseye images. + # https://salsa.debian.org/ci-team/autopkgtest/-/blob/debian/5.21/virt/autopkgtest-virt-lxc#L131 + subprocess.check_call(['systemctl', 'disable', 'systemd-networkd.service'], + stderr=subprocess.STDOUT) + + # create a few temporary files to ensure that they get cleaned up on boot + os.close(os.open('/tmp/newfile.test', + os.O_CREAT | os.O_EXCL | os.O_WRONLY)) + os.close(os.open('/var/tmp/newfile.test', + os.O_CREAT | os.O_EXCL | os.O_WRONLY)) + # we can't use utime() here, as systemd looks for ctime + if not is_container: + cur_time = time.clock_gettime(time.CLOCK_REALTIME) + time.clock_settime(time.CLOCK_REALTIME, cur_time - 2 * 30 * 86400) + try: + os.close(os.open('/tmp/oldfile.test', + os.O_CREAT | os.O_EXCL | os.O_WRONLY)) + os.close(os.open('/var/tmp/oldfile.test', + os.O_CREAT | os.O_EXCL | os.O_WRONLY)) + finally: + time.clock_settime(time.CLOCK_REALTIME, cur_time) + + # allow X to start even on headless machines + os.makedirs('/etc/X11/xorg.conf.d/', exist_ok=True) + with open('/etc/X11/xorg.conf.d/dummy.conf', 'w') as f: + f.write('''Section "Device" + Identifier "test" + Driver "dummy" +EndSection''') + + # accounts-daemon.service fails if /usr/share/accountsservice/interfaces does not exist. + # FIXME: remove this workaround again when + # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1038137 is fixed + os.makedirs('/usr/share/accountsservice/interfaces', exist_ok=True) + + +if __name__ == '__main__': + if not os.getenv('AUTOPKGTEST_REBOOT_MARK'): + pre_boot_setup() + print('Rebooting...') + subprocess.check_call(['/tmp/autopkgtest-reboot', 'boot1']) + + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, + verbosity=2)) diff --git a/debian/tests/boot-smoke b/debian/tests/boot-smoke new file mode 100755 index 0000000..d4105fc --- /dev/null +++ b/debian/tests/boot-smoke @@ -0,0 +1,95 @@ +#!/bin/sh +# test $TEST_REBOOTS successful reboots in a row +# Author: Martin Pitt <martin.pitt@ubuntu.com> +# For bisecting/testing you can replace individual binaries in /lib/systemd +# with --copy /host/path/systemd-foo:/tmp/systemd-replace/systemd-foo +set -e + +IS_SYSTEM_RUNNING_TIMEOUT=300 +TEST_REBOOTS=5 + +. `dirname $0`/assert.sh + +fail() { + [ -n "$1" ] && echo "$1" + set +e + journalctl --sync + journalctl -a > "$AUTOPKGTEST_ARTIFACTS/boot-smoke-journal.txt" + systemctl --no-pager --no-legend list-jobs > "$AUTOPKGTEST_ARTIFACTS/boot-smoke-running-jobs.txt" + udevadm info --export-db > "$AUTOPKGTEST_ARTIFACTS/boot-smoke-udevdb.txt" + exit 1 +} + +if [ -z "$AUTOPKGTEST_REBOOT_MARK" ]; then + # enable persistent journal + mkdir -p /var/log/journal + # allow X to start even on headless machines + mkdir -p /etc/X11/xorg.conf.d/ + cat << EOF > /etc/X11/xorg.conf.d/dummy.conf +Section "Device" + Identifier "test" + Driver "dummy" +EndSection +EOF + + # This test installs network-manager, which seems to cause + # systemd-networkd-wait-online to be stuck as they conflict, + # so systemctl start network-online.target ran by autopkgtest + # gets stuck, at least in Debian Bullseye images. + # https://salsa.debian.org/ci-team/autopkgtest/-/blob/debian/5.21/virt/autopkgtest-virt-lxc#L131 + systemctl disable systemd-networkd.service + + AUTOPKGTEST_REBOOT_MARK=0 + if [ -d /tmp/systemd-replace/ ]; then + for f in /tmp/systemd-replace/*; do + echo "Installing $f..." + rm -f /lib/systemd/$(basename $f) + cp $f /lib/systemd/ + done + fi +else + echo "waiting to boot..." + TIMEOUT=${IS_SYSTEM_RUNNING_TIMEOUT} + while [ $TIMEOUT -ge 0 ]; do + state="$(systemctl is-system-running || true)" + case $state in + running|degraded) + break + ;; + *) + sleep 1 + TIMEOUT=$((TIMEOUT - 1)) + ;; + esac + done + + echo "checking for running system" + if [ "$state" = "degraded" ]; then + systemctl --no-pager --no-legend --failed list-units > "$AUTOPKGTEST_ARTIFACTS/boot-smoke-failed-units.txt" || true + echo "systemctl is-system-running: degraded (non-fatal)" + elif [ "$state" != "running" ]; then + fail "system not running after timeout $IS_SYSTEM_RUNNING_TIMEOUT, state: $state" + fi + + echo "checking for failed unmounts for user systemd" + # grep complete journal to catch shutdown messages + if journalctl | grep -E "systemd\[([2-9]|[1-9][0-9]+)\].*Failed unmounting"; then + fail "found failed unmount in journal" + fi + + # grep only this boot's journal, earlier ones complain about missing "render" group + echo "checking for connection timeouts" + if journalctl -b | grep "Connection timed out"; then + fail "found connection timeout in journal for this boot" + fi + + echo "checking that NetworkManager runs" + pidof NetworkManager || fail "NetworkManager was not running" +fi + +if [ "$AUTOPKGTEST_REBOOT_MARK" -ge "$TEST_REBOOTS" ]; then + exit 0 +fi + +echo "reboot #$AUTOPKGTEST_REBOOT_MARK" +/tmp/autopkgtest-reboot $(($AUTOPKGTEST_REBOOT_MARK + 1)) diff --git a/debian/tests/build-login b/debian/tests/build-login new file mode 100755 index 0000000..def83b1 --- /dev/null +++ b/debian/tests/build-login @@ -0,0 +1,38 @@ +#!/bin/sh +# autopkgtest check: Test build against libsystemd-login-dev +# (C) 2014 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> + +set -e + +WORKDIR=$(mktemp -d) +trap "rm -rf $WORKDIR" 0 INT QUIT ABRT PIPE TERM +cd $WORKDIR +cat <<EOF > loginmonitor.c +#include <assert.h> +#include <stdio.h> +#include <systemd/sd-login.h> + +int main(int argc, char **argv) +{ + sd_login_monitor* mon = NULL; + int res; + + res = sd_login_monitor_new(NULL, &mon); + if (res < 0) { + fprintf(stderr, "sd_login_monitor_new failed with value %i\n", res); + return 1; + } + + assert(sd_login_monitor_get_fd(mon) > 0); + sd_login_monitor_unref(mon); + + return 0; +} +EOF + +gcc -Wall -Werror -o loginmonitor loginmonitor.c `pkg-config --cflags --libs libsystemd` +echo "build: OK" +[ -x loginmonitor ] +./loginmonitor +echo "run: OK" diff --git a/debian/tests/build-with-static-libsystemd b/debian/tests/build-with-static-libsystemd new file mode 100755 index 0000000..6b91009 --- /dev/null +++ b/debian/tests/build-with-static-libsystemd @@ -0,0 +1,38 @@ +#!/bin/sh +# autopkgtest check: Test build against static libsystemd + +set -e + +WORKDIR=$(mktemp -d) +trap "rm -rf $WORKDIR" 0 INT QUIT ABRT PIPE TERM +cd $WORKDIR +cat <<EOF > uuid-gen.c +#include <assert.h> +#include <stdio.h> + +#include <systemd/sd-id128.h> + +int main(void) +{ + sd_id128_t id; + char id_str[SD_ID128_STRING_MAX]; + int r; + + r = sd_id128_randomize(&id); + if (r < 0) { + fprintf(stderr, "Failed to generate id: %s\n", strerror(-r)); + return 1; + } + + assert(sd_id128_to_string(id, id_str) > 0); + printf("uuid: %s\n", id_str); + + return 0; +} +EOF + +gcc -Wall -Werror -o uuid-gen uuid-gen.c -l:libsystemd.a -lcap +echo "build: OK" +[ -x uuid-gen ] +./uuid-gen +echo "run: OK" diff --git a/debian/tests/control b/debian/tests/control new file mode 100644 index 0000000..3b2390f --- /dev/null +++ b/debian/tests/control @@ -0,0 +1,224 @@ +Tests: timedated, hostnamed, localed-locale, localed-x11-keymap, logind +Depends: systemd, + systemd-timesyncd, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, +Restrictions: needs-root, isolation-container, skippable + +Tests: unit-config +Depends: systemd, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, + python3, + pkg-config, +Restrictions: needs-root, allow-stderr + +Tests: storage +Depends: systemd, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, + python3, + pkg-config, + cryptsetup-bin, +Restrictions: needs-root, isolation-machine + +Tests: networkd-test.py +Tests-Directory: test +Depends: systemd, + systemd-resolved, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, + python3, + pkg-config, + cryptsetup-bin, + systemd-sysv, + polkitd, + netlabel-tools, + dnsmasq-base +Restrictions: needs-root, isolation-container, breaks-testbed + +Tests: build-login +Depends: systemd, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, + python3, + pkg-config, + cryptsetup-bin, + systemd-sysv, + polkitd, + dnsmasq-base, + build-essential, + libsystemd-dev, +Restrictions: isolation-container + +Tests: boot-and-services +Depends: systemd-sysv, + systemd-container, + systemd-coredump, + libpam-systemd, + libdw-dev, + libelf-dev, + xserver-xorg-video-dummy, + xserver-xorg, + gdm3 [!s390x], + cron, + network-manager, + busybox-static, + rsyslog, + apparmor, + pkg-config, + python3, + python3-pefile, + dhcpcd-base, +Restrictions: needs-root, isolation-container, breaks-testbed, allow-stderr + +Tests: unit-tests +Depends: systemd-tests, + libpam-systemd, + libnss-myhostname, + libnss-mymachines, + libnss-resolve, + libnss-systemd, + udev, + tree, + perl, + python3, + python3-colorama, + python3-pefile, + xz-utils, + libcap2-bin, + iproute2, + liblz4-tool, + acl, + iputils-ping, + dbus-user-session, + zstd, + libtss2-dev, + libfido2-dev, + libdw-dev, + libqrencode-dev, + tzdata-legacy | tzdata (<< 2024a-1), +Restrictions: needs-root, isolation-container, breaks-testbed + +Tests: upstream +Depends: systemd-tests, + systemd-journal-remote, + systemd-container, + systemd-coredump, + systemd-timesyncd, + systemd-oomd, + systemd-homed, + systemd-resolved, + systemd-boot-efi, + libnss-myhostname, + libnss-mymachines, + libnss-resolve, + libnss-systemd, + attr, + tree, + perl, + xz-utils, + libcap2-bin, + iproute2, + liblz4-tool, + acl, + dmeventd, + kbd, + cryptsetup-bin, + cryptsetup-initramfs, + net-tools, + isc-dhcp-client, + iputils-ping, + strace, + qemu-system-x86 [amd64 i386], + qemu-system-arm [arm64 armhf], + qemu-system-ppc [ppc64el], + qemu-system-s390x [s390x], + seabios, + less, + pkg-config, + gcc, + libc6-dev | libc-dev, + make, + quota, + fdisk, + netcat-openbsd, + socat, + busybox-static, + plymouth, + e2fsprogs, + zstd, + squashfs-tools, + vim-tiny, + dosfstools, + mtools, + erofs-utils, + libdw-dev, + libelf-dev, + dbus-user-session, + libtss2-dev, + libfido2-dev, + libqrencode-dev, + libp11-kit-dev, + libssl-dev, + python3-pexpect, + screen, + swtpm, + tpm2-tools, + openssl, + bsdutils, + knot, + knot-dnssecutils | knot-dnsutils, + bind9-dnsutils, + bind9-host, + nftables, + jq, + psmisc, + xkb-data, + locales, + locales-all, + stress, + curl, + tzdata-legacy | tzdata (<< 2024a-1), + python3-pefile, + nvme-cli, + mdadm, + lvm2, + btrfs-progs, + multipath-tools, + kpartx, + gnutls-bin, + opensc, + softhsm2, + openssh-client, + openssh-server, +Restrictions: needs-root, allow-stderr, isolation-machine + +Tests: boot-smoke +Depends: systemd-sysv, + network-manager, + gdm3 [!s390x], + xserver-xorg-video-dummy, +Restrictions: needs-root, isolation-container, allow-stderr, breaks-testbed + +Tests: build-with-static-libsystemd +Depends: systemd, + pkg-config, + build-essential, + libsystemd-dev, + libcap-dev, diff --git a/debian/tests/hostnamed b/debian/tests/hostnamed new file mode 100755 index 0000000..1b22869 --- /dev/null +++ b/debian/tests/hostnamed @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +. `dirname $0`/assert.sh + +ORIG_HOST=`cat /etc/hostname` +echo "original hostname: $ORIG_HOST" + +# should activate daemon and work +STATUS="`hostnamectl`" +assert_in "Static hostname: $ORIG_HOST" "$STATUS" +assert_in "Kernel:.* `uname -r`" "$STATUS" + +# change hostname +assert_eq "`hostnamectl set-hostname testhost 2>&1`" "" +assert_eq "`cat /etc/hostname`" "testhost" +assert_in "Static hostname: testhost" "`hostnamectl`" + +# reset to original +assert_eq "`hostnamectl set-hostname $ORIG_HOST 2>&1`" "" +assert_eq "`cat /etc/hostname`" "$ORIG_HOST" +assert_in "Static hostname: $ORIG_HOST" "`hostnamectl`" diff --git a/debian/tests/lidswitch.evemu b/debian/tests/lidswitch.evemu new file mode 100644 index 0000000..de1d590 --- /dev/null +++ b/debian/tests/lidswitch.evemu @@ -0,0 +1,34 @@ +# EVEMU 1.2 +# Input device name: "Lid Switch" +# Input device ID: bus 0x19 vendor 0000 product 0x05 version 0000 +# Supported events: +# Event type 0 (EV_SYN) +# Event code 0 (SYN_REPORT) +# Event code 5 (FF_STATUS_MAX) +# Event type 5 (EV_SW) +# Event code 0 (SW_LID) +# Properties: +N: Fake Lid Switch +I: 0019 0000 0005 0000 +P: 00 00 00 00 00 00 00 00 +B: 00 21 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 02 00 00 00 00 00 00 00 00 +B: 03 00 00 00 00 00 00 00 00 +B: 04 00 00 00 00 00 00 00 00 +B: 05 01 00 00 00 00 00 00 00 +B: 11 00 00 00 00 00 00 00 00 +B: 12 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 diff --git a/debian/tests/localed-locale b/debian/tests/localed-locale new file mode 100755 index 0000000..d666be2 --- /dev/null +++ b/debian/tests/localed-locale @@ -0,0 +1,59 @@ +#!/bin/sh +set -e + +. `dirname $0`/assert.sh + +if [ -f /etc/locale.conf ]; then + cp /etc/locale.conf /etc/locale.conf.orig +fi +if [ -f /etc/vconsole.conf ]; then + mv /etc/vconsole.conf /etc/vconsole.conf.orig +fi + +# ensure tested locale exist +mv /etc/locale.gen /etc/locale.gen.orig +echo "en_US.UTF-8 UTF-8" > /etc/locale.gen +locale-gen en_US.UTF-8 + +/bin/echo -e 'XKBMODEL=us\nXKBLAYOUT=pc105' > /etc/vconsole.conf + +# should activate daemon and work +assert_in "System Locale:" "`localectl --no-pager`" + +# change locale +assert_eq "`localectl --no-pager set-locale LANG=C LC_CTYPE=en_US.UTF-8 2>&1`" "" +sync +assert_eq "`cat /etc/locale.conf`" "LANG=C +LC_CTYPE=en_US.UTF-8" + +STATUS=`localectl` +assert_in "System Locale: LANG=C" "$STATUS" +assert_in "LC_CTYPE=en_US.UTF-8" "$STATUS" + +# test if localed auto-runs locale-gen + +# ensure tested locale does not exist +assert_rc 1 validlocale de_DE.UTF-8 2>&1 + +# change locale +assert_eq "`localectl --no-pager set-locale de_DE.UTF-8 2>&1`" "" +sync +assert_eq "`cat /etc/locale.conf`" "LANG=de_DE.UTF-8 +LC_CTYPE=en_US.UTF-8" + +# ensure tested locale exists and works now +assert_rc 0 validlocale de_DE.UTF-8 2>&1 + +# reset locale to original +if [ -f /etc/locale.conf.orig ]; then + mv /etc/locale.conf.orig /etc/locale.conf +else + rm -f /etc/locale.conf +fi +if [ -f /etc/vconsole.conf.orig ]; then + mv /etc/vconsole.conf.orig /etc/vconsole.conf +else + rm /etc/vconsole.conf +fi +mv /etc/locale.gen.orig /etc/locale.gen +locale-gen diff --git a/debian/tests/localed-x11-keymap b/debian/tests/localed-x11-keymap new file mode 100755 index 0000000..1fd29d3 --- /dev/null +++ b/debian/tests/localed-x11-keymap @@ -0,0 +1,63 @@ +#!/bin/sh +set -e + +case "${DEB_BUILD_PROFILES:-}" in + *pkg.systemd.upstream*) ;; + *) + echo "keymapping not supported for now, SKIP test" + exit 77 +esac + +. `dirname $0`/assert.sh + +if [ -f /etc/default/keyboard ]; then + ORIG_KBD=`cat /etc/default/keyboard` +else + ORIG_KBD="" +fi + +cleanup() { + # reset locale to original + if [ -n "ORIG_KBD" ]; then + echo "$ORIG_KBD" > /etc/default/keyboard + else + rm -f /etc/default/keyboard + fi + rm -f /etc/X11/xorg.conf.d/00-keyboard.conf +} +trap cleanup EXIT INT QUIT PIPE + +# should activate daemon and work +STATUS=`localectl` +assert_in "X11 Layout:" "`localectl --no-pager`" + +# change layout +assert_eq "`localectl --no-pager set-x11-keymap et pc101 2>&1`" "" +sync + +case "${DEB_BUILD_PROFILES:-}" in + *pkg.systemd.upstream*) + # Upstream writes xorg.conf.d file + assert_in 'Option "XkbLayout" "et' "`cat /etc/X11/xorg.conf.d/00-keyboard.conf`" + assert_in 'Option "XkbModel" "pc101"' "`cat /etc/X11/xorg.conf.d/00-keyboard.conf`" + ;; + *) + # Debian console-setup config file + assert_in 'XKBLAYOUT="\?et"\?' "`cat /etc/default/keyboard`" + assert_in 'XKBMODEL="\?pc101"\?' "`cat /etc/default/keyboard`" + ! [ -f /etc/X11/xorg.conf.d/00-keyboard.conf ] + ;; +esac + +STATUS=`localectl --no-pager` +assert_in "X11 Layout: et" "$STATUS" +assert_in "X11 Model: pc101" "$STATUS" + +case "${DEB_BUILD_PROFILES:-}" in + *pkg.systemd.upstream*) ;; + *) + rm /etc/default/keyboard + systemctl stop systemd-localed + assert_in "X11 Layout: (unset)" "`localectl --no-pager`" + ;; +esac diff --git a/debian/tests/logind b/debian/tests/logind new file mode 100755 index 0000000..eea4005 --- /dev/null +++ b/debian/tests/logind @@ -0,0 +1,210 @@ +#!/bin/sh +set -e + +test_started() { + # ensure the *old* logind from before the upgrade isn't running + echo " * try-restarting systemd-logind" + systemctl try-restart systemd-logind + + echo " * daemon is started" + # should start at boot, not with D-BUS activation + LOGINDPID=$(pidof systemd-logind) + + # loginctl should succeed + echo " * loginctl succeeds" + LOGINCTL_OUT=`loginctl` +} + +test_properties() { + # Default KillUserProcesses should be off for debian/ubuntu builds + r=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager KillUserProcesses) + [ "$r" = "b false" ] +} + +# args: <timeout> +wait_suspend() { + timeout=$1 + while [ $timeout -gt 0 ] && [ ! -e /run/suspend.flag ]; do + sleep 1 + timeout=$((timeout - 1)) + [ $(($timeout % 5)) -ne 0 ] || echo " waiting for suspend, ${timeout}s remaining..." + done + if [ ! -e /run/suspend.flag ]; then + echo "closing lid did not cause suspend" >&2 + exit 1 + fi + rm /run/suspend.flag + echo " * closing lid caused suspend" +} + +test_suspend_on_lid() { + if systemd-detect-virt --quiet --container; then + echo " * Skipping suspend test in container" + return + fi + if ! grep -s -q mem /sys/power/state; then + echo " * suspend not supported on this testbed, skipping" + return + fi + + # cleanup handler + trap 'rm -f /run/udev/rules.d/70-logindtest-*.rules; udevadm control --reload; + kill $KILL_PID; + rm /run/systemd/system/systemd-suspend.service.d/override.conf; + if [ -d /sys/module/scsi_debug ]; then rmmod scsi_debug 2>/dev/null || (sleep 2; rmmod scsi_debug ) || true; fi' \ + EXIT INT QUIT TERM PIPE + + # watch what's going on + journalctl -f -u systemd-logind.service -u systemd-suspend.service & + KILL_PID="$KILL_PID $!" + + # create fake suspend + mkdir -p /run/systemd/system/systemd-suspend.service.d + cat >/run/systemd/system/systemd-suspend.service.d/override.conf <<EOF +[Service] +ExecStart= +ExecStart=touch /run/suspend.flag +EOF + sync + systemctl daemon-reload + + # create fake lid switch + mkdir -p /run/udev/rules.d + echo 'SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="Fake Lid Switch", TAG+="power-switch"' \ + > /run/udev/rules.d/70-logindtest-lid.rules + sync + udevadm control --reload + evemu-device $(dirname $0)/lidswitch.evemu & + KILL_PID="$KILL_PID $!" + while [ -z "$O" ]; do + sleep 0.1 + O=$(grep -l '^Fake Lid Switch' /sys/class/input/*/device/name) + done + O=${O%/device/name} + LID_DEV=/dev/${O#/sys/class/} + udevadm info --wait-for-initialization=10s $LID_DEV + udevadm settle + + # close lid + evemu-event $LID_DEV --sync --type 5 --code 0 --value 1 + # need to wait for 30s suspend inhibition after boot + wait_suspend 31 + # open lid again + evemu-event $LID_DEV --sync --type 5 --code 0 --value 0 + + echo " * waiting for 30s inhibition time between suspends" + sleep 30 + + # now closing lid should cause instant suspend + evemu-event $LID_DEV --sync --type 5 --code 0 --value 1 + wait_suspend 2 + evemu-event $LID_DEV --sync --type 5 --code 0 --value 0 + + P=$(pidof systemd-logind) + [ "$P" = "$LOGINDPID" ] || { echo "logind crashed" >&2; exit 1; } +} + +test_shutdown() { + echo " * scheduled shutdown with wall message" + shutdown 2>&1 + sleep 5 + shutdown -c || true + # logind should still be running + P=$(pidof systemd-logind) + [ "$P" = "$LOGINDPID" ] || { echo "logind crashed" >&2; exit 1; } + + echo " * scheduled shutdown without wall message" + shutdown --no-wall 2>&1 + sleep 5 + shutdown -c --no-wall || true + P=$(pidof systemd-logind) + [ "$P" = "$LOGINDPID" ] || { echo "logind crashed" >&2; exit 1; } +} + +test_in_logind_session() { + echo " * XDG_SESSION_ID=$XDG_SESSION_ID" + # cgroup v1: "1:name=systemd:/user.slice/..."; unified hierarchy: "0::/user.slice" + if grep -E '(name=systemd|^0:):.*session.*scope' /proc/self/cgroup; then + echo " * process is in session cgroup" + else + echo "FAIL: process is not in session cgroup" + echo "/proc/self/cgroup:" + cat /proc/self/cgroup + loginctl + loginctl show-session "$XDG_SESSION_ID" + exit 1 + fi +} + +test_acl() { + # ACL tests + if ! echo "$LOGINCTL_OUT" | grep -q "seat0"; then + echo " * Skipping ACL tests, as there is no seat" + return + fi + if systemd-detect-virt --quiet --container; then + echo " * Skipping ACL tests in container" + return + fi + + # determine user + USER=`echo "$OUT" | grep seat0 | awk '{print $3}'` + echo "seat user: $USER" + + # scsi_debug should not be loaded yet + ! test -d /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block + + # we use scsi_debug to create new devices which we can put ACLs on + # tell udev about the tagging, so that logind can pick it up + cat <<EOF > /run/udev/rules.d/70-logindtest-scsi_debug-user.rules +SUBSYSTEM=="block", ATTRS{model}=="scsi_debug*", TAG+="uaccess" +EOF + sync + udevadm control --reload + + echo " * coldplug: logind started with existing device" + killall systemd-logind + modprobe scsi_debug + while ! dev=/dev/`ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null`; do sleep 0.1; done + test -b $dev + echo "got block device $dev" + udevadm settle + # trigger logind + loginctl > /dev/null + sleep 1 + if getfacl -p $dev | grep -q "user:$USER:rw-"; then + echo "$dev has ACL for user $USER" + else + echo "$dev has no ACL for user $USER:" >&2 + getfacl -p $dev >&2 + exit 1 + fi + + rmmod scsi_debug + + echo " * hotplug: new device appears while logind is running" + modprobe scsi_debug + while ! dev=/dev/`ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block`; do sleep 0.1; done + test -b $dev + echo "got block device $dev" + udevadm settle + sleep 1 + if getfacl -p $dev | grep -q "user:$USER:rw-"; then + echo "$dev has ACL for user $USER" + else + echo "$dev has no ACL for user $USER:" >&2 + getfacl -p $dev >&2 + exit 1 + fi +} + +# +# main +# + +test_started +test_properties +test_in_logind_session +test_suspend_on_lid +test_shutdown +test_acl diff --git a/debian/tests/process-killer b/debian/tests/process-killer new file mode 100755 index 0000000..6ca10b8 --- /dev/null +++ b/debian/tests/process-killer @@ -0,0 +1,9 @@ +#!/bin/sh +# loop until we can kill the process given in arg + +while : +do + /usr/bin/pkill -x $* + [ $? -eq 0 ] && break + sleep 1 +done diff --git a/debian/tests/storage b/debian/tests/storage new file mode 100755 index 0000000..d1e42df --- /dev/null +++ b/debian/tests/storage @@ -0,0 +1,271 @@ +#!/usr/bin/env python3 +# systemd integration test: Handling of storage devices +# (C) 2015 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> + +import os +import random +import subprocess +import sys +import time +import unittest + +from glob import glob +from threading import Thread + + +TIMEOUT_SERVICE_START = 10 +TIMEOUT_PASSWORD_AGENT_STOP = 10 +TIMEOUT_PLAINTEXT_DEV = 30 +TIMEOUT_SCSI_DEBUG_ADD_HOST = 5 + +SCSI_DEBUG_DIR = '/sys/bus/pseudo/drivers/scsi_debug' + +class FakeDriveTestBase(unittest.TestCase): + @classmethod + def setUpClass(cls): + if os.path.isdir(SCSI_DEBUG_DIR): + return + + # Consider missing scsi_debug module a test failure + subprocess.check_call(['modprobe', 'scsi_debug', 'dev_size_mb=32']) + assert os.path.isdir(SCSI_DEBUG_DIR) + + def setUp(self): + existing_adapters = set(glob(os.path.join(SCSI_DEBUG_DIR, 'adapter*'))) + with open(os.path.join(SCSI_DEBUG_DIR, 'add_host'), 'w') as f: + f.write('1') + new_adapters = set(glob(os.path.join(SCSI_DEBUG_DIR, 'adapter*'))) - existing_adapters + self.assertEqual(len(new_adapters), 1) + self.adapter = new_adapters.pop() + for timeout in range(TIMEOUT_SCSI_DEBUG_ADD_HOST): + devices = set(glob(os.path.join(self.adapter, 'host*/target*/*:*/block/*'))) + if len(devices) > 0: + break + time.sleep(1) + else: + self.fail('Timed out waiting for scsi_debug block device name') + self.assertEqual(len(devices), 1) + self.device = os.path.join('/dev/', os.path.basename(devices.pop())) + + def tearDown(self): + existing_adapters = set(glob(os.path.join(SCSI_DEBUG_DIR, 'adapter*'))) + with open(os.path.join(SCSI_DEBUG_DIR, 'add_host'), 'w') as f: + f.write('-1') + removed_adapters = existing_adapters - set(glob(os.path.join(SCSI_DEBUG_DIR, 'adapter*'))) + self.assertEqual(len(removed_adapters), 1) + adapter = removed_adapters.pop() + self.assertEqual(self.adapter, adapter) + self.adapter = None + self.device = None + + +class CryptsetupTest(FakeDriveTestBase): + def setUp(self): + testname = self.id().split('.')[-1] + self.plaintext_name = 'testcrypt_%s' % testname + self.plaintext_dev = '/dev/mapper/' + self.plaintext_name + self.service_name = 'systemd-cryptsetup@%s.service' % self.plaintext_name + if os.path.exists(self.plaintext_dev): + self.fail('%s exists already' % self.plaintext_dev) + + super().setUp() + + if os.path.exists('/etc/crypttab'): + os.rename('/etc/crypttab', '/etc/crypttab.systemdtest') + self.password = 'pwd%i' % random.randint(1000, 10000) + self.password_agent = None + self.password_agent_stop = False + + def tearDown(self): + if self.password_agent: + self.password_agent_stop = True + self.password_agent.join(timeout=TIMEOUT_PASSWORD_AGENT_STOP) + self.assertFalse(self.password_agent.is_alive()) + self.password_agent = None + for timeout in range(TIMEOUT_SERVICE_START): + state = subprocess.run(['systemctl', 'show', '--no-pager', self.service_name, '--property', 'ActiveState'], + stdout=subprocess.PIPE, universal_newlines=True).stdout + state = state.strip().replace('ActiveState=', '', 1) + if state in ['active', 'failed']: + break + time.sleep(1) + else: + self.fail('Timed out waiting for %s to start (or fail)' % self.service_name) + subprocess.call(['umount', self.plaintext_dev], stderr=subprocess.DEVNULL) + if state == 'active': + subprocess.call(['systemctl', 'stop', self.service_name], stderr=subprocess.STDOUT) + if os.path.exists('/etc/crypttab'): + os.unlink('/etc/crypttab') + if os.path.exists('/etc/crypttab.systemdtest'): + os.rename('/etc/crypttab.systemdtest', '/etc/crypttab') + if os.path.exists(self.plaintext_dev): + subprocess.call(['dmsetup', 'remove', self.plaintext_dev], + stderr=subprocess.STDOUT) + subprocess.check_call(['systemctl', 'daemon-reload']) + + super().tearDown() + + def format_luks(self): + '''Format test device with LUKS''' + + p = subprocess.Popen(['cryptsetup', '--batch-mode', 'luksFormat', self.device, '-'], + stdin=subprocess.PIPE) + p.communicate(self.password.encode()) + self.assertEqual(p.returncode, 0) + os.sync() + subprocess.check_call(['udevadm', 'settle']) + + def start_password_agent(self): + '''Run password agent to answer passphrase request for crypt device''' + + # wait for incoming request + found = False + while not found: + for ask in glob('/run/systemd/ask-password/ask.*'): + with open(ask) as f: + contents = f.read() + if self.plaintext_name in contents: + found = True + break + if not found: + if self.password_agent_stop: + return + time.sleep(0.5) + + # parse Socket= + for line in contents.splitlines(): + if line.startswith('Socket='): + socket = line.split('=', 1)[1] + break + else: + self.fail('Could not find socket') + + # send reply + p = subprocess.Popen(['/lib/systemd/systemd-reply-password', '1', socket], + stdin=subprocess.PIPE) + p.communicate(self.password.encode()) + self.assertEqual(p.returncode, 0) + + def apply(self, target): + '''Tell systemd to generate and run the cryptsetup units''' + + subprocess.check_call(['systemctl', 'daemon-reload']) + + self.password_agent = Thread(target=self.start_password_agent); + self.password_agent.start() + subprocess.check_call(['systemctl', '--no-ask-password', 'restart', target]) + for timeout in range(TIMEOUT_PLAINTEXT_DEV): + if os.path.exists(self.plaintext_dev): + break + time.sleep(1) + else: + self.fail('Timed out waiting for %s to appear' % self.plaintext_dev) + + def test_luks_by_devname(self): + '''LUKS device by plain device name, empty''' + + self.format_luks() + with open('/etc/crypttab', 'w') as f: + f.write('%s %s none luks\n' % (self.plaintext_name, self.device)) + self.apply('cryptsetup.target') + + # should not be mounted + with open('/proc/mounts') as f: + self.assertNotRegex(f.read(), "(?<!/run/credentials/systemd-cryptsetup@)%s" % (self.plaintext_name)) + + # device should not have anything on it + p = subprocess.Popen(['blkid', self.plaintext_dev], stdout=subprocess.PIPE) + out = p.communicate()[0] + self.assertEqual(out, b'') + self.assertNotEqual(p.returncode, 0) + + def test_luks_by_uuid(self): + '''LUKS device by UUID, empty''' + + self.format_luks() + uuid = subprocess.check_output(['blkid', '-ovalue', '-sUUID', self.device], + universal_newlines=True).strip() + with open('/etc/crypttab', 'w') as f: + f.write('%s UUID=%s none luks\n' % (self.plaintext_name, uuid)) + self.apply('cryptsetup.target') + + # should not be mounted + with open('/proc/mounts') as f: + self.assertNotRegex(f.read(), "(?<!/run/credentials/systemd-cryptsetup@)%s" % (self.plaintext_name)) + + # device should not have anything on it + p = subprocess.Popen(['blkid', self.plaintext_dev], stdout=subprocess.PIPE) + out = p.communicate()[0] + self.assertEqual(out, b'') + self.assertNotEqual(p.returncode, 0) + + def test_luks_swap(self): + '''LUKS device with "swap" option''' + + self.format_luks() + with open('/etc/crypttab', 'w') as f: + f.write('%s %s none luks,swap\n' % (self.plaintext_name, self.device)) + self.apply('cryptsetup.target') + + # should not be mounted + with open('/proc/mounts') as f: + self.assertNotRegex(f.read(), "(?<!/run/credentials/systemd-cryptsetup@)%s" % (self.plaintext_name)) + + # device should be formatted with swap + out = subprocess.check_output(['blkid', '-ovalue', '-sTYPE', self.plaintext_dev]) + self.assertEqual(out, b'swap\n') + + def test_luks_tmp(self): + '''LUKS device with "tmp" option''' + + self.format_luks() + with open('/etc/crypttab', 'w') as f: + f.write('%s %s none luks,tmp\n' % (self.plaintext_name, self.device)) + self.apply('cryptsetup.target') + + # should not be mounted + with open('/proc/mounts') as f: + self.assertNotRegex(f.read(), "(?<!/run/credentials/systemd-cryptsetup@)%s" % (self.plaintext_name)) + + # device should be formatted with ext2 or (with newer systemd) ext4 + out = subprocess.check_output(['blkid', '-ovalue', '-sTYPE', self.plaintext_dev]) + self.assertRegex(out, b'ext[24]') + + def test_luks_fstab(self): + '''LUKS device in /etc/fstab''' + + self.format_luks() + with open('/etc/crypttab', 'w') as f: + f.write('%s %s none luks,tmp\n' % (self.plaintext_name, self.device)) + + mountpoint = '/run/crypt1.systemdtest' + os.mkdir(mountpoint) + self.addCleanup(os.rmdir, mountpoint) + os.rename('/etc/fstab', '/etc/fstab.systemdtest') + self.addCleanup(os.rename, '/etc/fstab.systemdtest', '/etc/fstab') + with open('/etc/fstab', 'a') as f: + with open('/etc/fstab.systemdtest') as forig: + f.write(forig.read()) + f.write('%s %s auto defaults 0 0\n' % (self.plaintext_dev, mountpoint)) + + # this should now be a requirement of local-fs.target + self.apply('local-fs.target') + + # should be mounted + found = False + with open('/proc/mounts') as f: + for line in f: + fields = line.split() + if fields[0] == self.plaintext_dev: + self.assertEqual(fields[1], mountpoint) + self.assertRegex(fields[2], 'ext[24]') + found = True + break + if not found: + self.fail('%s is not mounted' % self.plaintext_dev) + + +if __name__ == '__main__': + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, + verbosity=2)) diff --git a/debian/tests/timedated b/debian/tests/timedated new file mode 100755 index 0000000..0a69b67 --- /dev/null +++ b/debian/tests/timedated @@ -0,0 +1,186 @@ +#!/bin/sh +set -e + +. `dirname $0`/assert.sh + +ORIG_TZ=`readlink /etc/localtime | sed 's#^.*zoneinfo/##'` +echo "original tz: $ORIG_TZ" + +echo 'timedatectl works' +assert_in "Local time:" "`timedatectl --no-pager`" + +echo 'change timezone' +assert_eq "`timedatectl --no-pager set-timezone Europe/Moscow 2>&1`" "" +assert_eq "`readlink /etc/localtime | sed 's#^.*zoneinfo/##'`" "Europe/Moscow" +assert_in "Time.*zone: Europe/Moscow (MSK, +" "`timedatectl --no-pager`" + +echo 'reset timezone to original' +assert_eq "`timedatectl --no-pager set-timezone $ORIG_TZ 2>&1`" "" +assert_eq "`readlink /etc/localtime | sed 's#^.*zoneinfo/##'`" "$ORIG_TZ" + +# test setting UTC vs. LOCAL in /etc/adjtime +if [ -e /etc/adjtime ]; then + ORIG_ADJTIME=`cat /etc/adjtime` + trap "echo '$ORIG_ADJTIME' > /etc/adjtime" EXIT INT QUIT PIPE +else + trap "rm -f /etc/adjtime" EXIT INT QUIT PIPE +fi + +echo 'no adjtime file' +rm -f /etc/adjtime +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' + +echo 'UTC set in adjtime file' +printf '0.0 0 0\n0\nUTC\n' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +UTC" +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" + +echo 'non-zero values in adjtime file' +printf '0.1 123 0\n0\nLOCAL\n' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_eq "`cat /etc/adjtime`" "0.1 123 0 +0 +UTC" +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.1 123 0 +0 +LOCAL" + +echo 'fourth line adjtime file' +printf '0.0 0 0\n0\nLOCAL\nsomethingelse\n' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +UTC +somethingelse" +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL +somethingelse" + +echo 'no final newline in adjtime file' +printf '0.0 0 0\n0\nUTC' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' +printf '0.0 0 0\n0\nUTC' > /etc/adjtime +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" + +echo 'only one line in adjtime file' +printf '0.0 0 0\n' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' +printf '0.0 0 0\n' > /etc/adjtime +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" + +echo 'only one line in adjtime file, no final newline' +printf '0.0 0 0' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' +printf '0.0 0 0' > /etc/adjtime +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" + +echo 'only two lines in adjtime file' +printf '0.0 0 0\n0\n' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' +printf '0.0 0 0\n0\n' > /etc/adjtime +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" + + +echo 'only two lines in adjtime file, no final newline' +printf '0.0 0 0\n0' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' +printf '0.0 0 0\n0' > /etc/adjtime +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" + +echo 'unknown value in 3rd line of adjtime file' +printf '0.0 0 0\n0\nFOO\n' > /etc/adjtime +timedatectl set-local-rtc 0 +assert_true '[ ! -e /etc/adjtime ]' +printf '0.0 0 0\n0\nFOO\n' > /etc/adjtime +timedatectl set-local-rtc 1 +assert_eq "`cat /etc/adjtime`" "0.0 0 0 +0 +LOCAL" + +# timesyncd has ConditionVirtualization=!container by default; drop/mock that for testing +if systemd-detect-virt --container --quiet; then + systemctl disable --quiet --now systemd-timesyncd + mkdir -p /run/systemd/system/systemd-timesyncd.service.d + printf '[Unit]\nConditionVirtualization=\n[Service]\nType=simple\nAmbientCapabilities=\nExecStart=\nExecStart=/bin/sleep infinity' > /run/systemd/system/systemd-timesyncd.service.d/container.conf + systemctl daemon-reload +fi + +mon=$(mktemp -t dbusmon.XXXXXX) +trap "rm -f $mon" EXIT INT QUIT PIPE + +assert_ntp() { + V=$(busctl get-property org.freedesktop.timedate1 /org/freedesktop/timedate1 org.freedesktop.timedate1 NTP) + assert_eq "$V" "b $1" +} + +start_mon() { + dbus-monitor --system "type='signal', member='PropertiesChanged', path='/org/freedesktop/timedate1'" > $mon & + MONPID=$! +} + +wait_mon() { + for retry in $(seq 10); do + grep -q "$1" $mon && break + sleep 1 + done + assert_in "$2" "$(cat $mon)" + kill $MONPID + wait $MONPID 2>/dev/null || true +} + +echo 'disable NTP' +timedatectl set-ntp false +while [ "$(systemctl --no-pager show systemd-timesyncd --property ActiveState)" != "ActiveState=inactive" ]; do sleep 1; done +assert_ntp false +assert_rc 3 systemctl is-active --quiet systemd-timesyncd + +echo 'enable NTP' +start_mon +timedatectl set-ntp true +wait_mon "NTP" "boolean true" +assert_ntp true +while [ "$(systemctl --no-pager show systemd-timesyncd --property ActiveState)" != "ActiveState=active" ]; do sleep 1; done +assert_rc 0 systemctl is-active --quiet systemd-timesyncd + +echo 're-disable NTP' +start_mon +timedatectl set-ntp false +wait_mon "NTP" "boolean false" +assert_ntp false +assert_rc 3 systemctl is-active --quiet systemd-timesyncd diff --git a/debian/tests/unit-config b/debian/tests/unit-config new file mode 100755 index 0000000..aef3652 --- /dev/null +++ b/debian/tests/unit-config @@ -0,0 +1,370 @@ +#!/usr/bin/python3 +# autopkgtest check: enable/disable/configure units +# (C) 2015 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> + +import unittest +import subprocess +import os +import sys +import tempfile +from glob import glob + +system_unit_dir = subprocess.check_output( + ['pkg-config', '--variable=systemdsystemunitdir', 'systemd'], + universal_newlines=True).strip() +systemd_sysv_install = os.path.join(os.path.dirname(system_unit_dir), + 'systemd-sysv-install') + + +class EnableTests(unittest.TestCase): + def tearDown(self): + # remove all traces from our test unit + f = glob(system_unit_dir + '/test_enable*.service') + f += glob(system_unit_dir + '/*/test_enable*.service') + f += glob('/etc/systemd/system/test_enable*.service') + f += glob('/etc/systemd/system/*/test_enable*.service') + f += glob('/etc/init.d/test_enable*') + f += glob('/etc/rc?.d/???test_enable*') + [os.unlink(i) for i in f] + subprocess.check_call(['systemctl', 'daemon-reload']) + + def create_unit(self, suffix='', enable=False): + '''Create a test unit''' + + unit = os.path.join(system_unit_dir, + 'test_enable%s.service' % suffix) + with open(unit, 'w') as f: + f.write('''[Unit] +Description=Testsuite unit %s +[Service] +ExecStart=/bin/echo hello +[Install] +WantedBy=multi-user.target +''' % suffix) + + if enable: + os.symlink(unit, '/etc/systemd/system/multi-user.target.wants/' + + os.path.basename(unit)) + + return unit + + def create_sysv(self, suffix='', enable=False): + '''Create a test SysV script''' + + script = '/etc/init.d/test_enable%s' % suffix + with open(script, 'w') as f: + f.write('''/bin/sh +### BEGIN INIT INFO +# Provides: test_enable%s +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Testsuite script%s +### END INIT INFO + +echo hello +''' % (suffix, suffix)) + os.chmod(script, 0o755) + + if enable: + subprocess.check_call( + [systemd_sysv_install, 'enable', os.path.basename(script)]) + + def assertEnabled(self, enabled, unit='test_enable.service'): + '''assert that given unit has expected state''' + + systemctl = subprocess.Popen(['systemctl', 'is-enabled', unit], + stdout=subprocess.PIPE, + universal_newlines=True) + out = systemctl.communicate()[0].strip() + if enabled: + self.assertEqual(systemctl.returncode, 0) + self.assertEqual(out, 'enabled') + else: + self.assertEqual(systemctl.returncode, 1) + self.assertEqual(out, 'disabled') + + def test_unit_enable(self): + '''no sysv: enable unit''' + + self.create_unit() + self.assertEnabled(False) + # also works without .service suffix + self.assertEnabled(False, unit='test_enable') + + subprocess.check_call(['systemctl', 'enable', 'test_enable']) + + self.assertEnabled(True) + # also works without .service suffix + self.assertEnabled(True, unit='test_enable') + + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertTrue(os.path.islink(l)) + self.assertTrue(os.readlink(l) == system_unit_dir + '/test_enable.service' or + os.readlink(l) == '../test_enable.service') + + # enable should be idempotent + subprocess.check_call(['systemctl', 'enable', 'test_enable.service']) + self.assertEnabled(True) + + def test_unit_disable(self): + '''no sysv: disable unit''' + + self.create_unit(enable=True) + self.assertEnabled(True) + # also works without .service suffix + self.assertEnabled(True, unit='test_enable') + + subprocess.check_call(['systemctl', 'disable', 'test_enable']) + + self.assertEnabled(False) + # also works without .service suffix + self.assertEnabled(False, unit='test_enable') + + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertFalse(os.path.islink(l)) + + # disable should be idempotent + subprocess.check_call(['systemctl', 'disable', 'test_enable.service']) + self.assertEnabled(False) + + def test_unit_sysv_enable(self): + '''with sysv: enable unit''' + + self.create_unit() + self.create_sysv() + self.assertEnabled(False) + # also works without .service suffix + self.assertEnabled(False, unit='test_enable') + + subprocess.check_call(['systemctl', 'enable', 'test_enable']) + + self.assertEnabled(True) + # also works without .service suffix + self.assertEnabled(True, unit='test_enable') + + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertTrue(os.path.islink(l)) + self.assertTrue(os.readlink(l) == system_unit_dir + '/test_enable.service' or + os.readlink(l) == '../test_enable.service') + + # enabled the sysv script + l = glob('/etc/rc2.d/S??test_enable') + self.assertEqual(len(l), 1, 'expect one symlink in %s' % repr(l)) + self.assertEqual(os.readlink(l[0]), '../init.d/test_enable') + + # enable should be idempotent + subprocess.check_call(['systemctl', 'enable', 'test_enable.service']) + self.assertEnabled(True) + + def test_unit_sysv_disable(self): + '''with sysv: disable unit''' + + self.create_unit(enable=True) + self.create_sysv(enable=True) + self.assertEnabled(True) + # also works without .service suffix + self.assertEnabled(True, unit='test_enable') + + subprocess.check_call(['systemctl', 'disable', 'test_enable']) + + self.assertEnabled(False) + # also works without .service suffix + self.assertEnabled(False, unit='test_enable') + + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertFalse(os.path.islink(l)) + + # disabled the sysv script + l = glob('/etc/rc2.d/S??test_enable') + self.assertEqual(l, []) + + # disable should be idempotent + subprocess.check_call(['systemctl', 'enable', 'test_enable.service']) + self.assertEnabled(True) + + def test_unit_alias_enable(self): + '''no sysv: enable unit with an alias''' + + u = self.create_unit() + with open(u, 'a') as f: + f.write('Alias=test_enablea.service\n') + + self.assertEnabled(False) + + subprocess.check_call(['systemctl', 'enable', 'test_enable']) + + self.assertEnabled(True) + + # enablement symlink + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertTrue(os.path.islink(l)) + self.assertTrue(os.readlink(l) == system_unit_dir + '/test_enable.service' or + os.readlink(l) == '../test_enable.service') + + # alias symlink + l = '/etc/systemd/system/test_enablea.service' + self.assertTrue(os.path.islink(l)) + self.assertTrue(os.readlink(l) == system_unit_dir + '/test_enable.service' or + os.readlink(l) == 'test_enable.service') + + def test_unit_alias_disable(self): + '''no sysv: disable unit with an alias''' + + u = self.create_unit() + with open(u, 'a') as f: + f.write('Alias=test_enablea.service\n') + os.symlink(system_unit_dir + '/test_enable.service', + '/etc/systemd/system/test_enablea.service') + + subprocess.check_call(['systemctl', 'disable', 'test_enable']) + + self.assertEnabled(False) + + # enablement symlink + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertFalse(os.path.islink(l)) + + # alias symlink + l = '/etc/systemd/system/test_enablea.service' + self.assertFalse(os.path.islink(l)) + + def test_unit_sysv_alias_enable(self): + '''with sysv: enable unit with an alias''' + + u = self.create_unit() + with open(u, 'a') as f: + f.write('Alias=test_enablea.service\n') + self.create_sysv() + + self.assertEnabled(False) + + subprocess.check_call(['systemctl', 'enable', 'test_enable']) + + # enablement symlink + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertTrue(os.path.islink(l)) + self.assertTrue(os.readlink(l) == system_unit_dir + '/test_enable.service' or + os.readlink(l) == '../test_enable.service') + + # alias symlink + l = '/etc/systemd/system/test_enablea.service' + self.assertTrue(os.path.islink(l)) + self.assertTrue(os.readlink(l) == system_unit_dir + '/test_enable.service' or + os.readlink(l) == 'test_enable.service') + + # enabled the sysv script + l = glob('/etc/rc2.d/S??test_enable') + self.assertEqual(len(l), 1, 'expect one symlink in %s' % repr(l)) + self.assertEqual(os.readlink(l[0]), '../init.d/test_enable') + + self.assertEnabled(True) + + def test_unit_sysv_alias_disable(self): + '''with sysv: disable unit with an alias''' + + u = self.create_unit(enable=True) + with open(u, 'a') as f: + f.write('Alias=test_enablea.service\n') + os.symlink(system_unit_dir + '/test_enable.service', + '/etc/systemd/system/test_enablea.service') + self.create_sysv(enable=True) + + subprocess.check_call(['systemctl', 'disable', 'test_enable']) + + # enablement symlink + l = '/etc/systemd/system/multi-user.target.wants/test_enable.service' + self.assertFalse(os.path.islink(l)) + + # alias symlink + l = '/etc/systemd/system/test_enablea.service' + self.assertFalse(os.path.islink(l)) + + # disabled the sysv script + l = glob('/etc/rc2.d/S??test_enable') + self.assertEqual(l, []) + + self.assertEnabled(False) + + def test_sysv_enable(self): + '''only sysv: enable''' + + self.create_sysv() + subprocess.check_call(['systemctl', 'enable', 'test_enable']) + + # enabled the sysv script + l = glob('/etc/rc2.d/S??test_enable') + self.assertEqual(len(l), 1, 'expect one symlink in %s' % repr(l)) + self.assertEqual(os.readlink(l[0]), '../init.d/test_enable') + + # enable should be idempotent + subprocess.check_call(['systemctl', 'enable', 'test_enable']) + self.assertEnabled(True) + + def test_sysv_disable(self): + '''only sysv: disable''' + + self.create_sysv(enable=True) + subprocess.check_call(['systemctl', 'disable', 'test_enable']) + + # disabled the sysv script + l = glob('/etc/rc2.d/S??test_enable') + self.assertEqual(l, []) + + # disable should be idempotent + subprocess.check_call(['systemctl', 'disable', 'test_enable']) + self.assertEnabled(False) + + def test_unit_link(self): + '''systemctl link''' + + with tempfile.NamedTemporaryFile(suffix='.service') as f: + f.write(b'[Unit]\n') + f.flush() + subprocess.check_call(['systemctl', 'link', f.name]) + + unit = os.path.basename(f.name) + l = os.path.join('/etc/systemd/system', unit) + self.assertEqual(os.readlink(l), f.name) + + # disable it again + subprocess.check_call(['systemctl', 'disable', unit]) + # this should also remove the unit symlink + self.assertFalse(os.path.islink(l)) + + def test_unit_enable_full_path(self): + '''systemctl enable a unit in a non-default path''' + + with tempfile.NamedTemporaryFile(suffix='.service') as f: + f.write(b'''[Unit] +Description=test +[Service] +ExecStart=/bin/true +[Install] +WantedBy=multi-user.target''') + f.flush() + unit = os.path.basename(f.name) + + # now enable it + subprocess.check_call(['systemctl', 'enable', f.name]) + self.assertEnabled(True, unit=unit) + l = os.path.join('/etc/systemd/system', unit) + self.assertEqual(os.readlink(l), f.name) + enable_l = '/etc/systemd/system/multi-user.target.wants/' + unit + self.assertTrue(os.readlink(enable_l) == f.name or + os.readlink(enable_l) == '../' + unit) + + # disable it again + subprocess.check_call(['systemctl', 'disable', unit]) + # self.assertEnabled(False) does not work as now systemd does not + # know about the unit at all any more + self.assertFalse(os.path.islink(enable_l)) + # this should also remove the unit symlink + self.assertFalse(os.path.islink(l)) + + +if __name__ == '__main__': + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, + verbosity=2)) diff --git a/debian/tests/unit-tests b/debian/tests/unit-tests new file mode 100755 index 0000000..95ccb9b --- /dev/null +++ b/debian/tests/unit-tests @@ -0,0 +1,6 @@ +#!/bin/sh +# run upstream unit tests +set -e + +export ARTIFACT_DIRECTORY=$AUTOPKGTEST_ARTIFACTS +/usr/lib/systemd/tests/run-unit-tests.py diff --git a/debian/tests/upstream b/debian/tests/upstream new file mode 100755 index 0000000..850add1 --- /dev/null +++ b/debian/tests/upstream @@ -0,0 +1,31 @@ +#!/bin/sh +# run upstream system integration tests +# Author: Martin Pitt <martin.pitt@ubuntu.com> +set -e + +DPKGARCH=$(dpkg --print-architecture) + +# Because this test is used both by upstream and by Debian, we use different deny-list filenames. +# For more details see https://salsa.debian.org/systemd-team/systemd/merge_requests/52 +case "${DEB_BUILD_PROFILES:-}" in + *pkg.systemd.upstream*) + denylist="deny-list-ubuntu-ci" + if [ "$DPKGARCH" = ppc64el ]; then + export TEST_NO_QEMU=1 + fi + ;; + *) + denylist="deny-list-upstream-ci" + ;; +esac + +export DENY_LIST_MARKERS="$denylist-$DPKGARCH $denylist" +export ARTIFACT_DIRECTORY="$AUTOPKGTEST_ARTIFACTS" +export TEST_SAVE_JOURNAL=fail +export TEST_SHOW_JOURNAL=warning +export TEST_REQUIRE_INSTALL_TESTS=0 +export TEST_PREFER_NSPAWN=1 +export NO_BUILD=1 +export QEMU_TIMEOUT=2400 +export NSPAWN_TIMEOUT=2400 +test/run-integration-tests.sh |