summaryrefslogtreecommitdiffstats
path: root/debian/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:19 +0000
commitb7e8d5af06ee840cc48217ca4629cf28aeeb3c50 (patch)
tree6aa8dc840b085de50db46e2ad0725eae248fef02 /debian/tests
parentAdding upstream version 252.22. (diff)
downloadsystemd-debian/252.22-1_deb12u1.tar.xz
systemd-debian/252.22-1_deb12u1.zip
Adding debian version 252.22-1~deb12u1.debian/252.22-1_deb12u1debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/tests')
-rw-r--r--debian/tests/assert.sh34
-rwxr-xr-xdebian/tests/boot-and-services569
-rwxr-xr-xdebian/tests/boot-smoke95
-rwxr-xr-xdebian/tests/build-login38
-rw-r--r--debian/tests/control202
-rwxr-xr-xdebian/tests/hostnamed22
-rw-r--r--debian/tests/lidswitch.evemu34
-rwxr-xr-xdebian/tests/localed-locale63
-rwxr-xr-xdebian/tests/localed-x11-keymap52
-rwxr-xr-xdebian/tests/logind210
-rwxr-xr-xdebian/tests/process-killer9
-rwxr-xr-xdebian/tests/storage271
-rwxr-xr-xdebian/tests/timedated186
-rwxr-xr-xdebian/tests/udev13
-rwxr-xr-xdebian/tests/unit-config370
-rwxr-xr-xdebian/tests/unit-tests6
-rwxr-xr-xdebian/tests/upstream27
17 files changed, 2201 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..1208922
--- /dev/null
+++ b/debian/tests/boot-and-services
@@ -0,0 +1,569 @@
+#!/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('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:.*')
+ # 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('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:.*')
+ # 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''')
+
+
+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/control b/debian/tests/control
new file mode 100644
index 0000000..1131fe5
--- /dev/null
+++ b/debian/tests/control
@@ -0,0 +1,202 @@
+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
+
+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 | policykit-1,
+ 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 | 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, allow-stderr
+
+Tests: udev
+Depends: systemd-tests,
+ python3,
+ tree,
+ perl,
+ xz-utils,
+Restrictions: needs-root, allow-stderr, isolation-container, skippable
+
+Tests: unit-tests
+Depends: systemd-tests,
+ libpam-systemd,
+ libnss-myhostname,
+ libnss-mymachines,
+ libnss-resolve,
+ libnss-systemd,
+ udev,
+ tree,
+ perl,
+ python3,
+ python3-colorama,
+ xz-utils,
+ libcap2-bin,
+ iproute2,
+ liblz4-tool,
+ acl,
+ iputils-ping,
+ dbus-user-session,
+ zstd,
+ libtss2-dev,
+ libfido2-dev,
+ libdw-dev,
+ libqrencode-dev,
+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,
+ 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,
+ python3-pexpect,
+ screen,
+ swtpm,
+ tpm2-tools,
+ openssl,
+ bsdutils,
+ knot,
+ knot-dnssecutils | knot-dnsutils,
+ bind9-dnsutils,
+ bind9-host,
+ jq,
+ psmisc,
+ xkb-data,
+ locales,
+ locales-all,
+ stress,
+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
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..472518f
--- /dev/null
+++ b/debian/tests/localed-locale
@@ -0,0 +1,63 @@
+#!/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 [ -f "$LOCALE_CONF" ]; then
+ cp "$LOCALE_CONF" "${LOCALE_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
+
+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"
+
+if [ -z "$TEST_UPSTREAM" ]; then
+ ! [ -f /etc/locale.conf ]
+fi
+
+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 $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 "${LOCALE_CONF}.orig" ]; then
+ mv "${LOCALE_CONF}.orig" "$LOCALE_CONF"
+else
+ rm "$LOCALE_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..b1818a8
--- /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: (unset)" "`localectl --no-pager`"
+fi
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..b64cd63
--- /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.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 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/udev b/debian/tests/udev
new file mode 100755
index 0000000..b294cfb
--- /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=${AUTOPKGTEST_TMP:=$(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..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..392d5d5
--- /dev/null
+++ b/debian/tests/unit-tests
@@ -0,0 +1,6 @@
+#!/bin/sh
+# run upstream unit tests
+set -e
+
+export ARTIFACT_DIRECTORY=$AUTOPKGTEST_ARTIFACTS
+test/run-unit-tests.py
diff --git a/debian/tests/upstream b/debian/tests/upstream
new file mode 100755
index 0000000..f05f14c
--- /dev/null
+++ b/debian/tests/upstream
@@ -0,0 +1,27 @@
+#!/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
+# The naming is transitioning from blacklist to deny-list, so currently both are supported
+# More details in https://github.com/systemd/systemd/pull/16262
+if [ -n "$TEST_UPSTREAM" ]; then
+ denylist="deny-list-ubuntu-ci"
+ blacklist="blacklist-ubuntu-ci"
+else
+ denylist="deny-list-upstream-ci"
+ blacklist="blacklist-upstream-ci"
+fi
+
+export BLACKLIST_MARKERS="$blacklist-$DPKGARCH $blacklist $denylist-$DPKGARCH $denylist"
+export DENY_LIST_MARKERS="$blacklist-$DPKGARCH $blacklist $denylist-$DPKGARCH $denylist"
+export ARTIFACT_DIRECTORY=$AUTOPKGTEST_ARTIFACTS
+export TEST_SHOW_JOURNAL=warning
+export TEST_REQUIRE_INSTALL_TESTS=0
+export TEST_PREFER_NSPAWN=1
+export NO_BUILD=1
+test/run-integration-tests.sh