Browse Source

Fixture to mock loopingcall wait()

Consumers of loopingcall may wish to exercise their code in test cases
without incurring actual wall clock sleep time in their test runs.
Heretofore, this required digging into the internals of the loopingcall
module and mocking something private (or something hidden by something
private).

This patch exposes a public oslo_service.fixture.SleepFixture for this
purpose. It can be maintained opaquely as internals change without
affecting its consumers.

See [1] for (one example of) the motivation behind this change.

[1] https://review.openstack.org/#/c/615724/

Change-Id: I0089c7778957456db66599abffaaad3a5332243c
Eric Fried 5 months ago
parent
commit
b85d9353fb

+ 1
- 0
doc/source/conf.py View File

@@ -22,6 +22,7 @@ sys.path.insert(0, os.path.abspath('../..'))
22 22
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
23 23
 extensions = [
24 24
     'sphinx.ext.autodoc',
25
+    'sphinx.ext.todo',
25 26
     'openstackdocstheme',
26 27
     'oslo_config.sphinxext',
27 28
 ]

+ 8
- 0
doc/source/reference/fixture.rst View File

@@ -0,0 +1,8 @@
1
+=========
2
+ fixture
3
+=========
4
+
5
+.. automodule:: oslo_service.fixture
6
+   :members:
7
+   :undoc-members:
8
+   :show-inheritance:

+ 1
- 0
doc/source/reference/index.rst View File

@@ -6,6 +6,7 @@ API Reference
6 6
    :maxdepth: 1
7 7
 
8 8
    eventlet_backdoor
9
+   fixture
9 10
    loopingcall
10 11
    periodic_task
11 12
    service

+ 52
- 0
oslo_service/fixture.py View File

@@ -0,0 +1,52 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import fixtures
14
+
15
+
16
+class SleepFixture(fixtures.Fixture):
17
+    """A fixture for mocking the ``wait()`` within :doc:`loopingcall` events.
18
+
19
+    This exists so test cases can exercise code that uses :doc:`loopingcall`
20
+    without actually incurring wall clock time for sleeping.
21
+
22
+    The mock for the ``wait()`` is accessible via the fixture's ``mock_wait``
23
+    attribute.
24
+
25
+    .. note:: It is not recommended to assert specific arguments (i.e. timeout
26
+              values) to the mock, as this relies on the internals of
27
+              :doc:`loopingcall` not changing.
28
+
29
+    .. todo:: Figure out a way to make an enforceable contract allowing
30
+              verification of timeout values.
31
+
32
+    Example usage::
33
+
34
+        from oslo.service import fixture
35
+        ...
36
+        class MyTest(...):
37
+            def setUp(self):
38
+                ...
39
+                self.sleepfx = self.useFixture(fixture.SleepFixture())
40
+                ...
41
+
42
+            def test_this(self):
43
+                ...
44
+                thing_that_hits_a_loopingcall()
45
+                ...
46
+                self.assertEqual(5, self.sleepfx.mock_wait.call_count)
47
+                ...
48
+    """
49
+    def _setUp(self):
50
+        # Provide access to the mock so that calls to it can be asserted
51
+        self.mock_wait = self.useFixture(fixtures.MockPatch(
52
+            'oslo_service.loopingcall._Event.wait')).mock

+ 36
- 0
oslo_service/tests/test_fixture.py View File

@@ -0,0 +1,36 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import mock
14
+from oslotest import base as test_base
15
+
16
+from oslo_service import fixture
17
+from oslo_service import loopingcall
18
+
19
+
20
+class FixtureTestCase(test_base.BaseTestCase):
21
+    def setUp(self):
22
+        super(FixtureTestCase, self).setUp()
23
+        self.sleepfx = self.useFixture(fixture.SleepFixture())
24
+
25
+    def test_sleep_fixture(self):
26
+        @loopingcall.RetryDecorator(max_retry_count=3, inc_sleep_time=2,
27
+                                    exceptions=(ValueError,))
28
+        def retried_method():
29
+            raise ValueError("!")
30
+
31
+        self.assertRaises(ValueError, retried_method)
32
+        self.assertEqual(3, self.sleepfx.mock_wait.call_count)
33
+        # TODO(efried): This is cheating, and shouldn't be done by real callers
34
+        # yet - see todo in SleepFixture.
35
+        self.sleepfx.mock_wait.assert_has_calls(
36
+            [mock.call(x) for x in (2, 4, 6)])

+ 1
- 0
requirements.txt View File

@@ -4,6 +4,7 @@
4 4
 
5 5
 WebOb>=1.7.1 # MIT
6 6
 eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
7
+fixtures>=3.0.0 # Apache-2.0/BSD
7 8
 greenlet>=0.4.10 # MIT
8 9
 monotonic>=0.6;python_version<'3.3'  # Apache-2.0
9 10
 oslo.utils>=3.33.0 # Apache-2.0

Loading…
Cancel
Save