Hello all,
We've had numerous problems with the SysV generator in the past, and
we just recently introduced another regression: init.d scripts which
end in ".sh" are now totally broken.
Thus I think it's high time to write some integration tests for that.
The attached patch provides the necessary framework and an initial set
of tests; e. g. test_multiple_provides() covers Michael's recent
commit b7e71846.
I can reproduce the ".sh" bug from above with a simple
| def test_sh_suffix(self):
| '''init.d script with .sh suffix'''
|
| self.add_sysv('foo.sh', {}, enable=True)
| err, results = self.run_generator()
| [... actual checks here, not written yet ...]
which currently fails with
| ======================================================================
| FAIL: test_sh_suffix (__main__.SysvGeneratorTest)
| init.d script with .sh suffix
| ----------------------------------------------------------------------
| Traceback (most recent call last):
| File "test/../test/sysv-generator-test.py", line 179, in test_sh_suffix
| err, results = self.run_generator()
| File "test/../test/sysv-generator-test.py", line 58, in run_generator
| self.assertFalse('Fail' in err, err)
| AssertionError: True is not false : Looking for unit files in (higher
priority first):
| /etc/systemd/system
| /run/systemd/system
| /usr/local/lib/systemd/system
| /lib/systemd/system
| /usr/lib/systemd/system
| Looking for SysV init scripts in:
| /tmp/sysv-gen-test.7qlq6kg2/init.d
| Looking for SysV rcN.d links in:
| /tmp/sysv-gen-test.7qlq6kg2
| Failed to create unit file /tmp/sysv-gen-test.7qlq6kg2/output/foo.service:
File exists
Indeed it just creates a symlink pointing to itself and nothing else.
I will look into that actual bug in a bit, and write a complete test
along with it. But before I spend more work on the tests, I'd
appreciate a quick review of it whether the general structure is ok
for you.
As this deals with temp dirs, cleaning them up, running external
programs, parsing their output etc., I chose Python for this, as this
stuff is just soooo much faster and convenient to write. We already
have test/rule-syntax-check.py, so there's precedent :-)
As automake's tests are rather limited and require a single command
without arguments, but I want to make this obey configure's $(PYTHON)
and skip the test properly if python 3 is not available, I created a
simple shell wrapper around it.
Obviously this is still lacking a lot of important cases; I'm happy to
add them later on, I just wanted to get some initial generic feedback.
Thanks,
Martin
--
Martin Pitt | http://www.piware.de
Ubuntu Developer (www.ubuntu.com) | Debian Developer (www.debian.org)
>From 7d4f85e42ff5a7a05477e712dcb58ab99d02a87a Mon Sep 17 00:00:00 2001
From: Martin Pitt <[email protected]>
Date: Tue, 20 Jan 2015 16:08:05 +0100
Subject: [PATCH] test: add initial integration test for systemd-sysv-generator
This is still missing a lot of important scenarios and corner cases, but
provides the groundwork and covers a recent bug (commit b7e718)
---
Makefile.am | 9 ++-
test/sysv-generator-test.py | 177 ++++++++++++++++++++++++++++++++++++++++++++
test/sysv-generator-test.sh | 33 +++++++++
3 files changed, 217 insertions(+), 2 deletions(-)
create mode 100644 test/sysv-generator-test.py
create mode 100755 test/sysv-generator-test.sh
diff --git a/Makefile.am b/Makefile.am
index 788e634..f7ae578 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3767,7 +3767,9 @@ endif
# ------------------------------------------------------------------------------
TESTS += \
test/udev-test.pl \
- test/rules-test.sh
+ test/rules-test.sh \
+ test/sysv-generator-test.sh \
+ $(NULL)
manual_tests += \
test-libudev \
@@ -3812,7 +3814,10 @@ EXTRA_DIST += \
test/sys.tar.xz \
test/udev-test.pl \
test/rules-test.sh \
- test/rule-syntax-check.py
+ test/rule-syntax-check.py \
+ test/sysv-generator-test.sh \
+ test/sysv-generator-test.py \
+ $(NULL)
# ------------------------------------------------------------------------------
ata_id_SOURCES = \
diff --git a/test/sysv-generator-test.py b/test/sysv-generator-test.py
new file mode 100644
index 0000000..a3f80ca
--- /dev/null
+++ b/test/sysv-generator-test.py
@@ -0,0 +1,177 @@
+# systemd-sysv-generator integration test
+#
+# (C) 2015 Canonical Ltd.
+# Author: Martin Pitt <[email protected]>
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import sys
+import os
+import subprocess
+import tempfile
+import configparser
+from glob import glob
+
+sysv_generator = os.path.join(os.environ.get('builddir', '.'), 'systemd-sysv-generator')
+
+
[email protected](os.path.exists(sysv_generator),
+ '%s does not exist' % sysv_generator)
+class SysvGeneratorTest(unittest.TestCase):
+ def setUp(self):
+ self.workdir = tempfile.TemporaryDirectory(prefix='sysv-gen-test.')
+ self.init_d_dir = os.path.join(self.workdir.name, 'init.d')
+ os.mkdir(self.init_d_dir)
+ self.rcnd_dir = self.workdir.name
+ self.out_dir = os.path.join(self.workdir.name, 'output')
+ os.mkdir(self.out_dir)
+
+ def run_generator(self, expect_error=False):
+ '''Run sysv-generator.
+
+ Fail if stderr contains any "Fail", unless expect_error is True.
+ Return (stderr, filename -> ConfigParser) pair with ouput to stderr and
+ parsed generated units.
+ '''
+ env = os.environ.copy()
+ env['SYSTEMD_LOG_LEVEL'] = 'debug'
+ env['SYSTEMD_SYSVINIT_PATH'] = self.init_d_dir
+ env['SYSTEMD_SYSVRCND_PATH'] = self.rcnd_dir
+ gen = subprocess.Popen(
+ [sysv_generator, 'ignored', 'ignored', self.out_dir],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=True, env=env)
+ (out, err) = gen.communicate()
+ if not expect_error:
+ self.assertFalse('Fail' in err, err)
+ self.assertEqual(gen.returncode, 0)
+
+ results = {}
+ for service in glob(self.out_dir + '/*.service'):
+ cp = configparser.RawConfigParser()
+ cp.optionxform = lambda o: o # don't lower-case option names
+ with open(service) as f:
+ cp.read_file(f)
+ results[os.path.basename(service)] = cp
+
+ return (err, results)
+
+ def add_sysv(self, fname, keys, enable=False):
+ '''Create a SysV init script with the given keys in the LSB header
+
+ There are sensible default values for all fields.
+
+ If enable is True, links will be created in the rcN.d dirs.
+ '''
+ name_without_sh = fname.endswith('.sh') and fname[:-3] or fname
+ keys.setdefault('Provides', name_without_sh)
+ keys.setdefault('Required-Start', '$local_fs')
+ keys.setdefault('Required-Stop', keys['Required-Start'])
+ keys.setdefault('Default-Start', '2 3 4 5')
+ keys.setdefault('Default-Stop', '0 1 6')
+ keys.setdefault('Short-Description', 'test %s service' %
+ name_without_sh)
+ keys.setdefault('Description', 'long description for test %s service' %
+ name_without_sh)
+ with open(os.path.join(self.init_d_dir, fname), 'w') as f:
+ f.write('#!/bin/init-d-interpreter\n### BEGIN INIT INFO\n')
+ for k, v in keys.items():
+ if v is not None:
+ f.write('#%20s %s\n' % (k + ':', v))
+ f.write('### END INIT INFO\ncode --goes here\n')
+ os.fchmod(f.fileno(), 0o755)
+
+ if enable:
+ def make_link(prefix, runlevel):
+ d = os.path.join(self.rcnd_dir, 'rc%s.d' % runlevel)
+ os.makedirs(d, exist_ok=True)
+ os.symlink('../init.d/' + fname, os.path.join(d, prefix + fname))
+
+ for rl in keys['Default-Start'].split():
+ make_link('S01', rl)
+ for rl in keys['Default-Stop'].split():
+ make_link('K01', rl)
+
+ def test_nothing(self):
+ '''no input files'''
+
+ results = self.run_generator()[1]
+ self.assertEqual(results, {})
+ self.assertEqual(os.listdir(self.out_dir), [])
+
+ def test_simple_disabled(self):
+ '''simple service without dependencies, disabled'''
+
+ self.add_sysv('foo', {}, enable=False)
+ err, results = self.run_generator()
+ self.assertEqual(len(results), 1)
+
+ # no enablement links or other stuff
+ self.assertEqual(os.listdir(self.out_dir), ['foo.service'])
+
+ s = results['foo.service']
+ self.assertEqual(s.sections(), ['Unit', 'Service'])
+ self.assertEqual(s.get('Unit', 'Description'), 'LSB: test foo service')
+ # $local_fs does not need translation, don't expect any dependency
+ # fields here
+ self.assertEqual(set(s.options('Unit')),
+ set(['Documentation', 'SourcePath', 'Description']))
+
+ self.assertEqual(s.get('Service', 'Type'), 'forking')
+ init_script = os.path.join(self.init_d_dir, 'foo')
+ self.assertEqual(s.get('Service', 'ExecStart'),
+ '%s start' % init_script)
+ self.assertEqual(s.get('Service', 'ExecStop'),
+ '%s stop' % init_script)
+
+ def test_simple_enabled(self):
+ '''simple service without dependencies, enabled'''
+
+ self.add_sysv('foo', {}, enable=True)
+ err, results = self.run_generator()
+ self.assertEqual(list(results.keys()), ['foo.service'])
+
+ # should be enabled
+ for runlevel in [2, 3, 4, 5]:
+ target = os.readlink(os.path.join(
+ self.out_dir, 'runlevel%i.target.wants' % runlevel, 'foo.service'))
+ self.assertTrue(os.path.exists(target))
+ self.assertEqual(os.path.basename(target), 'foo.service')
+
+ def test_lsb_dep_network(self):
+ '''LSB dependency: $network'''
+
+ self.add_sysv('foo', {'Required-Start': '$network'})
+ s = self.run_generator()[1]['foo.service']
+ self.assertEqual(set(s.options('Unit')),
+ set(['Documentation', 'SourcePath', 'Description', 'After', 'Wants']))
+ self.assertEqual(s.get('Unit', 'After'), 'network-online.target')
+ self.assertEqual(s.get('Unit', 'Wants'), 'network-online.target')
+
+ def test_multiple_provides(self):
+ '''multiple Provides: names'''
+
+ self.add_sysv('foo', {'Provides': 'foo bar baz'})
+ s = self.run_generator()[1]['foo.service']
+ self.assertEqual(set(s.options('Unit')),
+ set(['Documentation', 'SourcePath', 'Description']))
+ # should create symlinks for the alternative names
+ for f in ['bar.service', 'baz.service']:
+ self.assertEqual(os.readlink(os.path.join(self.out_dir, f)),
+ 'foo.service')
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
diff --git a/test/sysv-generator-test.sh b/test/sysv-generator-test.sh
new file mode 100755
index 0000000..dc26824
--- /dev/null
+++ b/test/sysv-generator-test.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Call the sysv-generator test, if python is available.
+#
+# (C) 2015 Canonical Ltd.
+# Author: Martin Pitt <[email protected]>
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+[ -n "$srcdir" ] || srcdir=`dirname $0`/..
+
+# skip if we don't have python3
+type ${PYTHON:=python3} >/dev/null 2>&1 || {
+ echo "$0: No $PYTHON installed, skipping udev rule syntax check"
+ exit 0
+}
+
+$PYTHON --version 2>&1 | grep -q ' 3.' || {
+ echo "$0: This check requires Python 3, skipping udev rule syntax check"
+ exit 0
+}
+
+$PYTHON $srcdir/test/sysv-generator-test.py
--
2.1.4
_______________________________________________
systemd-devel mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/systemd-devel