[tracker-miners/sam/umockdev: 2/2] functional-tests: Add miner-power test



commit b5d185d52fd86a792c4a0afa5aa40a4af74616e7
Author: Sam Thursfield <sam afuera me uk>
Date:   Fri May 22 22:08:19 2020 +0200

    functional-tests: Add miner-power test
    
    This test uses [umockdev] to verify that the miner-fs pauses when upower
    reports a low battery condition.
    
    The umockdev dependency is optional and the test will report as being
    skipped if umockdev isn't available.
    
    Depends on https://gitlab.gnome.org/GNOME/tracker-oci-images/-/merge_requests/23
    and https://gitlab.gnome.org/GNOME/tracker/-/merge_requests/254
    
    [umockdev]: https://github.com/martinpitt/umockdev

 meson.build                                  |   3 +
 tests/functional-tests/configuration.json.in |   3 +-
 tests/functional-tests/configuration.py      |   3 +-
 tests/functional-tests/fixtures.py           |   9 +-
 tests/functional-tests/meson.build           |  25 ++++-
 tests/functional-tests/minerhelper.py        | 147 +++++++++++++++------------
 tests/meson.build                            |   4 +-
 tests/test-bus.conf.in                       |  21 ----
 8 files changed, 117 insertions(+), 98 deletions(-)
---
diff --git a/meson.build b/meson.build
index 89e1e24bb..55409fb5d 100644
--- a/meson.build
+++ b/meson.build
@@ -93,6 +93,9 @@ libmath = cc.find_library('m', required: false)
 network_manager = dependency('libnm', required: get_option('network_manager'))
 have_network_manager = network_manager.found()
 
+# We use this in the functional-tests if available.
+umockdev = dependency('umockdev-1.0', required: false)
+
 have_tracker_extract = get_option('extract')
 have_tracker_miner_fs = get_option('miner_fs')
 have_tracker_miner_rss = get_option('miner_rss')
diff --git a/tests/functional-tests/configuration.json.in b/tests/functional-tests/configuration.json.in
index 7ef895fe7..a4cb8aa33 100644
--- a/tests/functional-tests/configuration.json.in
+++ b/tests/functional-tests/configuration.json.in
@@ -1,5 +1,6 @@
 {
-    "TEST_DBUS_DAEMON_CONFIG_FILE": "@TEST_DBUS_DAEMON_CONFIG_FILE@",
+    "TEST_SESSION_BUS_CONFIG_FILE": "@TEST_SESSION_BUS_CONFIG_FILE@",
+    "TEST_SYSTEM_BUS_CONFIG_FILE": "@TEST_SYSTEM_BUS_CONFIG_FILE@",
     "TEST_DCONF_PROFILE": "@TEST_DCONF_PROFILE@",
     "TEST_DOMAIN_ONTOLOGY_RULE": "@TEST_DOMAIN_ONTOLOGY_RULE@",
     "TEST_EXTRACTOR_RULES_DIR": "@TEST_EXTRACTOR_RULES_DIR@",
diff --git a/tests/functional-tests/configuration.py b/tests/functional-tests/configuration.py
index 3f3d96c0e..d4d9d5452 100644
--- a/tests/functional-tests/configuration.py
+++ b/tests/functional-tests/configuration.py
@@ -42,7 +42,8 @@ with open(os.environ['TRACKER_FUNCTIONAL_TEST_CONFIG']) as f:
     config = json.load(f)
 
 
-TEST_DBUS_DAEMON_CONFIG_FILE = config['TEST_DBUS_DAEMON_CONFIG_FILE']
+TEST_SESSION_BUS_CONFIG_FILE = config['TEST_SESSION_BUS_CONFIG_FILE']
+TEST_SYSTEM_BUS_CONFIG_FILE = config['TEST_SYSTEM_BUS_CONFIG_FILE']
 TRACKER_EXTRACT_PATH = config['TRACKER_EXTRACT_PATH']
 
 
diff --git a/tests/functional-tests/fixtures.py b/tests/functional-tests/fixtures.py
index e9fe1c6cc..f02e04f13 100644
--- a/tests/functional-tests/fixtures.py
+++ b/tests/functional-tests/fixtures.py
@@ -60,9 +60,9 @@ def tracker_test_main():
         # only errors and warnings should be output here unless the environment
         # contains G_MESSAGES_DEBUG= and/or TRACKER_VERBOSITY=1 or more.
         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')
@@ -96,7 +96,7 @@ class TrackerMinerTest(ut.TestCase):
         extra_env['LANG'] = 'en_GB.utf8'
 
         self.sandbox = trackertestutils.helpers.TrackerDBusSandbox(
-            dbus_daemon_config_file=cfg.TEST_DBUS_DAEMON_CONFIG_FILE, extra_env=extra_env)
+            session_bus_config_file=cfg.TEST_SESSION_BUS_CONFIG_FILE, extra_env=extra_env)
 
         self.sandbox.start()
 
@@ -109,9 +109,8 @@ class TrackerMinerTest(ut.TestCase):
 
             self.sandbox.set_config(self.config())
 
-            self.miner_fs = MinerFsHelper(self.sandbox.get_connection())
+            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
             self.miner_fs.start()
-            self.miner_fs.start_watching_progress()
 
             self.tracker = trackertestutils.helpers.StoreHelper(
                 self.miner_fs.get_sparql_connection())
diff --git a/tests/functional-tests/meson.build b/tests/functional-tests/meson.build
index 8a996432c..5bb7d7c82 100644
--- a/tests/functional-tests/meson.build
+++ b/tests/functional-tests/meson.build
@@ -1,5 +1,11 @@
 python = find_program('python3')
 
+if umockdev.found()
+  umockdev_wrapper = find_program('umockdev-wrapper')
+else
+  umockdev_wrapper = python
+endif
+
 # Configure functional tests to run completely from source tree.
 testconf = configuration_data()
 
@@ -7,7 +13,8 @@ config_json_full_path = meson.current_build_dir() / 'configuration.json'
 dconf_profile_full_path = meson.current_source_dir() / 'trackertest'
 tracker_extractors_dir = meson.current_build_dir() / '..' / '..' / 'src' / 'tracker-extract'
 
-testconf.set('TEST_DBUS_DAEMON_CONFIG_FILE', build_root / 'tests' / 'test-bus.conf')
+testconf.set('TEST_SESSION_BUS_CONFIG_FILE', build_root / 'tests' / 'test-session-bus.conf')
+testconf.set('TEST_SYSTEM_BUS_CONFIG_FILE', meson.current_source_dir() / '..' / 'test-system-bus.conf')
 testconf.set('TEST_DCONF_PROFILE', dconf_profile_full_path)
 testconf.set('TEST_DOMAIN_ONTOLOGY_RULE', meson.current_build_dir() / 'test-domain.rule')
 testconf.set('TEST_EXTRACTOR_RULES_DIR', tracker_uninstalled_extract_rules_dir)
@@ -149,6 +156,10 @@ else
   warning('No GStreamer h264 codec was detected. Some extractor tests will be disabled.')
 endif
 
+umockdev_tests = [
+  'miner-power',
+]
+
 test_env = environment()
 
 if get_option('tracker_core') == 'subproject'
@@ -184,3 +195,15 @@ foreach t: functional_tests
     suite: ['functional'],
     timeout: 120)
 endforeach
+
+if umockdev.found()
+  # FIXME: these tests don't appear in the test runner output if umockdev
+  # wasn't found. We should really report them as skipped, but it's tricky.
+  foreach t: umockdev_tests
+    file = meson.current_source_dir() / '@0@.py'.format(t)
+    test(t, umockdev_wrapper,
+      args: [python.path(), file],
+      env: test_env,
+      suite: ['functional', 'umockdev'])
+  endforeach
+endif
diff --git a/tests/functional-tests/minerhelper.py b/tests/functional-tests/minerhelper.py
index 4aadd97da..01e874c07 100644
--- a/tests/functional-tests/minerhelper.py
+++ b/tests/functional-tests/minerhelper.py
@@ -20,11 +20,13 @@
 
 import gi
 gi.require_version('Tracker', '3.0')
-from gi.repository import Gio, GLib
+from gi.repository import Gio, GLib, GObject
 from gi.repository import Tracker
 
+import contextlib
 import logging
 
+import trackertestutils.dbusdaemon
 import trackertestutils.mainloop
 
 import configuration
@@ -33,10 +35,82 @@ import configuration
 log = logging.getLogger(__name__)
 
 
-class WakeupCycleTimeoutException(RuntimeError):
+class AwaitTimeoutException(RuntimeError):
     pass
 
 
+def await_status(miner_iface, target_status, timeout=configuration.DEFAULT_TIMEOUT):
+    log.info("Blocking until miner reports status of %s", target_status)
+    loop = trackertestutils.mainloop.MainLoop()
+
+    if miner_iface.GetStatus() == target_status:
+        log.info("Status is %s now", target_status)
+        return
+
+    def signal_cb(proxy, sender_name, signal_name, parameters):
+        if signal_name == 'Progress':
+            status, progress, remaining_time = parameters.unpack()
+            log.debug("Got status: %s", status)
+            if status == target_status:
+                loop.quit()
+
+    def timeout_cb():
+        log.info("Timeout fired after %s seconds", timeout)
+        raise AwaitTimeoutException(
+            f"Timeout awaiting miner status of '{target_status}'")
+
+    signal_id = miner_iface.connect('g-signal', signal_cb)
+    timeout_id = GLib.timeout_add_seconds(timeout, timeout_cb)
+
+    loop.run_checked()
+
+    GObject.signal_handler_disconnect(miner_iface, signal_id)
+    GLib.source_remove(timeout_id)
+
+
+class await_signal():
+    """Context manager to await a specific D-Bus signal.
+
+    Useful to wait for org.freedesktop.Tracker3.Miner signals like
+    Paused and Resumed.
+
+    """
+    def __init__(self, miner_iface, signal_name,
+                 timeout=configuration.DEFAULT_TIMEOUT):
+        self.miner_iface = miner_iface
+        self.signal_name = signal_name
+        self.timeout = timeout
+
+        self.loop = trackertestutils.mainloop.MainLoop()
+
+    def __enter__(self):
+        log.info("Awaiting signal %s", self.signal_name)
+
+        def signal_cb(proxy, sender_name, signal_name, parameters):
+            if signal_name == self.signal_name:
+                log.debug("Received signal %s", signal_name)
+                self.loop.quit()
+
+        def timeout_cb():
+            log.info("Timeout fired after %s seconds", self.timeout)
+            raise AwaitTimeoutException(
+                f"Timeout awaiting signal '{self.signal_name}'")
+
+        self.signal_id = self.miner_iface.connect('g-signal', signal_cb)
+        self.timeout_id = GLib.timeout_add_seconds(self.timeout, timeout_cb)
+
+    def __exit__(self, etype, evalue, etraceback):
+        if etype is not None:
+            return False
+
+        self.loop.run_checked()
+
+        GLib.source_remove(self.timeout_id)
+        GObject.signal_handler_disconnect(self.miner_iface, self.signal_id)
+
+        return True
+
+
 class MinerFsHelper ():
 
     MINERFS_BUSNAME = "org.freedesktop.Tracker3.Miner.Files"
@@ -62,78 +136,17 @@ class MinerFsHelper ():
 
     def start(self):
         self.miner_fs.Start()
+        trackertestutils.dbusdaemon.await_bus_name(self.bus, self.MINERFS_BUSNAME)
 
     def stop(self):
         self.miner_fs.Stop()
 
+    def get_status(self):
+        return self.miner_fs.GetStatus()
+
     def get_sparql_connection(self):
         return Tracker.SparqlConnection.bus_new(
             'org.freedesktop.Tracker3.Miner.Files', None, self.bus)
 
-    def start_watching_progress(self):
-        self._previous_status = None
-        self._target_wakeup_count = None
-        self._wakeup_count = 0
-
-        def signal_handler(proxy, sender_name, signal_name, parameters):
-            if signal_name == 'Progress':
-                self._progress_cb(*parameters.unpack())
-
-        self._progress_handler_id = self.miner_fs.connect('g-signal', signal_handler)
-
-    def stop_watching_progress(self):
-        if self._progress_handler_id != 0:
-            self.miner_fs.disconnect(self._progress_handler_id)
-
-    def _progress_cb(self, status, progress, remaining_time):
-        if self._previous_status is None:
-            self._previous_status = status
-        if self._previous_status != 'Idle' and status == 'Idle':
-            self._wakeup_count += 1
-
-        if self._target_wakeup_count is not None and self._wakeup_count >= self._target_wakeup_count:
-            self.loop.quit()
-
-    def wakeup_count(self):
-        """Return the number of wakeup-to-idle cycles the miner-fs completed."""
-        return self._wakeup_count
-
-    def await_wakeup_count(self, target_wakeup_count, timeout=configuration.DEFAULT_TIMEOUT):
-        """Block until the miner has completed N wakeup-and-idle cycles.
-
-        This function is for use by miner-fs tests that should trigger an
-        operation in the miner, but which do not cause a new resource to be
-        inserted. These tests can instead wait for the status to change from
-        Idle to Processing... and then back to Idle.
-
-        The miner may change its status any number of times, but you can use
-        this function reliably as follows:
-
-            wakeup_count = miner_fs.wakeup_count()
-            # Trigger a miner-fs operation somehow ...
-            miner_fs.await_wakeup_count(wakeup_count + 1)
-            # The miner has probably finished processing the operation now.
-
-        If the timeout is reached before enough wakeup cycles complete, an
-        exception will be raised.
-
-        """
-
-        assert self._target_wakeup_count is None
-
-        if self._wakeup_count >= target_wakeup_count:
-            log.debug("miner-fs wakeup count is at %s (target is %s). No need to wait", self._wakeup_count, 
target_wakeup_count)
-        else:
-            def _timeout_cb():
-                raise WakeupCycleTimeoutException()
-            timeout_id = GLib.timeout_add_seconds(timeout, _timeout_cb)
-
-            log.debug("Waiting for miner-fs wakeup count of %s (currently %s)", target_wakeup_count, 
self._wakeup_count)
-            self._target_wakeup_count = target_wakeup_count
-            self.loop.run_checked()
-
-            self._target_wakeup_count = None
-            GLib.source_remove(timeout_id)
-
     def index_file(self, uri):
         return self.index.IndexFile('(s)', uri)
diff --git a/tests/meson.build b/tests/meson.build
index b559274ca..b58e0e15a 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -10,8 +10,8 @@ endif
 subdir('services')
 
 test_bus_conf_file = configure_file(
-  input: 'test-bus.conf.in',
-  output: 'test-bus.conf',
+  input: 'test-session-bus.conf.in',
+  output: 'test-session-bus.conf',
   configuration: conf)
 
 if get_option('functional_tests')


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