diff options
Diffstat (limited to '')
-rw-r--r-- | debian/tests/assert.sh | 34 | ||||
-rwxr-xr-x | debian/tests/boot-and-services | 555 | ||||
-rwxr-xr-x | debian/tests/boot-smoke | 71 | ||||
-rwxr-xr-x | debian/tests/build-login | 38 | ||||
-rw-r--r-- | debian/tests/control | 192 | ||||
-rwxr-xr-x | debian/tests/fsck | 27 | ||||
-rwxr-xr-x | debian/tests/hostnamed | 22 | ||||
-rw-r--r-- | debian/tests/lidswitch.evemu | 34 | ||||
-rwxr-xr-x | debian/tests/localed-locale | 42 | ||||
-rwxr-xr-x | debian/tests/localed-x11-keymap | 52 | ||||
-rwxr-xr-x | debian/tests/logind | 204 | ||||
-rwxr-xr-x | debian/tests/process-killer | 9 | ||||
-rw-r--r-- | debian/tests/root-unittests | 26 | ||||
-rwxr-xr-x | debian/tests/storage | 248 | ||||
-rwxr-xr-x | debian/tests/systemd-fsckd | 297 | ||||
-rwxr-xr-x | debian/tests/timedated | 188 | ||||
-rwxr-xr-x | debian/tests/udev | 13 | ||||
-rwxr-xr-x | debian/tests/unit-config | 369 | ||||
-rwxr-xr-x | debian/tests/upstream | 53 |
19 files changed, 2474 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..fe2cdfb --- /dev/null +++ b/debian/tests/boot-and-services @@ -0,0 +1,555 @@ +#!/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'], + 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/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('network-manager') + + 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('TEST_UPSTREAM' in os.environ, + '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:.*[cC]ommand line:') + # has init messages + self.assertRegex(log, 'systemd.*Reached 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 + 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 + + # 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('TEST_UPSTREAM' in os.environ, + '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:.*[cC]ommand line:') + # has init messages + self.assertRegex(out, b'systemd.*Reached 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.assertIn(b'code=killed, signal=SYS', out) + 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) + + # 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''') + + +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..ed52bf6 --- /dev/null +++ b/debian/tests/boot-smoke @@ -0,0 +1,71 @@ +#!/bin/sh +# test 20 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 + +. `dirname $0`/assert.sh + +fail() { + journalctl --sync + journalctl -a > "$AUTOPKGTEST_ARTIFACTS/boot-smoke-journal.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 + + + 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 "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 + 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 + fi + + echo "checking that polkitd runs" + pidof polkitd + + echo "checking that there are no running jobs" + TIMEOUT=10 + while [ $TIMEOUT -ge 0 ]; do + running="$(systemctl --no-pager --no-legend list-jobs || true)" + [ -n "$running" ] || break + TIMEOUT=$((TIMEOUT - 1)) + done + if [ -n "$running" ]; then + echo "running jobs after remaining timeout $TIMEOUT: $running" + fail + fi +fi + +if [ "$AUTOPKGTEST_REBOOT_MARK" -ge 5 ]; 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/control b/debian/tests/control new file mode 100644 index 0000000..f7ea7cd --- /dev/null +++ b/debian/tests/control @@ -0,0 +1,192 @@ +Tests: timedated, hostnamed, localed-locale, localed-x11-keymap +Depends: systemd, + libpam-systemd, + libnss-systemd, + acl, + locales, +Restrictions: needs-root, isolation-container + +Tests: logind +Depends: systemd, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, +Restrictions: needs-root, isolation-container + +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, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, + python3, + pkg-config, + cryptsetup-bin, + systemd-sysv, + policykit-1, + dnsmasq-base +Restrictions: needs-root, isolation-container, flaky + +Tests: build-login +Depends: systemd, + libpam-systemd, + libnss-systemd, + acl, + locales, + evemu-tools, + python3, + pkg-config, + cryptsetup-bin, + systemd-sysv, + policykit-1, + dnsmasq-base, + build-essential, + libsystemd-dev, +Restrictions: isolation-container + +Tests: boot-and-services +Depends: systemd-sysv, + systemd-container, + systemd-coredump, + libpam-systemd, + xserver-xorg-video-dummy, + xserver-xorg, + gdm3 [!s390x], + cron, + network-manager, + busybox-static, + rsyslog, + apparmor, + pkg-config, + python3 +Restrictions: needs-root, isolation-container, breaks-testbed + +Tests: udev +Depends: systemd-tests, + python3, + tree, + perl, + xz-utils, +Restrictions: needs-root, allow-stderr, isolation-container + +Tests: root-unittests +Depends: systemd-tests, + libpam-systemd, + tree, + perl, + xz-utils, + libcap2-bin, + iproute2, + liblz4-tool, + acl, + iputils-ping, + dbus-user-session, +Restrictions: needs-root, allow-stderr, isolation-container + +Tests: upstream +Depends: libsystemd-dev, + tree, + perl, + xz-utils, + libcap2-bin, + iproute2, + liblz4-tool, + acl, + kbd, + cryptsetup-bin, + net-tools, + isc-dhcp-client, + iputils-ping, + strace, + qemu-system-x86 [amd64 i386], + qemu-system-arm [arm64 armhf], + qemu-system-s390x [s390x], + less, + pkg-config, + gcc, + libc6-dev | libc-dev, + make, + quota, + systemd-journal-remote, + systemd-container, + systemd-coredump, + fdisk | util-linux (<< 2.29.2-3~), + netcat-openbsd, + socat, + busybox-static, + plymouth, + e2fsprogs, +Restrictions: needs-root, allow-stderr, isolation-machine + +Tests: boot-smoke +Depends: libsystemd-dev, + tree, + perl, + xz-utils, + libcap2-bin, + iproute2, + liblz4-tool, + acl, + kbd, + cryptsetup-bin, + net-tools, + isc-dhcp-client, + iputils-ping, + strace, + qemu-system-x86 [amd64 i386], + qemu-system-arm [arm64 armhf], + qemu-system-s390x [s390x], + less, + pkg-config, + gcc, + libc6-dev | libc-dev, + make, + quota, + systemd-journal-remote, + systemd-container, + systemd-coredump, + systemd-sysv, + fdisk | util-linux (<< 2.29.2-3~), + netcat-openbsd, + busybox-static, + plymouth, + network-manager, + policykit-1, + gdm3 [!s390x], + xserver-xorg-video-dummy, +Restrictions: needs-root, isolation-container, allow-stderr, breaks-testbed + +# NOUPSTREAM: Do not run these tests for upstream builds + +Tests: systemd-fsckd +Depends: systemd-sysv, + python3, + plymouth +Restrictions: needs-root, isolation-machine, breaks-testbed diff --git a/debian/tests/fsck b/debian/tests/fsck new file mode 100755 index 0000000..77b50d7 --- /dev/null +++ b/debian/tests/fsck @@ -0,0 +1,27 @@ +#!/bin/bash +fd=0 + +OPTIND=1 +while getopts "C:aTlM" opt; do + case "$opt" in + C) + fd=$OPTARG + ;; + \?);; + esac +done + +shift "$((OPTIND-1))" +device=$1 + +echo "Running fake fsck on $device" + +declare -a maxpass=(30 5 2 30 60) + +for pass in {1..5}; do + maxprogress=${maxpass[$((pass-1))]} + for (( current=0; current<=${maxprogress}; current++)); do + echo "$pass $current $maxprogress $device">&$fd + sleep 0.1 + done +done 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..468258d --- /dev/null +++ b/debian/tests/localed-locale @@ -0,0 +1,42 @@ +#!/bin/sh +set -e + +. `dirname $0`/assert.sh + +if [ -n "$TEST_UPSTREAM" ]; then + LOCALE_CONF=/etc/locale.conf +else + LOCALE_CONF=/etc/default/locale +fi + +if ! ORIG_LOC=`grep -v '^#' $LOCALE_CONF 2>/dev/null`; then + # set up for a minimal unconfigured system + if [ -e /etc/locale.gen ]; then + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + fi + locale-gen en_US.UTF-8 + ORIG_LOC='LANG="en_US.UTF-8"' + echo "$ORIG_LOC" > $LOCALE_CONF +fi + +if ! [ -e /etc/default/keyboard ]; then + /bin/echo -e 'XKBMODEL=us\nXKBLAYOUT=pc105' > /etc/default/keyboard +fi + +# 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 $LOCALE_CONF`" "LANG=C +LC_CTYPE=en_US.UTF-8" + +! [ -f /etc/locale.conf ] + +STATUS=`localectl` +assert_in "System Locale: LANG=C" "$STATUS" +assert_in "LC_CTYPE=en_US.UTF-8" "$STATUS" + +# reset locale to original +echo "$ORIG_LOC" > $LOCALE_CONF diff --git a/debian/tests/localed-x11-keymap b/debian/tests/localed-x11-keymap new file mode 100755 index 0000000..34f4808 --- /dev/null +++ b/debian/tests/localed-x11-keymap @@ -0,0 +1,52 @@ +#!/bin/sh +set -e + +. `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 + +if [ -n "$TEST_UPSTREAM" ]; then + # 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`" +else + # 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 ] +fi + +STATUS=`localectl --no-pager` +assert_in "X11 Layout: et" "$STATUS" +assert_in "X11 Model: pc101" "$STATUS" + +# gets along without config file +if [ -z "$TEST_UPSTREAM" ]; then + rm /etc/default/keyboard + systemctl stop systemd-localed + assert_in "X11 Layout: n/a" "`localectl --no-pager`" +fi diff --git a/debian/tests/logind b/debian/tests/logind new file mode 100755 index 0000000..07a658b --- /dev/null +++ b/debian/tests/logind @@ -0,0 +1,204 @@ +#!/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 -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; + 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 & + KILL_PID="$KILL_PID $!" + + # create fake suspend + UNIT=$(systemctl show -pFragmentPath --value systemd-suspend.service) + sed '/^ExecStart=/ s_=.*$_=/bin/touch /run/suspend.flag_' $UNIT > /run/systemd/system/systemd-suspend.service + 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/} + + # 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/root-unittests b/debian/tests/root-unittests new file mode 100644 index 0000000..96416e2 --- /dev/null +++ b/debian/tests/root-unittests @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu + +EXFAIL="" + +res=0 +for t in /usr/lib/systemd/tests/test-*; do + tname=$(basename $t) + # test-udev needs special prep and has its own test + [ "$tname" != test-udev ] || continue + echo "====== $tname =======" + # exit code 77 means "skip" + rc=0 + $t || rc=$? + if [ "$rc" = 0 ]; then + echo "PASS: $tname" + elif [ "$rc" = 77 ]; then + echo "SKIP: $tname" + elif [ "${EXFAIL%$tname*}" != "$EXFAIL" ]; then + echo "EXFAIL: $tname" + else + echo "FAIL: $tname (code: $rc)" + res=$rc + fi +done +exit $res diff --git a/debian/tests/storage b/debian/tests/storage new file mode 100755 index 0000000..a8403e0 --- /dev/null +++ b/debian/tests/storage @@ -0,0 +1,248 @@ +#!/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 sys +import unittest +import subprocess +import time +import random +from glob import glob + + +@unittest.skipIf(os.path.isdir('/sys/module/scsi_debug'), + 'The scsi_debug module is already loaded') +class FakeDriveTestBase(unittest.TestCase): + @classmethod + def setUpClass(klass): + # create a fake SCSI hard drive + subprocess.check_call(['modprobe', 'scsi_debug', 'dev_size_mb=32']) + # wait until drive got created + sys_dirs = [] + while not sys_dirs: + sys_dirs = glob('/sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block') + time.sleep(0.1) + assert len(sys_dirs) == 1 + devs = os.listdir(sys_dirs[0]) + assert len(devs) == 1 + klass.device = '/dev/' + devs[0] + + @classmethod + def tearDownClass(klass): + # create a fake SCSI hard drive + subprocess.check_call(['rmmod', 'scsi_debug']) + + def tearDown(self): + # clear drive + with open(self.device, 'wb') as f: + block = b'0' * 1048576 + try: + while True: + f.write(block) + except OSError: + pass + subprocess.check_call(['udevadm', 'settle']) + subprocess.check_call(['systemctl', 'daemon-reload']) + + +class CryptsetupTest(FakeDriveTestBase): + def setUp(self): + self.plaintext_name = 'testcrypt1' + self.plaintext_dev = '/dev/mapper/' + 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 + + def tearDown(self): + if self.password_agent: + os.kill(self.password_agent, 9) + os.waitpid(self.password_agent, 0) + self.password_agent = None + subprocess.call(['umount', self.plaintext_dev], stderr=subprocess.DEVNULL) + subprocess.call(['systemctl', 'start', '--no-ask-password', 'systemd-cryptsetup@%s.service' % self.plaintext_name], + stderr=subprocess.STDOUT) + subprocess.call(['systemctl', 'stop', 'systemd-cryptsetup@%s.service' % self.plaintext_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) + + 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''' + + pid = os.fork() + if pid > 0: + self.password_agent = pid + return + + # 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 'disk scsi_debug' in contents and self.plaintext_name in contents: + found = True + break + if not found: + time.sleep(0.5) + + # parse Socket= + for line in contents.splitlines(): + if line.startswith('Socket='): + socket = line.split('=', 1)[1] + break + + # send reply + p = subprocess.Popen(['/lib/systemd/systemd-reply-password', '1', socket], + stdin=subprocess.PIPE) + p.communicate(self.password.encode()) + assert p.returncode == 0 + + os._exit(0) + + def apply(self, target): + '''Tell systemd to generate and run the cryptsetup units''' + + subprocess.check_call(['systemctl', 'daemon-reload']) + + self.start_password_agent() + subprocess.check_call(['systemctl', '--no-ask-password', 'restart', target]) + for timeout in range(50): + if os.path.exists(self.plaintext_dev): + break + time.sleep(0.1) + else: + self.fail('timed out 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.assertNotIn(self.plaintext_name, f.read()) + + # 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.assertNotIn(self.plaintext_name, f.read()) + + # 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.assertNotIn(self.plaintext_name, f.read()) + + # 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.assertNotIn(self.plaintext_name, f.read()) + + # device should be formatted with ext2 + out = subprocess.check_output(['blkid', '-ovalue', '-sTYPE', self.plaintext_dev]) + self.assertEqual(out, b'ext2\n') + + 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 ext2 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.assertEqual(fields[2], 'ext2') + 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/systemd-fsckd b/debian/tests/systemd-fsckd new file mode 100755 index 0000000..09d68f5 --- /dev/null +++ b/debian/tests/systemd-fsckd @@ -0,0 +1,297 @@ +#!/usr/bin/python3 +# autopkgtest check: Ensure that systemd-fsckd can report progress and cancel +# (C) 2015 Canonical Ltd. +# Author: Didier Roche <didrocks@ubuntu.com> + +from contextlib import suppress +import inspect +import fileinput +import os +import subprocess +import shutil +import stat +import sys +import unittest +from time import sleep, time + +GRUB_AUTOPKGTEST_CONFIG_PATH = "/etc/default/grub.d/50-cloudimg-settings.cfg" +TEST_AUTOPKGTEST_CONFIG_PATH = "/etc/default/grub.d/99-fsckdtest.cfg" + +SYSTEMD_ETC_SYSTEM_UNIT_DIR = "/etc/systemd/system/" +SYSTEMD_PROCESS_KILLER_PATH = os.path.join(SYSTEMD_ETC_SYSTEM_UNIT_DIR, "process-killer.service") + +SYSTEMD_FSCK_ROOT_PATH = "/lib/systemd/system/systemd-fsck-root.service" +SYSTEMD_FSCK_ROOT_ENABLE_PATH = os.path.join(SYSTEMD_ETC_SYSTEM_UNIT_DIR, 'local-fs.target.wants/systemd-fsck-root.service') + +SYSTEM_FSCK_PATH = '/sbin/fsck' +PROCESS_KILLER_PATH = '/sbin/process-killer' +SAVED_FSCK_PATH = "{}.real".format(SYSTEM_FSCK_PATH) + +FSCKD_TIMEOUT = 30 + + +class FsckdTest(unittest.TestCase): + '''Check that we run, report and can cancel fsck''' + + def __init__(self, test_name, after_reboot, return_code): + super().__init__(test_name) + self._test_name = test_name + self._after_reboot = after_reboot + self._return_code = return_code + + def setUp(self): + super().setUp() + # ensure we have our root fsck enabled by default (it detects it runs in a vm and doesn't pull the target) + # note that it can already exists in case of a reboot (as there was no tearDown as we wanted) + os.makedirs(os.path.dirname(SYSTEMD_FSCK_ROOT_ENABLE_PATH), exist_ok=True) + with suppress(FileExistsError): + os.symlink(SYSTEMD_FSCK_ROOT_PATH, SYSTEMD_FSCK_ROOT_ENABLE_PATH) + enable_plymouth() + + # note that the saved real fsck can still exists in case of a reboot (as there was no tearDown as we wanted) + if not os.path.isfile(SAVED_FSCK_PATH): + os.rename(SYSTEM_FSCK_PATH, SAVED_FSCK_PATH) + + # install mock fsck and killer + self.install_bin(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fsck'), + SYSTEM_FSCK_PATH) + self.install_bin(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'process-killer'), + PROCESS_KILLER_PATH) + + self.files_to_clean = [SYSTEMD_FSCK_ROOT_ENABLE_PATH, SYSTEM_FSCK_PATH, SYSTEMD_PROCESS_KILLER_PATH, PROCESS_KILLER_PATH] + + def tearDown(self): + # tearDown is only called once the test really ended (not while rebooting during tests) + for f in self.files_to_clean: + with suppress(FileNotFoundError): + os.remove(f) + os.rename(SAVED_FSCK_PATH, SYSTEM_FSCK_PATH) + super().tearDown() + + def test_fsckd_run(self): + '''Ensure we can reboot after a fsck was processed''' + if not self._after_reboot: + self.reboot() + else: + self.assertFsckdStop() + self.assertFsckProceeded() + self.assertSystemRunning() + + def test_fsckd_run_without_plymouth(self): + '''Ensure we can reboot without plymouth after a fsck was processed''' + if not self._after_reboot: + enable_plymouth(enable=False) + self.reboot() + else: + self.assertFsckdStop() + self.assertFsckProceeded(with_plymouth=False) + self.assertSystemRunning() + + def test_fsck_with_failure(self): + '''Ensure that a failing fsck doesn't prevent fsckd to stop''' + if not self._after_reboot: + self.install_process_killer_unit('fsck') + self.reboot() + else: + self.assertFsckdStop() + self.assertWasRunning('process-killer') + self.assertFalse(self.is_failed_unit('process-killer')) + self.assertFsckProceeded() + self.assertSystemRunning() + + def test_systemd_fsck_with_failure(self): + '''Ensure that a failing systemd-fsck doesn't prevent fsckd to stop''' + if not self._after_reboot: + self.install_process_killer_unit('systemd-fsck', kill=True) + self.reboot() + else: + self.assertFsckdStop() + self.assertProcessKilled() + self.assertTrue(self.is_failed_unit('systemd-fsck-root')) + self.assertWasRunning('systemd-fsckd') + self.assertWasRunning('plymouth-start') + self.assertSystemRunning() + + def test_systemd_fsckd_with_failure(self): + '''Ensure that a failing systemd-fsckd doesn't prevent system to boot''' + if not self._after_reboot: + self.install_process_killer_unit('systemd-fsckd', kill=True) + self.reboot() + else: + self.assertFsckdStop() + self.assertProcessKilled() + self.assertFalse(self.is_failed_unit('systemd-fsck-root')) + self.assertTrue(self.is_failed_unit('systemd-fsckd')) + self.assertWasRunning('plymouth-start') + self.assertSystemRunning() + + def test_systemd_fsck_with_plymouth_failure(self): + '''Ensure that a failing plymouth doesn't prevent fsckd to reconnect/exit''' + if not self._after_reboot: + self.install_process_killer_unit('plymouthd', kill=True) + self.reboot() + else: + self.assertFsckdStop() + self.assertWasRunning('process-killer') + self.assertFsckProceeded() + self.assertFalse(self.is_active_unit('plymouth-start')) + self.assertSystemRunning() + + def install_bin(self, source, dest): + '''install mock fsck''' + shutil.copy2(source, dest) + st = os.stat(dest) + os.chmod(dest, st.st_mode | stat.S_IEXEC) + + def is_active_unit(self, unit): + '''Check that given unit is active''' + + return subprocess.call(['systemctl', 'status', unit], + stdout=subprocess.PIPE) == 0 + + def is_failed_unit(self, unit): + '''Check that given unit failed''' + + p = subprocess.Popen(['systemctl', 'is-active', unit], stdout=subprocess.PIPE) + out, err = p.communicate() + if b'failed' in out: + return True + return False + + def assertWasRunning(self, unit, expect_running=True): + '''Assert that a given unit has been running''' + p = subprocess.Popen(['systemctl', 'status', '--no-pager', unit], + stdout=subprocess.PIPE, universal_newlines=True) + out = p.communicate()[0].strip() + if expect_running: + self.assertRegex(out, 'Active:.*since') + else: + self.assertNotRegex(out, 'Active:.*since') + self.assertIn(p.returncode, (0, 3)) + + def assertFsckdStop(self): + '''Ensure systemd-fsckd stops, which indicates no more fsck activity''' + timeout = time() + FSCKD_TIMEOUT + while time() < timeout: + if not self.is_active_unit('systemd-fsckd'): + return + sleep(1) + raise Exception("systemd-fsckd still active after {}s".format(FSCKD_TIMEOUT)) + + def assertFsckProceeded(self, with_plymouth=True): + '''Assert we executed most of the fsck-related services successfully''' + self.assertWasRunning('systemd-fsckd') + self.assertFalse(self.is_failed_unit('systemd-fsckd')) + self.assertTrue(self.is_active_unit('systemd-fsck-root')) # remains active after exit + if with_plymouth: + self.assertWasRunning('plymouth-start') + else: + self.assertWasRunning('plymouth-start', expect_running=False) + + def assertSystemRunning(self): + '''Assert that the system is running''' + + self.assertTrue(self.is_active_unit('default.target')) + + def assertProcessKilled(self): + '''Assert the targeted process was killed successfully''' + self.assertWasRunning('process-killer') + self.assertFalse(self.is_failed_unit('process-killer')) + + def reboot(self): + '''Reboot the system with the current test marker''' + subprocess.check_call(['/tmp/autopkgtest-reboot', "{}:{}".format(self._test_name, self._return_code)]) + + def install_process_killer_unit(self, process_name, kill=False): + '''Create a systemd unit which will kill process_name''' + with open(SYSTEMD_PROCESS_KILLER_PATH, 'w') as f: + f.write('''[Unit] +DefaultDependencies=no + +[Service] +Type=simple +ExecStart=/usr/bin/timeout 10 {} {} + +[Install] +WantedBy=systemd-fsck-root.service'''.format(PROCESS_KILLER_PATH, + '--signal SIGKILL {}'.format(process_name) if kill else process_name)) + subprocess.check_call(['systemctl', 'daemon-reload']) + subprocess.check_call(['systemctl', 'enable', 'process-killer'], stderr=subprocess.DEVNULL) + + +def enable_plymouth(enable=True): + '''ensure plymouth is enabled in grub config (doesn't reboot)''' + plymouth_enabled = 'splash' in open('/boot/grub/grub.cfg').read() + if enable and not plymouth_enabled: + if os.path.exists(GRUB_AUTOPKGTEST_CONFIG_PATH): + shutil.copy2(GRUB_AUTOPKGTEST_CONFIG_PATH, TEST_AUTOPKGTEST_CONFIG_PATH) + for line in fileinput.input([TEST_AUTOPKGTEST_CONFIG_PATH], inplace=True): + if line.startswith("GRUB_CMDLINE_LINUX_DEFAULT"): + print(line[:line.rfind('"')] + ' splash quiet"\n') + else: + os.makedirs(os.path.dirname(TEST_AUTOPKGTEST_CONFIG_PATH), exist_ok=True) + with open(TEST_AUTOPKGTEST_CONFIG_PATH, 'w') as f: + f.write('GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0 splash quiet"\n') + elif not enable and plymouth_enabled: + with suppress(FileNotFoundError): + os.remove(TEST_AUTOPKGTEST_CONFIG_PATH) + subprocess.check_call(['update-grub'], stderr=subprocess.DEVNULL) + + +def boot_with_systemd_distro(): + '''Reboot with systemd as init and distro setup for grub''' + enable_plymouth() + subprocess.check_call(['/tmp/autopkgtest-reboot', 'systemd-started']) + + +def getAllTests(unitTestClass): + '''get all test names in predictable sorted order from unitTestClass''' + return sorted([test[0] for test in inspect.getmembers(unitTestClass, predicate=inspect.isfunction) + if test[0].startswith('test_')]) + + +# AUTOPKGTEST_REBOOT_MARK contains the test name to pursue after reboot +# (to check results and states after reboot, mostly). +# we append the previous global return code (0 or 1) to it. +# Example: AUTOPKGTEST_REBOOT_MARK=test_foo:0 +if __name__ == '__main__': + if os.path.exists('/run/initramfs/fsck-root'): + print('SKIP: root file system is being checked by initramfs already') + sys.exit(0) + + all_tests = getAllTests(FsckdTest) + reboot_marker = os.getenv('AUTOPKGTEST_REBOOT_MARK') + + current_test_after_reboot = "" + if not reboot_marker: + boot_with_systemd_distro() + + # first test + if reboot_marker == "systemd-started": + current_test = all_tests[0] + return_code = 0 + else: + (current_test_after_reboot, return_code) = reboot_marker.split(':') + current_test = current_test_after_reboot + return_code = int(return_code) + + # loop on remaining tests to run + try: + remaining_tests = all_tests[all_tests.index(current_test):] + except ValueError: + print("Invalid value for AUTOPKGTEST_REBOOT_MARK, {} is not a valid test name".format(reboot_marker)) + sys.exit(2) + + # run all remaining tests + for test_name in remaining_tests: + after_reboot = False + # if this tests needed a reboot (and it has been performed), executes second part of it + if test_name == current_test_after_reboot: + after_reboot = True + suite = unittest.TestSuite() + suite.addTest(FsckdTest(test_name, after_reboot, return_code)) + result = unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite) + if len(result.failures) != 0 or len(result.errors) != 0: + return_code = 1 + + sys.exit(return_code) diff --git a/debian/tests/timedated b/debian/tests/timedated new file mode 100755 index 0000000..fe90e13 --- /dev/null +++ b/debian/tests/timedated @@ -0,0 +1,188 @@ +#!/bin/sh +set -e + +. `dirname $0`/assert.sh + +ORIG_TZ=`grep -v '^#' /etc/timezone` +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" +[ -n "$TEST_UPSTREAM" ] || assert_eq "`cat /etc/timezone`" "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" +[ -n "$TEST_UPSTREAM" ] || assert_eq "`cat /etc/timezone`" "$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 +} + +echo 'disable NTP' +timedatectl set-ntp false +while systemctl is-active --quiet systemd-timesyncd; 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 is-active systemd-timesyncd)" = "activating" ]; 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/udev b/debian/tests/udev new file mode 100755 index 0000000..9ef5384 --- /dev/null +++ b/debian/tests/udev @@ -0,0 +1,13 @@ +#!/bin/sh +# autopkgtest check: Run upstream udev test script +# (C) 2016 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> +set -euC + +TEST_DIR=${ADTTMP:=$(mktemp -d)} +mkdir -p $TEST_DIR/test +test/sys-script.py $TEST_DIR/test +cp test/udev-test.pl $TEST_DIR +cp /usr/lib/systemd/tests/manual/test-udev $TEST_DIR +cd $TEST_DIR +./udev-test.pl diff --git a/debian/tests/unit-config b/debian/tests/unit-config new file mode 100755 index 0000000..1cfa4d4 --- /dev/null +++ b/debian/tests/unit-config @@ -0,0 +1,369 @@ +#!/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.assertEqual(os.readlink(l), + system_unit_dir + '/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.assertEqual(os.readlink(l), + system_unit_dir + '/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.assertEqual(os.readlink(l), + system_unit_dir + '/test_enable.service') + + # alias symlink + l = '/etc/systemd/system/test_enablea.service' + self.assertTrue(os.path.islink(l)) + self.assertEqual(os.readlink(l), + system_unit_dir + '/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.assertEqual(os.readlink(l), + system_unit_dir + '/test_enable.service') + + # alias symlink + l = '/etc/systemd/system/test_enablea.service' + self.assertTrue(os.path.islink(l)) + self.assertEqual(os.readlink(l), + system_unit_dir + '/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.assertEqual(os.readlink(enable_l), f.name) + + # 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/upstream b/debian/tests/upstream new file mode 100755 index 0000000..4e434c7 --- /dev/null +++ b/debian/tests/upstream @@ -0,0 +1,53 @@ +#!/bin/sh +# run upstream system integration tests +# Author: Martin Pitt <martin.pitt@ubuntu.com> +set -e + +# even after installing policycoreutils this fails with +# "Failed to install /usr/libexec/selinux/hll/pp" +BLACKLIST="TEST-06-SELINUX" + +# some tests are flaky +BLACKLIST="$BLACKLIST +TEST-02-CRYPTSETUP +TEST-10-ISSUE-2467 +TEST-13-NSPAWN-SMOKE +TEST-16-EXTEND-TIMEOUT +TEST-17-UDEV-WANTS +" + +# quiesce Makefile.guess; not really relevant as systemd/nspawn run from +# installed packages +export BUILD_DIR=. + +# modify the image build scripts to install systemd from the debs instead of +# from a "make/ninja install" as we don't have a built tree here. Also call +# systemd-nspawn from the system. +sed -i '/DESTDIR.* install/ s%^.*$% for p in `grep ^Package: '`pwd`'/debian/control | cut -f2 -d\\ |grep -Ev -- "-(udeb|dev)"`; do (cd /tmp; apt-get download $p \&\& dpkg-deb --fsys-tarfile ${p}[._]*deb | tar -C $initdir --dereference -x); done%; s_[^" ]*/systemd-nspawn_systemd-nspawn_g; s/\(_ninja_bin=\).*/\1dummy-ninja/' test/test-functions + +# adjust path +sed -i 's_/usr/libexec/selinux/hll/pp_/usr/lib/selinux/hll/pp_' test/TEST-06-SELINUX/test.sh + +FAILED="" + +for t in test/TEST*; do + echo "$BLACKLIST" | grep -q "$(basename $t)" && continue + echo "========== `basename $t` ==========" + rm -rf /var/tmp/systemd-test.* + if ! make -C $t setup run clean; then + for j in /var/tmp/systemd-test.*/journal/*; do + [ -e "$j" ] || continue + # keep the entire journal in artifacts, in case one needs the debug messages + cp -r "$j" "$AUTOPKGTEST_ARTIFACTS/$(basename $t)-$(basename $j)" + echo "---- $j ----" + journalctl --priority=warning --directory=$j + done + FAILED="$FAILED $t" + fi + echo +done + +if [ -n "$FAILED" ]; then + echo FAILED TESTS: "$FAILED" + exit 1 +fi |