[tracker/sam/umockdev: 3/3] trackertestutils: Add an optional D-Bus system bus to the sandbox



commit 5c5728149100d87147c85ad2fac46f3cd9d4e25e
Author: Sam Thursfield <sam afuera me uk>
Date:   Thu May 21 12:42:24 2020 +0200

    trackertestutils: Add an optional D-Bus system bus to the sandbox
    
    This allows us to work with umockdev to simulate hardware events.

 tests/functional-tests/fixtures.py   |  4 +--
 utils/trackertestutils/__main__.py   |  2 +-
 utils/trackertestutils/dbusdaemon.py | 64 ++++++++++++++++++++++++++++++------
 utils/trackertestutils/dconf.py      |  2 +-
 utils/trackertestutils/helpers.py    |  2 +-
 utils/trackertestutils/sandbox.py    | 58 ++++++++++++++++++++++++--------
 6 files changed, 103 insertions(+), 29 deletions(-)
---
diff --git a/tests/functional-tests/fixtures.py b/tests/functional-tests/fixtures.py
index b42d742c4..08d16da17 100644
--- a/tests/functional-tests/fixtures.py
+++ b/tests/functional-tests/fixtures.py
@@ -55,9 +55,9 @@ def tracker_test_main():
         # only errors and warnings should be output here unless the environment
         # contains G_MESSAGES_DEBUG=.
         handler_stderr = logging.StreamHandler(stream=sys.stderr)
-        handler_stderr.addFilter(logging.Filter('trackertestutils.dbusdaemon.stderr'))
+        handler_stderr.addFilter(logging.Filter('sandbox-session-bus.stderr'))
         handler_stdout = logging.StreamHandler(stream=sys.stderr)
-        handler_stdout.addFilter(logging.Filter('trackertestutils.dbusdaemon.stdout'))
+        handler_stdout.addFilter(logging.Filter('sandbox-session-bus.stdout'))
         logging.basicConfig(level=logging.INFO,
                             handlers=[handler_stderr, handler_stdout],
                             format='%(message)s')
diff --git a/utils/trackertestutils/__main__.py b/utils/trackertestutils/__main__.py
index eb75279a9..f5136c81a 100644
--- a/utils/trackertestutils/__main__.py
+++ b/utils/trackertestutils/__main__.py
@@ -272,7 +272,7 @@ class MinerStatusWatch():
     def setup(self):
         log.debug(f"Set up status watch on {self.dbus_name}")
         self._proxy = Gio.DBusProxy.new_sync(
-            self._sandbox.get_connection(),
+            self._sandbox.get_session_bus_connection(),
             Gio.DBusProxyFlags.NONE, None,
             self.dbus_name, self.object_path, 'org.freedesktop.Tracker3.Miner',
             None)
diff --git a/utils/trackertestutils/dbusdaemon.py b/utils/trackertestutils/dbusdaemon.py
index e55c03595..67c947af5 100644
--- a/utils/trackertestutils/dbusdaemon.py
+++ b/utils/trackertestutils/dbusdaemon.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018,2019, Sam Thursfield <sam afuera me uk>
+# Copyright (C) 2018-2020, Sam Thursfield <sam afuera me uk>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -26,19 +26,49 @@ import signal
 import subprocess
 import threading
 
+from . import mainloop
+
+DEFAULT_TIMEOUT = 10
+
 log = logging.getLogger(__name__)
-dbus_stderr_log = logging.getLogger(__name__ + '.stderr')
-dbus_stdout_log = logging.getLogger(__name__ + '.stdout')
 
 
 class DaemonNotStartedError(Exception):
     pass
 
 
+def await_bus_name(conn, bus_name, timeout=DEFAULT_TIMEOUT):
+    """Blocks until 'bus_name' has an owner."""
+
+    log.info("Blocking until name %s has owner", bus_name)
+    loop = mainloop.MainLoop()
+
+    def name_appeared_cb(connection, name, name_owner):
+        log.info("Name %s appeared (owned by %s)", name, name_owner)
+        loop.quit()
+
+    def timeout_cb():
+        log.info("Timeout fired after %s seconds", timeout)
+        raise AwaitTimeoutException(
+            f"Timeout awaiting bus name '{bus_name}'")
+
+    watch_id = Gio.bus_watch_name_on_connection(
+        conn, bus_name, Gio.BusNameWatcherFlags.NONE, name_appeared_cb, None)
+    timeout_id = GLib.timeout_add_seconds(timeout, timeout_cb)
+
+    loop.run_checked()
+
+    Gio.bus_unwatch_name(watch_id)
+    GLib.source_remove(timeout_id)
+
+
 class DBusDaemon:
-    """The private D-Bus instance that provides the sandbox's session bus."""
+    """A private D-Bus daemon instance."""
+
+    def __init__(self, config_file=None, name='dbus-daemon'):
+        self.name = name
+        self.config_file = config_file
 
-    def __init__(self):
         self.process = None
 
         self.address = None
@@ -77,12 +107,13 @@ class DBusDaemon:
 
         return dbus_daemon
 
-    def start(self, config_file=None, env=None, new_session=False):
+    def start(self, env=None, new_session=False):
         dbus_command = [self._dbus_daemon_path(), '--print-address=1', '--print-pid=1']
-        if config_file:
-            dbus_command += ['--config-file=' + config_file]
+        if self.config_file:
+            dbus_command += ['--config-file=' + self.config_file]
         else:
             dbus_command += ['--session']
+
         log.debug("Running: %s", dbus_command)
         self.process = subprocess.Popen(
             dbus_command, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
@@ -101,10 +132,13 @@ class DBusDaemon:
         log.debug("Using new D-Bus session with address '%s' with PID %d",
                     self.address, self.pid)
 
+        stderr_log = logging.getLogger(self.name + '.stderr')
+        stdout_log = logging.getLogger(self.name + '.stdout')
+
         # We must read from the pipes continuously, otherwise the daemon
         # process will block.
-        self._threads=[threading.Thread(target=self.pipe_to_log, args=(self.process.stdout, 
dbus_stdout_log), daemon=True),
-                        threading.Thread(target=self.pipe_to_log, args=(self.process.stderr, 
dbus_stdout_log), daemon=True)]
+        self._threads=[threading.Thread(target=self.pipe_to_log, args=(self.process.stdout, stdout_log), 
daemon=True),
+                        threading.Thread(target=self.pipe_to_log, args=(self.process.stderr, stderr_log), 
daemon=True)]
         self._threads[0].start()
         self._threads[1].start()
 
@@ -199,3 +233,13 @@ class DBusDaemon:
                 return None
             else:
                 raise
+
+    def activate_service(self, bus_name, object_path):
+        GDBUS_DEFAULT_TIMEOUT = -1
+        self.get_connection().call_sync(
+            bus_name, object_path, 'org.freedesktop.DBus.Peer', 'Ping',
+            None, None, Gio.DBusCallFlags.NONE, GDBUS_DEFAULT_TIMEOUT, None)
+        self.await_bus_name(bus_name)
+
+    def await_bus_name(self, bus_name, timeout=DEFAULT_TIMEOUT):
+        await_bus_name(self.get_connection(), bus_name, timeout)
diff --git a/utils/trackertestutils/dconf.py b/utils/trackertestutils/dconf.py
index fe6d981fb..965ab80dc 100644
--- a/utils/trackertestutils/dconf.py
+++ b/utils/trackertestutils/dconf.py
@@ -42,7 +42,7 @@ class DConfClient(object):
     def __init__(self, sandbox):
         self.env = os.environ
         self.env.update(sandbox.extra_env)
-        self.env['DBUS_SESSION_BUS_ADDRESS'] = sandbox.daemon.get_address()
+        self.env['DBUS_SESSION_BUS_ADDRESS'] = sandbox.session_bus.get_address()
 
     def _check_using_correct_dconf_profile(self):
         profile = self.env.get("DCONF_PROFILE")
diff --git a/utils/trackertestutils/helpers.py b/utils/trackertestutils/helpers.py
index c1795b974..91086618f 100644
--- a/utils/trackertestutils/helpers.py
+++ b/utils/trackertestutils/helpers.py
@@ -17,5 +17,5 @@
 
 # FIXME: Compatibility module due to recent API breaks.
 # Remove this before 3.0.
-from .sandbox import TrackerSandbox
+from .sandbox import TrackerSandbox as TrackerDBusSandbox
 from .storehelper import StoreHelper
diff --git a/utils/trackertestutils/sandbox.py b/utils/trackertestutils/sandbox.py
index 2036faaeb..9794702f1 100644
--- a/utils/trackertestutils/sandbox.py
+++ b/utils/trackertestutils/sandbox.py
@@ -23,13 +23,17 @@ Sandbox environment for running tests.
 The sandbox is essentially a private D-Bus daemon.
 """
 
+from gi.repository import Gio
+
 import atexit
 import logging
 import os
 import signal
+import subprocess
 
 from . import dbusdaemon
 from . import dconf
+from . import devices
 from . import psutil_mini as psutil
 
 log = logging.getLogger(__name__)
@@ -51,16 +55,25 @@ atexit.register(_cleanup_processes)
 
 class TrackerSandbox:
     """
-    Private D-Bus session bus which executes a sandboxed Tracker instance.
+    Run Tracker daemons isolated from the real user session.
+
+    The primary method of sandboxing is running one or more private D-Bus
+    daemons, which take place of the host's session and system bus.
 
     """
-    def __init__(self, dbus_daemon_config_file, extra_env=None):
-        self.dbus_daemon_config_file = dbus_daemon_config_file
+    def __init__(self, session_bus_config_file, system_bus_config_file=None,
+                 extra_env=None):
         self.extra_env = extra_env or {}
 
-        self.daemon = dbusdaemon.DBusDaemon()
+        self.session_bus = dbusdaemon.DBusDaemon(
+            name='sandbox-session-bus', config_file=session_bus_config_file)
+        if system_bus_config_file:
+            self.system_bus = dbusdaemon.DBusDaemon(
+                name='sandbox-system-bus', config_file=system_bus_config_file)
+        else:
+            self.system_bus = None
 
-    def start(self, new_session=False):
+    def get_environment(self):
         env = os.environ
         env.update(self.extra_env)
         env['G_MESSAGES_PREFIXED'] = 'all'
@@ -81,17 +94,25 @@ class TrackerSandbox:
         if xdg_runtime_dir:
             os.makedirs(xdg_runtime_dir, exist_ok=True)
 
-        log.info("Starting D-Bus daemon for sandbox.")
+    def start(self, new_session=False):
+        if self.system_bus:
+            log.info("Starting D-Bus system bus for sandbox.")
+            log.debug("Added environment variables: %s", self.extra_env)
+            self.system_bus.start(env=self.get_environment(), new_session=new_session)
+
+            self.extra_env['DBUS_SYSTEM_BUS_ADDRESS'] = self.system_bus.get_address()
+
+        log.info("Starting D-Bus session bus for sandbox.")
         log.debug("Added environment variables: %s", self.extra_env)
-        self.daemon.start(self.dbus_daemon_config_file, env=env, new_session=new_session)
+        self.session_bus.start(env=self.get_environment(), new_session=new_session)
 
     def stop(self):
         tracker_processes = []
 
-        log.info("Looking for active Tracker processes on the bus")
-        for busname in self.daemon.list_names_sync():
+        log.info("Looking for active Tracker processes on the session bus")
+        for busname in self.session_bus.list_names_sync():
             if busname.startswith(TRACKER_DBUS_PREFIX):
-                pid = self.daemon.get_connection_unix_process_id_sync(busname)
+                pid = self.session_bus.get_connection_unix_process_id_sync(busname)
                 if pid is not None:
                     tracker_processes.append(pid)
 
@@ -109,8 +130,12 @@ class TrackerSandbox:
         #
         #  (tracker-miner-fs:14955): GLib-GIO-CRITICAL **: 11:38:40.386: Error  while sending AddMatch() 
message: The connection is closed
 
-        log.info("Stopping D-Bus daemon for sandbox.")
-        self.daemon.stop()
+        log.info("Stopping D-Bus session bus for sandbox.")
+        self.session_bus.stop()
+
+        if self.system_bus:
+            log.info("Stopping D-Bus system bus for sandbox.")
+            self.system_bus.stop()
 
     def stop_daemon(self, busname):
         """Stops the daemon that owns 'busname'.
@@ -128,8 +153,13 @@ class TrackerSandbox:
         else:
             log.info("Couldn't find a process owning %s.", busname)
 
-    def get_connection(self):
-        return self.daemon.get_connection()
+    def get_session_bus_connection(self):
+        """Return a Gio.BusConnection to the sandbox D-Bus session bus."""
+        return self.session_bus.get_connection()
+
+    def get_system_bus_connection(self):
+        """Return a Gio.BusConnection to the sandbox D-Bus system bus."""
+        return self.system_bus.get_connection()
 
     def set_config(self, schema_config_dict):
         """Set config values in multiple GSettings schemas.


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]