python-dbusmock icon indicating copy to clipboard operation
python-dbusmock copied to clipboard

Add assertEventually helper

Open hadess opened this issue 4 years ago • 2 comments

As used and implemented in upower.

As python-dbusmock uses the GLib mainloop in a number of places, it might be useful to add this helper to python-dbusmock.

hadess avatar Aug 11 '21 13:08 hadess

That seems fine to me. I just really wonder how you use that in upower, I left a question there.

FTR, I'm on PTO until Aug 25 with no access to computers, so this will take a while.

martinpitt avatar Aug 12 '21 07:08 martinpitt

I tried this patch:

diff --git dbusmock/testcase.py dbusmock/testcase.py
index 6fb81d6..9814b8e 100644
--- dbusmock/testcase.py
+++ dbusmock/testcase.py
@@ -21,6 +21,8 @@ from typing import Tuple, Dict, Any
 
 import dbus
 
+from gi.repository import GLib
+
 from dbusmock.mockobject import MOCK_IFACE, OBJECT_MANAGER_IFACE, load_module
 
 
@@ -254,3 +256,20 @@ class DBusTestCase(unittest.TestCase):
                         dbus_interface=MOCK_IFACE)
 
         return (daemon, obj)
+
+    def assertEventually(self, condition, message=None, timeout=50):
+        '''Assert that condition function eventually returns True
+
+        Timeout is in deciseconds, defaulting to 50 (5 seconds). message is
+        printed on failure. GLib main loop is iterated while waiting.
+        '''
+        context = GLib.MainContext.default()
+        while timeout >= 0:
+            while context.iteration(False):
+                pass
+            if condition():
+                break
+            timeout -= 1
+            time.sleep(0.1)
+        else:
+            self.fail(message or 'timed out waiting for ' + str(condition))
diff --git tests/test_iio_sensors_proxy.py tests/test_iio_sensors_proxy.py
index 481ad58..2025720 100644
--- tests/test_iio_sensors_proxy.py
+++ tests/test_iio_sensors_proxy.py
@@ -62,9 +62,9 @@ class TestIIOSensorsProxyBase(dbusmock.DBusTestCase):
     def set_internal_property(self, name, value):
         return self.p_obj.SetInternalProperty(self.dbus_interface, name, value)
 
-    def wait_for_properties_changed(self, max_wait=2000):
+    # max_wait is timeout in deciseconds
+    def wait_for_properties_changed(self, max_wait=20):
         changed_properties = []
-        timeout_id = 0
 
         def on_properties_changed(interface, properties, _invalidated):
             nonlocal changed_properties
@@ -72,23 +72,12 @@ class TestIIOSensorsProxyBase(dbusmock.DBusTestCase):
             if interface == self.dbus_interface:
                 changed_properties = properties.keys()
 
-        def on_timeout():
-            nonlocal timeout_id
-
-            timeout_id = 0
-
-        loop = GLib.MainLoop()
-        timeout_id = GLib.timeout_add(max_wait, on_timeout)
+        # loop = GLib.MainLoop()
         match = self.p_obj.connect_to_signal('PropertiesChanged',
                                              on_properties_changed,
                                              dbus.PROPERTIES_IFACE)
 
-        while not changed_properties and timeout_id != 0:
-            loop.get_context().iteration(True)
-
-        if timeout_id:
-            GLib.source_remove(timeout_id)
-
+        self.assertEventually(lambda: changed_properties, timeout=max_wait)
         match.remove()
 
         return changed_properties
@@ -140,7 +129,7 @@ class TestIIOSensorsProxy(TestIIOSensorsProxyBase):
         self.set_internal_property('HasAccelerometer', True)
         self.assertTrue(self.get_property('HasAccelerometer'))
         self.set_internal_property('AccelerometerOrientation', 'normal')
-        self.assertFalse(self.wait_for_properties_changed(max_wait=500))
+        self.assertFalse(self.wait_for_properties_changed(max_wait=5))
         self.assertEqual(self.get_property('AccelerometerOrientation'),
                          'normal')
 
@@ -189,7 +178,7 @@ class TestIIOSensorsProxy(TestIIOSensorsProxyBase):
         self.set_internal_property('HasAmbientLight', True)
         self.assertTrue(self.get_property('HasAmbientLight'))
         self.set_internal_property('LightLevelUnit', 'vendor')
-        self.assertFalse(self.wait_for_properties_changed(max_wait=500))
+        self.assertFalse(self.wait_for_properties_changed(max_wait=5))
         self.assertEqual(self.get_property('LightLevelUnit'), 'vendor')
 
     def test_proximity_none(self):
@@ -230,7 +219,7 @@ class TestIIOSensorsProxy(TestIIOSensorsProxyBase):
         self.set_internal_property('HasProximity', True)
         self.assertTrue(self.get_property('HasProximity'))
         self.set_internal_property('ProximityNear', True)
-        self.assertFalse(self.wait_for_properties_changed(max_wait=500))
+        self.assertFalse(self.wait_for_properties_changed(max_wait=5))
         self.assertTrue(self.get_property('ProximityNear'))
 
 
@@ -277,7 +266,7 @@ class TestIIOSensorsProxyCompass(TestIIOSensorsProxyBase):
         self.set_internal_property('HasCompass', True)
         self.assertTrue(self.get_property('HasCompass'))
         self.set_internal_property('CompassHeading', 85)
-        self.assertFalse(self.wait_for_properties_changed(max_wait=500))
+        self.assertFalse(self.wait_for_properties_changed(max_wait=5))
         self.assertEqual(self.get_property('CompassHeading'), 85)

 

but it doesn't work:

# PYTHONPATH=. python3 tests/test_iio_sensors_proxy.py TestIIOSensorsProxy.test_accelerometer_unclaimed_properties_changes
test_accelerometer_unclaimed_properties_changes (__main__.TestIIOSensorsProxy) ... FAIL

======================================================================
FAIL: test_accelerometer_unclaimed_properties_changes (__main__.TestIIOSensorsProxy)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/source/tests/test_iio_sensors_proxy.py", line 132, in test_accelerometer_unclaimed_properties_changes
    self.assertFalse(self.wait_for_properties_changed(max_wait=5))
  File "/source/tests/test_iio_sensors_proxy.py", line 80, in wait_for_properties_changed
    self.assertEventually(lambda: changed_properties)
  File "/source/dbusmock/testcase.py", line 275, in assertEventually
    self.fail(message or 'timed out waiting for ' + str(condition))
AssertionError: timed out waiting for <function TestIIOSensorsProxyBase.wait_for_properties_changed.<locals>.<lambda> at 0x7f06099725f0>

It's also a wee bit weird, as this is adding GLib dependency/importing to the otherwise rather generic testcase.py (but dbusmock depends on GLib anyway, so I don't mind that much).

So this needs more debugging.

martinpitt avatar Sep 09 '21 15:09 martinpitt