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



commit 071bee0301057b9da627d676249ed545db8794d1
Author: Sam Thursfield <sam afuera me uk>
Date:   Thu May 21 23:41:07 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.
    
    [umockdev]: https://github.com/martinpitt/umockdev

 meson.build                                 |   3 +
 src/libtracker-miner/tracker-miner-object.c |   6 +-
 tests/functional-tests/fixtures.py          |   1 -
 tests/functional-tests/meson.build          |  22 +++++
 tests/functional-tests/miner-power.py       |  88 +++++++++++++++++
 tests/functional-tests/minerhelper.py       | 147 +++++++++++++++-------------
 tests/test-bus.conf.in                      |   1 +
 7 files changed, 199 insertions(+), 69 deletions(-)
---
diff --git a/meson.build b/meson.build
index d55194c5a..48965f2f4 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/src/libtracker-miner/tracker-miner-object.c b/src/libtracker-miner/tracker-miner-object.c
index 733b54f2a..bd99273e5 100644
--- a/src/libtracker-miner/tracker-miner-object.c
+++ b/src/libtracker-miner/tracker-miner-object.c
@@ -468,7 +468,11 @@ miner_get_property (GObject    *object,
 
        switch (prop_id) {
        case PROP_STATUS:
-               g_value_set_string (value, miner->priv->status);
+               if (tracker_miner_is_paused (miner)) {
+                       g_value_set_string (value, "Paused");
+               } else {
+                       g_value_set_string (value, miner->priv->status);
+               }
                break;
        case PROP_PROGRESS:
                g_value_set_double (value, miner->priv->progress);
diff --git a/tests/functional-tests/fixtures.py b/tests/functional-tests/fixtures.py
index dc466221d..696a047bd 100644
--- a/tests/functional-tests/fixtures.py
+++ b/tests/functional-tests/fixtures.py
@@ -115,7 +115,6 @@ class TrackerMinerTest(ut.TestCase):
 
             self.miner_fs = MinerFsHelper(self.sandbox.get_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..9273ec8bc 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()
 
@@ -149,6 +155,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 +194,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/miner-power.py b/tests/functional-tests/miner-power.py
new file mode 100755
index 000000000..183356bc3
--- /dev/null
+++ b/tests/functional-tests/miner-power.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2020, Sam Thursfield (sam afuera me uk)
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA  02110-1301, USA.
+
+"""
+Test that the miner responds to changes in power / battery status.
+"""
+
+import os
+import sys
+
+import configuration
+import fixtures
+import minerhelper
+import trackertestutils.devices
+
+
+testbed = None
+
+class MinerPowerTest(fixtures.TrackerMinerTest):
+    def setUp(self):
+        # We don't use setUp() from the base class because we need to start
+        # upowerd before the miner-fs.
+
+        extra_env = configuration.test_environment(self.workdir)
+        extra_env['LANG'] = 'en_GB.utf8'
+
+        # This sets the UMOCKDEV_DIR variable in the process environment.
+        #testbed = self.sandbox.get_umockdev_testbed()
+        self.battery = trackertestutils.devices.MockBattery(testbed)
+        self.battery.set_battery_power_normal_charge()
+
+        self.sandbox = trackertestutils.helpers.TrackerDBusSandbox(
+            dbus_daemon_config_file=configuration.TEST_DBUS_DAEMON_CONFIG_FILE, extra_env=extra_env)
+        self.sandbox.start()
+
+        try:
+            for schema_name, contents in self.config().items():
+                dconf = trackertestutils.dconf.DConfClient(self.sandbox)
+                for key, value in contents.items():
+                    dconf.write(schema_name, key, value)
+
+            self.sandbox.start_upowerd(testbed)
+            self.miner_fs = minerhelper.MinerFsHelper(self.sandbox.get_connection())
+            self.miner_fs.start()
+        except Exception:
+            self.sandbox.stop()
+            raise
+
+    def test_miner_fs_pause_on_low_battery(self):
+        """The miner-fs should stop indexing if there's a low battery warning."""
+        minerhelper.await_status(self.miner_fs.miner_fs, "Idle")
+
+        with minerhelper.await_signal(self.miner_fs.miner_fs, "Paused"):
+            self.battery.set_battery_power_low_charge()
+        self.assertEqual(self.miner_fs.get_status(), "Paused")
+
+        with minerhelper.await_signal(self.miner_fs.miner_fs, "Resumed"):
+            self.battery.set_ac_power()
+        self.assertEqual(self.miner_fs.get_status(), "Idle")
+
+
+if __name__ == "__main__":
+    if not trackertestutils.devices.HAVE_UMOCKDEV:
+        # Return 'skipped' error code so `meson test` reports the test
+        # correctly.
+        sys.exit(77)
+
+    if not trackertestutils.devices.libumockdev_loaded():
+        raise RuntimeError("This test must be run inside umockdev-wrapper.")
+
+    # This sets a process-wide environment variable UMOCKDEV_DIR.
+    testbed = trackertestutils.devices.create_testbed()
+
+    fixtures.tracker_test_main()
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/test-bus.conf.in b/tests/test-bus.conf.in
index 2f4b2ef1b..dea2d3ea7 100644
--- a/tests/test-bus.conf.in
+++ b/tests/test-bus.conf.in
@@ -8,6 +8,7 @@
 
   <servicedir>@abs_top_builddir@/tests/services/</servicedir>
   <standard_session_servicedirs/>
+  <standard_system_servicedirs/>
 
   <policy context="default">
     <!-- Allow everything to be sent -->


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