[tracker/sam/tracker-2.3-developer-experience: 9/38] functional-tests: Terminate Tracker processes before the message bus



commit 12efd3258ec3fa4b5c1a3bfc9ca65e7761384c18
Author: Sam Thursfield <sam afuera me uk>
Date:   Tue Sep 10 12:14:35 2019 +0200

    functional-tests: Terminate Tracker processes before the message bus
    
    This reduces the volume of criticals like this that we see in the log
    output:
    
        (tracker-miner-fs:14955): GLib-GIO-CRITICAL **: 11:38:40.386: Error while sending AddMatch() message: 
The connection is closed
    
    I did still see one test that output a bunch of similar criticals from
    tracker-extract.

 utils/trackertestutils/dbusdaemon.py  | 24 +++++++++
 utils/trackertestutils/helpers.py     | 25 +++++++++
 utils/trackertestutils/meson.build    |  3 +-
 utils/trackertestutils/psutil_mini.py | 98 +++++++++++++++++++++++++++++++++++
 4 files changed, 149 insertions(+), 1 deletion(-)
---
diff --git a/utils/trackertestutils/dbusdaemon.py b/utils/trackertestutils/dbusdaemon.py
index 43fe8f146..c7e4707f3 100644
--- a/utils/trackertestutils/dbusdaemon.py
+++ b/utils/trackertestutils/dbusdaemon.py
@@ -17,6 +17,7 @@
 
 
 from gi.repository import Gio
+from gi.repository import GLib
 
 import logging
 import os
@@ -193,6 +194,29 @@ class DBusDaemon:
         self.stop()
 
     def ping_sync(self):
+        """Call the daemon Ping() method to check that it is alive."""
         self._gdbus_connection.call_sync(
             'org.freedesktop.DBus', '/', 'org.freedesktop.DBus', 'GetId',
             None, None, Gio.DBusCallFlags.NONE, 10000, None)
+
+    def list_names_sync(self):
+        """Get the name of every client connected to the bus."""
+        conn = self.get_connection()
+        result = conn.call_sync('org.freedesktop.DBus',
+                                '/org/freedesktop/DBus',
+                                'org.freedesktop.DBus', 'ListNames', None,
+                                GLib.VariantType('(as)'),
+                                Gio.DBusCallFlags.NONE, -1, None)
+        return result[0]
+
+    def get_connection_unix_process_id_sync(self, name):
+        """Get the process ID for one of the names connected to the bus."""
+        conn = self.get_connection()
+        result = conn.call_sync('org.freedesktop.DBus',
+                                '/org/freedesktop/DBus',
+                                'org.freedesktop.DBus',
+                                'GetConnectionUnixProcessID',
+                                GLib.Variant('(s)', [name]),
+                                GLib.VariantType('(u)'),
+                                Gio.DBusCallFlags.NONE, -1, None)
+        return result[0]
diff --git a/utils/trackertestutils/helpers.py b/utils/trackertestutils/helpers.py
index 45fa67242..fc81b52e6 100644
--- a/utils/trackertestutils/helpers.py
+++ b/utils/trackertestutils/helpers.py
@@ -24,9 +24,12 @@ from gi.repository import GLib
 import atexit
 import logging
 import os
+import signal
 
 from . import dbusdaemon
 from . import mainloop
+from . import psutil_mini as psutil
+
 
 log = logging.getLogger(__name__)
 
@@ -463,6 +466,28 @@ class TrackerDBusSandbox:
         self.daemon.start_if_needed(self.dbus_daemon_config_file, env=env)
 
     def stop(self):
+        tracker_processes = []
+
+        log.info("Looking for active Tracker processes on the bus")
+        for busname in self.daemon.list_names_sync():
+            if busname.startswith('org.freedesktop.Tracker1'):
+                pid = self.daemon.get_connection_unix_process_id_sync(busname)
+                tracker_processes.append(pid)
+
+        log.info("Terminating %i Tracker processes", len(tracker_processes))
+        for pid in tracker_processes:
+            os.kill(pid, signal.SIGTERM)
+
+        log.info("Waiting for %i Tracker processes", len(tracker_processes))
+        for pid in tracker_processes:
+            psutil.wait_pid(pid)
+
+        # We need to wait until Tracker processes have stopped before we
+        # terminate the D-Bus daemon, otherwise lots of criticals like this
+        # appear in the log output:
+        #
+        #  (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()
 
diff --git a/utils/trackertestutils/meson.build b/utils/trackertestutils/meson.build
index a144794d5..e8ab94c72 100644
--- a/utils/trackertestutils/meson.build
+++ b/utils/trackertestutils/meson.build
@@ -3,7 +3,8 @@ sources = [
   'dbusdaemon.py',
   'dconf.py',
   'helpers.py',
-  'mainloop.py'
+  'mainloop.py',
+  'psutil_mini.py',
 ]
 
 install_data(sources,
diff --git a/utils/trackertestutils/psutil_mini.py b/utils/trackertestutils/psutil_mini.py
new file mode 100644
index 000000000..d0c93565d
--- /dev/null
+++ b/utils/trackertestutils/psutil_mini.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found at https://github.com/giampaolo/psutil/blob/master/LICENSE
+#
+# Taken from https://github.com/giampaolo/psutil/blob/master/psutil/_psposix.py
+# by Sam Thursfield to avoid adding a dependency between the Tracker testsuite
+# and the 'psutil' module.
+
+
+import os
+import time
+
+
+class TimeoutExpired(Exception):
+    pass
+
+
+def pid_exists(pid):
+    """Check whether pid exists in the current process table."""
+    if pid == 0:
+        # According to "man 2 kill" PID 0 has a special meaning:
+        # it refers to <<every process in the process group of the
+        # calling process>> so we don't want to go any further.
+        # If we get here it means this UNIX platform *does* have
+        # a process with id 0.
+        return True
+    try:
+        os.kill(pid, 0)
+    except ProcessLookupError:
+        return False
+    except PermissionError:
+        # EPERM clearly means there's a process to deny access to
+        return True
+    # According to "man 2 kill" possible error values are
+    # (EINVAL, EPERM, ESRCH)
+    else:
+        return True
+
+
+def wait_pid(pid, timeout=None, proc_name=None):
+    """Wait for process with pid 'pid' to terminate and return its
+    exit status code as an integer.
+    If pid is not a children of os.getpid() (current process) just
+    waits until the process disappears and return None.
+    If pid does not exist at all return None immediately.
+    Raise TimeoutExpired on timeout expired.
+    """
+    def check_timeout(delay):
+        if timeout is not None:
+            if timer() >= stop_at:
+                raise TimeoutExpired(timeout, pid=pid, name=proc_name)
+        time.sleep(delay)
+        return min(delay * 2, 0.04)
+
+    timer = getattr(time, 'monotonic', time.time)
+    if timeout is not None:
+        def waitcall():
+            return os.waitpid(pid, os.WNOHANG)
+        stop_at = timer() + timeout
+    else:
+        def waitcall():
+            return os.waitpid(pid, 0)
+
+    delay = 0.0001
+    while True:
+        try:
+            retpid, status = waitcall()
+        except InterruptedError:
+            delay = check_timeout(delay)
+        except ChildProcessError:
+            # This has two meanings:
+            # - pid is not a child of os.getpid() in which case
+            #   we keep polling until it's gone
+            # - pid never existed in the first place
+            # In both cases we'll eventually return None as we
+            # can't determine its exit status code.
+            while True:
+                if pid_exists(pid):
+                    delay = check_timeout(delay)
+                else:
+                    return
+        else:
+            if retpid == 0:
+                # WNOHANG was used, pid is still running
+                delay = check_timeout(delay)
+                continue
+            # process exited due to a signal; return the integer of
+            # that signal
+            if os.WIFSIGNALED(status):
+                return -os.WTERMSIG(status)
+            # process exited using exit(2) system call; return the
+            # integer exit(2) system call has been called with
+            elif os.WIFEXITED(status):
+                return os.WEXITSTATUS(status)
+            else:
+                # should never happen
+                raise ValueError("unknown process exit status %r" % status)


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