diff options
author | Bastien Nocera <hadess@hadess.net> | 2022-07-22 17:56:37 +0200 |
---|---|---|
committer | Bastien Nocera <hadess@hadess.net> | 2022-07-24 19:56:26 +0200 |
commit | b7aade9391ab49ff551fd60e6f5c7e63f93ffd1b (patch) | |
tree | a58f26effe5be2c147916acf26c3e982aa12bfdb | |
parent | 1777c0393914880800a01ce146a56f975ca06f25 (diff) | |
download | accountsservice-b7aade9391ab49ff551fd60e6f5c7e63f93ffd1b.tar.gz |
tests: Add simple daemon test
Just checks whether the daemon can start.
-rw-r--r-- | src/meson.build | 2 | ||||
-rw-r--r-- | tests/meson.build | 8 | ||||
-rwxr-xr-x | tests/test-daemon.py | 234 |
3 files changed, 242 insertions, 2 deletions
diff --git a/src/meson.build b/src/meson.build index 95ecc24..6837a29 100644 --- a/src/meson.build +++ b/src/meson.build @@ -64,7 +64,7 @@ deps = [ polkit_gobject_dep, ] -executable( +daemon = executable( 'accounts-daemon', sources, include_directories: top_inc, diff --git a/tests/meson.build b/tests/meson.build index 0a72697..dedbe67 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -4,6 +4,8 @@ envs = environment() # Enable debug messages and abort on critical warnings envs.set('G_DEBUG', 'fatal-criticals') envs.set('G_MESSAGES_DEBUG', 'all') +envs.set('top_srcdir', meson.source_root()) +envs.set('top_builddir', meson.build_root()) envs.set('MOCKLIBC_LD_PRELOAD', libmocklibc.full_path()) # Setup paths @@ -30,6 +32,7 @@ endif tests = [ 'libaccountsservice', + 'daemon' ] if python3_test_modules_found @@ -87,5 +90,8 @@ foreach test_name : tests include_directories: top_inc, c_args: cflags, dependencies: deps) - test(test_name, exe) + test(test_name, exe, + depends: daemon, + env: envs, + ) endforeach diff --git a/tests/test-daemon.py b/tests/test-daemon.py new file mode 100755 index 0000000..74ddd37 --- /dev/null +++ b/tests/test-daemon.py @@ -0,0 +1,234 @@ +#!/usr/bin/python3 + +# accountsservice daemon test +# +# Copyright: (C) 2011 Martin Pitt <martin.pitt@ubuntu.com> +# (C) 2022 Bastien Nocera <hadess@hadess.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. + +import os +import sys +import dbus +import tempfile +import shutil +import subprocess +import unittest +import time + +try: + import gi + from gi.repository import GLib + from gi.repository import Gio +except ImportError as e: + sys.stderr.write('Skipping tests, PyGobject not available for Python 3, or missing GI typelibs: %s\n' % str(e)) + sys.exit(0) + +try: + import dbusmock +except ImportError: + sys.stderr.write('Skipping tests, python-dbusmock not available (http://pypi.python.org/pypi/python-dbusmock).\n') + sys.exit(0) + +if os.geteuid() == 0 or os.getuid() == 0: + sys.stderr.write('Skipping tests, daemon tests cannot run as root\n') + sys.exit(77) + +AD = 'org.freedesktop.Accounts' +AD_PATH = '/org/freedesktop/Accounts' + +class Tests(dbusmock.DBusTestCase): + @classmethod + def setUpClass(cls): + # run from local build tree if we are in one, otherwise use system instance + builddir = os.getenv('top_builddir', '.') + if os.access(os.path.join(builddir, 'src', 'accounts-daemon'), os.X_OK): + cls.daemon_path = os.path.join(builddir, 'src', 'accounts-daemon') + print('Testing binaries from local build tree (%s)' % cls.daemon_path) + elif os.environ.get('UNDER_JHBUILD', False): + jhbuild_prefix = os.environ['JHBUILD_PREFIX'] + cls.daemon_path = os.path.join(jhbuild_prefix, 'libexec', 'accounts-daemon') + print('Testing binaries from JHBuild (%s)' % cls.daemon_path) + else: + cls.daemon_path = None + with open('/usr/lib/systemd/system/accounts-daemon.service') as f: + for line in f: + if line.startswith('ExecStart='): + cls.daemon_path = line.split('=', 1)[1].strip() + break + assert cls.daemon_path, 'could not determine daemon path from systemd .service file' + print('Testing installed system binary (%s)' % cls.daemon_path) + + assert(os.getenv('top_srcdir')) + + # fail on CRITICALs on client side + GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_WARNING | + GLib.LogLevelFlags.LEVEL_ERROR | + GLib.LogLevelFlags.LEVEL_CRITICAL) + + # set up a fake system D-BUS + cls.test_bus = Gio.TestDBus.new(Gio.TestDBusFlags.NONE) + cls.test_bus.up() + try: + del os.environ['DBUS_SESSION_BUS_ADDRESS'] + except KeyError: + pass + os.environ['DBUS_SYSTEM_BUS_ADDRESS'] = cls.test_bus.get_bus_address() + + cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) + cls.dbus_con = cls.get_dbus(True) + + @classmethod + def tearDownClass(cls): + cls.test_bus.down() + dbusmock.DBusTestCase.tearDownClass() + + def setUp(self): + self.proxy = None + self.log = None + self.daemon = None + + def run(self, result=None): + super(Tests, self).run(result) + if result and len(result.errors) + len(result.failures) > 0 and self.log: + with open(self.log.name) as f: + sys.stderr.write('\n-------------- daemon log: ----------------\n') + sys.stderr.write(f.read()) + sys.stderr.write('------------------------------\n') + + def tearDown(self): + self.stop_daemon() + + # + # Daemon control and D-BUS I/O + # + + def start_daemon(self): + '''Start daemon and create DBus proxy. + + When done, this sets self.proxy as the Gio.DBusProxy for accounts-daemon. + ''' + env = os.environ.copy() + env['G_DEBUG'] = 'fatal-criticals' + env['G_MESSAGES_DEBUG'] = 'all' + + # Set up tempdir + self.test_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.test_dir) + srcdir = os.getenv('top_srcdir') + shutil.copytree(os.path.join(srcdir, 'tests', 'data', 'etc'), os.path.join(self.test_dir, 'etc')) + shutil.copytree(os.path.join(srcdir, 'tests', 'data', 'var'), os.path.join(self.test_dir, 'var')) + + rupert_path = os.path.join(self.test_dir, 'var', 'lib', 'AccountsService', 'users', 'rupert') + with open(rupert_path + '.in') as f: + content = f.read() + content = content.replace("@ROOTDIR@", self.test_dir) + os.remove(rupert_path + '.in') + with open(rupert_path, "w") as d: + d.write(content) + d.close() + + env['ROOTDIR'] = self.test_dir + env['LD_PRELOAD'] = os.getenv('MOCKLIBC_LD_PRELOAD') + env['MOCK_PASSWD'] = os.path.join(self.test_dir, 'etc', 'passwd') + env['MOCK_GROUP'] = os.path.join(self.test_dir, 'etc', 'group') + + self.log = tempfile.NamedTemporaryFile() + if os.getenv('VALGRIND') != None: + daemon_path = ['valgrind', self.daemon_path, '--debug'] + else: + daemon_path = [self.daemon_path, '--debug'] + + self.daemon = subprocess.Popen(daemon_path, + env=env, stdout=self.log, + stderr=subprocess.STDOUT) + + # wait until the daemon gets online + timeout = 100 + while timeout > 0: + time.sleep(0.1) + timeout -= 1 + try: + self.get_dbus_property('DaemonVersion') + break + except GLib.GError: + pass + else: + self.fail('daemon did not start in 10 seconds') + + self.proxy = Gio.DBusProxy.new_sync( + self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, AD, + AD_PATH, AD, None) + + self.assertEqual(self.daemon.poll(), None, 'daemon crashed') + + def stop_daemon(self): + '''Stop the daemon if it is running.''' + + if self.daemon: + try: + self.daemon.kill() + except OSError: + pass + self.daemon.wait() + self.daemon = None + self.proxy = None + + def get_dbus_property(self, name): + '''Get property value from daemon D-Bus interface.''' + + proxy = Gio.DBusProxy.new_sync( + self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, AD, + AD_PATH, 'org.freedesktop.DBus.Properties', None) + return proxy.Get('(ss)', AD, name) + + def have_text_in_log(self, text): + return self.count_text_in_log(text) > 0 + + def count_text_in_log(self, text): + with open(self.log.name) as f: + return f.read().count(text) + + def assertEventually(self, condition, message=None, timeout=50): + '''Assert that condition function eventually returns True. + + Timeout is in deciseconds, defaulting to 50 (5 seconds). message is + printed on failure. + ''' + while timeout >= 0: + context = GLib.MainContext.default() + while context.iteration(False): + pass + if condition(): + break + timeout -= 1 + time.sleep(0.1) + else: + self.fail(message or 'timed out waiting for ' + str(condition)) + + # + # Actual test cases + # + + def test_startup(self): + '''startup test''' + + self.start_daemon() + + process = subprocess.Popen(['gdbus', 'introspect', '--system', '--dest', AD, '--object-path', AD_PATH]) + process.wait() + # print (self.get_dbus_property('DaemonVersion')) + + self.stop_daemon() + + +if __name__ == '__main__': + unittest.main() |