On Tue, Jan 20, 2015 at 04:19:24PM +0100, Martin Pitt wrote: > 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 <martin.p...@ubuntu.com> > 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 <martin.p...@ubuntu.com> > +# > +# 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') > + > + > +@unittest.skipUnless(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') Python part looks nice. > +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 <martin.p...@ubuntu.com> > +# > +# 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 Maybe we could do this check in configure.ac/Makefile.am (add the test to the list conditinally)? We already test for python presence and extract the version, so we shouldn't duplicate the tests here and have an extra wrapper. Zbyszek _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel