Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 33.9k
[WIP] bpo-17013: Implement WaitableMock to create Mock objects that can wait until called#12818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
826a69eaa73fbc0f0482ef51f5b7a941ca13031310c67715b5b59f34File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -18,16 +18,19 @@ | ||
| 'NonCallableMagicMock', | ||
| 'mock_open', | ||
| 'PropertyMock', | ||
| 'WaitableMock', | ||
| 'seal', | ||
| ) | ||
| __version__ = '1.0' | ||
| from collections import defaultdict | ||
| import inspect | ||
| import pprint | ||
| import sys | ||
| import threading | ||
| import builtins | ||
| from types import ModuleType | ||
| from unittest.util import safe_repr | ||
| @@ -2482,6 +2485,46 @@ def __set__(self, obj, val): | ||
| self(val) | ||
| class WaitableMock(MagicMock): | ||
| """ | ||
| A mock that can be used to wait until it was called. | ||
| `event_class` - Class to be used to create event object. | ||
| Defaults to Threading.Event and can take values like multiprocessing.Event. | ||
| """ | ||
| def __init__(self, *args, event_class=threading.Event, **kwargs): | ||
| _safe_super(WaitableMock, self).__init__(*args, **kwargs) | ||
| self._event = event_class() | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The usage self._event vs self._expected_calls which contains events is non obvious. Can add a comment to explain where each event is used and how. | ||
| self._expected_calls = defaultdict(event_class) | ||
| def _mock_call(self, *args, **kwargs): | ||
| ret_value = _safe_super(WaitableMock, self)._mock_call(*args, **kwargs) | ||
| for call in self._mock_mock_calls: | ||
| event = self._expected_calls[call.args] | ||
| event.set() | ||
| self._event.set() | ||
| return ret_value | ||
| def wait_until_called(self, timeout=1.0): | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove default timeout. There is no such "good default" timeout. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know. 1.0 seconds seems like a good timeout to me. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. It also prolongs the test suite as more tests are added. @mariocj89 would be creating Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO it's a bad idea to hardcode a default timeout. We cannot make assumption on the hardware performance. Please have a look at https://bugs.python.org/issue36369 : Python is slow on a Raspberry Pi Zero W. Does it surprise anyone? Why would Python expect that it's always run on fast x86-64? I suggest to either block by default (make the timeout optional) or to require a timeout value. In the worst case, please make at least the default configurable. I spent like 5 years to fix the Python test suite because too many tests used hardcoded timeouts which fit well for a fast desktop computer, but not for our slowest buildbot workers. Get a test failure only because the timeout is too short is annoying. | ||
| """Wait until the mock object is called. | ||
| `timeout` - time to wait for in seconds. Defaults to 1. | ||
| """ | ||
| return self._event.wait(timeout=timeout) | ||
| def wait_until_called_with(self, *args, timeout=1.0): | ||
Contributor
| ||
| """Wait until the mock object is called with given args. | ||
| `timeout` - time to wait for in seconds. Defaults to 1. | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t have a great alternative, but feels a pity if users cannot wait for calls that had a timeout parameter as discussed in the issue. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I would wait if someone has a less conflicting name or perhaps receive | ||
| """ | ||
| event = self._expected_calls[args] | ||
| return event.wait(timeout=timeout) | ||
| def seal(mock): | ||
| """Disable the automatic generation of child mocks. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| importthreading | ||
| importtime | ||
| importunittest | ||
| fromtest.supportimportstart_threads | ||
| fromunittest.mockimportpatch, WaitableMock, call | ||
| classSomething: | ||
| defmethod_1(self): | ||
| pass | ||
| defmethod_2(self): | ||
| pass | ||
| classTestWaitableMock(unittest.TestCase): | ||
| def_call_after_delay(self, func, *args, delay): | ||
| time.sleep(delay) | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be painful but it might be better to use events to wait for things to happen rather than relying in sleep. Otherwise, this might be adding considerable time to the test suit. I'd say wait for a core dev to comment about it, it might be fine though. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have kept the delays 0.1-0.5s to simulate latency of the call. Currently, it runs on 2.5 seconds on my local machine and might increase once I add more tests. I am new to using event objects so if there is a better way to simulate latency then I can refactor my tests. | ||
| func(*args) | ||
| def_create_thread(self, func, *args, **kwargs): | ||
| thread=threading.Thread(target=self._call_after_delay, | ||
| args=(func,) +args, kwargs=kwargs) | ||
| returnthread | ||
| deftest_instance_check(self): | ||
| waitable_mock=WaitableMock(event_class=threading.Event) | ||
| withpatch(f'{__name__}.Something', waitable_mock): | ||
| something=Something() | ||
| self.assertIsInstance(something.method_1, WaitableMock) | ||
| self.assertIsInstance( | ||
| something.method_1().method_2(), WaitableMock) | ||
| deftest_side_effect(self): | ||
| waitable_mock=WaitableMock(event_class=threading.Event) | ||
| withpatch(f'{__name__}.Something', waitable_mock): | ||
| something=Something() | ||
| something.method_1.side_effect= [1] | ||
| self.assertEqual(something.method_1(), 1) | ||
| deftest_spec(self): | ||
| waitable_mock=WaitableMock( | ||
| event_class=threading.Event, spec=Something) | ||
| withpatch(f'{__name__}.Something', waitable_mock) asm: | ||
| something=m() | ||
| self.assertIsInstance(something.method_1, WaitableMock) | ||
| self.assertIsInstance( | ||
| something.method_1().method_2(), WaitableMock) | ||
| withself.assertRaises(AttributeError): | ||
| m.test | ||
| deftest_wait_until_called(self): | ||
| waitable_mock=WaitableMock(event_class=threading.Event) | ||
| withpatch(f'{__name__}.Something', waitable_mock): | ||
| something=Something() | ||
| thread=self._create_thread(something.method_1, delay=0.5) | ||
| withstart_threads([thread]): | ||
| something.method_1.wait_until_called() | ||
| something.method_1.assert_called_once() | ||
| deftest_wait_until_called_magic_method(self): | ||
| waitable_mock=WaitableMock(event_class=threading.Event) | ||
| withpatch(f'{__name__}.Something', waitable_mock): | ||
| something=Something() | ||
| thread=self._create_thread(something.method_1.__str__, delay=0.5) | ||
| withstart_threads([thread]): | ||
| something.method_1.__str__.wait_until_called() | ||
| something.method_1.__str__.assert_called_once() | ||
| deftest_wait_until_called_timeout(self): | ||
| waitable_mock=WaitableMock(event_class=threading.Event) | ||
| withpatch(f'{__name__}.Something', waitable_mock): | ||
| something=Something() | ||
| thread=self._create_thread(something.method_1, delay=0.5) | ||
| withstart_threads([thread]): | ||
| something.method_1.wait_until_called(timeout=0.1) | ||
| something.method_1.assert_not_called() | ||
| something.method_1.wait_until_called() | ||
| something.method_1.assert_called_once() | ||
| deftest_wait_until_called_with(self): | ||
| waitable_mock=WaitableMock(event_class=threading.Event) | ||
| withpatch(f'{__name__}.Something', waitable_mock): | ||
| something=Something() | ||
| thread_1=self._create_thread(something.method_1, 1, delay=0.5) | ||
| thread_2=self._create_thread(something.method_2, 1, delay=0.1) | ||
| thread_3=self._create_thread(something.method_2, 2, delay=0.1) | ||
| withstart_threads([thread_1, thread_2, thread_3]): | ||
| something.method_1.wait_until_called_with(1, timeout=0.1) | ||
| something.method_1.assert_not_called() | ||
| something.method_1.wait_until_called(timeout=2.0) | ||
| something.method_1.assert_called_once_with(1) | ||
| self.assertEqual(something.method_1.mock_calls, [call(1)]) | ||
| something.method_2.assert_has_calls( | ||
| [call(1), call(2)], any_order=True) | ||
| deftest_wait_until_called_with_no_argument(self): | ||
| waitable_mock=WaitableMock(event_class=threading.Event) | ||
| withpatch(f'{__name__}.Something', waitable_mock): | ||
| something=Something() | ||
| something.method_1(1) | ||
| something.method_1.assert_called_once_with(1) | ||
| self.assertFalse( | ||
| something.method_1.wait_until_called_with(timeout=0.1)) | ||
| thread_1=self._create_thread(something.method_1, delay=0.1) | ||
| withstart_threads([thread_1]): | ||
| self.assertTrue(something.method_1.wait_until_called_with()) | ||
| if__name__=="__main__": | ||
| unittest.main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Add `WaitableMock` to :mod:`unittest.mock` that can be used to create Mock | ||
| objects that can wait until they are called. Patch by Karthikeyan | ||
| Singaravelan. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really cool that the event creation can be customised with any callable :).