From b85d9353fb3b41409f90df91154f87616b4ca61e Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 6 Nov 2018 13:06:28 -0600 Subject: [PATCH] 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 --- doc/source/conf.py | 1 + doc/source/reference/fixture.rst | 8 +++++ doc/source/reference/index.rst | 1 + oslo_service/fixture.py | 52 ++++++++++++++++++++++++++++++ oslo_service/tests/test_fixture.py | 36 +++++++++++++++++++++ requirements.txt | 1 + 6 files changed, 99 insertions(+) create mode 100644 doc/source/reference/fixture.rst create mode 100644 oslo_service/fixture.py create mode 100644 oslo_service/tests/test_fixture.py diff --git a/doc/source/conf.py b/doc/source/conf.py index 92f02171..40bd6bb8 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -22,6 +22,7 @@ sys.path.insert(0, os.path.abspath('../..')) # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.todo', 'openstackdocstheme', 'oslo_config.sphinxext', ] diff --git a/doc/source/reference/fixture.rst b/doc/source/reference/fixture.rst new file mode 100644 index 00000000..b44b8a8b --- /dev/null +++ b/doc/source/reference/fixture.rst @@ -0,0 +1,8 @@ +========= + fixture +========= + +.. automodule:: oslo_service.fixture + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst index 17dedc47..60190941 100644 --- a/doc/source/reference/index.rst +++ b/doc/source/reference/index.rst @@ -6,6 +6,7 @@ API Reference :maxdepth: 1 eventlet_backdoor + fixture loopingcall periodic_task service diff --git a/oslo_service/fixture.py b/oslo_service/fixture.py new file mode 100644 index 00000000..056eef60 --- /dev/null +++ b/oslo_service/fixture.py @@ -0,0 +1,52 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import fixtures + + +class SleepFixture(fixtures.Fixture): + """A fixture for mocking the ``wait()`` within :doc:`loopingcall` events. + + This exists so test cases can exercise code that uses :doc:`loopingcall` + without actually incurring wall clock time for sleeping. + + The mock for the ``wait()`` is accessible via the fixture's ``mock_wait`` + attribute. + + .. note:: It is not recommended to assert specific arguments (i.e. timeout + values) to the mock, as this relies on the internals of + :doc:`loopingcall` not changing. + + .. todo:: Figure out a way to make an enforceable contract allowing + verification of timeout values. + + Example usage:: + + from oslo.service import fixture + ... + class MyTest(...): + def setUp(self): + ... + self.sleepfx = self.useFixture(fixture.SleepFixture()) + ... + + def test_this(self): + ... + thing_that_hits_a_loopingcall() + ... + self.assertEqual(5, self.sleepfx.mock_wait.call_count) + ... + """ + def _setUp(self): + # Provide access to the mock so that calls to it can be asserted + self.mock_wait = self.useFixture(fixtures.MockPatch( + 'oslo_service.loopingcall._Event.wait')).mock diff --git a/oslo_service/tests/test_fixture.py b/oslo_service/tests/test_fixture.py new file mode 100644 index 00000000..08107a4f --- /dev/null +++ b/oslo_service/tests/test_fixture.py @@ -0,0 +1,36 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from oslotest import base as test_base + +from oslo_service import fixture +from oslo_service import loopingcall + + +class FixtureTestCase(test_base.BaseTestCase): + def setUp(self): + super(FixtureTestCase, self).setUp() + self.sleepfx = self.useFixture(fixture.SleepFixture()) + + def test_sleep_fixture(self): + @loopingcall.RetryDecorator(max_retry_count=3, inc_sleep_time=2, + exceptions=(ValueError,)) + def retried_method(): + raise ValueError("!") + + self.assertRaises(ValueError, retried_method) + self.assertEqual(3, self.sleepfx.mock_wait.call_count) + # TODO(efried): This is cheating, and shouldn't be done by real callers + # yet - see todo in SleepFixture. + self.sleepfx.mock_wait.assert_has_calls( + [mock.call(x) for x in (2, 4, 6)]) diff --git a/requirements.txt b/requirements.txt index 08537f0f..4afebff0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ WebOb>=1.7.1 # MIT eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT +fixtures>=3.0.0 # Apache-2.0/BSD greenlet>=0.4.10 # MIT monotonic>=0.6;python_version<'3.3' # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0