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

Reply via email to