+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1+
+#
# systemd-sysv-generator integration test
#
-# (C) 2015 Canonical Ltd.
+# © 2015 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@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 collections
import os
+import shutil
import subprocess
+import sys
import tempfile
-import shutil
-from glob import glob
+import unittest
-try:
- from configparser import RawConfigParser
-except ImportError:
- # python 2
- from ConfigParser import RawConfigParser
+from configparser import RawConfigParser
+from glob import glob
-sysv_generator = os.path.join(os.environ.get('builddir', '.'), 'systemd-sysv-generator')
+sysv_generator = './systemd-sysv-generator'
+class MultiDict(collections.OrderedDict):
+ def __setitem__(self, key, value):
+ if isinstance(value, list) and key in self:
+ self[key].extend(value)
+ else:
+ super(MultiDict, self).__setitem__(key, value)
class SysvGeneratorTest(unittest.TestCase):
def setUp(self):
'''Run sysv-generator.
Fail if stderr contains any "Fail", unless expect_error is True.
- Return (stderr, filename -> ConfigParser) pair with ouput to stderr and
+ Return (stderr, filename -> ConfigParser) pair with output to stderr and
parsed generated units.
'''
env = os.environ.copy()
for service in glob(self.out_dir + '/*.service'):
if os.path.islink(service):
continue
- cp = RawConfigParser()
+ try:
+ # for python3 we need here strict=False to parse multiple
+ # lines with the same key
+ cp = RawConfigParser(dict_type=MultiDict, strict=False)
+ except TypeError:
+ # RawConfigParser in python2 does not have the strict option
+ # but it allows multiple lines with the same key by default
+ cp = RawConfigParser(dict_type=MultiDict)
cp.optionxform = lambda o: o # don't lower-case option names
with open(service) as f:
cp.readfp(f)
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)
+ keys.setdefault('Short-Description', 'test {} service'.format(name_without_sh))
+ keys.setdefault('Description', 'long description for test {} service'.format(name_without_sh))
script = os.path.join(self.init_d_dir, fname)
with open(script, '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('#{:>20} {}\n'.format(k + ':', v))
f.write('### END INIT INFO\ncode --goes here\n')
os.chmod(script, 0o755)
if enable:
def make_link(prefix, runlevel):
- d = os.path.join(self.rcnd_dir, 'rc%s.d' % runlevel)
+ d = os.path.join(self.rcnd_dir, 'rc{}.d'.format(runlevel))
if not os.path.isdir(d):
os.mkdir(d)
os.symlink('../init.d/' + fname, os.path.join(d, prefix + fname))
# should be enabled
for target in all_targets:
- link = os.path.join(self.out_dir, '%s.target.wants' % target, unit)
+ link = os.path.join(self.out_dir, '{}.target.wants'.format(target), unit)
if target in targets:
unit_file = os.readlink(link)
- self.assertTrue(os.path.exists(unit_file))
+ # os.path.exists() will fail on a dangling symlink
+ self.assertTrue(os.path.exists(link))
self.assertEqual(os.path.basename(unit_file), unit)
else:
self.assertFalse(os.path.exists(link),
- '%s unexpectedly exists' % link)
+ '{} unexpectedly exists'.format(link))
#
# test cases
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)
+ '{} start'.format(init_script))
self.assertEqual(s.get('Service', 'ExecStop'),
- '%s stop' % init_script)
+ '{} stop'.format(init_script))
self.assertNotIn('Overwriting', err)
self.add_sysv('foo+', {})
self.add_sysv('foo-admin', {})
err, results = self.run_generator()
- self.assertEqual(list(results), ['foo-admin.service', 'foo\\x2b.service'])
+ self.assertEqual(set(results), {'foo-admin.service', 'foo\\x2b.service'})
self.assertNotIn('Overwriting', err)
def test_simple_enabled_some(self):
s = self.run_generator()[1]['foo.service']
self.assertEqual(set(s.options('Unit')),
set(['Documentation', 'SourcePath', 'Description', 'After']))
- self.assertEqual(s.get('Unit', 'After'), 'nss-lookup.target rpcbind.target')
+ self.assertEqual(s.get('Unit', 'After').split(), ['nss-lookup.target', 'rpcbind.target'])
def test_lsb_deps(self):
'''LSB header dependencies to other services'''
d = os.path.join(self.rcnd_dir, 'rc2.d')
if not os.path.isdir(d):
os.mkdir(d)
- os.symlink('../init.d/' + name, os.path.join(d, 'S%02i%s' % (prio, name)))
+ os.symlink('../init.d/' + name, os.path.join(d, 'S{:>2}{}'.format(prio, name)))
err, results = self.run_generator()
self.assertEqual(sorted(results), ['consumer.service', 'provider.service'])
err, results = self.run_generator()
self.assertEqual(list(results), ['foo.service'])
self.assertEqual(os.readlink(os.path.join(self.out_dir, 'foo\\x2b.service')),
- 'foo.service')
+ 'foo.service')
self.assertNotIn('Overwriting', err)
def test_same_provides_in_multiple_scripts(self):
# calls correct script with .sh
init_script = os.path.join(self.init_d_dir, 'foo.sh')
self.assertEqual(s.get('Service', 'ExecStart'),
- '%s start' % init_script)
+ '{} start'.format(init_script))
self.assertEqual(s.get('Service', 'ExecStop'),
- '%s stop' % init_script)
+ '{} stop'.format(init_script))
self.assert_enabled('foo.service', ['multi-user', 'graphical'])
# backup files (not enabled in rcN.d/)
shutil.copy(script, script + '.bak')
shutil.copy(script, script + '.old')
+ shutil.copy(script, script + '.tmp')
+ shutil.copy(script, script + '.new')
err, results = self.run_generator()
print(err)
- self.assertEqual(sorted(results),
- ['foo.bak.service', 'foo.old.service', 'foo.service'])
+ self.assertEqual(sorted(results), ['foo.service', 'foo.tmp.service'])
# ensure we don't try to create a symlink to itself
self.assertNotIn('itself', err)