[tracker/sam/functional-tests-shared: 2/6] functional-tests: Clean up use of the common.utils.configuration module



commit 70aa3671406616e1699cd7c7697b45bf7262c1b6
Author: Sam Thursfield <sam afuera me uk>
Date:   Wed Aug 14 16:12:03 2019 +0200

    functional-tests: Clean up use of the common.utils.configuration module
    
    This module exists to pass the build configuration to the tests. This
    commit removes a lot of cruft from the module.
    
    The 'helpers' module is now disconnected from the configuration module.
    This allows it to be moved into the shared 'trackertestutils' library
    and shared with the tracker-miners repo.

 tests/functional-tests/14-signals.py               |  18 ++-
 tests/functional-tests/17-ontology-changes.py      |   2 +-
 .../functional-tests/common/utils/configuration.py |  54 +-------
 tests/functional-tests/common/utils/helpers.py     | 147 +++++++++++----------
 tests/functional-tests/common/utils/storetest.py   |  10 +-
 tests/functional-tests/configuration.json.in       |   3 -
 6 files changed, 96 insertions(+), 138 deletions(-)
---
diff --git a/tests/functional-tests/14-signals.py b/tests/functional-tests/14-signals.py
index 44806e8a2..658cf6ca5 100755
--- a/tests/functional-tests/14-signals.py
+++ b/tests/functional-tests/14-signals.py
@@ -23,18 +23,16 @@ are emitted. Theses tests are not extensive (only few selected signals
 are tested)
 """
 
-import unittest as ut
-from common.utils.storetest import CommonTrackerStoreTest as CommonTrackerStoreTest
-from common.utils import configuration as cfg
-
 from gi.repository import Gio
 from gi.repository import GLib
+
 import time
+import unittest as ut
 
-GRAPH_UPDATED_SIGNAL = "GraphUpdated"
+from common.utils.storetest import CommonTrackerStoreTest as CommonTrackerStoreTest
 
-SIGNALS_PATH = "/org/freedesktop/Tracker1/Resources"
-SIGNALS_IFACE = "org.freedesktop.Tracker1.Resources"
+
+GRAPH_UPDATED_SIGNAL = "GraphUpdated"
 
 CONTACT_CLASS_URI = "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#PersonContact";
 
@@ -70,10 +68,10 @@ class TrackerStoreSignalsTests (CommonTrackerStoreTest):
         After connecting to the signal, call self.__wait_for_signal.
         """
         self.cb_id = self.bus.signal_subscribe(
-            sender=cfg.TRACKER_BUSNAME,
-            interface_name=SIGNALS_IFACE,
+            sender=self.tracker.TRACKER_BUSNAME,
+            interface_name=self.tracker.RESOURCES_IFACE,
             member=GRAPH_UPDATED_SIGNAL,
-            object_path=SIGNALS_PATH,
+            object_path=self.tracker.TRACKER_OBJ_PATH,
             arg0=CONTACT_CLASS_URI,
             flags=Gio.DBusSignalFlags.NONE,
             callback=self.__signal_received_cb)
diff --git a/tests/functional-tests/17-ontology-changes.py b/tests/functional-tests/17-ontology-changes.py
index 0abf98e6e..c52b99d2b 100755
--- a/tests/functional-tests/17-ontology-changes.py
+++ b/tests/functional-tests/17-ontology-changes.py
@@ -117,7 +117,7 @@ class TrackerSystemAbstraction (object):
         """
         self.set_up_environment(confdir, ontodir)
 
-        self.store = helpers.StoreHelper()
+        self.store = helpers.StoreHelper(cfg.TRACKER_STORE_PATH)
         self.store.start()
 
     def tracker_store_restart_with_new_ontologies(self, ontodir):
diff --git a/tests/functional-tests/common/utils/configuration.py 
b/tests/functional-tests/common/utils/configuration.py
index 58f580a7e..938798746 100644
--- a/tests/functional-tests/common/utils/configuration.py
+++ b/tests/functional-tests/common/utils/configuration.py
@@ -33,63 +33,13 @@ if 'TRACKER_FUNCTIONAL_TEST_CONFIG' not in os.environ:
 with open(os.environ['TRACKER_FUNCTIONAL_TEST_CONFIG']) as f:
     config = json.load(f)
 
-TRACKER_BUSNAME = 'org.freedesktop.Tracker1'
-TRACKER_OBJ_PATH = '/org/freedesktop/Tracker1/Resources'
-RESOURCES_IFACE = "org.freedesktop.Tracker1.Resources"
-
-MINERFS_BUSNAME = "org.freedesktop.Tracker1.Miner.Files"
-MINERFS_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files"
-MINER_IFACE = "org.freedesktop.Tracker1.Miner"
-MINERFS_INDEX_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files/Index"
-MINER_INDEX_IFACE = "org.freedesktop.Tracker1.Miner.Files.Index"
-
-TRACKER_BACKUP_OBJ_PATH = "/org/freedesktop/Tracker1/Backup"
-BACKUP_IFACE = "org.freedesktop.Tracker1.Backup"
-
-TRACKER_STATS_OBJ_PATH = "/org/freedesktop/Tracker1/Statistics"
-STATS_IFACE = "org.freedesktop.Tracker1.Statistics"
-
-TRACKER_STATUS_OBJ_PATH = "/org/freedesktop/Tracker1/Status"
-STATUS_IFACE = "org.freedesktop.Tracker1.Status"
-
-TRACKER_EXTRACT_BUSNAME = "org.freedesktop.Tracker1.Miner.Extract"
-TRACKER_EXTRACT_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Extract"
-
-WRITEBACK_BUSNAME = "org.freedesktop.Tracker1.Writeback"
-
-
-DCONF_MINER_SCHEMA = "org.freedesktop.Tracker.Miner.Files"
-
-# Autoconf substitutes paths in the configuration.json file without
-# expanding variables, so we need to manually insert these.
-
-
-def expandvars(variable):
-    # Note: the order matters!
-    result = variable
-    for var, value in [("${datarootdir}", RAW_DATAROOT_DIR),
-                       ("${exec_prefix}", RAW_EXEC_PREFIX),
-                       ("${prefix}", PREFIX),
-                       ("@top_srcdir@", TOP_SRCDIR),
-                       ("@top_builddir@", TOP_BUILDDIR)]:
-        result = result.replace(var, value)
-
-    return result
-
-
-PREFIX = config['PREFIX']
-RAW_EXEC_PREFIX = config['RAW_EXEC_PREFIX']
-RAW_DATAROOT_DIR = config['RAW_DATAROOT_DIR']
 
 TOP_SRCDIR = os.path.dirname(os.path.dirname(
     os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
 TOP_BUILDDIR = os.environ['TRACKER_FUNCTIONAL_TEST_BUILD_DIR']
 
-TEST_ONTOLOGIES_DIR = os.path.normpath(
-    expandvars(config['TEST_ONTOLOGIES_DIR']))
-
-TRACKER_STORE_PATH = os.path.normpath(expandvars(config['TRACKER_STORE_PATH']))
-
+TEST_ONTOLOGIES_DIR = config['TEST_ONTOLOGIES_DIR']
+TRACKER_STORE_PATH = config['TRACKER_STORE_PATH']
 disableJournal = (len(config['disableJournal']) == 0)
 
 
diff --git a/tests/functional-tests/common/utils/helpers.py b/tests/functional-tests/common/utils/helpers.py
index f8c5742eb..0a82b395a 100644
--- a/tests/functional-tests/common/utils/helpers.py
+++ b/tests/functional-tests/common/utils/helpers.py
@@ -28,16 +28,12 @@ import sys
 import subprocess
 import time
 
-from common.utils import configuration as cfg
-
 
 class NoMetadataException (Exception):
     pass
 
 REASONABLE_TIMEOUT = 30
 
-log = logging.getLogger(__name__)
-
 
 class Helper:
     """
@@ -53,10 +49,16 @@ class Helper:
     is entered (or straight away if currently running the main loop).
     """
 
-    BUS_NAME = None
-    PROCESS_NAME = None
+    STARTUP_TIMEOUT = 200   # milliseconds
+    SHUTDOWN_TIMEOUT = 200  #
+
+    def __init__(self, helper_name, bus_name, process_path):
+        self.name = helper_name
+        self.bus_name = bus_name
+        self.process_path = process_path
+
+        self.log = logging.getLogger(f'{__name__}.{self.name}')
 
-    def __init__(self):
         self.process = None
         self.available = False
 
@@ -77,37 +79,33 @@ class Helper:
             sys.exit(1)
         sys.excepthook = new_hook
 
-    def _start_process(self, env=None):
-        path = self.PROCESS_PATH
-        flags = getattr(self,
-                        "FLAGS",
-                        [])
-
-        kws = {}
+    def _start_process(self, command_args=None, extra_env=None):
+        command = [self.process_path] + (command_args or [])
+        self.log.debug("Starting %s.", ' '.join(command))
 
-        if env:
-            kws['env'] = env
+        env = os.environ
+        if extra_env:
+            self.log.debug("  starting with extra environment: %s", extra_env)
+            env.update(extra_env)
 
-        command = [path] + flags
-        log.debug("Starting %s", ' '.join(command))
         try:
-            return subprocess.Popen([path] + flags, **kws)
+            return subprocess.Popen(command, env=env)
         except OSError as e:
             raise RuntimeError("Error starting %s: %s" % (path, e))
 
-    def _bus_name_appeared(self, name, owner, data):
-        log.debug("[%s] appeared in the bus as %s", self.PROCESS_NAME, owner)
+    def _bus_name_appeared(self, connection, name, owner):
+        self.log.debug("%s appeared on the message bus, owned by %s", name, owner)
         self.available = True
         self.loop.quit()
 
-    def _bus_name_vanished(self, name, data):
-        log.debug("[%s] disappeared from the bus", self.PROCESS_NAME)
+    def _bus_name_vanished(self, connection, name):
+        self.log.debug("%s vanished from the message bus", name)
         self.available = False
         self.loop.quit()
 
     def _process_watch_cb(self):
         if self.process_watch_timeout == 0:
-            # The GLib seems to call the timeout after we've removed it
+            # GLib seems to call the timeout after we've removed it
             # sometimes, which causes errors unless we detect it.
             return False
 
@@ -119,38 +117,40 @@ class Helper:
             return True    # continue
         else:
             self.process_watch_timeout = 0
-            raise RuntimeError("%s exited with status: %i" %
-                               (self.PROCESS_NAME, status))
+            raise RuntimeError(f"{self.name} exited with status: {self.status}")
 
-    def _timeout_on_idle_cb(self):
-        log.debug("[%s] Timeout waiting... asumming idle.", self.PROCESS_NAME)
+    def _process_startup_timeout_cb(self):
+        self.log.debug(f"Process timeout of {self.STARTUP_TIMEOUT}ms was called")
         self.loop.quit()
         self.timeout_id = None
         return False
 
-    def start(self, env=None):
+    def start(self, command_args=None, extra_env=None):
         """
         Start an instance of process and wait for it to appear on the bus.
         """
         if self.process is not None:
-            raise RuntimeError(
-                "%s process already started" % self.PROCESS_NAME)
+            raise RuntimeError("%s: already started" % self.name)
 
         self._bus_name_watch_id = Gio.bus_watch_name_on_connection(
-            self.bus, self.BUS_NAME, Gio.BusNameWatcherFlags.NONE,
+            self.bus, self.bus_name, Gio.BusNameWatcherFlags.NONE,
             self._bus_name_appeared, self._bus_name_vanished)
+
+        # We expect the _bus_name_vanished callback to be called here,
+        # causing the loop to exit again.
         self.loop.run()
 
         if self.available:
             # It's running, but we didn't start it...
-            raise Exception("Unable to start test instance of %s: "
-                            "already running " % self.PROCESS_NAME)
+            raise RuntimeError("Unable to start test instance of %s: "
+                               "already running" % self.name)
+
+        self.process = self._start_process(command_args=command_args,
+                                           extra_env=extra_env)
+        self.log.debug('Started with PID %i', self.process.pid)
 
-        self.process = self._start_process(env=env)
-        log.debug('[%s] Started process %i',
-            self.PROCESS_NAME, self.process.pid)
-        self.process_watch_timeout = GLib.timeout_add(
-            200, self._process_watch_cb)
+        self.process_startup_timeout = GLib.timeout_add(
+            self.STARTUP_TIMEOUT, self._process_startup_timeout_cb)
 
         self.abort_if_process_exits_with_status_0 = True
 
@@ -166,23 +166,21 @@ class Helper:
 
         start = time.time()
         if self.process.poll() == None:
-            GLib.source_remove(self.process_watch_timeout)
-            self.process_watch_timeout = 0
+            GLib.source_remove(self.process_startup_timeout)
+            self.process_startup_timeout = 0
 
             self.process.terminate()
+            returncode = self.process.wait(timeout=self.SHUTDOWN_TIMEOUT * 1000)
+            if returncode is None:
+                self.log.debug("Process failed to terminate in time, sending kill!")
+                self.process.kill()
+                self.process.wait()
+            elif returncode > 0:
+                self.log.warn("Process returned error code %s", returncode)
 
-            while self.process.poll() == None:
-                time.sleep(0.1)
+        self.log.debug("Process stopped.")
 
-                if time.time() > (start + REASONABLE_TIMEOUT):
-                    log.debug("[%s] Failed to terminate, sending kill!",
-                        self.PROCESS_NAME)
-                    self.process.kill()
-                    self.process.wait()
-
-        log.debug("[%s] stopped.", self.PROCESS_NAME)
-
-        # Run the loop until the bus name appears, or the process dies.
+        # Run the loop to handle the expected name_vanished signal.
         self.loop.run()
         Gio.bus_unwatch_name(self._bus_name_watch_id)
 
@@ -201,47 +199,60 @@ class Helper:
 
         self.process = None
 
-        log.debug("[%s] killed.", self.PROCESS_NAME)
+        self.log.debug("Process killed.")
 
 
 class StoreHelper (Helper):
     """
-    Wrapper for the Store API
-
-    Every method tries to reconnect once if there is a dbus exception
-    (some tests kill the daemon and make the connection useless)
+    Helper for starting and testing the tracker-store daemon.
     """
 
-    PROCESS_NAME = "tracker-store"
-    PROCESS_PATH = cfg.TRACKER_STORE_PATH
-    BUS_NAME = cfg.TRACKER_BUSNAME
+    TRACKER_BUSNAME = 'org.freedesktop.Tracker1'
+    TRACKER_OBJ_PATH = '/org/freedesktop/Tracker1/Resources'
+    RESOURCES_IFACE = "org.freedesktop.Tracker1.Resources"
+
+    TRACKER_BACKUP_OBJ_PATH = "/org/freedesktop/Tracker1/Backup"
+    BACKUP_IFACE = "org.freedesktop.Tracker1.Backup"
 
-    def start(self, env=None):
-        Helper.start(self, env=env)
+    TRACKER_STATS_OBJ_PATH = "/org/freedesktop/Tracker1/Statistics"
+    STATS_IFACE = "org.freedesktop.Tracker1.Statistics"
+
+    TRACKER_STATUS_OBJ_PATH = "/org/freedesktop/Tracker1/Status"
+    STATUS_IFACE = "org.freedesktop.Tracker1.Status"
+
+    def __init__(self, process_path):
+        Helper.__init__(self, "tracker-store", self.TRACKER_BUSNAME, process_path)
+
+    def start(self, command_args=None, extra_env=None):
+        Helper.start(self, command_args, extra_env)
 
         self.resources = Gio.DBusProxy.new_sync(
             self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
-            cfg.TRACKER_BUSNAME, cfg.TRACKER_OBJ_PATH, cfg.RESOURCES_IFACE)
+            self.TRACKER_BUSNAME, self.TRACKER_OBJ_PATH, self.RESOURCES_IFACE)
 
         self.backup_iface = Gio.DBusProxy.new_sync(
             self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
-            cfg.TRACKER_BUSNAME, cfg.TRACKER_BACKUP_OBJ_PATH, cfg.BACKUP_IFACE)
+            self.TRACKER_BUSNAME, self.TRACKER_BACKUP_OBJ_PATH, self.BACKUP_IFACE)
 
         self.stats_iface = Gio.DBusProxy.new_sync(
             self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
-            cfg.TRACKER_BUSNAME, cfg.TRACKER_STATS_OBJ_PATH, cfg.STATS_IFACE)
+            self.TRACKER_BUSNAME, self.TRACKER_STATS_OBJ_PATH, self.STATS_IFACE)
 
         self.status_iface = Gio.DBusProxy.new_sync(
             self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
-            cfg.TRACKER_BUSNAME, cfg.TRACKER_STATUS_OBJ_PATH, cfg.STATUS_IFACE)
+            self.TRACKER_BUSNAME, self.TRACKER_STATUS_OBJ_PATH, self.STATUS_IFACE)
 
-        log.debug("[%s] booting...", self.PROCESS_NAME)
+        self.log.debug("Calling %s.Wait() method", self.STATUS_IFACE)
         self.status_iface.Wait()
-        log.debug("[%s] ready.", self.PROCESS_NAME)
+        self.log.debug("Ready")
 
     def stop(self):
         Helper.stop(self)
 
+    # Note: The methods below call the tracker-store D-Bus API directly. This
+    # is useful for testing this API surface, but we recommand that all regular
+    # applications use libtracker-sparql library to talk to the database.
+
     def query(self, query, timeout=5000, **kwargs):
         return self.resources.SparqlQuery('(s)', query, timeout=timeout, **kwargs)
 
diff --git a/tests/functional-tests/common/utils/storetest.py 
b/tests/functional-tests/common/utils/storetest.py
index dad9a0ac6..5cec5475e 100644
--- a/tests/functional-tests/common/utils/storetest.py
+++ b/tests/functional-tests/common/utils/storetest.py
@@ -31,14 +31,16 @@ from common.utils import configuration as cfg
 class CommonTrackerStoreTest (ut.TestCase):
     """
     Common superclass for tests that just require a fresh store running
+
+    Note that the store is started per test-suite, not per test.
     """
+
     @classmethod
     def setUpClass(self):
-        env = os.environ
-        env['LC_COLLATE'] = 'en_GB.utf8'
+        extra_env = {'LC_COLLATE': 'en_GB.utf8'}
 
-        self.tracker = StoreHelper()
-        self.tracker.start(env=env)
+        self.tracker = StoreHelper(cfg.TRACKER_STORE_PATH)
+        self.tracker.start(extra_env=extra_env)
 
     @classmethod
     def tearDownClass(self):
diff --git a/tests/functional-tests/configuration.json.in b/tests/functional-tests/configuration.json.in
index 06a5be195..46c5126f7 100644
--- a/tests/functional-tests/configuration.json.in
+++ b/tests/functional-tests/configuration.json.in
@@ -1,7 +1,4 @@
 {
-    "PREFIX": "@prefix@",
-    "RAW_EXEC_PREFIX": "@exec_prefix@",
-    "RAW_DATAROOT_DIR": "@datarootdir@",
     "TEST_ONTOLOGIES_DIR": "@FUNCTIONAL_TESTS_ONTOLOGIES_DIR@",
     "TRACKER_STORE_PATH": "@FUNCTIONAL_TESTS_TRACKER_STORE_PATH@",
     "disableJournal": "@DISABLE_JOURNAL_TRUE@"


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