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
This commit is contained in:
Eric Fried 2018-11-06 13:06:28 -06:00
parent 643578c899
commit b85d9353fb
6 changed files with 99 additions and 0 deletions

View File

@ -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',
]

View File

@ -0,0 +1,8 @@
=========
fixture
=========
.. automodule:: oslo_service.fixture
:members:
:undoc-members:
:show-inheritance:

View File

@ -6,6 +6,7 @@ API Reference
:maxdepth: 1
eventlet_backdoor
fixture
loopingcall
periodic_task
service

52
oslo_service/fixture.py Normal file
View File

@ -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

View File

@ -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)])

View File

@ -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