diff --git a/.carthorse.yml b/.carthorse.yml new file mode 100644 index 00000000..1bee3707 --- /dev/null +++ b/.carthorse.yml @@ -0,0 +1,9 @@ +carthorse: + version-from: setup.py + tag-format: "{version}" + when: + - version-not-tagged + actions: + - run: "pip install -e .[build]" + - run: "twine upload -u __token__ -p $PYPI_TOKEN dist/*" + - create-tag diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..6e2dad74 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,95 @@ +version: 2.1 + +orbs: + python: cjw296/python-ci@5 + +jobs: + check-package: + parameters: + image: + type: string + python: + type: string + default: "python" + docker: + - image: << parameters.image >> + steps: + - python/check-package: + package: "mock" + test: + - run: + name: "Import package" + command: << parameters.python >> -c "import mock" + + +common: &common + jobs: + + - python/pip-run-tests: + matrix: + alias: python-tests + parameters: + image: + - cimg/python:3.6 + - cimg/python:3.7 + - cimg/python:3.8 + - cimg/python:3.9 + - cimg/python:3.10 + - cimg/python:3.11 + - cimg/python:3.12 + - cimg/python:3.13 + + - python/pip-run-tests: + python: pypy3 + matrix: + alias: pypy-tests + parameters: + image: + - pypy:3.7-7.3.2 + - pypy:latest + + - python/coverage: + name: coverage + requires: + - python-tests + - pypy-tests + + - python/pip-docs: + name: docs + requires: + - coverage + + - python/pip-setuptools-build-package: + name: package + requires: + - docs + filters: + branches: + only: master + + - check-package: + matrix: + parameters: + image: + - cimg/python:3.6 + - cimg/python:3.13 + requires: + - package + + - python/release: + name: release + config: .carthorse.yml + requires: + - check-package + +workflows: + push: + <<: *common + periodic: + <<: *common + triggers: + - schedule: + cron: "0 1 * * *" + filters: + branches: + only: master diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..7b4f0a23 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,14 @@ +[run] +source = mock + +[report] +exclude_lines = + pragma: no cover + if __name__ == .__main__.: + : pass + : yield + +[paths] +source = + mock/ + /root/project/mock/ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..593dda5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Only report bugs here that are specific to this backport. +title: '' +labels: '' +assignees: '' + +--- + +This package is a rolling backport of [`unittest.mock`](https://github.com/python/cpython/blob/master/Lib/unittest/mock.py). +As such, any problems you encounter most likely need to be fixed upstream. + +Before submitting an issue here, please try and reproduce the problem on the latest release of Python 3, including alphas, and replace any import from `mock` with ones from `unittest.mock`. + +If the issue still occurs, then please report upstream through https://github.com/python/cpython/issues as it will need to be fixed there so that it can be backported here and released to you. + +If the issue does not occur upstream, please file an issue using the template below as it may be an issue specific to the backport: + +**What versions are you using?** + - Python: [e.g. 3.7.1] + - Mock: [e.g. 4.0.2] + - Operating System: [e.g.Linux, macOS, Windows] + +**What happened?** + + + +**What were you hoping to happen instead?** diff --git a/.gitignore b/.gitignore index 93d11555..b488ed92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .*\.pyc -*.rej html/ mock\.egg-info/ mock\.wpu @@ -13,8 +12,6 @@ runtox *.pyc .testrepository .*.swp -AUTHORS -ChangeLog -.eggs -README.saved README.html +.coverage +.coverage.* diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..63c8ae61 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,16 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3" + +python: + install: + - method: pip + path: . + extra_requirements: + - docs +sphinx: + fail_on_warning: true + configuration: docs/conf.py diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 8a65628a..00000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover . $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7f855dd0..00000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -sudo: false -language: python -python: - - "2.6" - - "2.7" - - "3.3" - - "3.4" - - pypy - - pypy3 -matrix: - include: -# Travis nightly look to be 3.5.0a4, b3 is out and the syntax error we see -# doesn't happen in trunk. - - python: "nightly" - env: SKIP_DOCS=1 -install: - - pip install -U pip - - pip install -U wheel setuptools - - pip install -U .[docs,test] - - pip list - - python --version -script: - - unit2 - - if [ -z "$SKIP_DOCS" ]; then python setup.py build_sphinx; fi - - rst2html.py --strict README.rst README.html diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..cd1b360c --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,497 @@ +5.2.0 +----- + +This is release is `in memory of Michael Foord`__, who originally authored the `mock` package +and passed away in January 2025. + +__ https://discuss.python.org/t/in-memoriam-michael-foord-1974-2025/78317 + +- gh-65454: :func:`unittest.mock.Mock.attach_mock` no longer triggers a call + to a ``PropertyMock`` being attached. + +- gh-117765: Improved documentation for :func:`unittest.mock.patch.dict` + +- gh-124176: Add support for :func:`dataclasses.dataclass` in + :func:`unittest.mock.create_autospec`. Now ``create_autospec`` will check + for potential dataclasses and use :func:`dataclasses.fields` function to + retrieve the spec information. + +- gh-123934: Fix :class:`unittest.mock.MagicMock` reseting magic methods + return values after ``.reset_mock(return_value=True)`` was called. + +- gh-90848: Fixed :func:`unittest.mock.create_autospec` to configure parent + mock with keyword arguments. + +- gh-113569: Indicate if there were no actual calls in unittest + :meth:`~unittest.mock.Mock.assert_has_calls` failure. + +- gh-122858: Deprecate :func:`!asyncio.iscoroutinefunction` in favor of + :func:`inspect.iscoroutinefunction`. + +- gh-104745: Limit starting a patcher (from :func:`unittest.mock.patch` or + :func:`unittest.mock.patch.object`) more than once without stopping it + +- gh-75988: Fixed :func:`unittest.mock.create_autospec` to pass the call + through to the wrapped object to return the real result. + +- gh-119600: Fix :func:`unittest.mock.patch` to not read attributes of the + target when ``new_callable`` is set. Patch by Robert Collins. + +- gh-113407: Fix import of :mod:`unittest.mock` when CPython is built + without docstrings. + +- gh-120732: Fix ``name`` passing to :class:`unittest.mock.Mock` object when + using :func:`unittest.mock.create_autospec`. + +5.1.0 +----- + +- bpo-44185: :func:`unittest.mock.mock_open` will call the :func:`close` + method of the file handle mock when it is exiting from the context + manager. Patch by Samet Yaslan. + +- gh-94924: :func:`unittest.mock.create_autospec` now properly returns + coroutine functions compatible with :func:`inspect.iscoroutinefunction` + +- bpo-17013: Add ``ThreadingMock`` to :mod:`unittest.mock` that can be used + to create Mock objects that can wait until they are called. Patch by + Karthikeyan Singaravelan and Mario Corchero. + +- bpo-41768: :mod:`unittest.mock` speccing no longer calls class properties. + Patch by Melanie Witt. + +5.0.2 +----- + +- gh-102978: Fixes :func:`unittest.mock.patch` not enforcing function + signatures for methods decorated with ``@classmethod`` or + ``@staticmethod`` when patch is called with ``autospec=True``. + +- gh-103329: Regression tests for the behaviour of + ``unittest.mock.PropertyMock`` were added. + +5.0.1 +----- + +- gh-100740: Fix ``unittest.mock.Mock`` not respecting the spec for + attribute names prefixed with ``assert``. + +- gh-100690: ``Mock`` objects which are not unsafe will now raise an + ``AttributeError`` when accessing an attribute that matches the name of an + assertion but without the prefix ``assert_``, e.g. accessing + ``called_once`` instead of ``assert_called_once``. This is in addition to + this already happening for accessing attributes with prefixes ``assert``, + ``assret``, ``asert``, ``aseert``, and ``assrt``. + +- gh-96127: ``inspect.signature`` was raising ``TypeError`` on call with + mock objects. Now it correctly returns ``(*args, **kwargs)`` as infered + signature. + +5.0.0 +----- + +- gh-98624: Add a mutex to unittest.mock.NonCallableMock to protect + concurrent access to mock attributes. + +- bpo-43478: Mocks can no longer be used as the specs for other Mocks. As a + result, an already-mocked object cannot have an attribute mocked using + `autospec=True` or be the subject of a `create_autospec(...)` call. This + can uncover bugs in tests since these Mock-derived Mocks will always pass + certain tests (e.g. isinstance) and builtin assert functions (e.g. + assert_called_once_with) will unconditionally pass. + +- bpo-45156: Fixes infinite loop on :func:`unittest.mock.seal` of mocks + created by :func:`~unittest.create_autospec`. + +- bpo-41403: Make :meth:`mock.patch` raise a :exc:`TypeError` with a + relevant error message on invalid arg. Previously it allowed a cryptic + :exc:`AttributeError` to escape. + +- gh-91803: Fix an error when using a method of objects mocked with + :func:`unittest.mock.create_autospec` after it was sealed with + :func:`unittest.mock.seal` function. + +- bpo-41877: AttributeError for suspected misspellings of assertions on + mocks are now pointing out that the cause are misspelled assertions and + also what to do if the misspelling is actually an intended attribute name. + The unittest.mock document is also updated to reflect the current set of + recognised misspellings. + +- bpo-43478: Mocks can no longer be provided as the specs for other Mocks. + As a result, an already-mocked object cannot be passed to `mock.Mock()`. + This can uncover bugs in tests since these Mock-derived Mocks will always + pass certain tests (e.g. isinstance) and builtin assert functions (e.g. + assert_called_once_with) will unconditionally pass. + +- bpo-45010: Remove support of special method ``__div__`` in + :mod:`unittest.mock`. It is not used in Python 3. + +- gh-84753: :func:`inspect.iscoroutinefunction` now properly returns + ``True`` when an instance of :class:`unittest.mock.AsyncMock` is passed to + it. This makes it consistent with behavior of + :func:`asyncio.iscoroutinefunction`. Patch by Mehdi ABAAKOUK. + +- bpo-46852: Remove the undocumented private ``float.__set_format__()`` + method, previously known as ``float.__setformat__()`` in Python 3.7. Its + docstring said: "You probably don't want to use this function. It exists + mainly to be used in Python's test suite." Patch by Victor Stinner. + +- gh-98086: Make sure ``patch.dict()`` can be applied on async functions. + +- gh-100287: Fix the interaction of :func:`unittest.mock.seal` with + :class:`unittest.mock.AsyncMock`. + +- gh-83076: Instantiation of ``Mock()`` and ``AsyncMock()`` is now 3.8x + faster. + +- bpo-41877: A check is added against misspellings of autospect, auto_spec + and set_spec being passed as arguments to patch, patch.object and + create_autospec. + +4.0.3 +----- + +- bpo-42532: Remove unexpected call of ``__bool__`` when passing a + ``spec_arg`` argument to a Mock. + +- bpo-39966: Revert bpo-25597. :class:`unittest.mock.MagicMock` with + wraps' set uses default return values for magic methods. + +- bpo-41877: Mock objects which are not unsafe will now raise an + AttributeError if an attribute with the prefix asert, aseert, or assrt is + accessed, in addition to this already happening for the prefixes assert or + assret. + +- bpo-40126: Fixed reverting multiple patches in unittest.mock. Patcher's + ``__exit__()`` is now never called if its ``__enter__()`` is failed. + Returning true from ``__exit__()`` silences now the exception. + +4.0.2 +----- + +- bpo-39915: Ensure :attr:`unittest.mock.AsyncMock.await_args_list` has + call objects in the order of awaited arguments instead of using + :attr:`unittest.mock.Mock.call_args` which has the last value of the call. + Patch by Karthikeyan Singaravelan. + +4.0.1 +----- + +- Remove the universal marker from the wheel. + +4.0.0 +----- + +- No Changes from 4.0.0b1. + +4.0.0b1 +------- + +- The release is a fresh cut of cpython's `4a686504`__. All changes to :mod:`mock` + from that commit and before are included in this release along with the + subsequent changes listed below. + + __ https://github.com/python/cpython/commit/4a686504eb2bbf69adf78077458508a7ba131667 + +- bpo-37972: Subscripts to the `unittest.mock.call` objects now receive + the same chaining mechanism as any other custom attributes, so that the + following usage no longer raises a `TypeError`: + + call().foo().__getitem__('bar') + + Patch by blhsing + +- bpo-38839: Fix some unused functions in tests. Patch by Adam Johnson. + +- bpo-39485: Fix a bug in :func:`unittest.mock.create_autospec` that + would complain about the wrong number of arguments for custom descriptors + defined in an extension module returning functions. + +- bpo-39082: Allow AsyncMock to correctly patch static/class methods + +- bpo-38093: Fixes AsyncMock so it doesn't crash when used with + AsyncContextManagers or AsyncIterators. + +- bpo-38859: AsyncMock now returns StopAsyncIteration on the exaustion of + a side_effects iterable. Since PEP-479 its Impossible to raise a + StopIteration exception from a coroutine. + +- bpo-38163: Child mocks will now detect their type as either synchronous + or asynchronous, asynchronous child mocks will be AsyncMocks and + synchronous child mocks will be either MagicMock or Mock (depending on + their parent type). + +- bpo-38473: Use signature from inner mock for autospecced methods + attached with :func:`unittest.mock.attach_mock`. Patch by Karthikeyan + Singaravelan. + +- bpo-38136: Changes AsyncMock call count and await count to be two + different counters. Now await count only counts when a coroutine has been + awaited, not when it has been called, and vice-versa. Update the + documentation around this. + +- bpo-37555: Fix `NonCallableMock._call_matcher` returning tuple instead + of `_Call` object when `self._spec_signature` exists. Patch by Elizabeth + Uselton + +- bpo-37251: Remove `__code__` check in AsyncMock that incorrectly + evaluated function specs as async objects but failed to evaluate classes + with `__await__` but no `__code__` attribute defined as async objects. + +- bpo-38669: Raise :exc:`TypeError` when passing target as a string with + :meth:`unittest.mock.patch.object`. + +- bpo-25597: Ensure, if ``wraps`` is supplied to + :class:`unittest.mock.MagicMock`, it is used to calculate return values + for the magic methods instead of using the default return values. Patch by + Karthikeyan Singaravelan. + +- bpo-38108: Any synchronous magic methods on an AsyncMock now return a + MagicMock. Any asynchronous magic methods on a MagicMock now return an + AsyncMock. + +- bpo-21478: Record calls to parent when autospecced object is attached + to a mock using :func:`unittest.mock.attach_mock`. Patch by Karthikeyan + Singaravelan. + +- bpo-38857: AsyncMock fix for return values that are awaitable types. + This also covers side_effect iterable values that happend to be awaitable, + and wraps callables that return an awaitable type. Before these awaitables + were being awaited instead of being returned as is. + +- bpo-38932: Mock fully resets child objects on reset_mock(). Patch by + Vegard Stikbakke + +- bpo-37685: Fixed ``__eq__``, ``__lt__`` etc implementations in some + classes. They now return :data:`NotImplemented` for unsupported type of + the other operand. This allows the other operand to play role (for example + the equality comparison with :data:`~unittest.mock.ANY` will return + ``True``). + +- bpo-37212: :func:`unittest.mock.call` now preserves the order of + keyword arguments in repr output. Patch by Karthikeyan Singaravelan. + +- bpo-37828: Fix default mock name in + :meth:`unittest.mock.Mock.assert_called` exceptions. Patch by Abraham + Toriz Cruz. + +- bpo-36871: Improve error handling for the assert_has_calls and + assert_has_awaits methods of mocks. Fixed a bug where any errors + encountered while binding the expected calls to the mock's spec were + silently swallowed, leading to misleading error output. + +- bpo-21600: Fix :func:`mock.patch.stopall` to stop active patches that + were created with :func:`mock.patch.dict`. + +- bpo-38161: Removes _AwaitEvent from AsyncMock. + +- bpo-36871: Ensure method signature is used instead of constructor + signature of a class while asserting mock object against method calls. + Patch by Karthikeyan Singaravelan. + +3.0.5 +----- + +- bpo-31855: :func:`unittest.mock.mock_open` results now respects the + argument of read([size]). Patch contributed by Rémi Lapeyre. + +3.0.4 +----- + +- Include the license, readme and changelog in the source distribution. + +3.0.3 +----- + +- Fixed patching of dictionaries, when specifying the target with a + unicode on Python 2. + +3.0.2 +----- + +- Add missing ``funcsigs`` dependency on Python 2. + +3.0.1 +----- + +- Fix packaging issue where ``six`` was missed as a dependency. + +3.0.0 +----- + +- bpo-35226: Recursively check arguments when testing for equality of + :class:`unittest.mock.call` objects and add note that tracking of + parameters used to create ancestors of mocks in ``mock_calls`` is not + possible. + +- bpo-31177: Fix bug that prevented using :meth:`reset_mock + ` on mock instances with deleted attributes + +- bpo-26704: Added test demonstrating double-patching of an instance + method. Patch by Anthony Sottile. + +- bpo-35500: Write expected and actual call parameters on separate lines + in :meth:`unittest.mock.Mock.assert_called_with` assertion errors. + Contributed by Susan Su. + +- bpo-35330: When a :class:`Mock` instance was used to wrap an object, if + `side_effect` is used in one of the mocks of it methods, don't call the + original implementation and return the result of using the side effect the + same way that it is done with return_value. + +- bpo-30541: Add new function to seal a mock and prevent the + automatically creation of child mocks. Patch by Mario Corchero. + +- bpo-35022: :class:`unittest.mock.MagicMock` now supports the + ``__fspath__`` method (from :class:`os.PathLike`). + +- bpo-33516: :class:`unittest.mock.MagicMock` now supports the + ``__round__`` magic method. + +- bpo-35512: :func:`unittest.mock.patch.dict` used as a decorator with + string target resolves the target during function call instead of during + decorator construction. Patch by Karthikeyan Singaravelan. + +- bpo-36366: Calling ``stop()`` on an unstarted or stopped + :func:`unittest.mock.patch` object will now return `None` instead of + raising :exc:`RuntimeError`, making the method idempotent. Patch + byKarthikeyan Singaravelan. + +- bpo-35357: Internal attributes' names of unittest.mock._Call and + unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with + _mock_ in order to prevent clashes with widely used object attributes. + Fixed minor typo in test function name. + +- bpo-20239: Allow repeated assignment deletion of + :class:`unittest.mock.Mock` attributes. Patch by Pablo Galindo. + +- bpo-35082: Don't return deleted attributes when calling dir on a + :class:`unittest.mock.Mock`. + +- bpo-0: Improved an error message when mock assert_has_calls fails. + +- bpo-23078: Add support for :func:`classmethod` and :func:`staticmethod` + to :func:`unittest.mock.create_autospec`. Initial patch by Felipe Ochoa. + +- bpo-21478: Calls to a child function created with + :func:`unittest.mock.create_autospec` should propagate to the parent. + Patch by Karthikeyan Singaravelan. + +- bpo-36598: Fix ``isinstance`` check for Mock objects with spec when the + code is executed under tracing. Patch by Karthikeyan Singaravelan. + +- bpo-32933: :func:`unittest.mock.mock_open` now supports iteration over + the file contents. Patch by Tony Flury. + +- bpo-21269: Add ``args`` and ``kwargs`` properties to mock call objects. + Contributed by Kumar Akshay. + +- bpo-17185: Set ``__signature__`` on mock for :mod:`inspect` to get + signature. Patch by Karthikeyan Singaravelan. + +- bpo-35047: ``unittest.mock`` now includes mock calls in exception + messages if ``assert_not_called``, ``assert_called_once``, or + ``assert_called_once_with`` fails. Patch by Petter Strandmark. + +- bpo-28380: unittest.mock Mock autospec functions now properly support + assert_called, assert_not_called, and assert_called_once. + +- bpo-28735: Fixed the comparison of mock.MagickMock with mock.ANY. + +- bpo-20804: The unittest.mock.sentinel attributes now preserve their + identity when they are copied or pickled. + +- bpo-28961: Fix unittest.mock._Call helper: don't ignore the name parameter + anymore. Patch written by Jiajun Huang. + +- bpo-26750: unittest.mock.create_autospec() now works properly for + subclasses of property() and other data descriptors. + +- bpo-21271: New keyword only parameters in reset_mock call. + +- bpo-26807: mock_open 'files' no longer error on readline at end of file. + Patch from Yolanda Robla. + +- bpo-25195: Fix a regression in mock.MagicMock. _Call is a subclass of + tuple (changeset 3603bae63c13 only works for classes) so we need to + implement __ne__ ourselves. Patch by Andrew Plummer. + +2.0.0 and earlier +----------------- + +- bpo-26323: Add Mock.assert_called() and Mock.assert_called_once() + methods to unittest.mock. Patch written by Amit Saha. + +- bpo-22138: Fix mock.patch behavior when patching descriptors. Restore + original values after patching. Patch contributed by Sean McCully. + +- bpo-24857: Comparing call_args to a long sequence now correctly returns a + boolean result instead of raising an exception. Patch by A Kaptur. + +- bpo-23004: mock_open() now reads binary data correctly when the type of + read_data is bytes. Initial patch by Aaron Hill. + +- bpo-21750: mock_open.read_data can now be read from each instance, as it + could in Python 3.3. + +- bpo-18622: unittest.mock.mock_open().reset_mock would recurse infinitely. + Patch from Nicola Palumbo and Laurent De Buyst. + +- bpo-23661: unittest.mock side_effects can now be exceptions again. This + was a regression vs Python 3.4. Patch from Ignacio Rossi + +- bpo-23310: Fix MagicMock's initializer to work with __methods__, just + like configure_mock(). Patch by Kasia Jachim. + +- bpo-23568: Add rdivmod support to MagicMock() objects. + Patch by Håkan Lövdahl. + +- bpo-23581: Add matmul support to MagicMock. Patch by Håkan Lövdahl. + +- bpo-23326: Removed __ne__ implementations. Since fixing default __ne__ + implementation in bpo-21408 they are redundant. *** NOT BACKPORTED *** + +- bpo-21270: We now override tuple methods in mock.call objects so that + they can be used as normal call attributes. + +- bpo-21256: Printout of keyword args should be in deterministic order in + a mock function call. This will help to write better doctests. + +- bpo-21262: New method assert_not_called for Mock. + It raises AssertionError if the mock has been called. + +- bpo-21238: New keyword argument `unsafe` to Mock. It raises + `AttributeError` incase of an attribute startswith assert or assret. + +- bpo-21239: patch.stopall() didn't work deterministically when the same + name was patched more than once. + +- bpo-21222: Passing name keyword argument to mock.create_autospec now + works. + +- bpo-17826: setting an iterable side_effect on a mock function created by + create_autospec now works. Patch by Kushal Das. + +- bpo-17826: setting an iterable side_effect on a mock function created by + create_autospec now works. Patch by Kushal Das. + +- bpo-20968: unittest.mock.MagicMock now supports division. + Patch by Johannes Baiter. + +- bpo-20189: unittest.mock now no longer assumes that any object for + which it could get an inspect.Signature is a callable written in Python. + Fix courtesy of Michael Foord. + +- bpo-17467: add readline and readlines support to mock_open in + unittest.mock. + +- bpo-17015: When it has a spec, a Mock object now inspects its signature + when matching calls, so that arguments can be matched positionally or + by name. + +- bpo-15323: improve failure message of Mock.assert_called_once_with + +- bpo-14857: fix regression in references to PEP 3135 implicit __class__ + closure variable (Reopens bpo-12370) + +- bpo-14295: Add unittest.mock diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..27027db1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE.txt +include *.rst +recursive-include mock/tests *.py diff --git a/NEWS b/NEWS deleted file mode 100644 index 21204186..00000000 --- a/NEWS +++ /dev/null @@ -1,79 +0,0 @@ -Library -------- - -- Issue #26323: Add Mock.assert_called() and Mock.assert_called_once() - methods to unittest.mock. Patch written by Amit Saha. - -- Issue #22138: Fix mock.patch behavior when patching descriptors. Restore - original values after patching. Patch contributed by Sean McCully. - -- Issue #24857: Comparing call_args to a long sequence now correctly returns a - boolean result instead of raising an exception. Patch by A Kaptur. - -- Issue #23004: mock_open() now reads binary data correctly when the type of - read_data is bytes. Initial patch by Aaron Hill. - -- Issue #21750: mock_open.read_data can now be read from each instance, as it - could in Python 3.3. - -- Issue #18622: unittest.mock.mock_open().reset_mock would recurse infinitely. - Patch from Nicola Palumbo and Laurent De Buyst. - -- Issue #23661: unittest.mock side_effects can now be exceptions again. This - was a regression vs Python 3.4. Patch from Ignacio Rossi - -- Issue #23310: Fix MagicMock's initializer to work with __methods__, just - like configure_mock(). Patch by Kasia Jachim. - -- Issue #23568: Add rdivmod support to MagicMock() objects. - Patch by Håkan Lövdahl. - -- Issue #23581: Add matmul support to MagicMock. Patch by Håkan Lövdahl. - -- Issue #23326: Removed __ne__ implementations. Since fixing default __ne__ - implementation in issue #21408 they are redundant. *** NOT BACKPORTED *** - -- Issue #21270: We now override tuple methods in mock.call objects so that - they can be used as normal call attributes. - -- Issue #21256: Printout of keyword args should be in deterministic order in - a mock function call. This will help to write better doctests. - -- Issue #21262: New method assert_not_called for Mock. - It raises AssertionError if the mock has been called. - -- Issue #21238: New keyword argument `unsafe` to Mock. It raises - `AttributeError` incase of an attribute startswith assert or assret. - -- Issue #21239: patch.stopall() didn't work deterministically when the same - name was patched more than once. - -- Issue #21222: Passing name keyword argument to mock.create_autospec now - works. - -- Issue #17826: setting an iterable side_effect on a mock function created by - create_autospec now works. Patch by Kushal Das. - -- Issue #17826: setting an iterable side_effect on a mock function created by - create_autospec now works. Patch by Kushal Das. - -- Issue #20968: unittest.mock.MagicMock now supports division. - Patch by Johannes Baiter. - -- Issue #20189: unittest.mock now no longer assumes that any object for - which it could get an inspect.Signature is a callable written in Python. - Fix courtesy of Michael Foord. - -- Issue #17467: add readline and readlines support to mock_open in - unittest.mock. - -- Issue #17015: When it has a spec, a Mock object now inspects its signature - when matching calls, so that arguments can be matched positionally or - by name. - -- Issue #15323: improve failure message of Mock.assert_called_once_with - -- Issue #14857: fix regression in references to PEP 3135 implicit __class__ - closure variable (Reopens issue #12370) - -- Issue #14295: Add unittest.mock diff --git a/README.rst b/README.rst index a7b632ce..279f2dc0 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ mock is now part of the Python standard library, available as `unittest.mock onwards. This package contains a rolling backport of the standard library mock code -compatible with Python 2.6 and up, and 3.3 and up. +compatible with Python 3.6 and up. Please see the standard library documentation for more details. @@ -17,13 +17,20 @@ Please see the standard library documentation for more details. :License: `BSD License`_ :Support: `Mailing list (testing-in-python@lists.idyll.org) `_ -:Issue tracker: `Github Issues +:Code: `GitHub + `_ +:Issue tracker: `GitHub Issues `_ :Build status: - .. image:: https://travis-ci.org/testing-cabal/mock.svg?branch=master - :target: https://travis-ci.org/testing-cabal/mock + |CircleCI|_ |Docs|_ -.. _Mock Homepage: https://github.com/testing-cabal/mock -.. _BSD License: http://github.com/testing-cabal/mock/blob/master/LICENSE.txt + .. |CircleCI| image:: https://circleci.com/gh/testing-cabal/mock/tree/master.svg?style=shield + .. _CircleCI: https://circleci.com/gh/testing-cabal/mock/tree/master + + .. |Docs| image:: https://readthedocs.org/projects/mock/badge/?version=latest + .. _Docs: http://mock.readthedocs.org/en/latest/ + +.. _Mock Homepage: http://mock.readthedocs.org/en/latest/ +.. _BSD License: https://github.com/testing-cabal/mock/blob/master/LICENSE.txt .. _Python Docs: https://docs.python.org/dev/library/unittest.mock.html -.. _mock on PyPI: http://pypi.python.org/pypi/mock +.. _mock on PyPI: https://pypi.org/project/mock/ diff --git a/backport.py b/backport.py new file mode 100644 index 00000000..2def1d90 --- /dev/null +++ b/backport.py @@ -0,0 +1,165 @@ +import re +from argparse import ArgumentParser +from os.path import dirname, abspath, join +from subprocess import check_output, call + + +def git(command, repo): + return check_output('git '+command, cwd=repo, shell=True).decode() + + +def repo_state_bad(mock_repo): + status = git('status', mock_repo) + if 'You are in the middle of an am session' in status: + print(f'Mock repo at {mock_repo} needs cleanup:\n') + call('git status', shell=True) + return True + + +def cleanup_old_patches(mock_repo): + print('cleaning up old patches:') + call('rm -vf /tmp/*.mock.patch', shell=True) + call('find . -name "*.rej" -print -delete', shell=True, cwd=mock_repo) + + +def find_initial_cpython_rev(): + with open('lastsync.txt') as source: + return source.read().strip() + + +def cpython_revs_affecting_mock(cpython_repo, start): + revs = git(f'log --no-merges --format=%H {start}.. ' + f'-- ' + f'Lib/unittest/mock.py ' + f'Lib/unittest/test/testmock/ ' + f'Lib/test/test_unittest/testmock/', + repo=cpython_repo).split() + revs.reverse() + print(f'{len(revs)} patches that may need backporting') + return revs + + +def has_been_backported(mock_repo, cpython_rev): + backport_rev = git(f'log --format=%H --grep "Backports: {cpython_rev}"', + repo=mock_repo).strip() + if backport_rev: + print(f'{cpython_rev} backported in {backport_rev}') + return True + print(f'{cpython_rev} has not been backported') + + +def extract_patch_for(cpython_repo, rev): + return git(f'format-patch -1 --no-stat --keep-subject --signoff --stdout {rev}', + repo=cpython_repo) + + +def munge(rev, patch): + + sign_off = 'Signed-off-by:' + patch = patch.replace(sign_off, f'Backports: {rev}\n{sign_off}', 1) + + for pattern, sub in ( + ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'), + (r'(a|b)/Lib/unittest/test/testmock/(\S+)', r'\1/mock/tests/\2'), + (r'(a|b)/Lib/test/test_unittest/testmock/(\S+)', r'\1/mock/tests/\2'), + ('(a|b)/Misc/NEWS', r'\1/NEWS'), + ('(a|b)/NEWS.d/next/[^/]+/(.+\.rst)', r'\1/NEWS.d/\2'), + ): + patch = re.sub(pattern, sub, patch) + return patch + + +def apply_patch(mock_repo, rev, patch): + patch_path = f'/tmp/{rev}.mock.patch' + + with open(patch_path, 'w') as target: + target.write(patch) + print(f'wrote {patch_path}') + + call(f'git am -k ' + f'--include "mock/*" --include NEWS --include "NEWS.d/*" ' + f'--reject {patch_path} ', + cwd=mock_repo, shell=True) + + +def update_last_sync(mock_repo, rev): + with open(join(mock_repo, 'lastsync.txt'), 'w') as target: + target.write(rev+'\n') + print(f'update lastsync.txt to {rev}') + + +def rev_from_mock_patch(text): + match = re.search('Backports: ([a-z0-9]+)', text) + return match.group(1) + + +def skip_current(mock_repo, reason): + text = git('am --show-current-patch', repo=mock_repo) + rev = rev_from_mock_patch(text) + git('am --abort', repo=mock_repo) + print(f'skipping {rev}') + update_last_sync(mock_repo, rev) + call(f'git commit -m "Backports: {rev}, skipped: {reason}" lastsync.txt', shell=True, cwd=mock_repo) + cleanup_old_patches(mock_repo) + + +def commit_last_sync(revs, mock_repo): + print('Yay! All caught up!') + if len(revs): + git('commit -m "latest sync point" lastsync.txt', repo=mock_repo) + + +def main(): + args = parse_args() + + if args.skip_current: + return skip_current(args.mock, args.skip_reason) + + initial_cpython_rev = find_initial_cpython_rev() + + if args.list: + for rev in cpython_revs_affecting_mock(args.cpython, initial_cpython_rev): + print(git(f'show --name-only --oneline {rev}', args.cpython), end='') + has_been_backported(args.mock, rev) + print() + return + + if repo_state_bad(args.mock): + return + + cleanup_old_patches(args.mock) + + if args.rev: + revs = [args.rev] + else: + revs = cpython_revs_affecting_mock(args.cpython, initial_cpython_rev) + + for rev in revs: + + if has_been_backported(args.mock, rev): + update_last_sync(args.mock, rev) + continue + + patch = extract_patch_for(args.cpython, rev) + patch = munge(rev, patch) + apply_patch(args.mock, rev, patch) + break + + else: + if not args.rev: + commit_last_sync(revs, args.mock) + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('--cpython', default='../cpython') + parser.add_argument('--mock', default=abspath(dirname(__file__))) + parser.add_argument('--list', action='store_true', help='list revs remaining to backport') + parser.add_argument('--rev', help='backport a specific git hash') + parser.add_argument('--skip-current', action='store_true') + parser.add_argument('--skip-reason', default='it has no changes needed here.') + return parser.parse_args() + + +if __name__ == '__main__': + main() diff --git a/docs/changelog.txt b/docs/changelog.txt deleted file mode 120000 index 22ec9b8a..00000000 --- a/docs/changelog.txt +++ /dev/null @@ -1 +0,0 @@ -../ChangeLog \ No newline at end of file diff --git a/docs/changelog.txt b/docs/changelog.txt new file mode 100644 index 00000000..4de03af3 --- /dev/null +++ b/docs/changelog.txt @@ -0,0 +1,4 @@ +Changelog +========= + +.. include:: ../CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py index d32357da..e978b466 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,14 +26,19 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.doctest'] +extensions = [ + 'sphinx.ext.intersphinx', + ] + +intersphinx_mapping = { + 'python': ('http://docs.python.org/dev', None), +} doctest_global_setup = """ import os import sys import mock from mock import * # yeah, I know :-/ -import unittest2 import __main__ if os.getcwd() not in sys.path: @@ -72,10 +77,7 @@ def __init__(self): # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. Supplied by pbr. # -# The short X.Y version. -version = mock.mock._v.brief_string() -# The full version, including alpha/beta/rc tags. -release = mock.__version__ +version = release = mock.__version__ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: (Set from pbr) diff --git a/docs/index.txt b/docs/index.txt index 3889ab82..0c671ebf 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -1,55 +1,31 @@ -==================================== +=================================== Mock - Mocking and Testing Library -==================================== - -:Version: |release| -:Date: |today| -:Homepage: `Mock Homepage`_ -:Download: `Mock on PyPI`_ -:Documentation: `Python Docs`_ -:License: `BSD License`_ -:Support: `Mailing list (testing-in-python@lists.idyll.org) - `_ -:Issue tracker: `Github Issues - `_ -:Last sync: cb6aab1248c4aec4dd578bea717854505a6fb55d - -.. _Mock Homepage: https://github.com/testing-cabal/mock -.. _BSD License: http://github.com/testing-cabal/mock/blob/master/LICENSE.txt -.. _Python Docs: https://docs.python.org/dev/library/unittest.mock.html +=================================== + +.. include:: ../README.rst .. module:: mock :synopsis: Mock object and testing library. .. index:: introduction -TOC -+++ - .. toctree:: - :maxdepth: 2 + :hidden: changelog -Introduction -++++++++++++ +Python Version Compatibility +++++++++++++++++++++++++++++ -mock is a library for testing in Python. It allows you to replace parts of -your system under test with mock objects and make assertions about how they -have been used. +* Version 1.0.1 is the last version compatible with Python < 2.6. -mock is now part of the Python standard library, available as -``unittest.mock`` in Python 3.3 onwards. However, if you are writing code that -runs on multiple versions of Python the ``mock`` package is better, as you get -the newest features from the latest release of Python available for all -Pythons. +* Version 1.3.0 is the last version compatible with Python 3.2. -The ``mock`` package contains a rolling backport of the standard library mock -code compatible with Python 2.6 and up, and 3.3 and up. Python 3.2 is supported -by mock 1.3.0 and below - with pip no longer supporting 3.2, we cannot test -against that version anymore. +* Version 2.0.0 is the last version compatible with Python 2.6. -Please see the standard library documentation for usage details. +* Version 2.0.0 is the last version offering official Jython support. + +* version 3.0.5 is the last version supporting Python 3.5 and lower. .. index:: installing .. _installing: @@ -57,17 +33,13 @@ Please see the standard library documentation for usage details. Installing ++++++++++ -The current version is |release|. Mock is stable and widely used. - -* `mock on PyPI `_ - .. index:: repository .. index:: git -You can checkout the latest development version from Github +You can checkout the latest development version from GitHub repository with the following command: - ``git clone https://github.com/testing-cabal/mock`` + ``git clone https://github.com/testing-cabal/mock.git`` .. index:: pip @@ -76,18 +48,11 @@ You can install mock with pip: | ``pip install -U mock`` -Alternatively you can download the mock distribution from PyPI and after -unpacking run: - - ``python setup.py install`` - - .. index:: bug reports Bug Reports +++++++++++ -Mock uses `unittest2 `_ for its own Issues with the backport process, such as compatibility with a particular Python, should be reported to the `bug tracker `_. Feature requests and issues @@ -96,19 +61,10 @@ with Mock functionality should be reported to the `Python bug tracker .. index:: python changes -Python Changes -++++++++++++++ - -Python NEWS entries from cPython: - -.. include:: ../NEWS +Changelog ++++++++++ -.. index:: older versions - -Older Versions of Python -++++++++++++++++++++++++ - -Version 1.0.1 is the last version compatible with Python < 2.6. +See the :doc:`change log `. .. index:: maintainer notes @@ -116,74 +72,147 @@ Maintainer Notes ++++++++++++++++ Development -=========== +----------- Checkout from git (see :ref:`installing`) and submit pull requests. Committers can just push as desired: since all semantic development takes place in cPython, the backport process is as lightweight as we can make it. -mock is CI tested using Travis-CI on Python versions 2.6, 2.7, 3.3, 3.4, -3.5, nightly Python 3 builds, pypy, pypy3. Jython support is desired, if -someone could contribute a patch to .travis.yml to support it that would be -excellent. +If you end up fixing anything backport-specific, please add an entry +to the top of ``CHANGELOG.rst`` so it shows up in the next release +notes. Releasing -========= +--------- NB: please use semver. Bump the major component on API breaks, minor on all non-bugfix changes, patch on bugfix only changes. -1. tag -s, push --tags origin master -2. setup.py sdist bdist_wheel upload -s +1. Run ``release.py [major|minor|bugfix]`` which will roll out new + NEWS items, bump the version number and create a commit for the release. + +2. Review that commit, feel free to amend it if you want to note anything + manually in ``CHANGELOG.rst``. +3. Push to the ``master`` branch on + https://github.com/testing-cabal/mock.git and the Circle CI + automation will take care of pushing releases to PyPI and + creating a tag. Backporting rules -================= +----------------- + +- If code such as this causes coverage checking to drop below 100%: + + .. code-block:: python + + def will_never_be_called(): + pass + + It should be adjusted to the following pattern, preferably upstream, + so that the ``.coveragerc`` in this repo knows to ignore it: + + .. code-block:: python + + def will_never_be_called(): + pass # pragma: no cover + +- If code such as this causes coverage checking to drop below 100%: + + .. code-block:: python + + def will_never_be_called(): + yield + + It should be adjusted to the following pattern, preferably upstream, + so that the ``.coveragerc`` in this repo knows to ignore it: + + .. code-block:: python + + def will_never_be_called(): + yield # pragma: no cover + +- If a backported patch applies cleanly, but ends up needing to be skipped, + then commit the latest sync point and then revert the problematic commit in an immediately + subsequent commit and make a note of the reason for the revert in that commit message. + + See `bc04ea7`__ for an example where `f4c8dc7`__ broke compatibility for all Python + versions earlier than 3.10. + + __ https://github.com/testing-cabal/mock/commit/bc04ea76352c2064d79160f13649f879667a89cb + + __ https://github.com/testing-cabal/mock/commit/f4c8dc7098abb6b2f9a65ee86bad3891776abb50 -isinstance checks in cPython to ``type`` need to check ``ClassTypes``. -Code calling ``obj.isidentifier`` needs to change to ``_isidentifier(obj)``. Backporting process -=================== - -1. Patch your git am with `my patch `_. -2. Install the applypatch-transform hook from tools/ to your .git hooks dir. -3. Configure a pre-applypatch hook to test at least all the cPython versions - we support on each patch that is applied. I use containers, and a sample - script is in tools/pre-applypatch. -4. Pull down the cPython git mirror: https://github.com/python/cpython.git -5. Export the new revisions since the ``Last sync`` at the top of this - document:: - - revs=${lastsync} - rm migrate-export - git log --pretty="format:%H " $revs.. -- Lib/unittest/mock.py \ - Lib/unittest/test/testmock/ > migrate-revs - tac migrate-revs > migrate-sorted-revs - for rev in $(< migrate-sorted-revs); do - git format-patch -1 $rev -k --stdout >> migrate-export; - done - echo NEW SYNC POINT: $(git rev-parse HEAD) - -6. Import into mock:: - - git am -k --reject $path-to-cpython/migrate-export - - This will transform the patches automatically. Currently it will error - on every NEWS change as I haven't gotten around to making those patches - automatic. Fixup any errors that occur. When the patch is ready, do a ``git - add -u`` to update the index and then ``git am --continue`` to move onto - the next patch. If the patch is inappropriate e.g. the patch removing - __ne__ which would break older pythons, then either do ``git reset --hard; - git am --skip`` to discard any partially applied changes and skip over it, - or, if it has a NEWS entry thats worth preserving, edit it down to just - that, with a note such as we have for the ``__ne__`` patch, and continue on - from there. - - The goal is that every patch work at all times. - -7. After the import is complete, update this document with the new sync point. - -8. Push to a personal branch and propose a PR to the main repo. This will make - Travis-CI test it. If it works, push to the main repo. +------------------- + +1. Clone cpython and mock into the same directory, eg: + + .. code-block:: bash + + mkdir vcs + cd vcs + git clone https://github.com/python/cpython.git + git clone https://github.com/testing-cabal/mock.git + + Make sure ``cpython` is on ``main` and that ``main`` branch is fully up to date! + Make sure ``mock`` is on master and up fully up to date! + +2. Create a branch in your ``mock`` clone and switch to it. + +3. Make sure you build a suitable virtualenv for Mock development + and activate it. For backporting, this should use Python 3.7+. + +4. Run ``backport.py``: + + .. code-block:: bash + + cd vcs/mock + python backport.py + + This will find the next cpython patch that needs to be applied, munge it + and attempt to apply it with ``git am``. + + If it succeeds, run the tests and/or push your branch up to a fork and + do a pull request into the master branch of the main repo to kick off + the continuous integration tests. + + If it fails, you'll have to manually work with what ``git status`` shows + to get the patch committed. + + If it turns out that there's nothing that should be applied from the failed commit, + run ``python backport.py --skip-current``, maybe with ``--skip-reason``. + + If you have to make changes, please do a ``git commit --amend`` and add notes + about what needed doing below the ``Signed-off-by`` block. + + If you have to make changes because tests fail with an applied patch, please + make those changes in a followup commit and take note of the "Backporting rules" + above. + +5. Rinse and repeat until ``backport.py`` reports no more patches need applying. + +6. If ``backport.py`` has updated ``lastsync.txt`` but not committed it, + now would be a good time to commit that change. + +Checking coverage in upstream +----------------------------- + +Assuming you have the checkout structure as above, and you have compiled your cpython +master branch, then roughly as follows: + +.. code-block:: bash + + ~/vcs/cpython/python.exe -m venv ~/virtualenvs/cpython-master + source ~/virtualenvs/cpython-master/bin/activate + pip install -U setuptools pip + pip install pytest pytest-cov + cd vcs/cpython/Lib/unittest + pytest --cov unittest.mock --cov unittest.test.testmock \ + --cov-config ~/vcs/git/mock/.coveragerc \ + --cov-report term-missing:skip-covered \ + test/testmock/test* + +Ignore ``test/testmock/__*__.py`` as these aren't present in the backport. diff --git a/extendmock.py b/extendmock.py deleted file mode 100644 index 0550d9fd..00000000 --- a/extendmock.py +++ /dev/null @@ -1 +0,0 @@ -# merged into mock.py in Mock 0.7 diff --git a/lastsync.txt b/lastsync.txt new file mode 100644 index 00000000..ba0f9346 --- /dev/null +++ b/lastsync.txt @@ -0,0 +1 @@ +04091c083340dde7d4eeb6d945c70f3b37d88f85 diff --git a/mock.wpr b/mock.wpr deleted file mode 100644 index e1ded971..00000000 --- a/mock.wpr +++ /dev/null @@ -1,26 +0,0 @@ -#!wing -#!version=4.0 -################################################################## -# Wing IDE project file # -################################################################## -[project attributes] -proj.directory-list = [{'dirloc': loc('.'), - 'excludes': [u'latex', - u'.hg', - u'.tox', - u'dist', - u'htmlcov', - u'extendmock.py', - u'__pycache__', - u'html', - u'build', - u'mock.egg-info', - u'tests/__pycache__', - u'.hgignore', - u'.hgtags'], - 'filter': '*', - 'include_hidden': 0, - 'recursive': 1, - 'watch_for_changes': 1}] -proj.file-type = 'shared' -testing.auto-test-file-specs = ('test*.py',) diff --git a/mock/__init__.py b/mock/__init__.py index 82a31103..055601d8 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -1,7 +1,15 @@ from __future__ import absolute_import + +import re, sys + +IS_PYPY = 'PyPy' in sys.version + import mock.mock as _mock from mock.mock import * -__all__ = _mock.__all__ -#import mock.mock as _mock -#for name in dir(_mock): -# globals()[name] = getattr(_mock, name) + +__version__ = '5.2.0' +version_info = tuple(int(p) for p in + re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) + + +__all__ = ('__version__', 'version_info') + _mock.__all__ diff --git a/mock/backports.py b/mock/backports.py new file mode 100644 index 00000000..598a512b --- /dev/null +++ b/mock/backports.py @@ -0,0 +1,98 @@ +import sys + + +if sys.version_info[:2] > (3, 9): + from inspect import iscoroutinefunction +elif sys.version_info[:2] >= (3, 8): + from asyncio import iscoroutinefunction +else: + + import functools + from asyncio.coroutines import _is_coroutine + from inspect import ismethod, isfunction, CO_COROUTINE + + def _unwrap_partial(func): + while isinstance(func, functools.partial): + func = func.func + return func + + def _has_code_flag(f, flag): + """Return true if ``f`` is a function (or a method or functools.partial + wrapper wrapping a function) whose code object has the given ``flag`` + set in its flags.""" + while ismethod(f): + f = f.__func__ + f = _unwrap_partial(f) + if not isfunction(f): + return False + return bool(f.__code__.co_flags & flag) + + def iscoroutinefunction(obj): + """Return true if the object is a coroutine function. + + Coroutine functions are defined with "async def" syntax. + """ + return ( + _has_code_flag(obj, CO_COROUTINE) or + getattr(obj, '_is_coroutine', None) is _is_coroutine + ) + + +try: + from unittest import IsolatedAsyncioTestCase +except ImportError: + import asyncio + from unittest import TestCase + + + class IsolatedAsyncioTestCase(TestCase): + + def __init__(self, methodName='runTest'): + super().__init__(methodName) + self._asyncioTestLoop = None + self._asyncioCallsQueue = None + + async def _asyncioLoopRunner(self, fut): + self._asyncioCallsQueue = queue = asyncio.Queue() + fut.set_result(None) + while True: + query = await queue.get() + queue.task_done() + assert query is None + + def _setupAsyncioLoop(self): + assert self._asyncioTestLoop is None + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.set_debug(True) + self._asyncioTestLoop = loop + fut = loop.create_future() + self._asyncioCallsTask = loop.create_task(self._asyncioLoopRunner(fut)) + loop.run_until_complete(fut) + + def _tearDownAsyncioLoop(self): + assert self._asyncioTestLoop is not None + loop = self._asyncioTestLoop + self._asyncioTestLoop = None + self._asyncioCallsQueue.put_nowait(None) + loop.run_until_complete(self._asyncioCallsQueue.join()) + + try: + # shutdown asyncgens + loop.run_until_complete(loop.shutdown_asyncgens()) + finally: + asyncio.set_event_loop(None) + loop.close() + + def run(self, result=None): + self._setupAsyncioLoop() + try: + return super().run(result) + finally: + self._tearDownAsyncioLoop() + + +try: + from asyncio import _set_event_loop_policy as set_event_loop_policy +except ImportError: + from asyncio import set_event_loop_policy diff --git a/mock/mock.py b/mock/mock.py index c674a858..236fcac7 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1,42 +1,10 @@ # mock.py # Test tools for mocking and patching. -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# -# mock 1.0.1 -# http://www.voidspace.org.uk/python/mock/ -# -# Copyright (c) 2007-2013, Michael Foord & the mock team -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import absolute_import +# Maintained by Michael Foord +# Backport for other versions of Python available from +# https://pypi.org/project/mock __all__ = ( - '__version__', - 'version_info', 'Mock', 'MagicMock', 'patch', @@ -45,104 +13,68 @@ 'ANY', 'call', 'create_autospec', + 'AsyncMock', + 'ThreadingMock', 'FILTER_DIR', - 'CallableMixin', 'NonCallableMock', 'NonCallableMagicMock', 'mock_open', 'PropertyMock', + 'seal', ) -from functools import partial +import asyncio +import contextlib +import io import inspect import pprint import sys -try: - import builtins -except ImportError: - import __builtin__ as builtins -from types import ModuleType - -import six -from six import wraps -from pbr.version import VersionInfo - -_v = VersionInfo('mock').semantic_version() -__version__ = _v.release_string() -version_info = _v.version_tuple() - -import mock +import threading +import builtins try: - inspectsignature = inspect.signature -except AttributeError: - import funcsigs - inspectsignature = funcsigs.signature + from dataclasses import fields, is_dataclass + HAS_DATACLASSES = True +except ImportError: + HAS_DATACLASSES = False +from types import CodeType, ModuleType, MethodType +from unittest.util import safe_repr +from functools import wraps, partial +from threading import RLock -# TODO: use six. -try: - unicode -except NameError: - # Python 3 - basestring = unicode = str -try: - long -except NameError: - # Python 3 - long = int +from mock import IS_PYPY +from .backports import iscoroutinefunction -try: - BaseException -except NameError: - # Python 2.4 compatibility - BaseException = Exception -if six.PY2: - # Python 2's next() can't handle a non-iterator with a __next__ method. - _next = next - def next(obj, _next=_next): - if getattr(obj, '__next__', None): - return obj.__next__() - return _next(obj) +class InvalidSpecError(Exception): + """Indicates that an invalid value was used as a mock spec.""" - del _next +_builtins = {name for name in dir(builtins) if not name.startswith('_')} -_builtins = set(name for name in dir(builtins) if not name.startswith('_')) +FILTER_DIR = True -BaseExceptions = (BaseException,) -if 'java' in sys.platform: - # jython - import java - BaseExceptions = (BaseException, java.lang.Throwable) +# Workaround for issue #12370 +# Without this, the __class__ properties wouldn't be set correctly +_safe_super = super -try: - _isidentifier = str.isidentifier -except AttributeError: - # Python 2.X - import keyword - import re - regex = re.compile(r'^[a-z_][a-z0-9_]*$', re.I) - def _isidentifier(string): - if string in keyword.kwlist: - return False - return regex.match(string) +def _is_async_obj(obj): + if _is_instance_mock(obj) and not isinstance(obj, AsyncMock): + return False + if hasattr(obj, '__func__'): + obj = getattr(obj, '__func__') + return iscoroutinefunction(obj) or inspect.isawaitable(obj) -self = 'im_self' -builtin = '__builtin__' -if six.PY3: - self = '__self__' - builtin = 'builtins' -# NOTE: This FILTER_DIR is not used. The binding in mock.FILTER_DIR is. -FILTER_DIR = True +def _is_async_func(func): + if getattr(func, '__code__', None): + return iscoroutinefunction(func) + else: + return False -# Workaround for Python issue #12370 -# Without this, the __class__ properties wouldn't be set correctly -_safe_super = super def _is_instance_mock(obj): # can't use isinstance on Mock objects because they override __class__ @@ -152,19 +84,18 @@ def _is_instance_mock(obj): def _is_exception(obj): return ( - isinstance(obj, BaseExceptions) or - isinstance(obj, ClassTypes) and issubclass(obj, BaseExceptions) + isinstance(obj, BaseException) or + isinstance(obj, type) and issubclass(obj, BaseException) ) -class _slotted(object): - __slots__ = ['a'] - - -DescriptorTypes = ( - type(_slotted.a), - property, -) +def _extract_mock(obj): + # Autospecced functions will return a FunctionType with "mock" attribute + # which is the actual mock object that needs to be used. + if isinstance(obj, FunctionTypes) and hasattr(obj, 'mock'): + return obj.mock + else: + return obj def _get_signature_object(func, as_instance, eat_self): @@ -173,14 +104,17 @@ def _get_signature_object(func, as_instance, eat_self): signature object. Return a (reduced func, signature) tuple, or None. """ - if isinstance(func, ClassTypes) and not as_instance: + if isinstance(func, type) and not as_instance: # If it's a type and should be modelled as a type, use __init__. - try: - func = func.__init__ - except AttributeError: - return None + func = func.__init__ # Skip the `self` argument in __init__ eat_self = True + elif isinstance(func, (classmethod, staticmethod)): + if isinstance(func, classmethod): + # Skip the `cls` argument of a class method + eat_self = True + # Use the original decorated method to extract the correct function signature + func = func.__func__ elif not isinstance(func, FunctionTypes): # If we really want to model an instance of the passed type, # __call__ should be looked up, not __init__. @@ -192,9 +126,8 @@ def _get_signature_object(func, as_instance, eat_self): sig_func = partial(func, None) else: sig_func = func - try: - return func, inspectsignature(sig_func) + return func, inspect.signature(sig_func) except ValueError: # Certain callable types are not supported by inspect.signature() return None @@ -209,37 +142,27 @@ def checksig(_mock_self, *args, **kwargs): sig.bind(*args, **kwargs) _copy_func_details(func, checksig) type(mock)._mock_check_sig = checksig + type(mock).__signature__ = sig def _copy_func_details(func, funcopy): - funcopy.__name__ = func.__name__ - funcopy.__doc__ = func.__doc__ - try: - funcopy.__text_signature__ = func.__text_signature__ - except AttributeError: - pass # we explicitly don't copy func.__dict__ into this copy as it would # expose original attributes that should be mocked - try: - funcopy.__module__ = func.__module__ - except AttributeError: - pass - try: - funcopy.__defaults__ = func.__defaults__ - except AttributeError: - pass - try: - funcopy.__kwdefaults__ = func.__kwdefaults__ - except AttributeError: - pass - if six.PY2: - funcopy.func_defaults = func.func_defaults - return + for attribute in ( + '__name__', '__doc__', '__text_signature__', + '__module__', '__defaults__', '__kwdefaults__', + ): + try: + setattr(funcopy, attribute, getattr(func, attribute)) + except AttributeError: + pass def _callable(obj): - if isinstance(obj, ClassTypes): + if isinstance(obj, type): return True + if isinstance(obj, (staticmethod, classmethod, MethodType)): + return _callable(obj.__func__) if getattr(obj, '__call__', None) is not None: return True return False @@ -254,25 +177,15 @@ def _is_list(obj): def _instance_callable(obj): """Given an object, return True if the object is callable. For classes, return True if instances would be callable.""" - if not isinstance(obj, ClassTypes): + if not isinstance(obj, type): # already an instance return getattr(obj, '__call__', None) is not None - if six.PY3: - # *could* be broken by a class overriding __mro__ or __dict__ via - # a metaclass - for base in (obj,) + obj.__mro__: - if base.__dict__.get('__call__') is not None: - return True - else: - klass = obj - # uses __bases__ instead of __mro__ so that we work with old style classes - if klass.__dict__.get('__call__') is not None: + # *could* be broken by a class overriding __mro__ or __dict__ via + # a metaclass + for base in (obj,) + obj.__mro__: + if base.__dict__.get('__call__') is not None: return True - - for base in klass.__bases__: - if _instance_callable(base): - return True return False @@ -280,40 +193,62 @@ def _set_signature(mock, original, instance=False): # creates a function with signature (*args, **kwargs) that delegates to a # mock. It still does signature checking by calling a lambda with the same # signature as the original. - if not _callable(original): - return - skipfirst = isinstance(original, ClassTypes) + skipfirst = isinstance(original, type) result = _get_signature_object(original, instance, skipfirst) if result is None: - return + return mock func, sig = result def checksig(*args, **kwargs): sig.bind(*args, **kwargs) _copy_func_details(func, checksig) name = original.__name__ - if not _isidentifier(name): + if not name.isidentifier(): name = 'funcopy' context = {'_checksig_': checksig, 'mock': mock} src = """def %s(*args, **kwargs): _checksig_(*args, **kwargs) return mock(*args, **kwargs)""" % name - six.exec_(src, context) + exec (src, context) funcopy = context[name] - _setup_func(funcopy, mock) + _setup_func(funcopy, mock, sig) return funcopy +def _set_async_signature(mock, original, instance=False, is_async_mock=False): + # creates an async function with signature (*args, **kwargs) that delegates to a + # mock. It still does signature checking by calling a lambda with the same + # signature as the original. -def _setup_func(funcopy, mock): - funcopy.mock = mock + skipfirst = isinstance(original, type) + func, sig = _get_signature_object(original, instance, skipfirst) + def checksig(*args, **kwargs): + sig.bind(*args, **kwargs) + _copy_func_details(func, checksig) - # can't use isinstance with mocks - if not _is_instance_mock(mock): - return + name = original.__name__ + context = {'_checksig_': checksig, 'mock': mock} + src = """async def %s(*args, **kwargs): + _checksig_(*args, **kwargs) + return await mock(*args, **kwargs)""" % name + exec (src, context) + funcopy = context[name] + _setup_func(funcopy, mock, sig) + _setup_async_mock(funcopy) + return funcopy + + +def _setup_func(funcopy, mock, sig): + funcopy.mock = mock def assert_called_with(*args, **kwargs): return mock.assert_called_with(*args, **kwargs) + def assert_called(*args, **kwargs): + return mock.assert_called(*args, **kwargs) + def assert_not_called(*args, **kwargs): + return mock.assert_not_called(*args, **kwargs) + def assert_called_once(*args, **kwargs): + return mock.assert_called_once(*args, **kwargs) def assert_called_once_with(*args, **kwargs): return mock.assert_called_once_with(*args, **kwargs) def assert_has_calls(*args, **kwargs): @@ -344,10 +279,41 @@ def reset_mock(): funcopy.assert_has_calls = assert_has_calls funcopy.assert_any_call = assert_any_call funcopy.reset_mock = reset_mock + funcopy.assert_called = assert_called + funcopy.assert_not_called = assert_not_called + funcopy.assert_called_once = assert_called_once + funcopy.__signature__ = sig mock._mock_delegate = funcopy +def _setup_async_mock(mock): + mock._is_coroutine = asyncio.coroutines._is_coroutine + mock.await_count = 0 + mock.await_args = None + mock.await_args_list = _CallList() + + # Mock is not configured yet so the attributes are set + # to a function and then the corresponding mock helper function + # is called when the helper is accessed similar to _setup_func. + def wrapper(attr, *args, **kwargs): + return getattr(mock.mock, attr)(*args, **kwargs) + + for attribute in ('assert_awaited', + 'assert_awaited_once', + 'assert_awaited_with', + 'assert_awaited_once_with', + 'assert_any_await', + 'assert_has_awaits', + 'assert_not_awaited'): + + # setattr(mock, attribute, wrapper) causes late binding + # hence attribute will always be the last value in the loop + # Use partial(wrapper, attribute) to ensure the attribute is bound + # correctly. + setattr(mock, attribute, partial(wrapper, attribute)) + + def _is_magic(name): return '__%s__' % name[2:-2] == name @@ -360,6 +326,9 @@ def __init__(self, name): def __repr__(self): return 'sentinel.%s' % self.name + def __reduce__(self): + return 'sentinel.%s' % self.name + class _Sentinel(object): """Access attributes to return a named object, usable as a sentinel.""" @@ -372,6 +341,9 @@ def __getattr__(self, name): raise AttributeError return self._sentinels.setdefault(name, _SentinelObject(name)) + def __reduce__(self): + return 'sentinel' + sentinel = _Sentinel() @@ -380,26 +352,11 @@ def __getattr__(self, name): _deleted = sentinel.DELETED -class OldStyleClass: - pass -ClassType = type(OldStyleClass) - - -def _copy(value): - if type(value) in (dict, list, tuple, set): - return type(value)(value) - return value - - -ClassTypes = (type,) -if six.PY2: - ClassTypes = (type, ClassType) - -_allowed_names = set(( +_allowed_names = { 'return_value', '_mock_return_value', 'side_effect', '_mock_side_effect', '_mock_parent', '_mock_new_parent', '_mock_name', '_mock_new_name' -)) +} def _delegating_property(name): @@ -442,6 +399,8 @@ def __repr__(self): def _check_and_set_parent(parent, value, name, new_name): + value = _extract_mock(value) + if not _is_instance_mock(value): return False if ((value._mock_name or value._mock_new_name) or @@ -469,8 +428,6 @@ def _check_and_set_parent(parent, value, name, new_name): class _MockIter(object): def __init__(self, obj): self.obj = iter(obj) - def __iter__(self): - return self def __next__(self): return next(self.obj) @@ -485,12 +442,30 @@ def __init__(self, *args, **kwargs): class NonCallableMock(Base): """A non-callable version of `Mock`""" - def __new__(cls, *args, **kw): + # Store a mutex as a class attribute in order to protect concurrent access + # to mock attributes. Using a class attribute allows all NonCallableMock + # instances to share the mutex for simplicity. + # + # See https://github.com/python/cpython/issues/98624 for why this is + # necessary. + _lock = RLock() + + def __new__( + cls, spec=None, wraps=None, name=None, spec_set=None, + parent=None, _spec_state=None, _new_name='', _new_parent=None, + _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs + ): # every instance has its own class # so we can create magic methods on the # class without stomping on other mocks - new = type(cls.__name__, (cls,), {'__doc__': cls.__doc__}) - instance = object.__new__(new) + bases = (cls,) + if not issubclass(cls, AsyncMockMixin): + # Check if spec is an async object or function + spec_arg = spec_set or spec + if spec_arg is not None and _is_async_obj(spec_arg): + bases = (AsyncMockMixin, cls) + new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) + instance = _safe_super(NonCallableMock, cls).__new__(new) return instance @@ -507,6 +482,7 @@ def __init__( __dict__['_mock_name'] = name __dict__['_mock_new_name'] = _new_name __dict__['_mock_new_parent'] = _new_parent + __dict__['_mock_sealed'] = False if spec_set is not None: spec = spec_set @@ -543,10 +519,12 @@ def attach_mock(self, mock, attribute): Attach a mock as an attribute of this one, replacing its name and parent. Calls to the attached mock will be recorded in the `method_calls` and `mock_calls` attributes of this one.""" - mock._mock_parent = None - mock._mock_new_parent = None - mock._mock_name = '' - mock._mock_new_name = None + inner_mock = _extract_mock(mock) + + inner_mock._mock_parent = None + inner_mock._mock_new_parent = None + inner_mock._mock_name = '' + inner_mock._mock_new_name = None setattr(self, attribute, mock) @@ -562,33 +540,49 @@ def mock_add_spec(self, spec, spec_set=False): def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _eat_self=False): + if _is_instance_mock(spec): + raise InvalidSpecError(f'Cannot spec a Mock object. [object={spec!r}]') + _spec_class = None _spec_signature = None + _spec_asyncs = [] if spec is not None and not _is_list(spec): - if isinstance(spec, ClassTypes): + if isinstance(spec, type): _spec_class = spec else: - _spec_class = _get_class(spec) + _spec_class = type(spec) res = _get_signature_object(spec, _spec_as_instance, _eat_self) _spec_signature = res and res[1] - spec = dir(spec) + spec_list = dir(spec) + + for attr in spec_list: + static_attr = inspect.getattr_static(spec, attr, None) + unwrapped_attr = static_attr + try: + unwrapped_attr = inspect.unwrap(unwrapped_attr) + except ValueError: + pass + if iscoroutinefunction(unwrapped_attr): + _spec_asyncs.append(attr) + + spec = spec_list __dict__ = self.__dict__ __dict__['_spec_class'] = _spec_class __dict__['_spec_set'] = spec_set __dict__['_spec_signature'] = _spec_signature __dict__['_mock_methods'] = spec - + __dict__['_spec_asyncs'] = _spec_asyncs def __get_return_value(self): ret = self._mock_return_value if self._mock_delegate is not None: ret = self._mock_delegate.return_value - if ret is DEFAULT: + if ret is DEFAULT and self._mock_wraps is None: ret = self._get_child_mock( _new_parent=self, _new_name='()' ) @@ -643,7 +637,9 @@ def __set_side_effect(self, value): side_effect = property(__get_side_effect, __set_side_effect) - def reset_mock(self, visited=None): + def reset_mock(self, visited=None, *, + return_value: bool = False, + side_effect: bool = False): "Restore the mock object to its initial state." if visited is None: visited = [] @@ -658,10 +654,15 @@ def reset_mock(self, visited=None): self.call_args_list = _CallList() self.method_calls = _CallList() + if return_value: + self._mock_return_value = DEFAULT + if side_effect: + self._mock_side_effect = None + for child in self._mock_children.values(): - if isinstance(child, _SpecState): + if isinstance(child, _SpecState) or child is _deleted: continue - child.reset_mock(visited) + child.reset_mock(visited, return_value=return_value, side_effect=side_effect) ret = self._mock_return_value if _is_instance_mock(ret) and ret is not self: @@ -691,44 +692,54 @@ def configure_mock(self, **kwargs): def __getattr__(self, name): - if name in ('_mock_methods', '_mock_unsafe'): + if name in {'_mock_methods', '_mock_unsafe'}: raise AttributeError(name) elif self._mock_methods is not None: if name not in self._mock_methods or name in _all_magics: raise AttributeError("Mock object has no attribute %r" % name) elif _is_magic(name): raise AttributeError(name) - if not self._mock_unsafe: - if name.startswith(('assert', 'assret')): + if not self._mock_unsafe and (not self._mock_methods or name not in self._mock_methods): + if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')) or name in _ATTRIB_DENY_LIST: + raise AttributeError( + f"{name!r} is not a valid assertion. Use a spec " + f"for the mock if {name!r} is meant to be an attribute.") + + with NonCallableMock._lock: + result = self._mock_children.get(name) + if result is _deleted: raise AttributeError(name) - - result = self._mock_children.get(name) - if result is _deleted: - raise AttributeError(name) - elif result is None: - wraps = None - if self._mock_wraps is not None: - # XXXX should we get the attribute without triggering code - # execution? - wraps = getattr(self._mock_wraps, name) - - result = self._get_child_mock( - parent=self, name=name, wraps=wraps, _new_name=name, - _new_parent=self - ) - self._mock_children[name] = result - - elif isinstance(result, _SpecState): - result = create_autospec( - result.spec, result.spec_set, result.instance, - result.parent, result.name - ) - self._mock_children[name] = result + elif result is None: + wraps = None + if self._mock_wraps is not None: + # XXXX should we get the attribute without triggering code + # execution? + wraps = getattr(self._mock_wraps, name) + + result = self._get_child_mock( + parent=self, name=name, wraps=wraps, _new_name=name, + _new_parent=self + ) + self._mock_children[name] = result + + elif isinstance(result, _SpecState): + try: + result = create_autospec( + result.spec, result.spec_set, result.instance, + result.parent, result.name + ) + except InvalidSpecError: + target_name = self.__dict__['_mock_name'] or self + raise InvalidSpecError( + f'Cannot autospec attr {name!r} from target ' + f'{target_name!r} as it has already been mocked out. ' + f'[target={self!r}, attr={result.spec!r}]') + self._mock_children[name] = result return result - def __repr__(self): + def _extract_mock_name(self): _name_list = [self._mock_new_name] _parent = self._mock_new_parent last = self @@ -736,7 +747,7 @@ def __repr__(self): dot = '.' if _name_list == ['()']: dot = '' - seen = set() + while _parent is not None: last = _parent @@ -747,18 +758,16 @@ def __repr__(self): _parent = _parent._mock_new_parent - # use ids here so as not to call __hash__ on the mocks - if id(_parent) in seen: - break - seen.add(id(_parent)) - _name_list = list(reversed(_name_list)) _first = last._mock_name or 'mock' if len(_name_list) > 1: if _name_list[1] not in ('()', '().'): _first += '.' _name_list[0] = _first - name = ''.join(_name_list) + return ''.join(_name_list) + + def __repr__(self): + name = self._extract_mock_name() name_string = '' if name not in ('mock', 'mock.'): @@ -780,21 +789,20 @@ def __repr__(self): def __dir__(self): """Filter the output of `dir(mock)` to only useful members.""" - if not mock.FILTER_DIR and getattr(object, '__dir__', None): - # object.__dir__ is not in 2.7 + if not FILTER_DIR: return object.__dir__(self) extras = self._mock_methods or [] from_type = dir(type(self)) from_dict = list(self.__dict__) + from_child_mocks = [ + m_name for m_name, m_value in self._mock_children.items() + if m_value is not _deleted] - if mock.FILTER_DIR: - # object.__dir__ is not in 2.7 - from_type = [e for e in from_type if not e.startswith('_')] - from_dict = [e for e in from_dict if not e.startswith('_') or - _is_magic(e)] - return sorted(set(extras + from_type + from_dict + - list(self._mock_children))) + from_type = [e for e in from_type if not e.startswith('_')] + from_dict = [e for e in from_dict if not e.startswith('_') or + _is_magic(e)] + return sorted(set(extras + from_type + from_dict + from_child_mocks)) def __setattr__(self, name, value): @@ -828,6 +836,14 @@ def __setattr__(self, name, value): else: if _check_and_set_parent(self, value, name, name): self._mock_children[name] = value + + if self._mock_sealed and not hasattr(self, name): + mock_name = f'{self._extract_mock_name()}.{name}' + raise AttributeError(f'Cannot set {mock_name}') + + if isinstance(value, PropertyMock): + self.__dict__[name] = value + return return object.__setattr__(self, name, value) @@ -839,11 +855,10 @@ def __delattr__(self, name): # not set on the instance itself return - if name in self.__dict__: - object.__delattr__(self, name) - obj = self._mock_children.get(name, _missing) - if obj is _deleted: + if name in self.__dict__: + _safe_super(NonCallableMock, self).__delattr__(name) + elif obj is _deleted: raise AttributeError(name) if obj is not _missing: del self._mock_children[name] @@ -855,24 +870,60 @@ def _format_mock_call_signature(self, args, kwargs): return _format_call_signature(name, args, kwargs) - def _format_mock_failure_message(self, args, kwargs): - message = 'Expected call: %s\nActual call: %s' + def _format_mock_failure_message(self, args, kwargs, action='call'): + message = 'expected %s not found.\nExpected: %s\n Actual: %s' expected_string = self._format_mock_call_signature(args, kwargs) call_args = self.call_args - if len(call_args) == 3: - call_args = call_args[1:] actual_string = self._format_mock_call_signature(*call_args) - return message % (expected_string, actual_string) + return message % (action, expected_string, actual_string) + + + def _get_call_signature_from_name(self, name): + """ + * If call objects are asserted against a method/function like obj.meth1 + then there could be no name for the call object to lookup. Hence just + return the spec_signature of the method/function being asserted against. + * If the name is not empty then remove () and split by '.' to get + list of names to iterate through the children until a potential + match is found. A child mock is created only during attribute access + so if we get a _SpecState then no attributes of the spec were accessed + and can be safely exited. + """ + if not name: + return self._spec_signature + + sig = None + names = name.replace('()', '').split('.') + children = self._mock_children + + for name in names: + child = children.get(name) + if child is None or isinstance(child, _SpecState): + break + else: + # If an autospecced object is attached using attach_mock the + # child would be a function with mock object as attribute from + # which signature has to be derived. + child = _extract_mock(child) + children = child._mock_children + sig = child._spec_signature + + return sig def _call_matcher(self, _call): """ - Given a call (or simply a (args, kwargs) tuple), return a + Given a call (or simply an (args, kwargs) tuple), return a comparison key suitable for matching with other calls. This is a best effort method which relies on the spec's signature, if available, or falls back on the arguments themselves. """ - sig = self._spec_signature + + if isinstance(_call, tuple) and len(_call) > 2: + sig = self._get_call_signature_from_name(_call[0]) + else: + sig = self._spec_signature + if sig is not None: if len(_call) == 2: name = '' @@ -880,10 +931,10 @@ def _call_matcher(self, _call): else: name, args, kwargs = _call try: - return name, sig.bind(*args, **kwargs) + bound_call = sig.bind(*args, **kwargs) + return call(name, bound_call.args, bound_call.kwargs) except TypeError as e: - e.__traceback__ = None - return e + return e.with_traceback(None) else: return _call @@ -892,8 +943,10 @@ def assert_not_called(_mock_self): """ self = _mock_self if self.call_count != 0: - msg = ("Expected '%s' to not have been called. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to not have been called. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) raise AssertionError(msg) def assert_called(_mock_self): @@ -902,7 +955,7 @@ def assert_called(_mock_self): self = _mock_self if self.call_count == 0: msg = ("Expected '%s' to have been called." % - self._mock_name or 'mock') + (self._mock_name or 'mock')) raise AssertionError(msg) def assert_called_once(_mock_self): @@ -910,40 +963,44 @@ def assert_called_once(_mock_self): """ self = _mock_self if not self.call_count == 1: - msg = ("Expected '%s' to have been called once. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to have been called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) raise AssertionError(msg) def assert_called_with(_mock_self, *args, **kwargs): - """assert that the mock was called with the specified arguments. + """assert that the last call was made with the specified arguments. Raises an AssertionError if the args and keyword args passed in are different to the last call to the mock.""" self = _mock_self if self.call_args is None: expected = self._format_mock_call_signature(args, kwargs) - raise AssertionError('Expected call: %s\nNot called' % (expected,)) + actual = 'not called.' + error_message = ('expected call not found.\nExpected: %s\n Actual: %s' + % (expected, actual)) + raise AssertionError(error_message) - def _error_message(cause): + def _error_message(): msg = self._format_mock_failure_message(args, kwargs) - if six.PY2 and cause is not None: - # Tack on some diagnostics for Python without __cause__ - msg = '%s\n%s' % (msg, str(cause)) return msg - expected = self._call_matcher((args, kwargs)) + expected = self._call_matcher(_Call((args, kwargs), two=True)) actual = self._call_matcher(self.call_args) - if expected != actual: + if actual != expected: cause = expected if isinstance(expected, Exception) else None - six.raise_from(AssertionError(_error_message(cause)), cause) + raise AssertionError(_error_message()) from cause def assert_called_once_with(_mock_self, *args, **kwargs): - """assert that the mock was called exactly once and with the specified - arguments.""" + """assert that the mock was called exactly once and that that call was + with the specified arguments.""" self = _mock_self if not self.call_count == 1: - msg = ("Expected '%s' to be called once. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to be called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) raise AssertionError(msg) return self.assert_called_with(*args, **kwargs) @@ -959,14 +1016,22 @@ def assert_has_calls(self, calls, any_order=False): If `any_order` is True then the calls can be in any order, but they must all appear in `mock_calls`.""" expected = [self._call_matcher(c) for c in calls] - cause = expected if isinstance(expected, Exception) else None + cause = next((e for e in expected if isinstance(e, Exception)), None) all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls) if not any_order: if expected not in all_calls: - six.raise_from(AssertionError( - 'Calls not found.\nExpected: %r\n' - 'Actual: %r' % (_CallList(calls), self.mock_calls) - ), cause) + if cause is None: + problem = 'Calls not found.' + else: + problem = ('Error processing expected calls.\n' + 'Errors: {}').format( + [e if isinstance(e, Exception) else None + for e in expected]) + raise AssertionError( + f'{problem}\n' + f'Expected: {_CallList(calls)}\n' + f' Actual: {safe_repr(self.mock_calls)}' + ) from cause return all_calls = list(all_calls) @@ -978,9 +1043,11 @@ def assert_has_calls(self, calls, any_order=False): except ValueError: not_found.append(kall) if not_found: - six.raise_from(AssertionError( - '%r not all found in call list' % (tuple(not_found),) - ), cause) + raise AssertionError( + '%r does not contain all of %r in its call list, ' + 'found %r instead' % (self._mock_name or 'mock', + tuple(not_found), all_calls) + ) from cause def assert_any_call(self, *args, **kwargs): @@ -989,14 +1056,14 @@ def assert_any_call(self, *args, **kwargs): The assert passes if the mock has *ever* been called, unlike `assert_called_with` and `assert_called_once_with` that only pass if the call is the most recent one.""" - expected = self._call_matcher((args, kwargs)) + expected = self._call_matcher(_Call((args, kwargs), two=True)) + cause = expected if isinstance(expected, Exception) else None actual = [self._call_matcher(c) for c in self.call_args_list] - if expected not in actual: - cause = expected if isinstance(expected, Exception) else None + if cause or expected not in _AnyComparer(actual): expected_string = self._format_mock_call_signature(args, kwargs) - six.raise_from(AssertionError( + raise AssertionError( '%s call not found' % expected_string - ), cause) + ) from cause def _get_child_mock(self, **kw): @@ -1007,17 +1074,79 @@ def _get_child_mock(self, **kw): For non-callable mocks the callable variant will be used (rather than any custom subclass).""" + if self._mock_sealed: + attribute = f".{kw['name']}" if "name" in kw else "()" + mock_name = self._extract_mock_name() + attribute + raise AttributeError(mock_name) + + _new_name = kw.get("_new_name") + if _new_name in self.__dict__['_spec_asyncs']: + return AsyncMock(**kw) + _type = type(self) - if not issubclass(_type, CallableMixin): + if issubclass(_type, MagicMock) and _new_name in _async_method_magics: + # Any asynchronous magic becomes an AsyncMock + klass = AsyncMock + elif issubclass(_type, AsyncMockMixin): + if (_new_name in _all_sync_magics or + self._mock_methods and _new_name in self._mock_methods): + # Any synchronous method on AsyncMock becomes a MagicMock + klass = MagicMock + else: + klass = AsyncMock + elif not issubclass(_type, CallableMixin): if issubclass(_type, NonCallableMagicMock): klass = MagicMock - elif issubclass(_type, NonCallableMock) : + elif issubclass(_type, NonCallableMock): klass = Mock else: klass = _type.__mro__[1] return klass(**kw) + def _calls_repr(self): + """Renders self.mock_calls as a string. + + Example: "\nCalls: [call(1), call(2)]." + + If self.mock_calls is empty, an empty string is returned. The + output will be truncated if very long. + """ + if not self.mock_calls: + return "" + return f"\nCalls: {safe_repr(self.mock_calls)}." + + +try: + removeprefix = str.removeprefix +except AttributeError: + # Py 3.8 and earlier: + def removeprefix(name, prefix): + return name[len(prefix):] + +# Denylist for forbidden attribute names in safe mode +_ATTRIB_DENY_LIST = frozenset({ + removeprefix(name, "assert_") + for name in dir(NonCallableMock) + if name.startswith("assert_") +}) + + +class _AnyComparer(list): + """A list which checks if it contains a call which may have an + argument of ANY, flipping the components of item and self from + their traditional locations so that ANY is guaranteed to be on + the left.""" + def __contains__(self, item): + for _call in self: + assert len(item) == len(_call) + if all([ + expected == actual + for expected, actual in zip(item, _call) + ]): + return True + return False + def _try_iter(obj): if obj is None: @@ -1034,14 +1163,12 @@ def _try_iter(obj): return obj - class CallableMixin(Base): def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, parent=None, _spec_state=None, _new_name='', _new_parent=None, **kwargs): self.__dict__['_mock_return_value'] = return_value - _safe_super(CallableMixin, self).__init__( spec, wraps, name, spec_set, parent, _spec_state, _new_name, _new_parent, **kwargs @@ -1059,80 +1186,89 @@ def __call__(_mock_self, *args, **kwargs): # can't use self in-case a function / method we are mocking uses self # in the signature _mock_self._mock_check_sig(*args, **kwargs) + _mock_self._increment_mock_call(*args, **kwargs) return _mock_self._mock_call(*args, **kwargs) def _mock_call(_mock_self, *args, **kwargs): + return _mock_self._execute_mock_call(*args, **kwargs) + + def _increment_mock_call(_mock_self, *args, **kwargs): self = _mock_self self.called = True self.call_count += 1 - _new_name = self._mock_new_name - _new_parent = self._mock_new_parent + # handle call_args + # needs to be set here so assertions on call arguments pass before + # execution in the case of awaited calls _call = _Call((args, kwargs), two=True) self.call_args = _call self.call_args_list.append(_call) - self.mock_calls.append(_Call(('', args, kwargs))) - seen = set() - skip_next_dot = _new_name == '()' + # initial stuff for method_calls: do_method_calls = self._mock_parent is not None - name = self._mock_name - while _new_parent is not None: - this_mock_call = _Call((_new_name, args, kwargs)) - if _new_parent._mock_new_name: - dot = '.' - if skip_next_dot: - dot = '' + method_call_name = self._mock_name - skip_next_dot = False - if _new_parent._mock_new_name == '()': - skip_next_dot = True + # initial stuff for mock_calls: + mock_call_name = self._mock_new_name + is_a_call = mock_call_name == '()' + self.mock_calls.append(_Call(('', args, kwargs))) - _new_name = _new_parent._mock_new_name + dot + _new_name + # follow up the chain of mocks: + _new_parent = self._mock_new_parent + while _new_parent is not None: + # handle method_calls: if do_method_calls: - if _new_name == name: - this_method_call = this_mock_call - else: - this_method_call = _Call((name, args, kwargs)) - _new_parent.method_calls.append(this_method_call) - + _new_parent.method_calls.append(_Call((method_call_name, args, kwargs))) do_method_calls = _new_parent._mock_parent is not None if do_method_calls: - name = _new_parent._mock_name + '.' + name + method_call_name = _new_parent._mock_name + '.' + method_call_name + # handle mock_calls: + this_mock_call = _Call((mock_call_name, args, kwargs)) _new_parent.mock_calls.append(this_mock_call) + + if _new_parent._mock_new_name: + if is_a_call: + dot = '' + else: + dot = '.' + is_a_call = _new_parent._mock_new_name == '()' + mock_call_name = _new_parent._mock_new_name + dot + mock_call_name + + # follow the parental chain: _new_parent = _new_parent._mock_new_parent - # use ids here so as not to call __hash__ on the mocks - _new_parent_id = id(_new_parent) - if _new_parent_id in seen: - break - seen.add(_new_parent_id) + def _execute_mock_call(_mock_self, *args, **kwargs): + self = _mock_self + # separate from _increment_mock_call so that awaited functions are + # executed separately from their call, also AsyncMock overrides this method - ret_val = DEFAULT effect = self.side_effect if effect is not None: if _is_exception(effect): raise effect - - if not _callable(effect): + elif not _callable(effect): result = next(effect) if _is_exception(result): raise result - if result is DEFAULT: - result = self.return_value + else: + result = effect(*args, **kwargs) + + if result is not DEFAULT: return result - ret_val = effect(*args, **kwargs) + if self._mock_return_value is not DEFAULT: + return self.return_value - if (self._mock_wraps is not None and - self._mock_return_value is DEFAULT): + if self._mock_delegate and self._mock_delegate.return_value is not DEFAULT: + return self.return_value + + if self._mock_wraps is not None: return self._mock_wraps(*args, **kwargs) - if ret_val is DEFAULT: - ret_val = self.return_value - return ret_val + + return self.return_value @@ -1161,9 +1297,6 @@ class or instance) that acts as the specification for the mock object. If arguments as the mock, and unless it returns `DEFAULT`, the return value of this function is used as the return value. - Alternatively `side_effect` can be an exception class or instance. In - this case the exception will be raised when the mock is called. - If `side_effect` is an iterable then each call to the mock will return the next value from the iterable. If any of the members of the iterable are exceptions they will be raised instead of returned. @@ -1172,6 +1305,13 @@ class or instance) that acts as the specification for the mock object. If this is a new Mock (created on first access). See the `return_value` attribute. + * `unsafe`: By default, accessing any attribute whose name starts with + *assert*, *assret*, *asert*, *aseert*, or *assrt* raises an AttributeError. + Additionally, an AttributeError is raised when accessing + attributes that match the name of an assertion method without the prefix + `assert_`, e.g. accessing `called_once` instead of `assert_called_once`. + Passing `unsafe=True` will allow access to these attributes. + * `wraps`: Item for the mock object to wrap. If `wraps` is not None then calling the Mock will pass the call through to the wrapped object (returning the real result). Attribute access on the mock will return a @@ -1191,7 +1331,6 @@ class or instance) that acts as the specification for the mock object. If """ - def _dot_lookup(thing, comp, import_path): try: return getattr(thing, comp) @@ -1211,9 +1350,15 @@ def _importer(target): return thing -def _is_started(patcher): - # XXXX horrible - return hasattr(patcher, 'is_local') +# _check_spec_arg_typos takes kwargs from commands like patch and checks that +# they don't contain common misspellings of arguments related to autospeccing. +def _check_spec_arg_typos(kwargs_to_check): + typos = ("autospect", "auto_spec", "set_spec") + for typo in typos: + if typo in kwargs_to_check: + raise RuntimeError( + f"{typo!r} might be a typo; use unsafe=True if this is intended" + ) class _patch(object): @@ -1223,7 +1368,7 @@ class _patch(object): def __init__( self, getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, *, unsafe=False ): if new_callable is not None: if new is not DEFAULT: @@ -1234,6 +1379,16 @@ def __init__( raise ValueError( "Cannot use 'autospec' and 'new_callable' together" ) + if not unsafe: + _check_spec_arg_typos(kwargs) + if _is_instance_mock(spec): + raise InvalidSpecError( + f'Cannot spec attr {attribute!r} as the spec ' + f'has already been mocked out. [spec={spec!r}]') + if _is_instance_mock(spec_set): + raise InvalidSpecError( + f'Cannot spec attr {attribute!r} as the spec_set ' + f'target has already been mocked out. [spec_set={spec_set!r}]') self.getter = getter self.attribute = attribute @@ -1246,6 +1401,7 @@ def __init__( self.autospec = autospec self.kwargs = kwargs self.additional_patchers = [] + self.is_started = False def copy(self): @@ -1262,8 +1418,10 @@ def copy(self): def __call__(self, func): - if isinstance(func, ClassTypes): + if isinstance(func, type): return self.decorate_class(func) + if iscoroutinefunction(func): + return self.decorate_async_callable(func) return self.decorate_callable(func) @@ -1281,41 +1439,50 @@ def decorate_class(self, klass): return klass + @contextlib.contextmanager + def decoration_helper(self, patched, args, keywargs): + extra_args = [] + with contextlib.ExitStack() as exit_stack: + for patching in patched.patchings: + arg = exit_stack.enter_context(patching) + if patching.attribute_name is not None: + keywargs.update(arg) + elif patching.new is DEFAULT: + extra_args.append(arg) + + args += tuple(extra_args) + yield (args, keywargs) + + def decorate_callable(self, func): + # NB. Keep the method in sync with decorate_async_callable() if hasattr(func, 'patchings'): func.patchings.append(self) return func @wraps(func) def patched(*args, **keywargs): - extra_args = [] - entered_patchers = [] + with self.decoration_helper(patched, + args, + keywargs) as (newargs, newkeywargs): + return func(*newargs, **newkeywargs) + + patched.patchings = [self] + return patched - exc_info = tuple() - try: - for patching in patched.patchings: - arg = patching.__enter__() - entered_patchers.append(patching) - if patching.attribute_name is not None: - keywargs.update(arg) - elif patching.new is DEFAULT: - extra_args.append(arg) - - args += tuple(extra_args) - return func(*args, **keywargs) - except: - if (patching not in entered_patchers and - _is_started(patching)): - # the patcher may have been started, but an exception - # raised whilst entering one of its additional_patchers - entered_patchers.append(patching) - # Pass the exception to __exit__ - exc_info = sys.exc_info() - # re-raise the exception - raise - finally: - for patching in reversed(entered_patchers): - patching.__exit__(*exc_info) + + def decorate_async_callable(self, func): + # NB. Keep the method in sync with decorate_callable() + if hasattr(func, 'patchings'): + func.patchings.append(self) + return func + + @wraps(func) + async def patched(*args, **keywargs): + with self.decoration_helper(patched, + args, + keywargs) as (newargs, newkeywargs): + return await func(*newargs, **newkeywargs) patched.patchings = [self] return patched @@ -1347,6 +1514,9 @@ def get_original(self): def __enter__(self): """Perform the patch.""" + if self.is_started: + raise RuntimeError("Patch is already started") + new, spec, spec_set = self.new, self.spec, self.spec_set autospec, kwargs = self.autospec, self.kwargs new_callable = self.new_callable @@ -1386,14 +1556,15 @@ def __enter__(self): if spec is not None or spec_set is not None: if original is DEFAULT: raise TypeError("Can't use 'spec' with create=True") - if isinstance(original, ClassTypes): + if isinstance(original, type): # If we're patching out a class and there is a spec inherit = True - Klass = MagicMock - _kwargs = {} + # Determine the Klass to use if new_callable is not None: Klass = new_callable + elif spec is None and _is_async_obj(original): + Klass = AsyncMock elif spec is not None or spec_set is not None: this_spec = spec if spec_set is not None: @@ -1401,10 +1572,17 @@ def __enter__(self): if _is_list(this_spec): not_callable = '__call__' not in this_spec else: - not_callable = not _callable(this_spec) - if not_callable: + not_callable = not callable(this_spec) + if _is_async_obj(this_spec): + Klass = AsyncMock + elif not_callable: Klass = NonCallableMagicMock + else: + Klass = MagicMock + else: + Klass = MagicMock + _kwargs = {} if spec is not None: _kwargs['spec'] = spec if spec_set is not None: @@ -1446,6 +1624,18 @@ def __enter__(self): if autospec is True: autospec = original + if _is_instance_mock(self.target): + raise InvalidSpecError( + f'Cannot autospec attr {self.attribute!r} as the patch ' + f'target has already been mocked out. ' + f'[target={self.target!r}, attr={autospec!r}]') + if _is_instance_mock(autospec): + target_name = getattr(self.target, '__name__', self.target) + raise InvalidSpecError( + f'Cannot autospec attr {self.attribute!r} from target ' + f'{target_name!r} as it has already been mocked out. ' + f'[target={self.target!r}, attr={autospec!r}]') + new = create_autospec(autospec, spec_set=spec_set, _name=self.attribute, **kwargs) elif kwargs: @@ -1457,24 +1647,29 @@ def __enter__(self): self.temp_original = original self.is_local = local - setattr(self.target, self.attribute, new_attr) - if self.attribute_name is not None: - extra_args = {} - if self.new is DEFAULT: - extra_args[self.attribute_name] = new - for patching in self.additional_patchers: - arg = patching.__enter__() - if patching.new is DEFAULT: - extra_args.update(arg) - return extra_args - - return new - + self._exit_stack = contextlib.ExitStack() + self.is_started = True + try: + setattr(self.target, self.attribute, new_attr) + if self.attribute_name is not None: + extra_args = {} + if self.new is DEFAULT: + extra_args[self.attribute_name] = new + for patching in self.additional_patchers: + arg = self._exit_stack.enter_context(patching) + if patching.new is DEFAULT: + extra_args.update(arg) + return extra_args + + return new + except: + if not self.__exit__(*sys.exc_info()): + raise def __exit__(self, *exc_info): """Undo the patch.""" - if not _is_started(self): - raise RuntimeError('stop called on unstarted patcher') + if not self.is_started: + return if self.is_local and self.temp_original is not DEFAULT: setattr(self.target, self.attribute, self.temp_original) @@ -1490,9 +1685,10 @@ def __exit__(self, *exc_info): del self.temp_original del self.is_local del self.target - for patcher in reversed(self.additional_patchers): - if _is_started(patcher): - patcher.__exit__(*exc_info) + exit_stack = self._exit_stack + del self._exit_stack + self.is_started = False + return exit_stack.__exit__(*exc_info) def start(self): @@ -1508,18 +1704,18 @@ def stop(self): self._active_patches.remove(self) except ValueError: # If the patch hasn't been started this will fail - pass + return None - return self.__exit__() + return self.__exit__(None, None, None) def _get_target(target): try: target, attribute = target.rsplit('.', 1) - except (TypeError, ValueError): - raise TypeError("Need a valid target to patch. You supplied: %r" % - (target,)) + except (TypeError, ValueError, AttributeError): + raise TypeError( + f"Need a valid target to patch. You supplied: {target!r}") getter = lambda: _importer(target) return getter, attribute @@ -1527,7 +1723,7 @@ def _get_target(target): def _patch_object( target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, - new_callable=None, **kwargs + new_callable=None, *, unsafe=False, **kwargs ): """ patch the named member (`attribute`) on an object (`target`) with a mock @@ -1542,10 +1738,14 @@ def _patch_object( When used as a class decorator `patch.object` honours `patch.TEST_PREFIX` for choosing which methods to wrap. """ + if type(target) is str: + raise TypeError( + f"{target!r} must be the actual object to be patched, not a str" + ) getter = lambda: target return _patch( getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, unsafe=unsafe ) @@ -1571,7 +1771,7 @@ def _patch_multiple(target, spec=None, create=False, spec_set=None, When used as a class decorator `patch.multiple` honours `patch.TEST_PREFIX` for choosing which methods to wrap. """ - if type(target) in (unicode, str): + if type(target) is str: getter = lambda: _importer(target) else: getter = lambda: target @@ -1600,7 +1800,7 @@ def _patch_multiple(target, spec=None, create=False, spec_set=None, def patch( target, new=DEFAULT, spec=None, create=False, - spec_set=None, autospec=None, new_callable=None, **kwargs + spec_set=None, autospec=None, new_callable=None, *, unsafe=False, **kwargs ): """ `patch` acts as a function decorator, class decorator or a context @@ -1608,8 +1808,9 @@ def patch( is patched with a `new` object. When the function/with statement exits the patch is undone. - If `new` is omitted, then the target is replaced with a - `MagicMock`. If `patch` is used as a decorator and `new` is + If `new` is omitted, then the target is replaced with an + `AsyncMock` if the patched object is an async function or a + `MagicMock` otherwise. If `patch` is used as a decorator and `new` is omitted, the created mock is passed in as an extra argument to the decorated function. If `patch` is used as a context manager the created mock is returned by the context manager. @@ -1627,8 +1828,8 @@ def patch( patch to pass in the object being mocked as the spec/spec_set object. `new_callable` allows you to specify a different class, or callable object, - that will be called to create the `new` object. By default `MagicMock` is - used. + that will be called to create the `new` object. By default `AsyncMock` is + used for async functions and `MagicMock` for the rest. A more powerful form of `spec` is `autospec`. If you set `autospec=True` then the mock will be created with a spec from the object being replaced. @@ -1661,8 +1862,13 @@ def patch( use "as" then the patched object will be bound to the name after the "as"; very useful if `patch` is creating a mock object for you. + Patch will raise a `RuntimeError` if passed some common misspellings of + the arguments autospec and spec_set. Pass the argument `unsafe` with the + value True to disable that check. + `patch` takes arbitrary keyword arguments. These will be passed to - the `Mock` (or `new_callable`) on construction. + `AsyncMock` if the patched object is asynchronous, to `MagicMock` + otherwise or to `new_callable` if specified. `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are available for alternate use-cases. @@ -1670,14 +1876,15 @@ def patch( getter, attribute = _get_target(target) return _patch( getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, unsafe=unsafe ) class _patch_dict(object): """ Patch a dictionary, or dictionary like object, and restore the dictionary - to its original state after the test. + to its original state after the test, where the restored dictionary is + a copy of the dictionary as it was before the test. `in_dict` can be a dictionary or a mapping like container. If it is a mapping then it must at least support getting, setting and deleting items @@ -1704,8 +1911,6 @@ class _patch_dict(object): """ def __init__(self, in_dict, values=(), clear=False, **kwargs): - if isinstance(in_dict, basestring): - in_dict = _importer(in_dict) self.in_dict = in_dict # support any argument supported by dict(...) constructor self.values = dict(values) @@ -1715,8 +1920,14 @@ def __init__(self, in_dict, values=(), clear=False, **kwargs): def __call__(self, f): - if isinstance(f, ClassTypes): + if isinstance(f, type): return self.decorate_class(f) + if iscoroutinefunction(f): + return self.decorate_async_callable(f) + return self.decorate_callable(f) + + + def decorate_callable(self, f): @wraps(f) def _inner(*args, **kw): self._patch_dict() @@ -1728,6 +1939,18 @@ def _inner(*args, **kw): return _inner + def decorate_async_callable(self, f): + @wraps(f) + async def _inner(*args, **kw): + self._patch_dict() + try: + return await f(*args, **kw) + finally: + self._unpatch_dict() + + return _inner + + def decorate_class(self, klass): for attr in dir(klass): attr_value = getattr(klass, attr) @@ -1742,10 +1965,13 @@ def decorate_class(self, klass): def __enter__(self): """Patch the dict.""" self._patch_dict() + return self.in_dict def _patch_dict(self): values = self.values + if isinstance(self.in_dict, str): + self.in_dict = _importer(self.in_dict) in_dict = self.in_dict clear = self.clear @@ -1785,11 +2011,27 @@ def _unpatch_dict(self): def __exit__(self, *args): """Unpatch the dict.""" - self._unpatch_dict() + if self._original is not None: + self._unpatch_dict() return False - start = __enter__ - stop = __exit__ + + def start(self): + """Activate a patch, returning any created mock.""" + result = self.__enter__() + _patch._active_patches.append(self) + return result + + + def stop(self): + """Stop an active patch.""" + try: + _patch._active_patches.remove(self) + except ValueError: + # If the patch hasn't been started this will fail + return None + + return self.__exit__(None, None, None) def _clear_dict(in_dict): @@ -1823,33 +2065,33 @@ def _patch_stopall(): # because there is no idivmod "divmod rdivmod neg pos abs invert " "complex int float index " - "trunc floor ceil " + "round trunc floor ceil " + "bool next " + "fspath " + "aiter " ) +if IS_PYPY: + # PyPy has no __sizeof__: http://doc.pypy.org/en/latest/cpython_differences.html + magic_methods = magic_methods.replace('sizeof ', '') + numerics = ( - "add sub mul matmul div floordiv mod lshift rshift and xor or pow" + "add sub mul matmul truediv floordiv mod lshift rshift and xor or pow" ) -if six.PY3: - numerics += ' truediv' inplace = ' '.join('i%s' % n for n in numerics.split()) right = ' '.join('r%s' % n for n in numerics.split()) -extra = '' -if six.PY3: - extra = 'bool next ' -else: - extra = 'unicode long nonzero oct hex truediv rtruediv ' # not including __prepare__, __instancecheck__, __subclasscheck__ # (as they are metaclass methods) # __del__ is not supported at all as it causes problems if it exists -_non_defaults = set(( - '__cmp__', '__getslice__', '__setslice__', '__coerce__', # <3.x +_non_defaults = { '__get__', '__set__', '__delete__', '__reversed__', '__missing__', '__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', - '__getstate__', '__setstate__', '__getformat__', '__setformat__', + '__getstate__', '__setstate__', '__getformat__', '__repr__', '__dir__', '__subclasses__', '__format__', -)) + '__getnewargs_ex__', +} def _get_method(name, func): @@ -1860,25 +2102,32 @@ def method(self, *args, **kw): return method -_magics = set( +_magics = { '__%s__' % method for method in - ' '.join([magic_methods, numerics, inplace, right, extra]).split() -) + ' '.join([magic_methods, numerics, inplace, right]).split() +} -_all_magics = _magics | _non_defaults +# Magic methods used for async `with` statements +_async_method_magics = {"__aenter__", "__aexit__", "__anext__"} +# Magic methods that are only used with async calls but are synchronous functions themselves +_sync_async_magics = {"__aiter__"} +_async_magics = _async_method_magics | _sync_async_magics -_unsupported_magics = set(( +_all_sync_magics = _magics | _non_defaults +_all_magics = _all_sync_magics | _async_magics + +_unsupported_magics = { '__getattr__', '__setattr__', - '__init__', '__new__', '__prepare__' + '__init__', '__new__', '__prepare__', '__instancecheck__', '__subclasscheck__', '__del__' -)) +} _calculate_return_value = { '__hash__': lambda self: object.__hash__(self), '__str__': lambda self: object.__str__(self), '__sizeof__': lambda self: object.__sizeof__(self), - '__unicode__': lambda self: unicode(object.__str__(self)), + '__fspath__': lambda self: f"{type(self).__name__}/{self._extract_mock_name()}/{id(self)}", } _return_values = { @@ -1893,11 +2142,8 @@ def method(self, *args, **kw): '__complex__': 1j, '__float__': 1.0, '__bool__': True, - '__nonzero__': True, - '__oct__': '1', - '__hex__': '0x1', - '__long__': long(1), '__index__': 1, + '__aexit__': False, } @@ -1906,14 +2152,18 @@ def __eq__(other): ret_val = self.__eq__._mock_return_value if ret_val is not DEFAULT: return ret_val - return self is other + if self is other: + return True + return NotImplemented return __eq__ def _get_ne(self): def __ne__(other): if self.__ne__._mock_return_value is not DEFAULT: return DEFAULT - return self is not other + if self is other: + return False + return NotImplemented return __ne__ def _get_iter(self): @@ -1926,10 +2176,19 @@ def __iter__(): return iter(ret_val) return __iter__ +def _get_async_iter(self): + def __aiter__(): + ret_val = self.__aiter__._mock_return_value + if ret_val is DEFAULT: + return _AsyncIterator(iter([])) + return _AsyncIterator(iter(ret_val)) + return __aiter__ + _side_effect_methods = { '__eq__': _get_eq, '__ne__': _get_ne, '__iter__': _get_iter, + '__aiter__': _get_async_iter } @@ -1940,14 +2199,9 @@ def _set_return_value(mock, method, name): method.return_value = fixed return - return_calulator = _calculate_return_value.get(name) - if return_calulator is not None: - try: - return_value = return_calulator(mock) - except AttributeError: - # XXXX why do we return AttributeError here? - # set it as a side_effect instead? - return_value = AttributeError(name) + return_calculator = _calculate_return_value.get(name) + if return_calculator is not None: + return_value = return_calculator(mock) method.return_value = return_value return @@ -1957,7 +2211,7 @@ def _set_return_value(mock, method, name): -class MagicMixin(object): +class MagicMixin(Base): def __init__(self, *args, **kw): self._mock_set_magics() # make magic work for kwargs in init _safe_super(MagicMixin, self).__init__(*args, **kw) @@ -1965,13 +2219,12 @@ def __init__(self, *args, **kw): def _mock_set_magics(self): - these_magics = _magics + orig_magics = _magics | _async_method_magics + these_magics = orig_magics if getattr(self, "_mock_methods", None) is not None: - these_magics = _magics.intersection(self._mock_methods) - - remove_magics = set() - remove_magics = _magics - these_magics + these_magics = orig_magics.intersection(self._mock_methods) + remove_magics = orig_magics - these_magics for entry in remove_magics: if entry in type(self).__dict__: @@ -1999,6 +2252,9 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_set_magics() +class AsyncMagicMixin(MagicMixin): + pass + class MagicMock(MagicMixin, Mock): """ @@ -2020,17 +2276,24 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_add_spec(spec, spec_set) self._mock_set_magics() + def reset_mock(self, *args, return_value: bool = False, **kwargs): + if ( + return_value + and self._mock_name + and _is_magic(self._mock_name) + ): + # Don't reset return values for magic methods, + # otherwise `m.__str__` will start + # to return `MagicMock` instances, instead of `str` instances. + return_value = False + super().reset_mock(*args, return_value=return_value, **kwargs) -class MagicProxy(object): +class MagicProxy(Base): def __init__(self, name, parent): self.name = name self.parent = parent - def __call__(self, *args, **kwargs): - m = self.create_mock() - return m(*args, **kwargs) - def create_mock(self): entry = self.name parent = self.parent @@ -2044,6 +2307,267 @@ def __get__(self, obj, _type=None): return self.create_mock() +try: + _CODE_SIG = inspect.signature(partial(CodeType.__init__, None)) + _CODE_ATTRS = dir(CodeType) +except ValueError: # pragma: no cover - backport is only tested against builds with docstrings + _CODE_SIG = None + + +class AsyncMockMixin(Base): + await_count = _delegating_property('await_count') + await_args = _delegating_property('await_args') + await_args_list = _delegating_property('await_args_list') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # iscoroutinefunction() checks _is_coroutine property to say if an + # object is a coroutine. Without this check it looks to see if it is a + # function/method, which in this case it is not (since it is an + # AsyncMock). + # It is set through __dict__ because when spec_set is True, this + # attribute is likely undefined. + self.__dict__['_is_coroutine'] = asyncio.coroutines._is_coroutine + self.__dict__['_mock_await_count'] = 0 + self.__dict__['_mock_await_args'] = None + self.__dict__['_mock_await_args_list'] = _CallList() + if _CODE_SIG: + code_mock = NonCallableMock(spec_set=_CODE_ATTRS) + code_mock.__dict__["_spec_class"] = CodeType + code_mock.__dict__["_spec_signature"] = _CODE_SIG + else: # pragma: no cover - backport is only tested against builds with docstrings + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = ( + inspect.CO_COROUTINE + + inspect.CO_VARARGS + + inspect.CO_VARKEYWORDS + ) + code_mock.co_argcount = 0 + code_mock.co_varnames = ('args', 'kwargs') + try: + code_mock.co_posonlyargcount = 0 + except AttributeError: + # Python 3.7 and earlier. + pass + code_mock.co_kwonlyargcount = 0 + self.__dict__['__code__'] = code_mock + self.__dict__['__name__'] = 'AsyncMock' + self.__dict__['__defaults__'] = tuple() + self.__dict__['__kwdefaults__'] = {} + self.__dict__['__annotations__'] = None + + async def _execute_mock_call(_mock_self, *args, **kwargs): + self = _mock_self + # This is nearly just like super(), except for special handling + # of coroutines + + _call = _Call((args, kwargs), two=True) + self.await_count += 1 + self.await_args = _call + self.await_args_list.append(_call) + + effect = self.side_effect + if effect is not None: + if _is_exception(effect): + raise effect + elif not _callable(effect): + try: + result = next(effect) + except StopIteration: + # It is impossible to propagate a StopIteration + # through coroutines because of PEP 479 + raise StopAsyncIteration + if _is_exception(result): + raise result + elif iscoroutinefunction(effect): + result = await effect(*args, **kwargs) + else: + result = effect(*args, **kwargs) + + if result is not DEFAULT: + return result + + if self._mock_return_value is not DEFAULT: + return self.return_value + + if self._mock_wraps is not None: + if iscoroutinefunction(self._mock_wraps): + return await self._mock_wraps(*args, **kwargs) + return self._mock_wraps(*args, **kwargs) + + return self.return_value + + def assert_awaited(_mock_self): + """ + Assert that the mock was awaited at least once. + """ + self = _mock_self + if self.await_count == 0: + msg = f"Expected {self._mock_name or 'mock'} to have been awaited." + raise AssertionError(msg) + + def assert_awaited_once(_mock_self): + """ + Assert that the mock was awaited exactly once. + """ + self = _mock_self + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def assert_awaited_with(_mock_self, *args, **kwargs): + """ + Assert that the last await was with the specified arguments. + """ + self = _mock_self + if self.await_args is None: + expected = self._format_mock_call_signature(args, kwargs) + raise AssertionError(f'Expected await: {expected}\nNot awaited') + + def _error_message(): + msg = self._format_mock_failure_message(args, kwargs, action='await') + return msg + + expected = self._call_matcher(_Call((args, kwargs), two=True)) + actual = self._call_matcher(self.await_args) + if actual != expected: + cause = expected if isinstance(expected, Exception) else None + raise AssertionError(_error_message()) from cause + + def assert_awaited_once_with(_mock_self, *args, **kwargs): + """ + Assert that the mock was awaited exactly once and with the specified + arguments. + """ + self = _mock_self + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + return self.assert_awaited_with(*args, **kwargs) + + def assert_any_await(_mock_self, *args, **kwargs): + """ + Assert the mock has ever been awaited with the specified arguments. + """ + self = _mock_self + expected = self._call_matcher(_Call((args, kwargs), two=True)) + cause = expected if isinstance(expected, Exception) else None + actual = [self._call_matcher(c) for c in self.await_args_list] + if cause or expected not in _AnyComparer(actual): + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError( + '%s await not found' % expected_string + ) from cause + + def assert_has_awaits(_mock_self, calls, any_order=False): + """ + Assert the mock has been awaited with the specified calls. + The :attr:`await_args_list` list is checked for the awaits. + + If `any_order` is False (the default) then the awaits must be + sequential. There can be extra calls before or after the + specified awaits. + + If `any_order` is True then the awaits can be in any order, but + they must all appear in :attr:`await_args_list`. + """ + self = _mock_self + expected = [self._call_matcher(c) for c in calls] + cause = cause = next((e for e in expected if isinstance(e, Exception)), None) + all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list) + if not any_order: + if expected not in all_awaits: + if cause is None: + problem = 'Awaits not found.' + else: + problem = ('Error processing expected awaits.\n' + 'Errors: {}').format( + [e if isinstance(e, Exception) else None + for e in expected]) + raise AssertionError( + f'{problem}\n' + f'Expected: {_CallList(calls)}\n' + f'Actual: {self.await_args_list}' + ) from cause + return + + all_awaits = list(all_awaits) + + not_found = [] + for kall in expected: + try: + all_awaits.remove(kall) + except ValueError: + not_found.append(kall) + if not_found: + raise AssertionError( + '%r not all found in await list' % (tuple(not_found),) + ) from cause + + def assert_not_awaited(_mock_self): + """ + Assert that the mock was never awaited. + """ + self = _mock_self + if self.await_count != 0: + msg = (f"Expected {self._mock_name or 'mock'} to not have been awaited." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def reset_mock(self, *args, **kwargs): + """ + See :func:`.Mock.reset_mock()` + """ + super().reset_mock(*args, **kwargs) + self.await_count = 0 + self.await_args = None + self.await_args_list = _CallList() + + +class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): + """ + Enhance :class:`Mock` with features allowing to mock + an async function. + + The :class:`AsyncMock` object will behave so the object is + recognized as an async function, and the result of a call is an awaitable: + + >>> mock = AsyncMock() + >>> iscoroutinefunction(mock) + True + >>> inspect.isawaitable(mock()) + True + + + The result of ``mock()`` is an async function which will have the outcome + of ``side_effect`` or ``return_value``: + + - if ``side_effect`` is a function, the async function will return the + result of that function, + - if ``side_effect`` is an exception, the async function will raise the + exception, + - if ``side_effect`` is an iterable, the async function will return the + next value of the iterable, however, if the sequence of result is + exhausted, ``StopIteration`` is raised immediately, + - if ``side_effect`` is not defined, the async function will return the + value defined by ``return_value``, hence, by default, the async function + returns a new :class:`AsyncMock` object. + + If the outcome of ``side_effect`` or ``return_value`` is an async function, + the mock async function obtained when the mock object is called will be this + async function itself (and not an async function returning an async + function). + + The test author can also specify a wrapped object with ``wraps``. In this + case, the :class:`Mock` object behavior is the same as with an + :class:`.Mock` object: the wrapped object may have methods + defined as async function functions. + + Based on Martin Richard's asynctest project. + """ + class _ANY(object): "A helper object that compares equal to everything." @@ -2065,15 +2589,8 @@ def _format_call_signature(name, args, kwargs): message = '%s(%%s)' % name formatted_args = '' args_string = ', '.join([repr(arg) for arg in args]) - - def encode_item(item): - if six.PY2 and isinstance(item, unicode): - return item.encode("utf-8") - else: - return item - kwargs_string = ', '.join([ - '%s=%r' % (encode_item(key), value) for key, value in sorted(kwargs.items()) + '%s=%r' % (key, value) for key, value in kwargs.items() ]) if args_string: formatted_args = args_string @@ -2105,9 +2622,8 @@ class _Call(tuple): If the _Call has no name then it will match any name. """ - def __new__(cls, value=(), name=None, parent=None, two=False, + def __new__(cls, value=(), name='', parent=None, two=False, from_kall=True): - name = '' args = () kwargs = {} _len = len(value) @@ -2115,7 +2631,7 @@ def __new__(cls, value=(), name=None, parent=None, two=False, name, args, kwargs = value elif _len == 2: first, second = value - if isinstance(first, basestring): + if isinstance(first, str): name = first if isinstance(second, tuple): args = second @@ -2125,7 +2641,7 @@ def __new__(cls, value=(), name=None, parent=None, two=False, args, kwargs = first, second elif _len == 1: value, = value - if isinstance(value, basestring): + if isinstance(value, str): name = value elif isinstance(value, tuple): args = value @@ -2140,18 +2656,16 @@ def __new__(cls, value=(), name=None, parent=None, two=False, def __init__(self, value=(), name=None, parent=None, two=False, from_kall=True): - self.name = name - self.parent = parent - self.from_kall = from_kall + self._mock_name = name + self._mock_parent = parent + self._mock_from_kall = from_kall def __eq__(self, other): - if other is ANY: - return True try: len_other = len(other) except TypeError: - return False + return NotImplemented self_name = '' if len(self) == 2: @@ -2159,6 +2673,10 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self + if (getattr(self, '_mock_parent', None) and getattr(other, '_mock_parent', None) + and self._mock_parent != other._mock_parent): + return False + other_name = '' if len_other == 0: other_args, other_kwargs = (), {} @@ -2169,7 +2687,7 @@ def __eq__(self, other): if isinstance(value, tuple): other_args = value other_kwargs = {} - elif isinstance(value, basestring): + elif isinstance(value, str): other_name = value other_args, other_kwargs = (), {} else: @@ -2178,7 +2696,7 @@ def __eq__(self, other): elif len_other == 2: # could be (name, args) or (name, kwargs) or (args, kwargs) first, second = other - if isinstance(first, basestring): + if isinstance(first, str): other_name = first if isinstance(second, tuple): other_args, other_kwargs = second, {} @@ -2196,34 +2714,49 @@ def __eq__(self, other): return (other_args, other_kwargs) == (self_args, self_kwargs) - def __ne__(self, other): - return not self.__eq__(other) + __ne__ = object.__ne__ def __call__(self, *args, **kwargs): - if self.name is None: + if self._mock_name is None: return _Call(('', args, kwargs), name='()') - name = self.name + '()' - return _Call((self.name, args, kwargs), name=name, parent=self) + name = self._mock_name + '()' + return _Call((self._mock_name, args, kwargs), name=name, parent=self) def __getattr__(self, attr): - if self.name is None: + if self._mock_name is None: return _Call(name=attr, from_kall=False) - name = '%s.%s' % (self.name, attr) + name = '%s.%s' % (self._mock_name, attr) return _Call(name=name, parent=self, from_kall=False) - def count(self, *args, **kwargs): - return self.__getattr__('count')(*args, **kwargs) + def __getattribute__(self, attr): + if attr in tuple.__dict__: + raise AttributeError + return tuple.__getattribute__(self, attr) + + + def _get_call_arguments(self): + if len(self) == 2: + args, kwargs = self + else: + name, args, kwargs = self + + return args, kwargs + + @property + def args(self): + return self._get_call_arguments()[0] - def index(self, *args, **kwargs): - return self.__getattr__('index')(*args, **kwargs) + @property + def kwargs(self): + return self._get_call_arguments()[1] def __repr__(self): - if not self.from_kall: - name = self.name or 'call' + if not self._mock_from_kall: + name = self._mock_name or 'call' if name.startswith('()'): name = 'call%s' % name return name @@ -2249,18 +2782,17 @@ def call_list(self): vals = [] thing = self while thing is not None: - if thing.from_kall: + if thing._mock_from_kall: vals.append(thing) - thing = thing.parent + thing = thing._mock_parent return _CallList(reversed(vals)) call = _Call(from_kall=False) - def create_autospec(spec, spec_set=False, instance=False, _parent=None, - _name=None, **kwargs): + _name=None, *, unsafe=False, **kwargs): """Create a mock object using another object as a spec. Attributes on the mock will use the corresponding attribute on the `spec` object as their spec. @@ -2276,6 +2808,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, spec for an instance object by passing `instance=True`. The returned mock will only be callable if instances of the mock are callable. + `create_autospec` will raise a `RuntimeError` if passed some common + misspellings of the arguments autospec and spec_set. Pass the argument + `unsafe` with the value True to disable that check. + `create_autospec` also takes arbitrary keyword arguments that are passed to the constructor of the created mock.""" if _is_list(spec): @@ -2283,9 +2819,20 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # interpreted as a list of strings spec = type(spec) - is_type = isinstance(spec, ClassTypes) + is_type = isinstance(spec, type) + if _is_instance_mock(spec): + raise InvalidSpecError(f'Cannot autospec a Mock object. ' + f'[object={spec!r}]') + is_async_func = _is_async_func(spec) + + entries = [(entry, _missing) for entry in dir(spec)] + if is_type and instance and HAS_DATACLASSES and is_dataclass(spec): + dataclass_fields = fields(spec) + entries.extend((f.name, f.type) for f in dataclass_fields) + _kwargs = {'spec': [f.name for f in dataclass_fields]} + else: + _kwargs = {'spec': spec} - _kwargs = {'spec': spec} if spec_set: _kwargs = {'spec_set': spec} elif spec is None: @@ -2293,44 +2840,56 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _kwargs = {} if _kwargs and instance: _kwargs['_spec_as_instance'] = True + if not unsafe: + _check_spec_arg_typos(kwargs) + + _name = kwargs.pop('name', _name) + _new_name = _name + if _parent is None: + # for a top level object no _new_name should be set + _new_name = '' _kwargs.update(kwargs) Klass = MagicMock - if type(spec) in DescriptorTypes: + if inspect.isdatadescriptor(spec): # descriptors don't have a spec # because we don't know what type they return _kwargs = {} + elif is_async_func: + if instance: + raise RuntimeError("Instance can not be True when create_autospec " + "is mocking an async function") + Klass = AsyncMock elif not _callable(spec): Klass = NonCallableMagicMock elif is_type and instance and not _instance_callable(spec): Klass = NonCallableMagicMock - _name = _kwargs.pop('name', _name) - - _new_name = _name - if _parent is None: - # for a top level object no _new_name should be set - _new_name = '' - mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name, name=_name, **_kwargs) if isinstance(spec, FunctionTypes): # should only happen at the top level because we don't # recurse for functions - mock = _set_signature(mock, spec) + if is_async_func: + mock = _set_async_signature(mock, spec) + else: + mock = _set_signature(mock, spec) else: _check_signature(spec, mock, is_type, instance) if _parent is not None and not instance: _parent._mock_children[_name] = mock + # Pop wraps from kwargs because it must not be passed to configure_mock. + wrapped = kwargs.pop('wraps', None) if is_type and not instance and 'return_value' not in kwargs: mock.return_value = create_autospec(spec, spec_set, instance=True, - _name='()', _parent=mock) + _name='()', _parent=mock, + wraps=wrapped) - for entry in dir(spec): + for entry, original in entries: if _is_magic(entry): # MagicMock already does the useful magic methods for us continue @@ -2344,14 +2903,18 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # AttributeError on being fetched? # we could be resilient against it, or catch and propagate the # exception when the attribute is fetched from the mock - try: - original = getattr(spec, entry) - except AttributeError: - continue + if original is _missing: + try: + original = getattr(spec, entry) + except AttributeError: + continue - kwargs = {'spec': original} + child_kwargs = {'spec': original} + # Wrap child attributes also. + if wrapped and hasattr(wrapped, entry): + child_kwargs.update(wraps=original) if spec_set: - kwargs = {'spec_set': original} + child_kwargs = {'spec_set': original} if not isinstance(original, FunctionTypes): new = _SpecState(original, spec_set, mock, entry, instance) @@ -2362,11 +2925,15 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, parent = mock.mock skipfirst = _must_skip(spec, entry, is_type) - kwargs['_eat_self'] = skipfirst - new = MagicMock(parent=parent, name=entry, _new_name=entry, - _new_parent=parent, - **kwargs) + child_kwargs['_eat_self'] = skipfirst + if iscoroutinefunction(original): + child_klass = AsyncMock + else: + child_klass = MagicMock + new = child_klass(parent=parent, name=entry, _new_name=entry, + _new_parent=parent, **child_kwargs) mock._mock_children[entry] = new + new.return_value = child_klass() _check_signature(original, new, skipfirst=skipfirst) # so functions created with _set_signature become instance attributes, @@ -2375,6 +2942,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # setting as an instance attribute? if isinstance(new, FunctionTypes): setattr(mock, entry, new) + # kwargs are passed with respect to the parent mock so, they are not used + # for creating return_value of the parent mock. So, this condition + # should be true only for the parent mock if kwargs are given. + if _is_instance_mock(mock) and kwargs: + mock.configure_mock(**kwargs) return mock @@ -2384,14 +2956,11 @@ def _must_skip(spec, entry, is_type): Return whether we should skip the first argument on spec's `entry` attribute. """ - if not isinstance(spec, ClassTypes): + if not isinstance(spec, type): if entry in getattr(spec, '__dict__', {}): # instance attribute - shouldn't skip return False spec = spec.__class__ - if not hasattr(spec, '__mro__'): - # old style class: can't have descriptors anyway - return is_type for klass in spec.__mro__: result = klass.__dict__.get(entry, DEFAULT) @@ -2399,26 +2968,17 @@ def _must_skip(spec, entry, is_type): continue if isinstance(result, (staticmethod, classmethod)): return False - elif isinstance(getattr(result, '__get__', None), MethodWrapperTypes): + elif isinstance(result, FunctionTypes): # Normal method => skip if looked up on type # (if looked up on instance, self is already skipped) return is_type else: return False - # shouldn't get here unless function is a dynamically provided attribute - # XXXX untested behaviour + # function is a dynamically provided attribute return is_type -def _get_class(obj): - try: - return obj.__class__ - except AttributeError: - # it is possible for objects to have no __class__ - return type(obj) - - class _SpecState(object): def __init__(self, spec, spec_set=False, parent=None, @@ -2438,32 +2998,17 @@ def __init__(self, spec, spec_set=False, parent=None, type(ANY.__eq__), ) -MethodWrapperTypes = ( - type(ANY.__eq__.__get__), -) - file_spec = None +open_spec = None + -def _iterate_read_data(read_data): - # Helper for mock_open: - # Retrieve lines from read_data via a generator so that separate calls to - # readline, read, and readlines are properly interleaved - sep = b'\n' if isinstance(read_data, bytes) else '\n' - data_as_list = [l + sep for l in read_data.split(sep)] - - if data_as_list[-1] == sep: - # If the last line ended in a newline, the list comprehension will have an - # extra entry that's just a newline. Remove this. - data_as_list = data_as_list[:-1] +def _to_stream(read_data): + if isinstance(read_data, bytes): + return io.BytesIO(read_data) else: - # If there wasn't an extra newline by itself, then the file being - # emulated doesn't have a newline to end the last line remove the - # newline that our naive format() added - data_as_list[-1] = data_as_list[-1][:-1] + return io.StringIO(read_data) - for line in data_as_list: - yield line def mock_open(mock=None, read_data=''): """ @@ -2474,44 +3019,57 @@ def mock_open(mock=None, read_data=''): default) then a `MagicMock` will be created for you, with the API limited to methods or attributes available on standard file handles. - `read_data` is a string for the `read` methoddline`, and `readlines` of the + `read_data` is a string for the `read`, `readline` and `readlines` of the file handle to return. This is an empty string by default. """ + _read_data = _to_stream(read_data) + _state = [_read_data, None] + def _readlines_side_effect(*args, **kwargs): if handle.readlines.return_value is not None: return handle.readlines.return_value - return list(_state[0]) + return _state[0].readlines(*args, **kwargs) def _read_side_effect(*args, **kwargs): if handle.read.return_value is not None: return handle.read.return_value - return type(read_data)().join(_state[0]) + return _state[0].read(*args, **kwargs) + + def _readline_side_effect(*args, **kwargs): + yield from _iter_side_effect() + while True: + yield _state[0].readline(*args, **kwargs) - def _readline_side_effect(): + def _iter_side_effect(): if handle.readline.return_value is not None: while True: yield handle.readline.return_value for line in _state[0]: yield line + def _next_side_effect(): + if handle.readline.return_value is not None: + return handle.readline.return_value + return next(_state[0]) + + def _exit_side_effect(exctype, excinst, exctb): + handle.close() global file_spec if file_spec is None: - # set on first use - if six.PY3: - import _io - file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) - else: - file_spec = file + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + global open_spec + if open_spec is None: + import _io + open_spec = list(set(dir(_io.open))) if mock is None: - mock = MagicMock(name='open', spec=open) + mock = MagicMock(name='open', spec=open_spec) handle = MagicMock(spec=file_spec) handle.__enter__.return_value = handle - _state = [_iterate_read_data(read_data), None] - handle.write.return_value = None handle.read.return_value = None handle.readline.return_value = None @@ -2521,9 +3079,12 @@ def _readline_side_effect(): _state[1] = _readline_side_effect() handle.readline.side_effect = _state[1] handle.readlines.side_effect = _readlines_side_effect + handle.__iter__.side_effect = _iter_side_effect + handle.__next__.side_effect = _next_side_effect + handle.__exit__.side_effect = _exit_side_effect def reset_data(*args, **kwargs): - _state[0] = _iterate_read_data(read_data) + _state[0] = _to_stream(read_data) if handle.readline.side_effect == _state[1]: # Only reset the side effect if the user hasn't overridden it. _state[1] = _readline_side_effect() @@ -2547,7 +3108,139 @@ class PropertyMock(Mock): def _get_child_mock(self, **kwargs): return MagicMock(**kwargs) - def __get__(self, obj, obj_type): + def __get__(self, obj, obj_type=None): return self() def __set__(self, obj, val): self(val) + + +_timeout_unset = sentinel.TIMEOUT_UNSET + +class ThreadingMixin(Base): + + DEFAULT_TIMEOUT = None + + def _get_child_mock(self, **kw): + if isinstance(kw.get("parent"), ThreadingMixin): + kw["timeout"] = kw["parent"]._mock_wait_timeout + elif isinstance(kw.get("_new_parent"), ThreadingMixin): + kw["timeout"] = kw["_new_parent"]._mock_wait_timeout + return super()._get_child_mock(**kw) + + def __init__(self, *args, timeout=_timeout_unset, **kwargs): + super().__init__(*args, **kwargs) + if timeout is _timeout_unset: + timeout = self.DEFAULT_TIMEOUT + self.__dict__["_mock_event"] = threading.Event() # Event for any call + self.__dict__["_mock_calls_events"] = [] # Events for each of the calls + self.__dict__["_mock_calls_events_lock"] = threading.Lock() + self.__dict__["_mock_wait_timeout"] = timeout + + def reset_mock(self, *args, **kwargs): + """ + See :func:`.Mock.reset_mock()` + """ + super().reset_mock(*args, **kwargs) + self.__dict__["_mock_event"] = threading.Event() + self.__dict__["_mock_calls_events"] = [] + + def __get_event(self, expected_args, expected_kwargs): + with self._mock_calls_events_lock: + for args, kwargs, event in self._mock_calls_events: + if (args, kwargs) == (expected_args, expected_kwargs): + return event + new_event = threading.Event() + self._mock_calls_events.append((expected_args, expected_kwargs, new_event)) + return new_event + + def _mock_call(self, *args, **kwargs): + ret_value = super()._mock_call(*args, **kwargs) + + call_event = self.__get_event(args, kwargs) + call_event.set() + + self._mock_event.set() + + return ret_value + + def wait_until_called(self, *, timeout=_timeout_unset): + """Wait until the mock object is called. + + `timeout` - time to wait for in seconds, waits forever otherwise. + Defaults to the constructor provided timeout. + Use None to block undefinetively. + """ + if timeout is _timeout_unset: + timeout = self._mock_wait_timeout + if not self._mock_event.wait(timeout=timeout): + msg = (f"{self._mock_name or 'mock'} was not called before" + f" timeout({timeout}).") + raise AssertionError(msg) + + def wait_until_any_call_with(self, *args, **kwargs): + """Wait until the mock object is called with given args. + + Waits for the timeout in seconds provided in the constructor. + """ + event = self.__get_event(args, kwargs) + if not event.wait(timeout=self._mock_wait_timeout): + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError(f'{expected_string} call not found') + + +class ThreadingMock(ThreadingMixin, MagicMixin, Mock): + """ + A mock that can be used to wait until on calls happening + in a different thread. + + The constructor can take a `timeout` argument which + controls the timeout in seconds for all `wait` calls of the mock. + + You can change the default timeout of all instances via the + `ThreadingMock.DEFAULT_TIMEOUT` attribute. + + If no timeout is set, it will block undefinetively. + """ + pass + + +def seal(mock): + """Disable the automatic generation of child mocks. + + Given an input Mock, seals it to ensure no further mocks will be generated + when accessing an attribute that was not already defined. + + The operation recursively seals the mock passed in, meaning that + the mock itself, any mocks generated by accessing one of its attributes, + and all assigned mocks without a name or spec will be sealed. + """ + mock._mock_sealed = True + for attr in dir(mock): + try: + m = getattr(mock, attr) + except AttributeError: + continue + if not isinstance(m, NonCallableMock): + continue + if isinstance(m._mock_children.get(attr), _SpecState): + continue + if m._mock_new_parent is mock: + seal(m) + + +class _AsyncIterator: + """ + Wraps an iterator in an asynchronous iterator. + """ + def __init__(self, iterator): + self.iterator = iterator + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = inspect.CO_ITERABLE_COROUTINE + self.__dict__['__code__'] = code_mock + + async def __anext__(self): + try: + return next(self.iterator) + except StopIteration: + pass + raise StopAsyncIteration diff --git a/mock/tests/__init__.py b/mock/tests/__init__.py index 54ddf2ec..e69de29b 100644 --- a/mock/tests/__init__.py +++ b/mock/tests/__init__.py @@ -1,3 +0,0 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ diff --git a/mock/tests/__main__.py b/mock/tests/__main__.py deleted file mode 100644 index 45c633a4..00000000 --- a/mock/tests/__main__.py +++ /dev/null @@ -1,18 +0,0 @@ -import os -import unittest - - -def load_tests(loader, standard_tests, pattern): - # top level directory cached on loader instance - this_dir = os.path.dirname(__file__) - pattern = pattern or "test*.py" - # We are inside unittest.test.testmock, so the top-level is three notches up - top_level_dir = os.path.dirname(os.path.dirname(os.path.dirname(this_dir))) - package_tests = loader.discover(start_dir=this_dir, pattern=pattern, - top_level_dir=top_level_dir) - standard_tests.addTests(package_tests) - return standard_tests - - -if __name__ == '__main__': - unittest.main() diff --git a/mock/tests/support.py b/mock/tests/support.py index 8e2082ff..19548747 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -1,17 +1,7 @@ +import contextlib import sys -info = sys.version_info -import unittest2 - - -try: - callable = callable -except NameError: - def callable(obj): - return hasattr(obj, '__call__') - - -with_available = sys.version_info[:2] >= (2, 5) +target = {'foo': 'FOO'} def is_instance(obj, klass): @@ -22,15 +12,52 @@ def is_instance(obj, klass): class SomeClass(object): class_attribute = None - def wibble(self): - pass + def wibble(self): pass class X(object): pass -try: - next = next -except NameError: - def next(obj): - return obj.next() +# A standin for weurkzeug.local.LocalProxy - issue 119600 +def _inaccessible(*args, **kwargs): + raise AttributeError + + +class OpaqueProxy: + __getattribute__ = _inaccessible + + +g = OpaqueProxy() + + +@contextlib.contextmanager +def uncache(*names): + """Uncache a module from sys.modules. + + A basic sanity check is performed to prevent uncaching modules that either + cannot/shouldn't be uncached. + + """ + for name in names: + assert name not in ('sys', 'marshal', 'imp') + try: + del sys.modules[name] + except KeyError: + pass + try: + yield + finally: + for name in names: + del sys.modules[name] + + +class _ALWAYS_EQ: + """ + Object that is equal to anything. + """ + def __eq__(self, other): + return True + def __ne__(self, other): + return False + +ALWAYS_EQ = _ALWAYS_EQ() diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py new file mode 100644 index 00000000..5bf01392 --- /dev/null +++ b/mock/tests/testasync.py @@ -0,0 +1,1135 @@ + +import asyncio +import gc +import inspect +import re +import unittest +from contextlib import contextmanager + +from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, + create_autospec, sentinel, seal) +from mock.backports import IsolatedAsyncioTestCase, iscoroutinefunction, set_event_loop_policy +from mock.mock import _CallList + + +try: + from asyncio import run +except ImportError: + def run(main): + loop = asyncio.new_event_loop() + try: + return_value = loop.run_until_complete(main) + finally: + loop.close() + return return_value + + +def tearDownModule(): + set_event_loop_policy(None) + + +class AsyncClass: + def __init__(self): pass + async def async_method(self): pass + def normal_method(self): pass + + @classmethod + async def async_class_method(cls): pass + + @staticmethod + async def async_static_method(): pass + + +class AwaitableClass: + def __await__(self): yield + +async def async_func(): pass + +async def async_func_args(a, b, *, c): pass + +def normal_func(): pass + +class NormalClass(object): + def a(self): pass + + +async_foo_name = f'{__name__}.AsyncClass' +normal_foo_name = f'{__name__}.NormalClass' + + +@contextmanager +def assertNeverAwaited(test): + with test.assertWarnsRegex(RuntimeWarning, "was never awaited$"): + yield + # In non-CPython implementations of Python, this is needed because timely + # deallocation is not guaranteed by the garbage collector. + gc.collect() + + +class AsyncPatchDecoratorTest(unittest.TestCase): + def test_is_coroutine_function_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + self.assertTrue(iscoroutinefunction(mock_method)) + test_async() + + def test_is_async_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + @patch(f'{async_foo_name}.async_method') + def test_no_parent_attribute(mock_method): + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + test_async() + test_no_parent_attribute() + + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_is_AsyncMock_patch_staticmethod(self): + @patch.object(AsyncClass, 'async_static_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_is_AsyncMock_patch_classmethod(self): + @patch.object(AsyncClass, 'async_class_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_async_def_patch(self): + @patch(f"{__name__}.async_func", return_value=1) + @patch(f"{__name__}.async_func_args", return_value=2) + async def test_async(func_args_mock, func_mock): + self.assertEqual(func_args_mock._mock_name, "async_func_args") + self.assertEqual(func_mock._mock_name, "async_func") + + self.assertIsInstance(async_func, AsyncMock) + self.assertIsInstance(async_func_args, AsyncMock) + + self.assertEqual(await async_func(), 1) + self.assertEqual(await async_func_args(1, 2, c=3), 2) + + run(test_async()) + self.assertTrue(inspect.iscoroutinefunction(async_func)) + + +class AsyncPatchCMTest(unittest.TestCase): + def test_is_async_function_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertTrue(iscoroutinefunction(mock_method)) + + test_async() + + def test_is_async_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + test_async() + + def test_is_AsyncMock_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_async_def_cm(self): + async def test_async(): + with patch(f"{__name__}.async_func", AsyncMock()): + self.assertIsInstance(async_func, AsyncMock) + self.assertTrue(inspect.iscoroutinefunction(async_func)) + + run(test_async()) + + def test_patch_dict_async_def(self): + foo = {'a': 'a'} + @patch.dict(foo, {'a': 'b'}) + async def test_async(): + self.assertEqual(foo['a'], 'b') + + self.assertTrue(iscoroutinefunction(test_async)) + run(test_async()) + + def test_patch_dict_async_def_context(self): + foo = {'a': 'a'} + async def test_async(): + with patch.dict(foo, {'a': 'b'}): + self.assertEqual(foo['a'], 'b') + + run(test_async()) + + +class AsyncMockTest(unittest.TestCase): + def test_iscoroutinefunction_default(self): + mock = AsyncMock() + self.assertTrue(iscoroutinefunction(mock)) + + def test_iscoroutinefunction_function(self): + async def foo(): pass + mock = AsyncMock(foo) + self.assertTrue(iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) + + def test_isawaitable(self): + mock = AsyncMock() + m = mock() + self.assertTrue(inspect.isawaitable(m)) + run(m) + self.assertIn('assert_awaited', dir(mock)) + + def test_iscoroutinefunction_normal_function(self): + def foo(): pass + mock = AsyncMock(foo) + self.assertTrue(iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) + + def test_future_isfuture(self): + loop = asyncio.new_event_loop() + fut = loop.create_future() + loop.stop() + loop.close() + mock = AsyncMock(fut) + self.assertIsInstance(mock, asyncio.Future) + + +class AsyncAutospecTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): + @patch(async_foo_name, autospec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method.async_method, AsyncMock) + self.assertIsInstance(mock_method, MagicMock) + + @patch(async_foo_name, autospec=True) + def test_normal_method(mock_method): + self.assertIsInstance(mock_method.normal_method, MagicMock) + + test_async() + test_normal_method() + + def test_create_autospec_instance(self): + with self.assertRaises(RuntimeError): + create_autospec(async_func, instance=True) + + def test_create_autospec(self): + spec = create_autospec(async_func_args) + awaitable = spec(1, 2, c=3) + async def main(): + await awaitable + + self.assertEqual(spec.await_count, 0) + self.assertIsNone(spec.await_args) + self.assertEqual(spec.await_args_list, []) + spec.assert_not_awaited() + + run(main()) + + self.assertTrue(iscoroutinefunction(spec)) + self.assertTrue(inspect.iscoroutinefunction(spec)) + self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.iscoroutine(awaitable)) + self.assertEqual(spec.await_count, 1) + self.assertEqual(spec.await_args, call(1, 2, c=3)) + self.assertEqual(spec.await_args_list, [call(1, 2, c=3)]) + spec.assert_awaited_once() + spec.assert_awaited_once_with(1, 2, c=3) + spec.assert_awaited_with(1, 2, c=3) + spec.assert_awaited() + + with self.assertRaises(AssertionError): + spec.assert_any_await(e=1) + + def test_autospec_checks_signature(self): + spec = create_autospec(async_func_args) + # signature is not checked when called + awaitable = spec() + self.assertListEqual(spec.mock_calls, []) + + async def main(): + await awaitable + + # but it is checked when awaited + with self.assertRaises(TypeError): + run(main()) + + # _checksig_ raises before running or awaiting the mock + self.assertListEqual(spec.mock_calls, []) + self.assertEqual(spec.await_count, 0) + self.assertIsNone(spec.await_args) + self.assertEqual(spec.await_args_list, []) + spec.assert_not_awaited() + + def test_patch_with_autospec(self): + + async def test_async(): + with patch(f"{__name__}.async_func_args", autospec=True) as mock_method: + awaitable = mock_method(1, 2, c=3) + self.assertIsInstance(mock_method.mock, AsyncMock) + + self.assertTrue(iscoroutinefunction(mock_method)) + self.assertTrue(inspect.iscoroutinefunction(mock_method)) + self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.iscoroutine(awaitable)) + self.assertTrue(inspect.isawaitable(awaitable)) + + # Verify the default values during mock setup + self.assertEqual(mock_method.await_count, 0) + self.assertEqual(mock_method.await_args_list, []) + self.assertIsNone(mock_method.await_args) + mock_method.assert_not_awaited() + + await awaitable + + self.assertEqual(mock_method.await_count, 1) + self.assertEqual(mock_method.await_args, call(1, 2, c=3)) + self.assertEqual(mock_method.await_args_list, [call(1, 2, c=3)]) + mock_method.assert_awaited_once() + mock_method.assert_awaited_once_with(1, 2, c=3) + mock_method.assert_awaited_with(1, 2, c=3) + mock_method.assert_awaited() + + mock_method.reset_mock() + self.assertEqual(mock_method.await_count, 0) + self.assertIsNone(mock_method.await_args) + self.assertEqual(mock_method.await_args_list, []) + + run(test_async()) + + +class AsyncSpecTest(unittest.TestCase): + def test_spec_normal_methods_on_class(self): + def inner_test(mock_type): + mock = mock_type(AsyncClass) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, MagicMock) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test method types with {mock_type}"): + inner_test(mock_type) + + def test_spec_normal_methods_on_class_with_mock(self): + mock = Mock(AsyncClass) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, Mock) + + def test_spec_normal_methods_on_class_with_mock_seal(self): + mock = Mock(AsyncClass) + seal(mock) + with self.assertRaises(AttributeError): + mock.normal_method + with self.assertRaises(AttributeError): + mock.async_method + + def test_spec_async_attributes_instance(self): + async_instance = AsyncClass() + async_instance.async_func_attr = async_func + async_instance.later_async_func_attr = normal_func + + mock_async_instance = Mock(spec_set=async_instance) + + async_instance.later_async_func_attr = async_func + + self.assertIsInstance(mock_async_instance.async_func_attr, AsyncMock) + # only the shape of the spec at the time of mock construction matters + self.assertNotIsInstance(mock_async_instance.later_async_func_attr, AsyncMock) + + def test_spec_mock_type_kw(self): + def inner_test(mock_type): + async_mock = mock_type(spec=async_func) + self.assertIsInstance(async_mock, mock_type) + with assertNeverAwaited(self): + self.assertTrue(inspect.isawaitable(async_mock())) + + sync_mock = mock_type(spec=normal_func) + self.assertIsInstance(sync_mock, mock_type) + + for mock_type in [AsyncMock, MagicMock, Mock]: + with self.subTest(f"test spec kwarg with {mock_type}"): + inner_test(mock_type) + + def test_spec_mock_type_positional(self): + def inner_test(mock_type): + async_mock = mock_type(async_func) + self.assertIsInstance(async_mock, mock_type) + with assertNeverAwaited(self): + self.assertTrue(inspect.isawaitable(async_mock())) + + sync_mock = mock_type(normal_func) + self.assertIsInstance(sync_mock, mock_type) + + for mock_type in [AsyncMock, MagicMock, Mock]: + with self.subTest(f"test spec positional with {mock_type}"): + inner_test(mock_type) + + def test_spec_as_normal_kw_AsyncMock(self): + mock = AsyncMock(spec=normal_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + def test_spec_as_normal_positional_AsyncMock(self): + mock = AsyncMock(normal_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + def test_spec_async_mock(self): + @patch.object(AsyncClass, 'async_method', spec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_spec_parent_not_async_attribute_is(self): + @patch(async_foo_name, spec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method, MagicMock) + self.assertIsInstance(mock_method.async_method, AsyncMock) + + test_async() + + def test_target_async_spec_not(self): + @patch.object(AsyncClass, 'async_method', spec=NormalClass.a) + def test_async_attribute(mock_method): + self.assertIsInstance(mock_method, MagicMock) + self.assertFalse(inspect.iscoroutine(mock_method)) + self.assertFalse(inspect.isawaitable(mock_method)) + + test_async_attribute() + + def test_target_not_async_spec_is(self): + @patch.object(NormalClass, 'a', spec=async_func) + def test_attribute_not_async_spec_is(mock_async_func): + self.assertIsInstance(mock_async_func, AsyncMock) + test_attribute_not_async_spec_is() + + def test_spec_async_attributes(self): + @patch(normal_foo_name, spec=AsyncClass) + def test_async_attributes_coroutines(MockNormalClass): + self.assertIsInstance(MockNormalClass.async_method, AsyncMock) + self.assertIsInstance(MockNormalClass, MagicMock) + + test_async_attributes_coroutines() + + +class AsyncSpecSetTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method', spec_set=True) + def test_async(async_method): + self.assertIsInstance(async_method, AsyncMock) + test_async() + + def test_is_async_AsyncMock(self): + mock = AsyncMock(spec_set=AsyncClass.async_method) + self.assertTrue(iscoroutinefunction(mock)) + self.assertIsInstance(mock, AsyncMock) + + def test_is_child_AsyncMock(self): + mock = MagicMock(spec_set=AsyncClass) + self.assertTrue(iscoroutinefunction(mock.async_method)) + self.assertFalse(iscoroutinefunction(mock.normal_method)) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, MagicMock) + self.assertIsInstance(mock, MagicMock) + + def test_magicmock_lambda_spec(self): + mock_obj = MagicMock() + mock_obj.mock_func = MagicMock(spec=lambda x: x) + + with patch.object(mock_obj, "mock_func") as cm: + self.assertIsInstance(cm, MagicMock) + + +class AsyncArguments(IsolatedAsyncioTestCase): + async def test_add_return_value(self): + async def addition(self, var): pass + + mock = AsyncMock(addition, return_value=10) + output = await mock(5) + + self.assertEqual(output, 10) + + async def test_add_side_effect_exception(self): + class CustomError(Exception): pass + async def addition(var): pass + mock = AsyncMock(addition, side_effect=CustomError('side-effect')) + with self.assertRaisesRegex(CustomError, 'side-effect'): + await mock(5) + + async def test_add_side_effect_coroutine(self): + async def addition(var): + return var + 1 + mock = AsyncMock(side_effect=addition) + result = await mock(5) + self.assertEqual(result, 6) + + async def test_add_side_effect_normal_function(self): + def addition(var): + return var + 1 + mock = AsyncMock(side_effect=addition) + result = await mock(5) + self.assertEqual(result, 6) + + async def test_add_side_effect_iterable(self): + vals = [1, 2, 3] + mock = AsyncMock(side_effect=vals) + for item in vals: + self.assertEqual(await mock(), item) + + with self.assertRaises(StopAsyncIteration) as e: + await mock() + + async def test_add_side_effect_exception_iterable(self): + class SampleException(Exception): + pass + + vals = [1, SampleException("foo")] + mock = AsyncMock(side_effect=vals) + self.assertEqual(await mock(), 1) + + with self.assertRaises(SampleException) as e: + await mock() + + async def test_return_value_AsyncMock(self): + value = AsyncMock(return_value=10) + mock = AsyncMock(return_value=value) + result = await mock() + self.assertIs(result, value) + + async def test_return_value_awaitable(self): + fut = asyncio.Future() + fut.set_result(None) + mock = AsyncMock(return_value=fut) + result = await mock() + self.assertIsInstance(result, asyncio.Future) + + async def test_side_effect_awaitable_values(self): + fut = asyncio.Future() + fut.set_result(None) + + mock = AsyncMock(side_effect=[fut]) + result = await mock() + self.assertIsInstance(result, asyncio.Future) + + with self.assertRaises(StopAsyncIteration): + await mock() + + async def test_side_effect_is_AsyncMock(self): + effect = AsyncMock(return_value=10) + mock = AsyncMock(side_effect=effect) + + result = await mock() + self.assertEqual(result, 10) + + async def test_wraps_coroutine(self): + value = asyncio.Future() + + ran = False + async def inner(): + nonlocal ran + ran = True + return value + + mock = AsyncMock(wraps=inner) + result = await mock() + self.assertEqual(result, value) + mock.assert_awaited() + self.assertTrue(ran) + + async def test_wraps_normal_function(self): + value = 1 + + ran = False + def inner(): + nonlocal ran + ran = True + return value + + mock = AsyncMock(wraps=inner) + result = await mock() + self.assertEqual(result, value) + mock.assert_awaited() + self.assertTrue(ran) + + async def test_await_args_list_order(self): + async_mock = AsyncMock() + mock2 = async_mock(2) + mock1 = async_mock(1) + await mock1 + await mock2 + async_mock.assert_has_awaits([call(1), call(2)]) + self.assertEqual(async_mock.await_args_list, [call(1), call(2)]) + self.assertEqual(async_mock.call_args_list, [call(2), call(1)]) + + +class AsyncMagicMethods(unittest.TestCase): + def test_async_magic_methods_return_async_mocks(self): + m_mock = MagicMock() + self.assertIsInstance(m_mock.__aenter__, AsyncMock) + self.assertIsInstance(m_mock.__aexit__, AsyncMock) + self.assertIsInstance(m_mock.__anext__, AsyncMock) + # __aiter__ is actually a synchronous object + # so should return a MagicMock + self.assertIsInstance(m_mock.__aiter__, MagicMock) + + def test_sync_magic_methods_return_magic_mocks(self): + a_mock = AsyncMock() + self.assertIsInstance(a_mock.__enter__, MagicMock) + self.assertIsInstance(a_mock.__exit__, MagicMock) + self.assertIsInstance(a_mock.__next__, MagicMock) + self.assertIsInstance(a_mock.__len__, MagicMock) + + def test_magicmock_has_async_magic_methods(self): + m_mock = MagicMock() + self.assertTrue(hasattr(m_mock, "__aenter__")) + self.assertTrue(hasattr(m_mock, "__aexit__")) + self.assertTrue(hasattr(m_mock, "__anext__")) + + def test_asyncmock_has_sync_magic_methods(self): + a_mock = AsyncMock() + self.assertTrue(hasattr(a_mock, "__enter__")) + self.assertTrue(hasattr(a_mock, "__exit__")) + self.assertTrue(hasattr(a_mock, "__next__")) + self.assertTrue(hasattr(a_mock, "__len__")) + + def test_magic_methods_are_async_functions(self): + m_mock = MagicMock() + self.assertIsInstance(m_mock.__aenter__, AsyncMock) + self.assertIsInstance(m_mock.__aexit__, AsyncMock) + # AsyncMocks are also coroutine functions + self.assertTrue(iscoroutinefunction(m_mock.__aenter__)) + self.assertTrue(iscoroutinefunction(m_mock.__aexit__)) + +class AsyncContextManagerTest(unittest.TestCase): + + class WithAsyncContextManager: + async def __aenter__(self, *args, **kwargs): pass + + async def __aexit__(self, *args, **kwargs): pass + + class WithSyncContextManager: + def __enter__(self, *args, **kwargs): pass + + def __exit__(self, *args, **kwargs): pass + + class ProductionCode: + # Example real-world(ish) code + def __init__(self): + self.session = None + + async def main(self): + async with self.session.post('https://python.org') as response: + val = await response.json() + return val + + def test_set_return_value_of_aenter(self): + def inner_test(mock_type): + pc = self.ProductionCode() + pc.session = MagicMock(name='sessionmock') + cm = mock_type(name='magic_cm') + response = AsyncMock(name='response') + response.json = AsyncMock(return_value={'json': 123}) + cm.__aenter__.return_value = response + pc.session.post.return_value = cm + result = run(pc.main()) + self.assertEqual(result, {'json': 123}) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test set return value of aenter with {mock_type}"): + inner_test(mock_type) + + def test_mock_supports_async_context_manager(self): + + def inner_test(mock_type): + called = False + cm = self.WithAsyncContextManager() + cm_mock = mock_type(cm) + + async def use_context_manager(): + nonlocal called + async with cm_mock as result: + called = True + return result + + cm_result = run(use_context_manager()) + self.assertTrue(called) + self.assertTrue(cm_mock.__aenter__.called) + self.assertTrue(cm_mock.__aexit__.called) + cm_mock.__aenter__.assert_awaited() + cm_mock.__aexit__.assert_awaited() + # We mock __aenter__ so it does not return self + self.assertIsNot(cm_mock, cm_result) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test context manager magics with {mock_type}"): + inner_test(mock_type) + + def test_mock_customize_async_context_manager(self): + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + expected_result = object() + mock_instance.__aenter__.return_value = expected_result + + async def use_context_manager(): + async with mock_instance as result: + return result + + self.assertIs(run(use_context_manager()), expected_result) + + def test_mock_customize_async_context_manager_with_coroutine(self): + enter_called = False + exit_called = False + + async def enter_coroutine(*args): + nonlocal enter_called + enter_called = True + + async def exit_coroutine(*args): + nonlocal exit_called + exit_called = True + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + mock_instance.__aenter__ = enter_coroutine + mock_instance.__aexit__ = exit_coroutine + + async def use_context_manager(): + async with mock_instance: + pass + + run(use_context_manager()) + self.assertTrue(enter_called) + self.assertTrue(exit_called) + + def test_context_manager_raise_exception_by_default(self): + async def raise_in(context_manager): + async with context_manager: + raise TypeError() + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + with self.assertRaises(TypeError): + run(raise_in(mock_instance)) + + +class AsyncIteratorTest(unittest.TestCase): + class WithAsyncIterator(object): + def __init__(self): + self.items = ["foo", "NormalFoo", "baz"] + + def __aiter__(self): pass + + async def __anext__(self): pass + + def test_aiter_set_return_value(self): + mock_iter = AsyncMock(name="tester") + mock_iter.__aiter__.return_value = [1, 2, 3] + async def main(): + return [i async for i in mock_iter] + result = run(main()) + self.assertEqual(result, [1, 2, 3]) + + def test_mock_aiter_and_anext_asyncmock(self): + def inner_test(mock_type): + instance = self.WithAsyncIterator() + mock_instance = mock_type(instance) + # Check that the mock and the real thing bahave the same + # __aiter__ is not actually async, so not a coroutinefunction + self.assertFalse(iscoroutinefunction(instance.__aiter__)) + self.assertFalse(iscoroutinefunction(mock_instance.__aiter__)) + # __anext__ is async + self.assertTrue(iscoroutinefunction(instance.__anext__)) + self.assertTrue(iscoroutinefunction(mock_instance.__anext__)) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test aiter and anext corourtine with {mock_type}"): + inner_test(mock_type) + + + def test_mock_async_for(self): + async def iterate(iterator): + accumulator = [] + async for item in iterator: + accumulator.append(item) + + return accumulator + + expected = ["FOO", "BAR", "BAZ"] + def test_default(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) + self.assertEqual(run(iterate(mock_instance)), []) + + + def test_set_return_value(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = expected[:] + self.assertEqual(run(iterate(mock_instance)), expected) + + def test_set_return_value_iter(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = iter(expected[:]) + self.assertEqual(run(iterate(mock_instance)), expected) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"default value with {mock_type}"): + test_default(mock_type) + + with self.subTest(f"set return_value with {mock_type}"): + test_set_return_value(mock_type) + + with self.subTest(f"set return_value iterator with {mock_type}"): + test_set_return_value_iter(mock_type) + + +class AsyncMockAssert(unittest.TestCase): + def setUp(self): + self.mock = AsyncMock() + + async def _runnable_test(self, *args, **kwargs): + await self.mock(*args, **kwargs) + + async def _await_coroutine(self, coroutine): + return await coroutine + + def test_assert_called_but_not_awaited(self): + mock = AsyncMock(AsyncClass) + with assertNeverAwaited(self): + mock.async_method() + self.assertTrue(iscoroutinefunction(mock.async_method)) + mock.async_method.assert_called() + mock.async_method.assert_called_once() + mock.async_method.assert_called_once_with() + with self.assertRaises(AssertionError): + mock.assert_awaited() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + + def test_assert_called_then_awaited(self): + mock = AsyncMock(AsyncClass) + mock_coroutine = mock.async_method() + mock.async_method.assert_called() + mock.async_method.assert_called_once() + mock.async_method.assert_called_once_with() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + + run(self._await_coroutine(mock_coroutine)) + # Assert we haven't re-called the function + mock.async_method.assert_called_once() + mock.async_method.assert_awaited() + mock.async_method.assert_awaited_once() + mock.async_method.assert_awaited_once_with() + + def test_assert_called_and_awaited_at_same_time(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + + with self.assertRaises(AssertionError): + self.mock.assert_called() + + run(self._runnable_test()) + self.mock.assert_called_once() + self.mock.assert_awaited_once() + + def test_assert_called_twice_and_awaited_once(self): + mock = AsyncMock(AsyncClass) + coroutine = mock.async_method() + # The first call will be awaited so no warning there + # But this call will never get awaited, so it will warn here + with assertNeverAwaited(self): + mock.async_method() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + mock.async_method.assert_called() + run(self._await_coroutine(coroutine)) + mock.async_method.assert_awaited() + mock.async_method.assert_awaited_once() + + def test_assert_called_once_and_awaited_twice(self): + mock = AsyncMock(AsyncClass) + coroutine = mock.async_method() + mock.async_method.assert_called_once() + run(self._await_coroutine(coroutine)) + with self.assertRaises(RuntimeError): + # Cannot reuse already awaited coroutine + run(self._await_coroutine(coroutine)) + mock.async_method.assert_awaited() + + def test_assert_awaited_but_not_called(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + with self.assertRaises(AssertionError): + self.mock.assert_called() + with self.assertRaises(TypeError): + # You cannot await an AsyncMock, it must be a coroutine + run(self._await_coroutine(self.mock)) + + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + with self.assertRaises(AssertionError): + self.mock.assert_called() + + def test_assert_has_calls_not_awaits(self): + kalls = [call('foo')] + with assertNeverAwaited(self): + self.mock('foo') + self.mock.assert_has_calls(kalls) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(kalls) + + def test_assert_has_mock_calls_on_async_mock_no_spec(self): + with assertNeverAwaited(self): + self.mock() + kalls_empty = [('', (), {})] + self.assertEqual(self.mock.mock_calls, kalls_empty) + + with assertNeverAwaited(self): + self.mock('foo') + with assertNeverAwaited(self): + self.mock('baz') + mock_kalls = ([call(), call('foo'), call('baz')]) + self.assertEqual(self.mock.mock_calls, mock_kalls) + + def test_assert_has_mock_calls_on_async_mock_with_spec(self): + a_class_mock = AsyncMock(AsyncClass) + with assertNeverAwaited(self): + a_class_mock.async_method() + kalls_empty = [('', (), {})] + self.assertEqual(a_class_mock.async_method.mock_calls, kalls_empty) + self.assertEqual(a_class_mock.mock_calls, [call.async_method()]) + + with assertNeverAwaited(self): + a_class_mock.async_method(1, 2, 3, a=4, b=5) + method_kalls = [call(), call(1, 2, 3, a=4, b=5)] + mock_kalls = [call.async_method(), call.async_method(1, 2, 3, a=4, b=5)] + self.assertEqual(a_class_mock.async_method.mock_calls, method_kalls) + self.assertEqual(a_class_mock.mock_calls, mock_kalls) + + def test_async_method_calls_recorded(self): + with assertNeverAwaited(self): + self.mock.something(3, fish=None) + with assertNeverAwaited(self): + self.mock.something_else.something(6, cake=sentinel.Cake) + + self.assertEqual(self.mock.method_calls, [ + ("something", (3,), {'fish': None}), + ("something_else.something", (6,), {'cake': sentinel.Cake}) + ], + "method calls not recorded correctly") + self.assertEqual(self.mock.something_else.method_calls, + [("something", (6,), {'cake': sentinel.Cake})], + "method calls not recorded correctly") + + def test_async_arg_lists(self): + def assert_attrs(mock): + names = ('call_args_list', 'method_calls', 'mock_calls') + for name in names: + attr = getattr(mock, name) + self.assertIsInstance(attr, _CallList) + self.assertIsInstance(attr, list) + self.assertEqual(attr, []) + + assert_attrs(self.mock) + with assertNeverAwaited(self): + self.mock() + with assertNeverAwaited(self): + self.mock(1, 2) + with assertNeverAwaited(self): + self.mock(a=3) + + self.mock.reset_mock() + assert_attrs(self.mock) + + a_mock = AsyncMock(AsyncClass) + with assertNeverAwaited(self): + a_mock.async_method() + with assertNeverAwaited(self): + a_mock.async_method(1, a=3) + + a_mock.reset_mock() + assert_attrs(a_mock) + + def test_assert_awaited(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + + run(self._runnable_test()) + self.mock.assert_awaited() + + def test_assert_awaited_once(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once() + + run(self._runnable_test()) + self.mock.assert_awaited_once() + + run(self._runnable_test()) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once() + + def test_assert_awaited_with(self): + msg = 'Not awaited' + with self.assertRaisesRegex(AssertionError, msg): + self.mock.assert_awaited_with('foo') + + run(self._runnable_test()) + msg = 'expected await not found' + with self.assertRaisesRegex(AssertionError, msg): + self.mock.assert_awaited_with('foo') + + run(self._runnable_test('foo')) + self.mock.assert_awaited_with('foo') + + run(self._runnable_test('SomethingElse')) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_with('foo') + + def test_assert_awaited_once_with(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once_with('foo') + + run(self._runnable_test('foo')) + self.mock.assert_awaited_once_with('foo') + + run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once_with('foo') + + def test_assert_any_wait(self): + with self.assertRaises(AssertionError): + self.mock.assert_any_await('foo') + + run(self._runnable_test('baz')) + with self.assertRaises(AssertionError): + self.mock.assert_any_await('foo') + + run(self._runnable_test('foo')) + self.mock.assert_any_await('foo') + + run(self._runnable_test('SomethingElse')) + self.mock.assert_any_await('foo') + + def test_assert_has_awaits_no_order(self): + calls = [call('foo'), call('baz')] + + with self.assertRaises(AssertionError) as cm: + self.mock.assert_has_awaits(calls) + self.assertEqual(len(cm.exception.args), 1) + + run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) + + run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) + + run(self._runnable_test('baz')) + self.mock.assert_has_awaits(calls) + + run(self._runnable_test('SomethingElse')) + self.mock.assert_has_awaits(calls) + + def test_awaits_asserts_with_any(self): + class Foo: + def __eq__(self, other): pass + + run(self._runnable_test(Foo(), 1)) + + self.mock.assert_has_awaits([call(ANY, 1)]) + self.mock.assert_awaited_with(ANY, 1) + self.mock.assert_any_await(ANY, 1) + + def test_awaits_asserts_with_spec_and_any(self): + class Foo: + def __eq__(self, other): pass + + mock_with_spec = AsyncMock(spec=Foo) + + async def _custom_mock_runnable_test(*args): + await mock_with_spec(*args) + + run(_custom_mock_runnable_test(Foo(), 1)) + mock_with_spec.assert_has_awaits([call(ANY, 1)]) + mock_with_spec.assert_awaited_with(ANY, 1) + mock_with_spec.assert_any_await(ANY, 1) + + def test_assert_has_awaits_ordered(self): + calls = [call('foo'), call('baz')] + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('baz')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('bamf')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('foo')) + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('qux')) + self.mock.assert_has_awaits(calls, any_order=True) + + def test_assert_not_awaited(self): + self.mock.assert_not_awaited() + + run(self._runnable_test()) + with self.assertRaises(AssertionError): + self.mock.assert_not_awaited() + + def test_assert_has_awaits_not_matching_spec_error(self): + async def f(x=None): pass + + self.mock = AsyncMock(spec=f) + run(self._runnable_test(1)) + + with self.assertRaisesRegex( + AssertionError, + '^{}$'.format( + re.escape('Awaits not found.\n' + 'Expected: [call()]\n' + 'Actual: [call(1)]'))) as cm: + self.mock.assert_has_awaits([call()]) + self.assertIsNone(cm.exception.__cause__) + + with self.assertRaisesRegex( + AssertionError, + '^{}$'.format( + re.escape( + 'Error processing expected awaits.\n' + "Errors: [None, TypeError('too many positional " + "arguments')]\n" + 'Expected: [call(), call(1, 2)]\n' + 'Actual: [call(1)]').replace( + "arguments\\'", "arguments\\',?") + )) as cm: + self.mock.assert_has_awaits([call(), call(1, 2)]) + self.assertIsInstance(cm.exception.__cause__, TypeError) + + +if __name__ == '__main__': + unittest.main() diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 10acdc35..41715ed1 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -2,15 +2,14 @@ # E-mail: fuzzyman AT voidspace DOT org DOT uk # http://www.voidspace.org.uk/python/mock/ -import unittest2 as unittest +import unittest from mock.tests.support import is_instance, X, SomeClass from mock import ( Mock, MagicMock, NonCallableMagicMock, NonCallableMock, patch, create_autospec, - CallableMixin ) - +from mock.mock import CallableMixin class TestCallable(unittest.TestCase): @@ -27,7 +26,7 @@ def test_non_callable(self): self.assertIn(mock.__class__.__name__, repr(mock)) - def test_heirarchy(self): + def test_hierarchy(self): self.assertTrue(issubclass(MagicMock, Mock)) self.assertTrue(issubclass(NonCallableMagicMock, NonCallableMock)) @@ -98,8 +97,7 @@ def test_patch_spec_set_instance(self): def test_patch_spec_callable_class(self): class CallableX(X): - def __call__(self): - pass + def __call__(self): pass class Sub(CallableX): pass @@ -107,15 +105,8 @@ class Sub(CallableX): class Multi(SomeClass, Sub): pass - class OldStyle: - def __call__(self): - pass - - class OldStyleSub(OldStyle): - pass - for arg in 'spec', 'spec_set': - for Klass in CallableX, Sub, Multi, OldStyle, OldStyleSub: + for Klass in CallableX, Sub, Multi: with patch('%s.X' % __name__, **{arg: Klass}) as mock: instance = mock() mock.assert_called_once_with() diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index a87df1b3..2dc1bf62 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1,27 +1,33 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - -import six -import unittest2 as unittest +import inspect +import time +import types +import unittest from mock import ( call, create_autospec, MagicMock, Mock, ANY, patch, PropertyMock ) -from mock.mock import _Call, _CallList +from mock.mock import _Call, _CallList, _callable +from mock import IS_PYPY + +try: + from dataclasses import dataclass, field, InitVar +except ImportError: + pass from datetime import datetime +from functools import partial +from typing import ClassVar -class SomeClass(object): - def one(self, a, b): - pass - def two(self): - pass - def three(self, a=None): - pass +import pytest +import sys +class SomeClass(object): + def one(self, a, b): pass + def two(self): pass + def three(self, a=None): pass + class AnyTest(unittest.TestCase): @@ -50,12 +56,9 @@ def test_any_and_datetime(self): def test_any_mock_calls_comparison_order(self): mock = Mock() - d = datetime.now() class Foo(object): - def __eq__(self, other): - return False - def __ne__(self, other): - return True + def __eq__(self, other): pass + def __ne__(self, other): pass for d in datetime.now(), Foo(): mock.reset_mock() @@ -72,7 +75,28 @@ def __ne__(self, other): self.assertEqual(expected, mock.mock_calls) self.assertEqual(mock.mock_calls, expected) + def test_any_no_spec(self): + # This is a regression test for bpo-37555 + class Foo: + def __eq__(self, other): pass + + mock = Mock() + mock(Foo(), 1) + mock.assert_has_calls([call(ANY, 1)]) + mock.assert_called_with(ANY, 1) + mock.assert_any_call(ANY, 1) + + def test_any_and_spec_set(self): + # This is a regression test for bpo-37555 + class Foo: + def __eq__(self, other): pass + + mock = Mock(spec=Foo) + mock(Foo(), 1) + mock.assert_has_calls([call(ANY, 1)]) + mock.assert_called_with(ANY, 1) + mock.assert_any_call(ANY, 1) class CallTest(unittest.TestCase): @@ -148,6 +172,8 @@ def test_call_with_args(self): self.assertEqual(args, ('foo', (1, 2, 3))) self.assertEqual(args, ('foo', (1, 2, 3), {})) self.assertEqual(args, ((1, 2, 3), {})) + self.assertEqual(args.args, (1, 2, 3)) + self.assertEqual(args.kwargs, {}) def test_named_call_with_args(self): @@ -155,6 +181,8 @@ def test_named_call_with_args(self): self.assertEqual(args, ('foo', (1, 2, 3))) self.assertEqual(args, ('foo', (1, 2, 3), {})) + self.assertEqual(args.args, (1, 2, 3)) + self.assertEqual(args.kwargs, {}) self.assertNotEqual(args, ((1, 2, 3),)) self.assertNotEqual(args, ((1, 2, 3), {})) @@ -167,6 +195,8 @@ def test_call_with_kwargs(self): self.assertEqual(args, ('foo', dict(a=3, b=4))) self.assertEqual(args, ('foo', (), dict(a=3, b=4))) self.assertEqual(args, ((), dict(a=3, b=4))) + self.assertEqual(args.args, ()) + self.assertEqual(args.kwargs, dict(a=3, b=4)) def test_named_call_with_kwargs(self): @@ -174,6 +204,8 @@ def test_named_call_with_kwargs(self): self.assertEqual(args, ('foo', dict(a=3, b=4))) self.assertEqual(args, ('foo', (), dict(a=3, b=4))) + self.assertEqual(args.args, ()) + self.assertEqual(args.kwargs, dict(a=3, b=4)) self.assertNotEqual(args, (dict(a=3, b=4),)) self.assertNotEqual(args, ((), dict(a=3, b=4))) @@ -181,6 +213,7 @@ def test_named_call_with_kwargs(self): def test_call_with_args_call_empty_name(self): args = _Call(((1, 2, 3), {})) + self.assertEqual(args, call(1, 2, 3)) self.assertEqual(call(1, 2, 3), args) self.assertIn(call(1, 2, 3), [args]) @@ -273,6 +306,22 @@ def test_extended_call(self): self.assertEqual(mock.mock_calls, last_call.call_list()) + def test_extended_not_equal(self): + a = call(x=1).foo + b = call(x=2).foo + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + + def test_nested_calls_not_equal(self): + a = call(x=1).foo().bar + b = call(x=2).foo().bar + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + def test_call_list(self): mock = MagicMock() mock(1) @@ -312,6 +361,31 @@ def test_two_args_call(self): other_args = _Call(((1, 2), {'a': 3})) self.assertEqual(args, other_args) + def test_call_with_name(self): + self.assertEqual(_Call((), 'foo')[0], 'foo') + self.assertEqual(_Call((('bar', 'barz'),),)[0], '') + self.assertEqual(_Call((('bar', 'barz'), {'hello': 'world'}),)[0], '') + + def test_dunder_call(self): + m = MagicMock() + m().foo()['bar']() + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__getitem__('bar'), call().foo().__getitem__()()] + ) + m = MagicMock() + m().foo()['bar'] = 1 + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__setitem__('bar', 1)] + ) + m = MagicMock() + iter(m().foo()) + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__iter__()] + ) + class SpecSignatureTest(unittest.TestCase): @@ -350,8 +424,7 @@ def test_basic(self): def test_create_autospec_return_value(self): - def f(): - pass + def f(): pass mock = create_autospec(f, return_value='foo') self.assertEqual(mock(), 'foo') @@ -371,8 +444,7 @@ def test_autospec_reset_mock(self): def test_mocking_unbound_methods(self): class Foo(object): - def foo(self, foo): - pass + def foo(self, foo): pass p = patch.object(Foo, 'foo') mock_foo = p.start() Foo().foo(1) @@ -380,35 +452,15 @@ def foo(self, foo): mock_foo.assert_called_with(1) - @unittest.expectedFailure - def test_create_autospec_unbound_methods(self): - # see mock issue 128 - class Foo(object): - def foo(self): - pass - - klass = create_autospec(Foo) - instance = klass() - self.assertRaises(TypeError, instance.foo, 1) - - # Note: no type checking on the "self" parameter - klass.foo(1) - klass.foo.assert_called_with(1) - self.assertRaises(TypeError, klass.foo) - - def test_create_autospec_keyword_arguments(self): class Foo(object): a = 3 m = create_autospec(Foo, a='3') self.assertEqual(m.a, '3') - @unittest.skipUnless(six.PY3, "Keyword only arguments Python 3 specific") + def test_create_autospec_keyword_only_arguments(self): - func_def = "def foo(a, *, b=None):\n pass\n" - namespace = {} - exec (func_def, namespace) - foo = namespace['foo'] + def foo(a, *, b=None): pass m = create_autospec(foo) m(1) @@ -418,10 +470,10 @@ def test_create_autospec_keyword_only_arguments(self): m(2, b=3) m.assert_called_with(2, b=3) + def test_function_as_instance_attribute(self): obj = SomeClass() - def f(a): - pass + def f(a): pass obj.f = f mock = create_autospec(obj) @@ -457,13 +509,57 @@ class Sub(SomeClass): self._check_someclass_mock(mock) + def test_spec_has_descriptor_returning_function(self): + + class CrazyDescriptor(object): + + def __get__(self, obj, type_): + if obj is None: + return lambda x: None + + class MyClass(object): + + some_attr = CrazyDescriptor() + + mock = create_autospec(MyClass) + mock.some_attr(1) + with self.assertRaises(TypeError): + mock.some_attr() + with self.assertRaises(TypeError): + mock.some_attr(1, 2) + + + def test_spec_has_function_not_in_bases(self): + + class CrazyClass(object): + + def __dir__(self): + return super(CrazyClass, self).__dir__()+['crazy'] + + def __getattr__(self, item): + if item == 'crazy': + return lambda x: x + raise AttributeError(item) + + inst = CrazyClass() + with self.assertRaises(AttributeError): + inst.other + self.assertEqual(inst.crazy(42), 42) + + mock = create_autospec(inst) + mock.crazy(42) + with self.assertRaises(TypeError): + mock.crazy() + with self.assertRaises(TypeError): + mock.crazy(1, 2) + + def test_builtin_functions_types(self): # we could replace builtin functions / methods with a function # with *args / **kwargs signature. Using the builtin method type # as a spec seems to work fairly well though. class BuiltinSubclass(list): - def bar(self, arg): - pass + def bar(self, arg): pass sorted = sorted attr = {} @@ -537,17 +633,13 @@ class Sub(SomeClass): def test_descriptors(self): class Foo(object): @classmethod - def f(cls, a, b): - pass + def f(cls, a, b): pass @staticmethod - def g(a, b): - pass + def g(a, b): pass - class Bar(Foo): - pass + class Bar(Foo): pass - class Baz(SomeClass, Bar): - pass + class Baz(SomeClass, Bar): pass for spec in (Foo, Foo(), Bar, Bar(), Baz, Baz()): mock = create_autospec(spec) @@ -558,32 +650,9 @@ class Baz(SomeClass, Bar): mock.g.assert_called_once_with(3, 4) - @unittest.skipIf(six.PY3, "No old style classes in Python 3") - def test_old_style_classes(self): - class Foo: - def f(self, a, b): - pass - - class Bar(Foo): - g = Foo() - - for spec in (Foo, Foo(), Bar, Bar()): - mock = create_autospec(spec) - mock.f(1, 2) - mock.f.assert_called_once_with(1, 2) - - self.assertRaises(AttributeError, getattr, mock, 'foo') - self.assertRaises(AttributeError, getattr, mock.f, 'foo') - - mock.g.f(1, 2) - mock.g.f.assert_called_once_with(1, 2) - self.assertRaises(AttributeError, getattr, mock.g, 'foo') - - def test_recursive(self): class A(object): - def a(self): - pass + def a(self): pass foo = 'foo bar baz' bar = foo @@ -605,11 +674,9 @@ def a(self): def test_spec_inheritance_for_classes(self): class Foo(object): - def a(self, x): - pass + def a(self, x): pass class Bar(object): - def f(self, y): - pass + def f(self, y): pass class_mock = create_autospec(Foo) @@ -689,8 +756,7 @@ def test_builtins(self): def test_function(self): - def f(a, b): - pass + def f(a, b): pass mock = create_autospec(f) self.assertRaises(TypeError, mock) @@ -720,9 +786,10 @@ class RaiserClass(object): def existing(a, b): return a + b + self.assertEqual(RaiserClass.existing(1, 2), 3) s = create_autospec(RaiserClass) self.assertRaises(TypeError, lambda x: s.existing(1, 2, 3)) - s.existing(1, 2) + self.assertEqual(s.existing(1, 2), s.existing.return_value) self.assertRaises(AttributeError, lambda: s.nonexisting) # check we can fetch the raiser attribute and it has no spec @@ -732,24 +799,7 @@ def existing(a, b): def test_signature_class(self): class Foo(object): - def __init__(self, a, b=3): - pass - - mock = create_autospec(Foo) - - self.assertRaises(TypeError, mock) - mock(1) - mock.assert_called_once_with(1) - - mock(4, 5) - mock.assert_called_with(4, 5) - - - @unittest.skipIf(six.PY3, 'no old style classes in Python 3') - def test_signature_old_style_class(self): - class Foo: - def __init__(self, a, b=3): - pass + def __init__(self, a, b=3): pass mock = create_autospec(Foo) @@ -773,21 +823,10 @@ class Foo(object): create_autospec(Foo) - @unittest.skipIf(six.PY3, 'no old style classes in Python 3') - def test_old_style_class_with_no_init(self): - # this used to raise an exception - # due to Foo.__init__ raising an AttributeError - class Foo: - pass - create_autospec(Foo) - - def test_signature_callable(self): class Callable(object): - def __init__(self, x, y): - pass - def __call__(self, a): - pass + def __init__(self, x, y): pass + def __call__(self, a): pass mock = create_autospec(Callable) mock(1, 2) @@ -843,8 +882,7 @@ class Foo(object): def test_autospec_functions_with_self_in_odd_place(self): class Foo(object): - def f(a, self): - pass + def f(a, self): pass a = create_autospec(Foo) a.f(10) @@ -855,36 +893,237 @@ def f(a, self): a.f.assert_called_with(self=10) - def test_autospec_property(self): + def test_autospec_data_descriptor(self): + class Descriptor(object): + def __init__(self, value): + self.value = value + + def __get__(self, obj, cls=None): + return self + + def __set__(self, obj, value): pass + + class MyProperty(property): + pass + class Foo(object): + __slots__ = ['slot'] + @property - def foo(self): - return 3 + def prop(self): pass + + @MyProperty + def subprop(self): pass + + desc = Descriptor(42) foo = create_autospec(Foo) - mock_property = foo.foo - # no spec on properties - self.assertIsInstance(mock_property, MagicMock) - mock_property(1, 2, 3) - mock_property.abc(4, 5, 6) - mock_property.assert_called_once_with(1, 2, 3) - mock_property.abc.assert_called_once_with(4, 5, 6) + def check_data_descriptor(mock_attr): + # Data descriptors don't have a spec. + self.assertIsInstance(mock_attr, MagicMock) + mock_attr(1, 2, 3) + mock_attr.abc(4, 5, 6) + mock_attr.assert_called_once_with(1, 2, 3) + mock_attr.abc.assert_called_once_with(4, 5, 6) + + # property + check_data_descriptor(foo.prop) + # property subclass + check_data_descriptor(foo.subprop) + # class __slot__ + check_data_descriptor(foo.slot) + # plain data descriptor + check_data_descriptor(foo.desc) + + + def test_autospec_on_bound_builtin_function(self): + meth = types.MethodType(time.ctime, time.time()) + self.assertIsInstance(meth(), str) + mocked = create_autospec(meth) + + # no signature, so no spec to check against + mocked() + mocked.assert_called_once_with() + mocked.reset_mock() + # but pypy gets this right: + if IS_PYPY: + with self.assertRaises(TypeError): + mocked(4, 5, 6) + else: + mocked(4, 5, 6) + mocked.assert_called_once_with(4, 5, 6) + + + def test_autospec_getattr_partial_function(self): + # bpo-32153 : getattr returning partial functions without + # __name__ should not create AttributeError in create_autospec + class Foo: + def __getattr__(self, attribute): + return partial(lambda name: name, attribute) - def test_autospec_slots(self): - class Foo(object): - __slots__ = ['a'] + proxy = Foo() + autospec = create_autospec(proxy) + self.assertFalse(hasattr(autospec, '__name__')) - foo = create_autospec(Foo) - mock_slot = foo.a - # no spec on slots - mock_slot(1, 2, 3) - mock_slot.abc(4, 5, 6) - mock_slot.assert_called_once_with(1, 2, 3) - mock_slot.abc.assert_called_once_with(4, 5, 6) + def test_autospec_signature_staticmethod(self): + class Foo: + @staticmethod + def static_method(a, b=10, *, c): pass + + mock = create_autospec(Foo.__dict__['static_method']) + self.assertEqual(inspect.signature(Foo.static_method), inspect.signature(mock)) + + + def test_autospec_signature_classmethod(self): + class Foo: + @classmethod + def class_method(cls, a, b=10, *, c): pass + mock = create_autospec(Foo.__dict__['class_method']) + self.assertEqual(inspect.signature(Foo.class_method), inspect.signature(mock)) + + + def test_spec_inspect_signature(self): + + def myfunc(x, y): pass + + mock = create_autospec(myfunc) + mock(1, 2) + mock(x=1, y=2) + + self.assertEqual(inspect.signature(mock), inspect.signature(myfunc)) + self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)]) + self.assertRaises(TypeError, mock, 1) + + + def test_spec_inspect_signature_annotations(self): + + def foo(a: int, b: int=10, *, c:int) -> int: + return a + b + c + + self.assertEqual(foo(1, 2 , c=3), 6) + mock = create_autospec(foo) + mock(1, 2, c=3) + mock(1, c=3) + + self.assertEqual(inspect.signature(mock), inspect.signature(foo)) + self.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)]) + self.assertRaises(TypeError, mock, 1) + self.assertRaises(TypeError, mock, 1, 2, 3, c=4) + + + def test_spec_function_no_name(self): + func = lambda: 'nope' + mock = create_autospec(func) + self.assertEqual(mock.__name__, 'funcopy') + + + def test_spec_function_assert_has_calls(self): + def f(a): pass + mock = create_autospec(f) + mock(1) + mock.assert_has_calls([call(1)]) + with self.assertRaises(AssertionError): + mock.assert_has_calls([call(2)]) + + + def test_spec_function_assert_any_call(self): + def f(a): pass + mock = create_autospec(f) + mock(1) + mock.assert_any_call(1) + with self.assertRaises(AssertionError): + mock.assert_any_call(2) + + + def test_spec_function_reset_mock(self): + def f(a): pass + rv = Mock() + mock = create_autospec(f, return_value=rv) + mock(1)(2) + self.assertEqual(mock.mock_calls, [call(1)]) + self.assertEqual(rv.mock_calls, [call(2)]) + mock.reset_mock() + self.assertEqual(mock.mock_calls, []) + self.assertEqual(rv.mock_calls, []) + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_post_init(self): + @dataclass + class WithPostInit: + a: int = field(init=False) + b: int = field(init=False) + def __post_init__(self): + self.a = 1 + self.b = 2 + + for mock in [ + create_autospec(WithPostInit, instance=True), + create_autospec(WithPostInit()), + ]: + with self.subTest(mock=mock): + self.assertIsInstance(mock.a, int) + self.assertIsInstance(mock.b, int) + + # Classes do not have these fields: + mock = create_autospec(WithPostInit) + msg = "Mock object has no attribute" + with self.assertRaisesRegex(AttributeError, msg): + mock.a + with self.assertRaisesRegex(AttributeError, msg): + mock.b + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_default(self): + @dataclass + class WithDefault: + a: int + b: int = 0 + + for mock in [ + create_autospec(WithDefault, instance=True), + create_autospec(WithDefault(1)), + ]: + with self.subTest(mock=mock): + self.assertIsInstance(mock.a, int) + self.assertIsInstance(mock.b, int) + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_with_method(self): + @dataclass + class WithMethod: + a: int + def b(self) -> int: + return 1 # pragma: no cover + + for mock in [ + create_autospec(WithMethod, instance=True), + create_autospec(WithMethod(1)), + ]: + with self.subTest(mock=mock): + self.assertIsInstance(mock.a, int) + mock.b.assert_not_called() + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_with_non_fields(self): + @dataclass + class WithNonFields: + a: ClassVar[int] + b: InitVar[int] + + msg = "Mock object has no attribute" + for mock in [ + create_autospec(WithNonFields, instance=True), + create_autospec(WithNonFields(1)), + ]: + with self.subTest(mock=mock): + with self.assertRaisesRegex(AttributeError, msg): + mock.a + with self.assertRaisesRegex(AttributeError, msg): + mock.b class TestCallList(unittest.TestCase): @@ -928,20 +1167,6 @@ def test_call_list_str(self): self.assertEqual(str(mock.mock_calls), expected) - @unittest.skipIf(six.PY3, "Unicode is properly handled with Python 3") - def test_call_list_unicode(self): - # See github issue #328 - mock = Mock() - - class NonAsciiRepr(object): - def __repr__(self): - return "\xe9" - - mock(**{unicode("a"): NonAsciiRepr()}) - - self.assertEqual(str(mock.mock_calls), "[call(a=\xe9)]") - - def test_propertymock(self): p = patch('%s.SomeClass.one' % __name__, new_callable=PropertyMock) mock = p.start() @@ -960,7 +1185,7 @@ def test_propertymock(self): p.stop() - def test_propertymock_returnvalue(self): + def test_propertymock_bare(self): m = MagicMock() p = PropertyMock() type(m).foo = p @@ -971,5 +1196,69 @@ def test_propertymock_returnvalue(self): self.assertNotIsInstance(returned, PropertyMock) + def test_propertymock_returnvalue(self): + m = MagicMock() + p = PropertyMock(return_value=42) + type(m).foo = p + + returned = m.foo + p.assert_called_once_with() + self.assertEqual(returned, 42) + self.assertNotIsInstance(returned, PropertyMock) + + + def test_propertymock_side_effect(self): + m = MagicMock() + p = PropertyMock(side_effect=ValueError) + type(m).foo = p + + with self.assertRaises(ValueError): + m.foo + p.assert_called_once_with() + + + def test_propertymock_attach(self): + m = Mock() + p = PropertyMock() + type(m).foo = p + m.attach_mock(p, 'foo') + self.assertEqual(m.mock_calls, []) + + +class TestCallablePredicate(unittest.TestCase): + + def test_type(self): + for obj in [str, bytes, int, list, tuple, SomeClass]: + self.assertTrue(_callable(obj)) + + def test_call_magic_method(self): + class Callable: + def __call__(self): pass + instance = Callable() + self.assertTrue(_callable(instance)) + + def test_staticmethod(self): + class WithStaticMethod: + @staticmethod + def staticfunc(): pass + self.assertTrue(_callable(WithStaticMethod.staticfunc)) + + def test_non_callable_staticmethod(self): + class BadStaticMethod: + not_callable = staticmethod(None) + self.assertFalse(_callable(BadStaticMethod.not_callable)) + + def test_classmethod(self): + class WithClassMethod: + @classmethod + def classfunc(cls): pass + self.assertTrue(_callable(WithClassMethod.classfunc)) + + def test_non_callable_classmethod(self): + class BadClassMethod: + not_callable = classmethod(None) + self.assertFalse(_callable(BadClassMethod.not_callable)) + + if __name__ == '__main__': unittest.main() diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index f47a2025..7e242f4a 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -1,28 +1,11 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - -from __future__ import division - -try: - unicode -except NameError: - # Python 3 - unicode = str - long = int - -import inspect -import sys -import textwrap - -import six -import unittest2 as unittest - -from mock import Mock, MagicMock +import math +import unittest +import os +from mock import AsyncMock, Mock, MagicMock +from mock.backports import iscoroutinefunction from mock.mock import _magics - class TestMockingMagicMethods(unittest.TestCase): def test_deleting_magic_methods(self): @@ -86,15 +69,6 @@ def test_str(self): self.assertEqual(str(mock), 'foo') - @unittest.skipIf(six.PY3, "no unicode in Python 3") - def test_unicode(self): - mock = Mock() - self.assertEqual(unicode(mock), unicode(str(mock))) - - mock.__unicode__ = lambda s: unicode('foo') - self.assertEqual(unicode(mock), unicode('foo')) - - def test_dict_methods(self): mock = Mock() @@ -166,16 +140,13 @@ def truediv(self, other): self.assertEqual(mock.value, 16) del mock.__truediv__ - if six.PY3: - def itruediv(mock): - mock /= 4 - self.assertRaises(TypeError, itruediv, mock) - mock.__itruediv__ = truediv - mock /= 8 - self.assertEqual(mock, original) - self.assertEqual(mock.value, 2) - else: - mock.value = 2 + def itruediv(mock): + mock /= 4 + self.assertRaises(TypeError, itruediv, mock) + mock.__itruediv__ = truediv + mock /= 8 + self.assertEqual(mock, original) + self.assertEqual(mock.value, 2) self.assertRaises(TypeError, lambda: 8 / mock) mock.__rtruediv__ = truediv @@ -197,12 +168,7 @@ def test_nonzero(self): m = Mock() self.assertTrue(bool(m)) - nonzero = lambda s: False - if six.PY2: - m.__nonzero__ = nonzero - else: - m.__bool__ = nonzero - + m.__bool__ = lambda s: False self.assertFalse(bool(m)) @@ -216,25 +182,18 @@ def comp(s, o): self. assertTrue(mock <= 3) self. assertTrue(mock >= 3) - if six.PY2: - # incomparable in Python 3 - self.assertEqual(Mock() < 3, object() < 3) - self.assertEqual(Mock() > 3, object() > 3) - self.assertEqual(Mock() <= 3, object() <= 3) - self.assertEqual(Mock() >= 3, object() >= 3) - else: - self.assertRaises(TypeError, lambda: MagicMock() < object()) - self.assertRaises(TypeError, lambda: object() < MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() < MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() > object()) - self.assertRaises(TypeError, lambda: object() > MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() > MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() <= object()) - self.assertRaises(TypeError, lambda: object() <= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() <= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() >= object()) - self.assertRaises(TypeError, lambda: object() >= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() >= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() < object()) + self.assertRaises(TypeError, lambda: object() < MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() < MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() > object()) + self.assertRaises(TypeError, lambda: object() > MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() > MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() <= object()) + self.assertRaises(TypeError, lambda: object() <= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() <= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() >= object()) + self.assertRaises(TypeError, lambda: object() >= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() >= MagicMock()) def test_equality(self): @@ -292,17 +251,13 @@ def test_magicmock(self): mock.__iter__.return_value = iter([1, 2, 3]) self.assertEqual(list(mock), [1, 2, 3]) - name = '__nonzero__' - other = '__bool__' - if six.PY3: - name, other = other, name - getattr(mock, name).return_value = False - self.assertFalse(hasattr(mock, other)) + getattr(mock, '__bool__').return_value = False + self.assertFalse(hasattr(mock, '__nonzero__')) self.assertFalse(bool(mock)) for entry in _magics: self.assertTrue(hasattr(mock, entry)) - self.assertFalse(hasattr(mock, '__imaginery__')) + self.assertFalse(hasattr(mock, '__imaginary__')) def test_magic_mock_equality(self): @@ -315,46 +270,110 @@ def test_magic_mock_equality(self): self.assertEqual(mock == mock, True) self.assertEqual(mock != mock, False) + def test_asyncmock_defaults(self): + mock = AsyncMock() + self.assertEqual(int(mock), 1) + self.assertEqual(complex(mock), 1j) + self.assertEqual(float(mock), 1.0) + self.assertNotIn(object(), mock) + self.assertEqual(len(mock), 0) + self.assertEqual(list(mock), []) + self.assertEqual(hash(mock), object.__hash__(mock)) + self.assertEqual(str(mock), object.__str__(mock)) + self.assertTrue(bool(mock)) + self.assertEqual(round(mock), mock.__round__()) + self.assertEqual(math.trunc(mock), mock.__trunc__()) + self.assertEqual(math.floor(mock), mock.__floor__()) + self.assertEqual(math.ceil(mock), mock.__ceil__()) + self.assertTrue(iscoroutinefunction(mock.__aexit__)) + self.assertTrue(iscoroutinefunction(mock.__aenter__)) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) + + # in Python 3 oct and hex use __index__ + # so these tests are for __index__ in py3k + self.assertEqual(oct(mock), '0o1') + self.assertEqual(hex(mock), '0x1') + # how to test __sizeof__ ? def test_magicmock_defaults(self): mock = MagicMock() self.assertEqual(int(mock), 1) self.assertEqual(complex(mock), 1j) self.assertEqual(float(mock), 1.0) - self.assertEqual(long(mock), long(1)) self.assertNotIn(object(), mock) self.assertEqual(len(mock), 0) self.assertEqual(list(mock), []) self.assertEqual(hash(mock), object.__hash__(mock)) self.assertEqual(str(mock), object.__str__(mock)) - self.assertEqual(unicode(mock), object.__str__(mock)) - self.assertIsInstance(unicode(mock), unicode) self.assertTrue(bool(mock)) - if six.PY2: - self.assertEqual(oct(mock), '1') - else: - # in Python 3 oct and hex use __index__ - # so these tests are for __index__ in py3k - self.assertEqual(oct(mock), '0o1') + self.assertEqual(round(mock), mock.__round__()) + self.assertEqual(math.trunc(mock), mock.__trunc__()) + self.assertEqual(math.floor(mock), mock.__floor__()) + self.assertEqual(math.ceil(mock), mock.__ceil__()) + self.assertTrue(iscoroutinefunction(mock.__aexit__)) + self.assertTrue(iscoroutinefunction(mock.__aenter__)) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) + + # in Python 3 oct and hex use __index__ + # so these tests are for __index__ in py3k + self.assertEqual(oct(mock), '0o1') self.assertEqual(hex(mock), '0x1') # how to test __sizeof__ ? - @unittest.skipIf(six.PY3, "no __cmp__ in Python 3") - def test_non_default_magic_methods(self): + def test_magic_methods_fspath(self): mock = MagicMock() - self.assertRaises(AttributeError, lambda: mock.__cmp__) + expected_path = mock.__fspath__() + mock.reset_mock() - mock = Mock() - mock.__cmp__ = lambda s, o: 0 + self.assertEqual(os.fspath(mock), expected_path) + mock.__fspath__.assert_called_once() + + def test_magic_mock_does_not_reset_magic_returns(self): + # https://github.com/python/cpython/issues/123934 + for reset in (True, False): + with self.subTest(reset=reset): + mm = MagicMock() + self.assertIs(type(mm.__str__()), str) + mm.__str__.assert_called_once() + + self.assertIs(type(mm.__hash__()), int) + mm.__hash__.assert_called_once() - self.assertEqual(mock, object()) + for _ in range(3): + # Repeat reset several times to be sure: + mm.reset_mock(return_value=reset) + self.assertIs(type(mm.__str__()), str) + mm.__str__.assert_called_once() + + self.assertIs(type(mm.__hash__()), int) + mm.__hash__.assert_called_once() + + def test_magic_mock_resets_manual_mocks(self): + mm = MagicMock() + mm.__iter__ = MagicMock(return_value=iter([1])) + mm.custom = MagicMock(return_value=2) + self.assertEqual(list(iter(mm)), [1]) + self.assertEqual(mm.custom(), 2) + + mm.reset_mock(return_value=True) + self.assertEqual(list(iter(mm)), []) + self.assertIsInstance(mm.custom(), MagicMock) + + def test_magic_mock_resets_manual_mocks_empty_iter(self): + mm = MagicMock() + mm.__iter__.return_value = [] + self.assertEqual(list(iter(mm)), []) + + mm.reset_mock(return_value=True) + self.assertEqual(list(iter(mm)), []) def test_magic_methods_and_spec(self): class Iterable(object): - def __iter__(self): - pass + def __iter__(self): pass mock = Mock(spec=Iterable) self.assertRaises(AttributeError, lambda: mock.__iter__) @@ -378,8 +397,7 @@ def set_int(): def test_magic_methods_and_spec_set(self): class Iterable(object): - def __iter__(self): - pass + def __iter__(self): pass mock = Mock(spec_set=Iterable) self.assertRaises(AttributeError, lambda: mock.__iter__) @@ -439,6 +457,7 @@ def test_magic_method_reset_mock(self): mock.reset_mock() self.assertFalse(mock.__str__.called) + def test_dir(self): # overriding the default implementation for mock in Mock(), MagicMock(): @@ -448,7 +467,6 @@ def _dir(self): self.assertEqual(dir(mock), ['foo']) - @unittest.skipIf('PyPy' in sys.version, "This fails differently on pypy") def test_bound_methods(self): m = Mock() @@ -484,20 +502,17 @@ def test_iterable_as_iter_return_value(self): self.assertEqual(list(m), [4, 5, 6]) self.assertEqual(list(m), []) - @unittest.skipIf(sys.version_info < (3, 5), "@ added in Python 3.5") + def test_matmul(self): - src = textwrap.dedent("""\ - m = MagicMock() - self.assertIsInstance(m @ 1, MagicMock) - m.__matmul__.return_value = 42 - m.__rmatmul__.return_value = 666 - m.__imatmul__.return_value = 24 - self.assertEqual(m @ 1, 42) - self.assertEqual(1 @ m, 666) - m @= 24 - self.assertEqual(m, 24) - """) - exec(src) + m = MagicMock() + self.assertIsInstance(m @ 1, MagicMock) + m.__matmul__.return_value = 42 + m.__rmatmul__.return_value = 666 + m.__imatmul__.return_value = 24 + self.assertEqual(m @ 1, 42) + self.assertEqual(1 @ m, 666) + m @= 24 + self.assertEqual(m, 24) def test_divmod_and_rdivmod(self): m = MagicMock() @@ -515,7 +530,7 @@ def test_divmod_and_rdivmod(self): self.assertIsInstance(bar_direct, MagicMock) # http://bugs.python.org/issue23310 - # Check if you can change behaviour of magic methds in MagicMock init + # Check if you can change behaviour of magic methods in MagicMock init def test_magic_in_initialization(self): m = MagicMock(**{'__str__.return_value': "12"}) self.assertEqual(str(m), "12") diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 7511c23d..44a40c62 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1,32 +1,19 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - import copy -import pickle +import re import sys import tempfile -import six -import unittest2 as unittest - -import mock +import unittest +from mock.tests.support import ALWAYS_EQ +from mock.tests.support import is_instance from mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, - create_autospec -) -from mock.mock import _CallList -from mock.tests.support import ( - callable, is_instance, next + NonCallableMagicMock, AsyncMock, + create_autospec, mock ) - - -try: - unicode -except NameError: - unicode = str +from mock.mock import _Call, _CallList, InvalidSpecError +import mock.mock as mock_module class Iter(object): @@ -43,26 +30,33 @@ def next(self): class Something(object): - def meth(self, a, b, c, d=None): - pass + def meth(self, a, b, c, d=None): pass @classmethod - def cmeth(cls, a, b, c, d=None): - pass + def cmeth(cls, a, b, c, d=None): pass @staticmethod - def smeth(a, b, c, d=None): - pass + def smeth(a, b, c, d=None): pass -class Subclass(MagicMock): - pass +class SomethingElse(object): + def __init__(self): + self._instance = None + + @property + def instance(self): + if not self._instance: + self._instance = 'object' + return self._instance + +class Typos(): + autospect = None + auto_spec = None + set_spec = None -class Thing(object): - attribute = 6 - foo = 'bar' +def something(a): pass class MockTest(unittest.TestCase): @@ -71,7 +65,7 @@ def test_all(self): # if __all__ is badly defined then import * will raise an error # We have to exec it because you can't import * inside a method # in Python 3 - exec("from mock import *") + exec("from mock.mock import *") def test_constructor(self): @@ -108,6 +102,39 @@ def test_return_value_in_constructor(self): "return value in constructor not honoured") + def test_change_return_value_via_delegate(self): + def f(): pass + mock = create_autospec(f) + mock.mock.return_value = 1 + self.assertEqual(mock(), 1) + + + def test_change_side_effect_via_delegate(self): + def f(): pass + mock = create_autospec(f) + mock.mock.side_effect = TypeError() + with self.assertRaises(TypeError): + mock() + + def test_create_autospec_should_be_configurable_by_kwargs(self): + """If kwargs are given to configure mock, the function must configure + the parent mock during initialization.""" + mocked_result = 'mocked value' + class_mock = create_autospec(spec=Something, **{ + 'return_value.meth.side_effect': [ValueError, DEFAULT], + 'return_value.meth.return_value': mocked_result}) + with self.assertRaises(ValueError): + class_mock().meth(a=None, b=None, c=None) + self.assertEqual(class_mock().meth(a=None, b=None, c=None), mocked_result) + # Only the parent mock should be configurable because the user will + # pass kwargs with respect to the parent mock. + self.assertEqual(class_mock().return_value.meth.side_effect, None) + + def test_create_autospec_correctly_handles_name(self): + class X: ... + mock = create_autospec(X, spec_set=True, name="Y") + self.assertEqual(mock._mock_name, "Y") + def test_repr(self): mock = Mock(name='foo') self.assertIn('foo', repr(mock)) @@ -186,8 +213,7 @@ def test_autospec_side_effect(self): results = [1, 2, 3] def effect(): return results.pop() - def f(): - pass + def f(): pass mock = create_autospec(f) mock.side_effect = [1, 2, 3] @@ -202,28 +228,109 @@ def f(): def test_autospec_side_effect_exception(self): # Test for issue 23661 - def f(): - pass + def f(): pass mock = create_autospec(f) mock.side_effect = ValueError('Bazinga!') self.assertRaisesRegex(ValueError, 'Bazinga!', mock) - @unittest.skipUnless('java' in sys.platform, - 'This test only applies to Jython') - def test_java_exception_side_effect(self): - import java - mock = Mock(side_effect=java.lang.RuntimeException("Boom!")) - # can't use assertRaises with java exceptions - try: - mock(1, 2, fish=3) - except java.lang.RuntimeException: - pass - else: - self.fail('java exception not raised') - mock.assert_called_with(1,2, fish=3) + def test_autospec_mock(self): + class A(object): + class B(object): + C = None + + with mock.patch.object(A, 'B'): + with self.assertRaisesRegex(InvalidSpecError, + "Cannot autospec attr 'B' from target always fails mock(4, 5, 6) self.assertRaises(AssertionError, mock.assert_called_once_with, @@ -514,8 +674,7 @@ def test_from_spec(self): class Something(object): x = 3 __something__ = None - def y(self): - pass + def y(self): pass def test_attributes(mock): # should work @@ -541,6 +700,14 @@ def test_wraps_calls(self): real = Mock() mock = Mock(wraps=real) + # If "Mock" wraps an object, just accessing its + # "return_value" ("NonCallableMock.__get_return_value") should not + # trigger its descriptor ("NonCallableMock.__set_return_value") so + # the default "return_value" should always be "sentinel.DEFAULT". + self.assertEqual(mock.return_value, DEFAULT) + # It will not be "sentinel.DEFAULT" if the mock is not wrapping any + # object. + self.assertNotEqual(real.return_value, DEFAULT) self.assertEqual(mock(), real()) real.reset_mock() @@ -549,6 +716,16 @@ def test_wraps_calls(self): real.assert_called_with(1, 2, fish=3) + def test_wraps_prevents_automatic_creation_of_mocks(self): + class Real(object): + pass + + real = Real() + mock = Mock(wraps=real) + + self.assertRaises(AttributeError, lambda: mock.new_attr()) + + def test_wraps_call_with_nondefault_return_value(self): real = Mock() @@ -575,6 +752,161 @@ class Real(object): self.assertEqual(result, Real.attribute.frog()) + def test_customize_wrapped_object_with_side_effect_iterable_with_default(self): + class Real(object): + def method(self): + return sentinel.ORIGINAL_VALUE + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, DEFAULT] + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.ORIGINAL_VALUE) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_side_effect_iterable(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.VALUE2) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_side_effect_exception(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = RuntimeError + + self.assertRaises(RuntimeError, mock.method) + + + def test_customize_wrapped_object_with_side_effect_function(self): + class Real(object): + def method(self): pass + def side_effect(): + return sentinel.VALUE + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = side_effect + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.return_value = sentinel.VALUE + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value_and_side_effect(self): + # side_effect should always take precedence over return_value. + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] + mock.method.return_value = sentinel.WRONG_VALUE + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.VALUE2) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_return_value_and_side_effect2(self): + # side_effect can return DEFAULT to default to return_value + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = lambda: DEFAULT + mock.method.return_value = sentinel.VALUE + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value_and_side_effect_default(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, DEFAULT] + mock.method.return_value = sentinel.RETURN + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.RETURN) + self.assertRaises(StopIteration, mock.method) + + + def test_magic_method_wraps_dict(self): + # bpo-25597: MagicMock with wrap doesn't call wrapped object's + # method for magic methods with default values. + data = {'foo': 'bar'} + + wrapped_dict = MagicMock(wraps=data) + self.assertEqual(wrapped_dict.get('foo'), 'bar') + # Accessing key gives a MagicMock + self.assertIsInstance(wrapped_dict['foo'], MagicMock) + # __contains__ method has a default value of False + self.assertFalse('foo' in wrapped_dict) + + # return_value is non-sentinel and takes precedence over wrapped value. + wrapped_dict.get.return_value = 'return_value' + self.assertEqual(wrapped_dict.get('foo'), 'return_value') + + # return_value is sentinel and hence wrapped value is returned. + wrapped_dict.get.return_value = sentinel.DEFAULT + self.assertEqual(wrapped_dict.get('foo'), 'bar') + + self.assertEqual(wrapped_dict.get('baz'), None) + self.assertIsInstance(wrapped_dict['baz'], MagicMock) + self.assertFalse('bar' in wrapped_dict) + + data['baz'] = 'spam' + self.assertEqual(wrapped_dict.get('baz'), 'spam') + self.assertIsInstance(wrapped_dict['baz'], MagicMock) + self.assertFalse('bar' in wrapped_dict) + + del data['baz'] + self.assertEqual(wrapped_dict.get('baz'), None) + + + def test_magic_method_wraps_class(self): + + class Foo: + + def __getitem__(self, index): + return index + + def __custom_method__(self): + return "foo" + + + klass = MagicMock(wraps=Foo) + obj = klass() + self.assertEqual(obj.__getitem__(2), 2) + self.assertEqual(obj[2], 2) + self.assertEqual(obj.__custom_method__(), "foo") + + def test_exceptional_side_effect(self): mock = Mock(side_effect=AttributeError) self.assertRaises(AttributeError, mock) @@ -593,7 +925,7 @@ def test_baseexceptional_side_effect(self): def test_assert_called_with_message(self): mock = Mock() - self.assertRaisesRegex(AssertionError, 'Not called', + self.assertRaisesRegex(AssertionError, 'not called', mock.assert_called_with) @@ -642,6 +974,26 @@ class X(object): self.assertIsInstance(mock, X) + def test_spec_class_no_object_base(self): + class X: + pass + + mock = Mock(spec=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec=X()) + self.assertIsInstance(mock, X) + + self.assertIs(mock.__class__, X) + self.assertEqual(Mock().__class__.__name__, 'Mock') + + mock = Mock(spec_set=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec_set=X()) + self.assertIsInstance(mock, X) + + def test_setting_attribute_with_spec_set(self): class X(object): y = 3 @@ -667,42 +1019,6 @@ def test_copy(self): copy.copy(Mock()) - @unittest.skipIf(six.PY3, "no old style classes in Python 3") - def test_spec_old_style_classes(self): - class Foo: - bar = 7 - - mock = Mock(spec=Foo) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - mock = Mock(spec=Foo()) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - - @unittest.skipIf(six.PY3, "no old style classes in Python 3") - def test_spec_set_old_style_classes(self): - class Foo: - bar = 7 - - mock = Mock(spec_set=Foo) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - def _set(): - mock.foo = 3 - self.assertRaises(AttributeError, _set) - - mock = Mock(spec_set=Foo()) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - def _set(): - mock.foo = 3 - self.assertRaises(AttributeError, _set) - - def test_subclass_with_properties(self): class SubClass(Mock): def _get(self): @@ -726,6 +1042,7 @@ def test(): def test_setting_call(self): mock = Mock() def __call__(self, a): + self._increment_mock_call(a) return self._mock_call(a) type(mock).__call__ = __call__ @@ -784,6 +1101,15 @@ def test_filter_dir(self): patcher.stop() + def test_dir_does_not_include_deleted_attributes(self): + mock = Mock() + mock.child.return_value = 1 + + self.assertIn('child', dir(mock)) + del mock.child + self.assertNotIn('child', dir(mock)) + + def test_configure_mock(self): mock = Mock(foo='bar') self.assertEqual(mock.foo, 'bar') @@ -807,25 +1133,20 @@ def test_configure_mock(self): def assertRaisesWithMsg(self, exception, message, func, *args, **kwargs): # needed because assertRaisesRegex doesn't work easily with newlines - try: + with self.assertRaises(exception) as context: func(*args, **kwargs) - except: - instance = sys.exc_info()[1] - self.assertIsInstance(instance, exception) - else: - self.fail('Exception %r not raised' % (exception,)) - - msg = str(instance) + msg = str(context.exception) self.assertEqual(msg, message) def test_assert_called_with_failure_message(self): mock = NonCallableMock() + actual = 'not called.' expected = "mock(1, '2', 3, bar='foo')" - message = 'Expected call: %s\nNot called' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( - AssertionError, message % (expected,), + AssertionError, message % (expected, actual), mock.assert_called_with, 1, '2', 3, bar='foo' ) @@ -838,7 +1159,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(1, '2', 3, bar='foo')" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, 1, '2', 3, bar='foo' @@ -848,7 +1169,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(bar='foo')" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, bar='foo' @@ -858,7 +1179,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(1, 2, 3)" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, 1, 2, 3 @@ -868,7 +1189,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo()" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth ) @@ -952,6 +1273,69 @@ def test_mock_calls(self): call().__int__().call_list()) + def test_child_mock_call_equal(self): + m = Mock() + result = m() + result.wibble() + # parent looks like this: + self.assertEqual(m.mock_calls, [call(), call().wibble()]) + # but child should look like this: + self.assertEqual(result.mock_calls, [call.wibble()]) + + + def test_mock_call_not_equal_leaf(self): + m = Mock() + m.foo().something() + self.assertNotEqual(m.mock_calls[1], call.foo().different()) + self.assertEqual(m.mock_calls[0], call.foo()) + + + def test_mock_call_not_equal_non_leaf(self): + m = Mock() + m.foo().bar() + self.assertNotEqual(m.mock_calls[1], call.baz().bar()) + self.assertNotEqual(m.mock_calls[0], call.baz()) + + + def test_mock_call_not_equal_non_leaf_params_different(self): + m = Mock() + m.foo(x=1).bar() + # This isn't ideal, but there's no way to fix it without breaking backwards compatibility: + self.assertEqual(m.mock_calls[1], call.foo(x=2).bar()) + + + def test_mock_call_not_equal_non_leaf_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.baz.bar()) + + + def test_mock_call_not_equal_non_leaf_call_versus_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.foo().bar()) + + + def test_mock_call_repr(self): + m = Mock() + m.foo().bar().baz.bob() + self.assertEqual(repr(m.mock_calls[0]), 'call.foo()') + self.assertEqual(repr(m.mock_calls[1]), 'call.foo().bar()') + self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') + + + def test_mock_call_repr_loop(self): + m = Mock() + m.foo = m + repr(m.foo()) + self.assertRegex(repr(m.foo()), r"") + + + def test_mock_calls_contains(self): + m = Mock() + self.assertFalse([call()] in m.mock_calls) + + def test_subclassing(self): class Subclass(Mock): pass @@ -1010,9 +1394,8 @@ def test_call_args_two_tuple(self): mock(2, b=4) self.assertEqual(len(mock.call_args), 2) - args, kwargs = mock.call_args - self.assertEqual(args, (2,)) - self.assertEqual(kwargs, dict(b=4)) + self.assertEqual(mock.call_args.args, (2,)) + self.assertEqual(mock.call_args.kwargs, dict(b=4)) expected_list = [((1,), dict(a=3)), ((2,), dict(b=4))] for expected, call_args in zip(expected_list, mock.call_args_list): @@ -1045,6 +1428,16 @@ class Foo(object): self.assertRaises(StopIteration, mock) + def test_side_effect_iterator_exceptions(self): + for Klass in Mock, MagicMock: + iterable = (ValueError, 3, KeyError, 6) + m = Klass(side_effect=iterable) + self.assertRaises(ValueError, m) + self.assertEqual(m(), 3) + self.assertRaises(KeyError, m) + self.assertEqual(m(), 6) + + def test_side_effect_setting_iterator(self): mock = Mock() mock.side_effect = iter([1, 2, 3]) @@ -1066,17 +1459,6 @@ def test_side_effect_setting_iterator(self): self.assertRaises(StopIteration, mock) self.assertIs(mock.side_effect, this_iter) - - def test_side_effect_iterator_exceptions(self): - for Klass in Mock, MagicMock: - iterable = (ValueError, 3, KeyError, 6) - m = Klass(side_effect=iterable) - self.assertRaises(ValueError, m) - self.assertEqual(m(), 3) - self.assertRaises(KeyError, m) - self.assertEqual(m(), 6) - - def test_side_effect_iterator_default(self): mock = Mock(return_value=2) mock.side_effect = iter([1, DEFAULT]) @@ -1166,9 +1548,56 @@ def test_assert_has_calls(self): ) + def test_assert_has_calls_nested_spec(self): + class Something: + + def __init__(self): pass + def meth(self, a, b, c, d=None): pass + + class Foo: + + def __init__(self, a): pass + def meth1(self, a, b): pass + + mock_class = create_autospec(Something) + + for m in [mock_class, mock_class()]: + m.meth(1, 2, 3, d=1) + m.assert_has_calls([call.meth(1, 2, 3, d=1)]) + m.assert_has_calls([call.meth(1, 2, 3, 1)]) + + mock_class.reset_mock() + + for m in [mock_class, mock_class()]: + self.assertRaises(AssertionError, m.assert_has_calls, [call.Foo()]) + m.Foo(1).meth1(1, 2) + m.assert_has_calls([call.Foo(1), call.Foo(1).meth1(1, 2)]) + m.Foo.assert_has_calls([call(1), call().meth1(1, 2)]) + + mock_class.reset_mock() + + invalid_calls = [call.meth(1), + call.non_existent(1), + call.Foo().non_existent(1), + call.Foo().meth(1, 2, 3, 4)] + + for kall in invalid_calls: + self.assertRaises(AssertionError, + mock_class.assert_has_calls, + [kall] + ) + + + def test_assert_has_calls_nested_without_spec(self): + m = MagicMock() + m().foo().bar().baz() + m.one().two().three() + calls = call.one().two().three().call_list() + m.assert_has_calls(calls) + + def test_assert_has_calls_with_function_spec(self): - def f(a, b, c, d=None): - pass + def f(a, b, c, d=None): pass mock = Mock(spec=f) @@ -1198,6 +1627,49 @@ def f(a, b, c, d=None): mock.assert_has_calls(calls[:-1]) mock.assert_has_calls(calls[:-1], any_order=True) + def test_assert_has_calls_not_matching_spec_error(self): + def f(x=None): pass + + mock = Mock(spec=f) + mock(1) + + with self.assertRaises(AssertionError) as cm: + mock.assert_has_calls([call()]) + self.assertEqual(str(cm.exception), + 'Calls not found.\n' + 'Expected: [call()]\n' + ' Actual: [call(1)]' + ) + self.assertIsNone(cm.exception.__cause__) + + uncalled_mock = Mock() + with self.assertRaises(AssertionError) as cm: + uncalled_mock.assert_has_calls([call()]) + self.assertEqual(str(cm.exception), + 'Calls not found.\n' + 'Expected: [call()]\n' + ' Actual: []' + ) + self.assertIsNone(cm.exception.__cause__) + + with self.assertRaises(AssertionError) as cm: + mock.assert_has_calls([call(), call(1, 2)]) + if sys.version_info[:2] > (3, 6): + message = ( + 'Error processing expected calls.\n' + "Errors: [None, TypeError('too many positional arguments')]\n" + 'Expected: [call(), call(1, 2)]\n' + ' Actual: [call(1)]' + ) + else: + message = ( + 'Error processing expected calls.\n' + "Errors: [None, TypeError('too many positional arguments',)]\n" + 'Expected: [call(), call(1, 2)]\n' + ' Actual: [call(1)]' + ) + self.assertEqual(str(cm.exception), message) + self.assertIsInstance(cm.exception.__cause__, TypeError) def test_assert_any_call(self): mock = Mock() @@ -1226,8 +1698,7 @@ def test_assert_any_call(self): def test_assert_any_call_with_function_spec(self): - def f(a, b, c, d=None): - pass + def f(a, b, c, d=None): pass mock = Mock(spec=f) @@ -1242,13 +1713,11 @@ def f(a, b, c, d=None): # Expected call doesn't match the spec's signature with self.assertRaises(AssertionError) as cm: mock.assert_any_call(e=8) - if hasattr(cm.exception, '__cause__'): - self.assertIsInstance(cm.exception.__cause__, TypeError) + self.assertIsInstance(cm.exception.__cause__, TypeError) def test_mock_calls_create_autospec(self): - def f(a, b): - pass + def f(a, b): pass obj = Iter() obj.f = f @@ -1269,16 +1738,77 @@ def test_create_autospec_with_name(self): m = mock.create_autospec(object(), name='sweet_func') self.assertIn('sweet_func', repr(m)) + #Issue23078 + def test_create_autospec_classmethod_and_staticmethod(self): + class TestClass: + @classmethod + def class_method(cls): pass + + @staticmethod + def static_method(): pass + for method in ('class_method', 'static_method'): + with self.subTest(method=method): + mock_method = mock.create_autospec(getattr(TestClass, method)) + mock_method() + mock_method.assert_called_once_with() + self.assertRaises(TypeError, mock_method, 'extra_arg') + #Issue21238 def test_mock_unsafe(self): m = Mock() - with self.assertRaises(AttributeError): + msg = "is not a valid assertion. Use a spec for the mock" + with self.assertRaisesRegex(AttributeError, msg): m.assert_foo_call() - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, msg): m.assret_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.asert_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.aseert_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.assrt_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.called_once_with() + with self.assertRaisesRegex(AttributeError, msg): + m.called_once() + with self.assertRaisesRegex(AttributeError, msg): + m.has_calls() + + class Foo(object): + def called_once(self): pass + + def has_calls(self): pass + + m = Mock(spec=Foo) + m.called_once() + m.has_calls() + + m.called_once.assert_called_once() + m.has_calls.assert_called_once() + m = Mock(unsafe=True) m.assert_foo_call() m.assret_foo_call() + m.asert_foo_call() + m.aseert_foo_call() + m.assrt_foo_call() + m.called_once() + m.called_once_with() + m.has_calls() + + # gh-100739 + def test_mock_safe_with_spec(self): + class Foo(object): + def assert_bar(self): pass + + def assertSome(self): pass + + m = Mock(spec=Foo) + m.assert_bar() + m.assertSome() + + m.assert_bar.assert_called_once() + m.assertSome.assert_called_once() #Issue21262 def test_assert_not_called(self): @@ -1288,6 +1818,13 @@ def test_assert_not_called(self): with self.assertRaises(AssertionError): m.hello.assert_not_called() + def test_assert_not_called_message(self): + m = Mock() + m(1, 2) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1, 2)]"), + m.assert_not_called) + def test_assert_called(self): m = Mock() with self.assertRaises(AssertionError): @@ -1309,11 +1846,25 @@ def test_assert_called_once(self): with self.assertRaises(AssertionError): m.hello.assert_called_once() - #Issue21256 printout of keyword args should be in deterministic order - def test_sorted_call_signature(self): + def test_assert_called_once_message(self): + m = Mock() + m(1, 2) + m(3) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1, 2), call(3)]"), + m.assert_called_once) + + def test_assert_called_once_message_not_called(self): + m = Mock() + with self.assertRaises(AssertionError) as e: + m.assert_called_once() + self.assertNotIn("Calls:", str(e.exception)) + + #Issue37212 printout of keyword args now preserves the original order + def test_ordered_call_signature(self): m = Mock() m.hello(name='hello', daddy='hero') - text = "call(daddy='hero', name='hello')" + text = "call(name='hello', daddy='hero')" self.assertEqual(repr(m.hello.call_args), text) #Issue21270 overrides tuple methods for mock.call objects @@ -1326,6 +1877,36 @@ def test_override_tuple_methods(self): self.assertEqual(m.method_calls[0], c) self.assertEqual(m.method_calls[1], i) + def test_reset_return_sideeffect(self): + m = Mock(return_value=10, side_effect=[2,3]) + m.reset_mock(return_value=True, side_effect=True) + self.assertIsInstance(m.return_value, Mock) + self.assertEqual(m.side_effect, None) + + def test_reset_return(self): + m = Mock(return_value=10, side_effect=[2,3]) + m.reset_mock(return_value=True) + self.assertIsInstance(m.return_value, Mock) + self.assertNotEqual(m.side_effect, None) + + def test_reset_sideeffect(self): + m = Mock(return_value=10, side_effect=[2, 3]) + m.reset_mock(side_effect=True) + self.assertEqual(m.return_value, 10) + self.assertEqual(m.side_effect, None) + + def test_reset_return_with_children(self): + m = MagicMock(f=MagicMock(return_value=1)) + self.assertEqual(m.f(), 1) + m.reset_mock(return_value=True) + self.assertNotEqual(m.f(), 1) + + def test_reset_return_with_children_side_effect(self): + m = MagicMock(f=MagicMock(side_effect=[2, 3])) + self.assertNotEqual(m.f.side_effect, None) + m.reset_mock(side_effect=True) + self.assertEqual(m.f.side_effect, None) + def test_mock_add_spec(self): class _One(object): one = 1 @@ -1397,7 +1978,8 @@ def test_mock_add_spec_magic_methods(self): def test_adding_child_mock(self): - for Klass in NonCallableMock, Mock, MagicMock, NonCallableMagicMock: + for Klass in (NonCallableMock, Mock, MagicMock, NonCallableMagicMock, + AsyncMock): mock = Klass() mock.foo = Mock() @@ -1470,6 +2052,34 @@ def test_mock_open_reuse_issue_21750(self): f2_data = f2.read() self.assertEqual(f1_data, f2_data) + def test_mock_open_dunder_iter_issue(self): + # Test dunder_iter method generates the expected result and + # consumes the iterator. + mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue') + f1 = mocked_open('a-name') + lines = [line for line in f1] + self.assertEqual(lines[0], 'Remarkable\n') + self.assertEqual(lines[1], 'Norwegian Blue') + self.assertEqual(list(f1), []) + + def test_mock_open_using_next(self): + mocked_open = mock.mock_open(read_data='1st line\n2nd line\n3rd line') + f1 = mocked_open('a-name') + line1 = next(f1) + line2 = f1.__next__() + lines = [line for line in f1] + self.assertEqual(line1, '1st line\n') + self.assertEqual(line2, '2nd line\n') + self.assertEqual(lines[0], '3rd line') + self.assertEqual(list(f1), []) + with self.assertRaises(StopIteration): + next(f1) + + def test_mock_open_next_with_readline_with_return_value(self): + mopen = mock.mock_open(read_data='foo\nbarn') + mopen.return_value.readline.return_value = 'abc' + self.assertEqual('abc', next(mopen())) + def test_mock_open_write(self): # Test exception in file writing write() mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV')) @@ -1488,7 +2098,19 @@ def test_mock_open_alter_readline(self): second = mopen().readline() self.assertEqual('abc', first) self.assertEqual('abc', second) - + + def test_mock_open_after_eof(self): + # read, readline and readlines should work after end of file. + _open = mock.mock_open(read_data='foo') + h = _open('bar') + h.read() + self.assertEqual('', h.read()) + self.assertEqual('', h.read()) + self.assertEqual('', h.readline()) + self.assertEqual('', h.readline()) + self.assertEqual([], h.readlines()) + self.assertEqual([], h.readlines()) + def test_mock_parents(self): for Klass in Mock, MagicMock: m = Klass() @@ -1551,6 +2173,55 @@ def test_attach_mock_return_value(self): self.assertEqual(m.mock_calls, call().foo().call_list()) + def test_attach_mock_patch_autospec(self): + parent = Mock() + + with mock.patch(f'{__name__}.something', autospec=True) as mock_func: + self.assertEqual(mock_func.mock._extract_mock_name(), 'something') + parent.attach_mock(mock_func, 'child') + parent.child(1) + something(2) + mock_func(3) + + parent_calls = [call.child(1), call.child(2), call.child(3)] + child_calls = [call(1), call(2), call(3)] + self.assertEqual(parent.mock_calls, parent_calls) + self.assertEqual(parent.child.mock_calls, child_calls) + self.assertEqual(something.mock_calls, child_calls) + self.assertEqual(mock_func.mock_calls, child_calls) + self.assertIn('mock.child', repr(parent.child.mock)) + self.assertEqual(mock_func.mock._extract_mock_name(), 'mock.child') + + + def test_attach_mock_patch_autospec_signature(self): + with mock.patch(f'{__name__}.Something.meth', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_meth') + obj = Something() + obj.meth(1, 2, 3, d=4) + manager.assert_has_calls([call.attach_meth(mock.ANY, 1, 2, 3, d=4)]) + obj.meth.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) + mocked.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) + + with mock.patch(f'{__name__}.something', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_func') + something(1) + manager.assert_has_calls([call.attach_func(1)]) + something.assert_has_calls([call(1)]) + mocked.assert_has_calls([call(1)]) + + with mock.patch(f'{__name__}.Something', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_obj') + obj = Something() + obj.meth(1, 2, 3, d=4) + manager.assert_has_calls([call.attach_obj(), + call.attach_obj().meth(1, 2, 3, d=4)]) + obj.meth.assert_has_calls([call(1, 2, 3, d=4)]) + mocked.assert_has_calls([call(), call().meth(1, 2, 3, d=4)]) + + def test_attribute_deletion(self): for mock in (Mock(), MagicMock(), NonCallableMagicMock(), NonCallableMock()): @@ -1564,6 +2235,43 @@ def test_attribute_deletion(self): self.assertRaises(AttributeError, getattr, mock, 'f') + def test_mock_does_not_raise_on_repeated_attribute_deletion(self): + # bpo-20239: Assigning and deleting twice an attribute raises. + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): + mock.foo = 3 + self.assertTrue(hasattr(mock, 'foo')) + self.assertEqual(mock.foo, 3) + + del mock.foo + self.assertFalse(hasattr(mock, 'foo')) + + mock.foo = 4 + self.assertTrue(hasattr(mock, 'foo')) + self.assertEqual(mock.foo, 4) + + del mock.foo + self.assertFalse(hasattr(mock, 'foo')) + + + def test_mock_raises_when_deleting_nonexistent_attribute(self): + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): + del mock.foo + with self.assertRaises(AttributeError): + del mock.foo + + + def test_reset_mock_does_not_raise_on_attr_deletion(self): + # bpo-31177: reset_mock should not raise AttributeError when attributes + # were deleted in a mock instance + mock = Mock() + mock.child = True + del mock.child + mock.reset_mock() + self.assertFalse(hasattr(mock, 'child')) + + def test_class_assignable(self): for mock in Mock(), MagicMock(): self.assertNotIsInstance(mock, int) @@ -1572,22 +2280,165 @@ def test_class_assignable(self): self.assertIsInstance(mock, int) mock.foo + def test_name_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".name") + self.assertIsNotNone(call.name) + self.assertEqual(type(call.name), _Call) + self.assertEqual(type(call.name().name), _Call) + + def test_parent_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".parent") + self.assertIsNotNone(call.parent) + self.assertEqual(type(call.parent), _Call) + self.assertEqual(type(call.parent().parent), _Call) + + + def test_parent_propagation_with_create_autospec(self): + + def foo(a, b): pass + + mock = Mock() + mock.child = create_autospec(foo) + mock.child(1, 2) + + self.assertRaises(TypeError, mock.child, 1) + self.assertEqual(mock.mock_calls, [call.child(1, 2)]) + self.assertIn('mock.child', repr(mock.child.mock)) + + def test_parent_propagation_with_autospec_attach_mock(self): + + def foo(a, b): pass + + parent = Mock() + parent.attach_mock(create_autospec(foo, name='bar'), 'child') + parent.child(1, 2) + + self.assertRaises(TypeError, parent.child, 1) + self.assertEqual(parent.child.mock_calls, [call.child(1, 2)]) + self.assertIn('mock.child', repr(parent.child.mock)) + + + def test_isinstance_under_settrace(self): + # bpo-36593 : __class__ is not set for a class that has __class__ + # property defined when it's used with sys.settrace(trace) set. + # Delete the module to force reimport with tracing function set + # restore the old reference later since there are other tests that are + # dependent on unittest.mock.patch. In testpatch.PatchTest + # test_patch_dict_test_prefix and test_patch_test_prefix not restoring + # causes the objects patched to go out of sync + + old_patch = mock_module.patch + + # Directly using __setattr__ on unittest.mock causes current imported + # reference to be updated. Use a lambda so that during cleanup the + # re-imported new reference is updated. + self.addCleanup(lambda patch: setattr(mock_module, 'patch', patch), + old_patch) + + with patch.dict('sys.modules'): + del sys.modules['mock'] - @unittest.expectedFailure - def test_pickle(self): - for Klass in (MagicMock, Mock, Subclass, NonCallableMagicMock): - mock = Klass(name='foo', attribute=3) - mock.foo(1, 2, 3) - data = pickle.dumps(mock) - new = pickle.loads(data) + # This trace will stop coverage being measured ;-) + def trace(frame, event, arg): # pragma: no cover + return trace - new.foo.assert_called_once_with(1, 2, 3) - self.assertFalse(new.called) - self.assertTrue(is_instance(new, Klass)) - self.assertIsInstance(new, Thing) - self.assertIn('name="foo"', repr(new)) - self.assertEqual(new.attribute, 3) + self.addCleanup(sys.settrace, sys.gettrace()) + sys.settrace(trace) + from mock.mock import ( + Mock, MagicMock, NonCallableMock, NonCallableMagicMock + ) + + mocks = [ + Mock, MagicMock, NonCallableMock, NonCallableMagicMock, AsyncMock + ] + + for mock in mocks: + obj = mock(spec=Something) + self.assertIsInstance(obj, Something) + + def test_bool_not_called_when_passing_spec_arg(self): + class Something: + def __init__(self): + self.obj_with_bool_func = mock_module.MagicMock() + + obj = Something() + with mock_module.patch.object(obj, 'obj_with_bool_func', spec=object): pass + + self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0) + + def test_misspelled_arguments(self): + class Foo(): + one = 'one' + # patch, patch.object and create_autospec need to check for misspelled + # arguments explicitly and throw a RuntimeError if found. + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', autospect=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', autospect=True): pass + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', auto_spec=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', auto_spec=True): pass + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', set_spec=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', set_spec=True): pass + with self.assertRaises(RuntimeError): + m = create_autospec(Foo, set_spec=True) + # patch.multiple, on the other hand, should flag misspelled arguments + # through an AttributeError, when trying to find the keys from kwargs + # as attributes on the target. + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, autospect=True): pass + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, auto_spec=True): pass + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, set_spec=True): pass + + with patch(f'{__name__}.Something.meth', unsafe=True, autospect=True): + pass + with patch.object(Foo, 'one', unsafe=True, autospect=True): pass + with patch(f'{__name__}.Something.meth', unsafe=True, auto_spec=True): + pass + with patch.object(Foo, 'one', unsafe=True, auto_spec=True): pass + with patch(f'{__name__}.Something.meth', unsafe=True, set_spec=True): + pass + with patch.object(Foo, 'one', unsafe=True, set_spec=True): pass + m = create_autospec(Foo, set_spec=True, unsafe=True) + with patch.multiple( + f'{__name__}.Typos', autospect=True, set_spec=True, auto_spec=True): + pass + + def test_property_not_called_with_spec_mock(self): + obj = SomethingElse() + self.assertIsNone(obj._instance, msg='before mock') + mock = Mock(spec=obj) + self.assertIsNone(obj._instance, msg='after mock') + self.assertEqual('object', obj.instance) + + def test_decorated_async_methods_with_spec_mock(self): + class Foo(): + @classmethod + async def class_method(cls): + pass # pragma: no cover + @staticmethod + async def static_method(): + pass # pragma: no cover + async def method(self): + pass # pragma: no cover + mock = Mock(spec=Foo) + for m in (mock.method, mock.class_method, mock.static_method): + if sys.version_info[:2] <= (3, 9) and m is not mock.method: + # class and static methods need bugfixes in cpython to work: + self.assertIsInstance(m, Mock) + continue + self.assertIsInstance(m, AsyncMock) if __name__ == '__main__': unittest.main() diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 32a6c271..193b6ce7 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -4,24 +4,21 @@ import os import sys +from collections import OrderedDict -import six -import unittest2 as unittest - +import unittest from mock.tests import support -from mock.tests.support import SomeClass, is_instance, callable +from mock.tests.support import SomeClass, is_instance +from .support import uncache from mock import ( - NonCallableMock, CallableMixin, patch, sentinel, + NonCallableMock, sentinel, MagicMock, Mock, NonCallableMagicMock, patch, DEFAULT, call ) -from mock.mock import _patch, _get_target +from mock.mock import CallableMixin, _patch, _get_target -builtin_string = '__builtin__' -if six.PY3: - builtin_string = 'builtins' - unicode = str +builtin_string = 'builtins' PTModule = sys.modules[__name__] MODNAME = '%s.PTModule' % __name__ @@ -47,23 +44,24 @@ def __delattr__(self, name): class Foo(object): - def __init__(self, a): - pass - def f(self, a): - pass - def g(self): - pass + def __init__(self, a): pass + def f(self, a): pass + def g(self): pass foo = 'bar' + @staticmethod + def static_method(): pass + + @classmethod + def class_method(cls): pass + class Bar(object): - def a(self): - pass + def a(self): pass foo_name = '%s.Foo' % __name__ -def function(a, b=Foo): - pass +def function(a, b=Foo): pass class Container(object): @@ -108,6 +106,10 @@ def test(): self.assertEqual(Something.attribute, sentinel.Original, "patch not restored") + def test_patchobject_with_string_as_target(self): + msg = "'Something' must be the actual object to be patched, not a str" + with self.assertRaisesRegex(TypeError, msg): + patch.object('Something', 'do_something') def test_patchobject_with_none(self): class Something(object): @@ -366,31 +368,19 @@ def test(): def test_patch_wont_create_by_default(self): - try: + with self.assertRaises(AttributeError): @patch('%s.frooble' % builtin_string, sentinel.Frooble) - def test(): - self.assertEqual(frooble, sentinel.Frooble) + def test(): pass test() - except AttributeError: - pass - else: - self.fail('Patching non existent attributes should fail') - self.assertRaises(NameError, lambda: frooble) def test_patchobject_wont_create_by_default(self): - try: + with self.assertRaises(AttributeError): @patch.object(SomeClass, 'ord', sentinel.Frooble) - def test(): - self.fail('Patching non existent attributes should fail') - + def test(): pass test() - except AttributeError: - pass - else: - self.fail('Patching non existent attributes should fail') self.assertFalse(hasattr(SomeClass, 'ord')) @@ -480,6 +470,9 @@ class Something(object): attribute = sentinel.Original class Foo(object): + + test_class_attr = 'whatever' + def test_method(other_self, mock_something): self.assertEqual(PTModule.something, mock_something, "unpatched") @@ -631,6 +624,13 @@ def test(): self.assertEqual(foo.values, original) + def test_patch_dict_as_context_manager(self): + foo = {'a': 'b'} + with patch.dict(foo, a='c') as patched: + self.assertEqual(patched, {'a': 'c'}) + self.assertEqual(foo, {'a': 'b'}) + + def test_name_preserved(self): foo = {} @@ -638,8 +638,7 @@ def test_name_preserved(self): @patch('%s.SomeClass' % __name__, object(), autospec=True) @patch.object(SomeClass, object()) @patch.dict(foo) - def some_name(): - pass + def some_name(): pass self.assertEqual(some_name.__name__, 'some_name') @@ -650,12 +649,9 @@ def test_patch_with_exception(self): @patch.dict(foo, {'a': 'b'}) def test(): raise NameError('Konrad') - try: + + with self.assertRaises(NameError): test() - except NameError: - pass - else: - self.fail('NameError not raised by test') self.assertEqual(foo, {}) @@ -668,57 +664,31 @@ def test(): test() - @unittest.expectedFailure - def test_patch_descriptor(self): - # would be some effort to fix this - we could special case the - # builtin descriptors: classmethod, property, staticmethod - class Nothing(object): - foo = None + def test_patch_dict_decorator_resolution(self): + # bpo-35512: Ensure that patch with a string target resolves to + # the new dictionary during function call + original = support.target.copy() - class Something(object): - foo = {} - - @patch.object(Nothing, 'foo', 2) - @classmethod - def klass(cls): - self.assertIs(cls, Something) - - @patch.object(Nothing, 'foo', 2) - @staticmethod - def static(arg): - return arg - - @patch.dict(foo) - @classmethod - def klass_dict(cls): - self.assertIs(cls, Something) - - @patch.dict(foo) - @staticmethod - def static_dict(arg): - return arg - - # these will raise exceptions if patching descriptors is broken - self.assertEqual(Something.static('f00'), 'f00') - Something.klass() - self.assertEqual(Something.static_dict('f00'), 'f00') - Something.klass_dict() + @patch.dict('mock.tests.support.target', {'bar': 'BAR'}) + def test(): + self.assertEqual(support.target, {'foo': 'BAZ', 'bar': 'BAR'}) - something = Something() - self.assertEqual(something.static('f00'), 'f00') - something.klass() - self.assertEqual(something.static_dict('f00'), 'f00') - something.klass_dict() + try: + support.target = {'foo': 'BAZ'} + test() + self.assertEqual(support.target, {'foo': 'BAZ'}) + finally: + support.target = original def test_patch_spec_set(self): - @patch('%s.SomeClass' % __name__, spec_set=SomeClass) + @patch('%s.SomeClass' % __name__, spec=SomeClass, spec_set=True) def test(MockClass): MockClass.z = 'foo' self.assertRaises(AttributeError, test) - @patch.object(support, 'SomeClass', spec_set=SomeClass) + @patch.object(support, 'SomeClass', spec=SomeClass, spec_set=True) def test(MockClass): MockClass.z = 'foo' @@ -759,10 +729,66 @@ def test_patch_start_stop(self): def test_stop_without_start(self): + # bpo-36366: calling stop without start will return None. + patcher = patch(foo_name, 'bar', 3) + self.assertIsNone(patcher.stop()) + + + def test_stop_idempotent(self): + # bpo-36366: calling stop on an already stopped patch will return None. patcher = patch(foo_name, 'bar', 3) - # calling stop without start used to produce a very obscure error - self.assertRaises(RuntimeError, patcher.stop) + patcher.start() + patcher.stop() + self.assertIsNone(patcher.stop()) + + + def test_exit_idempotent(self): + patcher = patch(foo_name, 'bar', 3) + with patcher: + patcher.__exit__(None, None, None) + + + def test_second_start_failure(self): + patcher = patch(foo_name, 'bar', 3) + patcher.start() + try: + self.assertRaises(RuntimeError, patcher.start) + finally: + patcher.stop() + + + def test_second_enter_failure(self): + patcher = patch(foo_name, 'bar', 3) + with patcher: + self.assertRaises(RuntimeError, patcher.start) + + + def test_second_start_after_stop(self): + patcher = patch(foo_name, 'bar', 3) + patcher.start() + patcher.stop() + patcher.start() + patcher.stop() + + + def test_property_setters(self): + mock_object = Mock() + mock_bar = mock_object.bar + patcher = patch.object(mock_object, 'bar', 'x') + with patcher: + self.assertEqual(patcher.is_local, False) + self.assertIs(patcher.target, mock_object) + self.assertEqual(patcher.temp_original, mock_bar) + patcher.is_local = True + patcher.target = mock_bar + patcher.temp_original = mock_object + self.assertEqual(patcher.is_local, True) + self.assertIs(patcher.target, mock_bar) + self.assertEqual(patcher.temp_original, mock_object) + # if changes are left intact, they may lead to disruption as shown below (it might be what someone needs though) + self.assertEqual(mock_bar.bar, mock_object) + self.assertEqual(mock_object.bar, 'x') def test_patchobject_start_stop(self): @@ -792,6 +818,14 @@ def test_patch_dict_start_stop(self): self.assertEqual(d, original) + def test_patch_dict_stop_without_start(self): + d = {'foo': 'bar'} + original = d.copy() + patcher = patch.dict(d, [('spam', 'eggs')], clear=True) + self.assertFalse(patcher.stop()) + self.assertEqual(d, original) + + def test_patch_dict_class_decorator(self): this = self d = {'spam': 'eggs'} @@ -902,17 +936,13 @@ def test_patch_dict_keyword_args(self): def test_autospec(self): class Boo(object): - def __init__(self, a): - pass - def f(self, a): - pass - def g(self): - pass + def __init__(self, a): pass + def f(self, a): pass + def g(self): pass foo = 'bar' class Bar(object): - def a(self): - pass + def a(self): pass def _test(mock): mock(1) @@ -974,8 +1004,14 @@ def function(mock): def test_autospec_function(self): @patch('%s.function' % __name__, autospec=True) def test(mock): + function.assert_not_called() + self.assertRaises(AssertionError, function.assert_called) + self.assertRaises(AssertionError, function.assert_called_once) function(1) + self.assertRaises(AssertionError, function.assert_not_called) function.assert_called_with(1) + function.assert_called() + function.assert_called_once() function(2, 3) function.assert_called_with(2, 3) @@ -996,6 +1032,48 @@ def test(mock_function): self.assertEqual(result, 3) + def test_autospec_staticmethod(self): + with patch('%s.Foo.static_method' % __name__, autospec=True) as method: + Foo.static_method() + method.assert_called_once_with() + + + def test_autospec_classmethod(self): + with patch('%s.Foo.class_method' % __name__, autospec=True) as method: + Foo.class_method() + method.assert_called_once_with() + + + def test_autospec_staticmethod_signature(self): + # Patched methods which are decorated with @staticmethod should have the same signature + class Foo: + @staticmethod + def static_method(a, b=10, *, c): pass + + Foo.static_method(1, 2, c=3) + + with patch.object(Foo, 'static_method', autospec=True) as method: + method(1, 2, c=3) + self.assertRaises(TypeError, method) + self.assertRaises(TypeError, method, 1) + self.assertRaises(TypeError, method, 1, 2, 3, c=4) + + + def test_autospec_classmethod_signature(self): + # Patched methods which are decorated with @classmethod should have the same signature + class Foo: + @classmethod + def class_method(cls, a, b=10, *, c): pass + + Foo.class_method(1, 2, c=3) + + with patch.object(Foo, 'class_method', autospec=True) as method: + method(1, 2, c=3) + self.assertRaises(TypeError, method) + self.assertRaises(TypeError, method, 1) + self.assertRaises(TypeError, method, 1, 2, 3, c=4) + + def test_autospec_with_new(self): patcher = patch('%s.function' % __name__, new=3, autospec=True) self.assertRaises(TypeError, patcher.start) @@ -1066,7 +1144,7 @@ def test_new_callable_patch(self): self.assertIsNot(m1, m2) for mock in m1, m2: - self.assertNotCallable(m1) + self.assertNotCallable(mock) def test_new_callable_patch_object(self): @@ -1079,7 +1157,7 @@ def test_new_callable_patch_object(self): self.assertIsNot(m1, m2) for mock in m1, m2: - self.assertNotCallable(m1) + self.assertNotCallable(mock) def test_new_callable_keyword_arguments(self): @@ -1265,7 +1343,6 @@ def test(f, foo): def test_patch_multiple_create_mocks_different_order(self): - # bug revealed by Jython! original_f = Foo.f original_g = Foo.g @@ -1442,20 +1519,17 @@ def test_nested_patch_failure(self): @patch.object(Foo, 'g', 1) @patch.object(Foo, 'missing', 1) @patch.object(Foo, 'f', 1) - def thing1(): - pass + def thing1(): pass @patch.object(Foo, 'missing', 1) @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) - def thing2(): - pass + def thing2(): pass @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) @patch.object(Foo, 'missing', 1) - def thing3(): - pass + def thing3(): pass for func in thing1, thing2, thing3: self.assertRaises(AttributeError, func) @@ -1474,20 +1548,17 @@ def crasher(): @patch.object(Foo, 'g', 1) @patch.object(Foo, 'foo', new_callable=crasher) @patch.object(Foo, 'f', 1) - def thing1(): - pass + def thing1(): pass @patch.object(Foo, 'foo', new_callable=crasher) @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) - def thing2(): - pass + def thing2(): pass @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) @patch.object(Foo, 'foo', new_callable=crasher) - def thing3(): - pass + def thing3(): pass for func in thing1, thing2, thing3: self.assertRaises(NameError, func) @@ -1513,8 +1584,7 @@ def test_patch_multiple_failure(self): patcher.additional_patchers = additionals @patcher - def func(): - pass + def func(): pass self.assertRaises(AttributeError, func) self.assertEqual(Foo.f, original_f) @@ -1542,8 +1612,7 @@ def crasher(): patcher.additional_patchers = additionals @patcher - def func(): - pass + def func(): pass self.assertRaises(NameError, func) self.assertEqual(Foo.f, original_f) @@ -1552,15 +1621,14 @@ def func(): def test_patch_multiple_string_subclasses(self): - for base in (str, unicode): - Foo = type('Foo', (base,), {'fish': 'tasty'}) - foo = Foo() - @patch.multiple(foo, fish='nearly gone') - def test(): - self.assertEqual(foo.fish, 'nearly gone') + Foo = type('Foo', (str,), {'fish': 'tasty'}) + foo = Foo() + @patch.multiple(foo, fish='nearly gone') + def test(): + self.assertEqual(foo.fish, 'nearly gone') - test() - self.assertEqual(foo.fish, 'tasty') + test() + self.assertEqual(foo.fish, 'tasty') @patch('mock.patch.TEST_PREFIX', 'foo') @@ -1624,15 +1692,12 @@ def test_patch_with_spec_mock_repr(self): def test_patch_nested_autospec_repr(self): - p = patch('mock.tests.support', autospec=True) - m = p.start() - try: + with patch('mock.tests.support', autospec=True) as m: self.assertIn(" name='support.SomeClass.wibble()'", repr(m.SomeClass.wibble())) self.assertIn(" name='support.SomeClass().wibble()'", repr(m.SomeClass().wibble())) - finally: - p.stop() + def test_mock_calls_with_patch(self): @@ -1663,22 +1728,21 @@ def test_mock_calls_with_patch(self): def test_patch_imports_lazily(self): - sys.modules.pop('squizz', None) - p1 = patch('squizz.squozz') self.assertRaises(ImportError, p1.start) - squizz = Mock() - squizz.squozz = 6 - sys.modules['squizz'] = squizz - p1 = patch('squizz.squozz') - squizz.squozz = 3 - p1.start() - p1.stop() - self.assertEqual(squizz.squozz, 3) + with uncache('squizz'): + squizz = Mock() + sys.modules['squizz'] = squizz + squizz.squozz = 6 + p1 = patch('squizz.squozz') + squizz.squozz = 3 + p1.start() + p1.stop() + self.assertEqual(squizz.squozz, 3) - def test_patch_propogrates_exc_on_exit(self): + def test_patch_propagates_exc_on_exit(self): class holder: exc_info = None, None, None @@ -1699,12 +1763,17 @@ def with_custom_patch(target): def test(mock): raise RuntimeError - self.assertRaises(RuntimeError, test) + with uncache('squizz'): + squizz = Mock() + sys.modules['squizz'] = squizz + + self.assertRaises(RuntimeError, test) + self.assertIs(holder.exc_info[0], RuntimeError) self.assertIsNotNone(holder.exc_info[1], - 'exception value not propgated') + 'exception value not propagated') self.assertIsNotNone(holder.exc_info[2], - 'exception traceback not propgated') + 'exception traceback not propagated') def test_create_and_specs(self): @@ -1808,32 +1877,6 @@ def patched(mock_path): patched() self.assertIs(os.path, path) - - def test_wrapped_patch(self): - decorated = patch('sys.modules')(function) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_several_times_patch(self): - decorated = patch('sys.modules')(function) - decorated = patch('sys.modules')(decorated) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_patch_object(self): - decorated = patch.object(sys, 'modules')(function) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_patch_dict(self): - decorated = patch.dict('sys.modules')(function) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_patch_multiple(self): - decorated = patch.multiple('sys', modules={})(function) - self.assertIs(decorated.__wrapped__, function) - def test_stopall_lifo(self): stopped = [] class thing(object): @@ -1851,6 +1894,56 @@ def stop(self): self.assertEqual(stopped, ["three", "two", "one"]) + def test_patch_dict_stopall(self): + dic1 = {} + dic2 = {1: 'a'} + dic3 = {1: 'A', 2: 'B'} + origdic1 = dic1.copy() + origdic2 = dic2.copy() + origdic3 = dic3.copy() + patch.dict(dic1, {1: 'I', 2: 'II'}).start() + patch.dict(dic2, {2: 'b'}).start() + + @patch.dict(dic3) + def patched(): + del dic3[1] + + patched() + self.assertNotEqual(dic1, origdic1) + self.assertNotEqual(dic2, origdic2) + self.assertEqual(dic3, origdic3) + + patch.stopall() + + self.assertEqual(dic1, origdic1) + self.assertEqual(dic2, origdic2) + self.assertEqual(dic3, origdic3) + + + def test_patch_and_patch_dict_stopall(self): + original_unlink = os.unlink + original_chdir = os.chdir + dic1 = {} + dic2 = {1: 'A', 2: 'B'} + origdic1 = dic1.copy() + origdic2 = dic2.copy() + + patch('os.unlink', something).start() + patch('os.chdir', something_else).start() + patch.dict(dic1, {1: 'I', 2: 'II'}).start() + patch.dict(dic2).start() + del dic2[1] + + self.assertIsNot(os.unlink, original_unlink) + self.assertIsNot(os.chdir, original_chdir) + self.assertNotEqual(dic1, origdic1) + self.assertNotEqual(dic2, origdic2) + patch.stopall() + self.assertIs(os.unlink, original_unlink) + self.assertIs(os.chdir, original_chdir) + self.assertEqual(dic1, origdic1) + self.assertEqual(dic2, origdic2) + def test_special_attrs(self): def foo(x=0): @@ -1860,24 +1953,86 @@ def foo(x=0): self.assertEqual(foo(), 1) self.assertEqual(foo(), 0) + orig_doc = foo.__doc__ with patch.object(foo, '__doc__', "FUN"): self.assertEqual(foo.__doc__, "FUN") - self.assertEqual(foo.__doc__, "TEST") + self.assertEqual(foo.__doc__, orig_doc) with patch.object(foo, '__module__', "testpatch2"): self.assertEqual(foo.__module__, "testpatch2") self.assertEqual(foo.__module__, __name__) - if hasattr(self.test_special_attrs, '__annotations__'): - with patch.object(foo, '__annotations__', dict([('s', 1, )])): - self.assertEqual(foo.__annotations__, dict([('s', 1, )])) - self.assertEqual(foo.__annotations__, dict()) + with patch.object(foo, '__annotations__', dict([('s', 1, )])): + self.assertEqual(foo.__annotations__, dict([('s', 1, )])) + self.assertEqual(foo.__annotations__, dict()) + + def foo(*a, x=0): + return x + with patch.object(foo, '__kwdefaults__', dict([('x', 1, )])): + self.assertEqual(foo(), 1) + self.assertEqual(foo(), 0) + + def test_patch_orderdict(self): + foo = OrderedDict() + foo['a'] = object() + foo['b'] = 'python' + + original = foo.copy() + update_values = list(zip('cdefghijklmnopqrstuvwxyz', range(26))) + patched_values = list(foo.items()) + update_values + + with patch.dict(foo, OrderedDict(update_values)): + self.assertEqual(list(foo.items()), patched_values) + + self.assertEqual(foo, original) + + with patch.dict(foo, update_values): + self.assertEqual(list(foo.items()), patched_values) + + self.assertEqual(foo, original) + + def test_dotted_but_module_not_loaded(self): + # This exercises the AttributeError branch of _dot_lookup. + + # make sure it's there + import mock.tests.support + # now make sure it's not: + with patch.dict('sys.modules'): + del sys.modules['mock.tests.support'] + del sys.modules['mock.tests'] + del sys.modules['mock.mock'] + del sys.modules['mock'] + + # now make sure we can patch based on a dotted path: + @patch('mock.tests.support.X') + def test(mock): + pass + test() + + + def test_invalid_target(self): + class Foo: + pass + + for target in ['', 12, Foo()]: + with self.subTest(target=target): + with self.assertRaises(TypeError): + patch(target) + + + def test_cant_set_kwargs_when_passing_a_mock(self): + @patch('mock.tests.support.X', new=object(), x=1) + def test(): pass + with self.assertRaises(TypeError): + test() + + def test_patch_proxy_object(self): + @patch("mock.tests.support.g", new_callable=MagicMock()) + def test(_): + pass + + test() - if hasattr(self.test_special_attrs, '__kwdefaults__'): - foo = eval("lambda *a, x=0: x") - with patch.object(foo, '__kwdefaults__', dict([('x', 1, )])): - self.assertEqual(foo(), 1) - self.assertEqual(foo(), 0) if __name__ == '__main__': unittest.main() diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py new file mode 100644 index 00000000..5e09b28a --- /dev/null +++ b/mock/tests/testsealable.py @@ -0,0 +1,237 @@ +import unittest +import mock + + +class SampleObject: + + def method_sample1(self): pass + + def method_sample2(self): pass + + +class TestSealable(unittest.TestCase): + + def test_attributes_return_more_mocks_by_default(self): + m = mock.Mock() + + self.assertIsInstance(m.test, mock.Mock) + self.assertIsInstance(m.test(), mock.Mock) + self.assertIsInstance(m.test().test2(), mock.Mock) + + def test_new_attributes_cannot_be_accessed_on_seal(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test + with self.assertRaises(AttributeError): + m() + + def test_new_attributes_cannot_be_set_on_seal(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test = 1 + + def test_existing_attributes_can_be_set_on_seal(self): + m = mock.Mock() + m.test.test2 = 1 + + mock.seal(m) + m.test.test2 = 2 + self.assertEqual(m.test.test2, 2) + + def test_new_attributes_cannot_be_set_on_child_of_seal(self): + m = mock.Mock() + m.test.test2 = 1 + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test.test3 = 1 + + def test_existing_attributes_allowed_after_seal(self): + m = mock.Mock() + + m.test.return_value = 3 + + mock.seal(m) + self.assertEqual(m.test(), 3) + + def test_initialized_attributes_allowed_after_seal(self): + m = mock.Mock(test_value=1) + + mock.seal(m) + self.assertEqual(m.test_value, 1) + + def test_call_on_sealed_mock_fails(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m() + + def test_call_on_defined_sealed_mock_succeeds(self): + m = mock.Mock(return_value=5) + + mock.seal(m) + self.assertEqual(m(), 5) + + def test_seals_recurse_on_added_attributes(self): + m = mock.Mock() + + m.test1.test2().test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2().test3, 4) + with self.assertRaises(AttributeError): + m.test1.test2().test4 + with self.assertRaises(AttributeError): + m.test1.test3 + + def test_seals_recurse_on_magic_methods(self): + m = mock.MagicMock() + + m.test1.test2["a"].test3 = 4 + m.test1.test3[2:5].test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2["a"].test3, 4) + self.assertEqual(m.test1.test2[2:5].test3, 4) + with self.assertRaises(AttributeError): + m.test1.test2["a"].test4 + with self.assertRaises(AttributeError): + m.test1.test3[2:5].test4 + + def test_seals_dont_recurse_on_manual_attributes(self): + m = mock.Mock(name="root_mock") + + m.test1.test2 = mock.Mock(name="not_sealed") + m.test1.test2.test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2.test3, 4) + m.test1.test2.test4 # Does not raise + m.test1.test2.test4 = 1 # Does not raise + + def test_integration_with_spec_att_definition(self): + """You are not restricted when using mock with spec""" + m = mock.Mock(SampleObject) + + m.attr_sample1 = 1 + m.attr_sample3 = 3 + + mock.seal(m) + self.assertEqual(m.attr_sample1, 1) + self.assertEqual(m.attr_sample3, 3) + with self.assertRaises(AttributeError): + m.attr_sample2 + + def test_integration_with_spec_method_definition(self): + """You need to define the methods, even if they are in the spec""" + m = mock.Mock(SampleObject) + + m.method_sample1.return_value = 1 + + mock.seal(m) + self.assertEqual(m.method_sample1(), 1) + with self.assertRaises(AttributeError): + m.method_sample2() + + def test_integration_with_spec_method_definition_respects_spec(self): + """You cannot define methods out of the spec""" + m = mock.Mock(SampleObject) + + with self.assertRaises(AttributeError): + m.method_sample3.return_value = 3 + + def test_sealed_exception_has_attribute_name(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.SECRETE_name + self.assertIn("SECRETE_name", str(cm.exception)) + + def test_attribute_chain_is_maintained(self): + m = mock.Mock(name="mock_name") + m.test1.test2.test3.test4 + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.test1.test2.test3.test4.boom + self.assertIn("mock_name.test1.test2.test3.test4.boom", str(cm.exception)) + + def test_call_chain_is_maintained(self): + m = mock.Mock() + m.test1().test2.test3().test4 + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.test1().test2.test3().test4() + self.assertIn("mock.test1().test2.test3().test4", str(cm.exception)) + + def test_seal_with_autospec(self): + # https://bugs.python.org/issue45156 + class Foo: + foo = 0 + def bar1(self): pass + def bar2(self): pass + + class Baz: + baz = 3 + def ban(self): pass + + for spec_set in (True, False): + with self.subTest(spec_set=spec_set): + foo = mock.create_autospec(Foo, spec_set=spec_set) + foo.bar1.return_value = 'a' + foo.Baz.ban.return_value = 'b' + + mock.seal(foo) + + self.assertIsInstance(foo.foo, mock.NonCallableMagicMock) + self.assertIsInstance(foo.bar1, mock.MagicMock) + self.assertIsInstance(foo.bar2, mock.MagicMock) + self.assertIsInstance(foo.Baz, mock.MagicMock) + self.assertIsInstance(foo.Baz.baz, mock.NonCallableMagicMock) + self.assertIsInstance(foo.Baz.ban, mock.MagicMock) + + # see gh-91803 + self.assertIsInstance(foo.bar2(), mock.MagicMock) + + self.assertEqual(foo.bar1(), 'a') + foo.bar1.return_value = 'new_a' + self.assertEqual(foo.bar1(), 'new_a') + self.assertEqual(foo.Baz.ban(), 'b') + foo.Baz.ban.return_value = 'new_b' + self.assertEqual(foo.Baz.ban(), 'new_b') + + with self.assertRaises(TypeError): + foo.foo() + with self.assertRaises(AttributeError): + foo.bar = 1 + with self.assertRaises(AttributeError): + foo.bar2().x + + foo.bar2.return_value = 'bar2' + self.assertEqual(foo.bar2(), 'bar2') + + with self.assertRaises(AttributeError): + foo.missing_attr + with self.assertRaises(AttributeError): + foo.missing_attr = 1 + with self.assertRaises(AttributeError): + foo.missing_method() + with self.assertRaises(TypeError): + foo.Baz.baz() + with self.assertRaises(AttributeError): + foo.Baz.missing_attr + with self.assertRaises(AttributeError): + foo.Baz.missing_attr = 1 + with self.assertRaises(AttributeError): + foo.Baz.missing_method() + + +if __name__ == "__main__": + unittest.main() diff --git a/mock/tests/testsentinel.py b/mock/tests/testsentinel.py index 69b20427..56664341 100644 --- a/mock/tests/testsentinel.py +++ b/mock/tests/testsentinel.py @@ -1,9 +1,6 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - -import unittest2 as unittest - +import unittest +import copy +import pickle from mock import sentinel, DEFAULT @@ -28,6 +25,17 @@ def testBases(self): # If this doesn't raise an AttributeError then help(mock) is broken self.assertRaises(AttributeError, lambda: sentinel.__bases__) + def testPickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL+1): + with self.subTest(protocol=proto): + pickled = pickle.dumps(sentinel.whatever, proto) + unpickled = pickle.loads(pickled) + self.assertIs(unpickled, sentinel.whatever) + + def testCopy(self): + self.assertIs(copy.copy(sentinel.whatever), sentinel.whatever) + self.assertIs(copy.deepcopy(sentinel.whatever), sentinel.whatever) + if __name__ == '__main__': unittest.main() diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py new file mode 100644 index 00000000..d65834d4 --- /dev/null +++ b/mock/tests/testthreadingmock.py @@ -0,0 +1,197 @@ +import time +import unittest +import concurrent.futures + +from mock import patch, ThreadingMock + +VERY_SHORT_TIMEOUT = 0.1 + + +class Something: + def method_1(self): + pass # pragma: no cover + + def method_2(self): + pass # pragma: no cover + + +class TestThreadingMock(unittest.TestCase): + def _call_after_delay(self, func, *args, **kwargs): + time.sleep(kwargs.pop("delay")) + func(*args, **kwargs) + + def setUp(self): + self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=5) + + def tearDown(self): + self._executor.shutdown() + + def run_async(self, func, *args, delay=0, **kwargs): + self._executor.submit( + self._call_after_delay, func, *args, **kwargs, delay=delay + ) + + def _make_mock(self, *args, **kwargs): + return ThreadingMock(*args, **kwargs) + + def test_spec(self): + waitable_mock = self._make_mock(spec=Something) + + with patch(f"{__name__}.Something", waitable_mock) as m: + something = m() + + self.assertIsInstance(something.method_1, ThreadingMock) + self.assertIsInstance(something.method_1().method_2(), ThreadingMock) + + with self.assertRaises(AttributeError): + m.test + + def test_side_effect(self): + waitable_mock = self._make_mock() + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + something.method_1.side_effect = [1] + + self.assertEqual(something.method_1(), 1) + + def test_instance_check(self): + waitable_mock = self._make_mock() + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + + self.assertIsInstance(something.method_1, ThreadingMock) + self.assertIsInstance(something.method_1().method_2(), ThreadingMock) + + def test_dynamic_child_mocks_are_threading_mocks(self): + waitable_mock = self._make_mock() + self.assertIsInstance(waitable_mock.child, ThreadingMock) + + def test_dynamic_child_mocks_inherit_timeout(self): + mock1 = self._make_mock() + self.assertIs(mock1._mock_wait_timeout, None) + mock2 = self._make_mock(timeout=2) + self.assertEqual(mock2._mock_wait_timeout, 2) + mock3 = self._make_mock(timeout=3) + self.assertEqual(mock3._mock_wait_timeout, 3) + + self.assertIs(mock1.child._mock_wait_timeout, None) + self.assertEqual(mock2.child._mock_wait_timeout, 2) + self.assertEqual(mock3.child._mock_wait_timeout, 3) + + self.assertEqual(mock2.really().__mul__().complex._mock_wait_timeout, 2) + + def test_no_name_clash(self): + waitable_mock = self._make_mock() + waitable_mock._event = "myevent" + waitable_mock.event = "myevent" + waitable_mock.timeout = "mytimeout" + waitable_mock("works") + waitable_mock.wait_until_called() + waitable_mock.wait_until_any_call_with("works") + + def test_patch(self): + waitable_mock = self._make_mock(spec=Something) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + something.method_1() + something.method_1.wait_until_called() + + def test_wait_already_called_success(self): + waitable_mock = self._make_mock(spec=Something) + waitable_mock.method_1() + waitable_mock.method_1.wait_until_called() + waitable_mock.method_1.wait_until_any_call_with() + waitable_mock.method_1.assert_called() + + def test_wait_until_called_success(self): + waitable_mock = self._make_mock(spec=Something) + self.run_async(waitable_mock.method_1, delay=VERY_SHORT_TIMEOUT) + waitable_mock.method_1.wait_until_called() + + def test_wait_until_called_method_timeout(self): + waitable_mock = self._make_mock(spec=Something) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_called(timeout=VERY_SHORT_TIMEOUT) + + def test_wait_until_called_instance_timeout(self): + waitable_mock = self._make_mock(spec=Something, timeout=VERY_SHORT_TIMEOUT) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_called() + + def test_wait_until_called_global_timeout(self): + with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): + ThreadingMock.DEFAULT_TIMEOUT = VERY_SHORT_TIMEOUT + waitable_mock = self._make_mock(spec=Something) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_called() + + def test_wait_until_any_call_with_success(self): + waitable_mock = self._make_mock() + self.run_async(waitable_mock, delay=VERY_SHORT_TIMEOUT) + waitable_mock.wait_until_any_call_with() + + def test_wait_until_any_call_with_instance_timeout(self): + waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT) + with self.assertRaises(AssertionError): + waitable_mock.wait_until_any_call_with() + + def test_wait_until_any_call_global_timeout(self): + with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): + ThreadingMock.DEFAULT_TIMEOUT = VERY_SHORT_TIMEOUT + waitable_mock = self._make_mock() + with self.assertRaises(AssertionError): + waitable_mock.wait_until_any_call_with() + + def test_wait_until_any_call_positional(self): + waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT) + waitable_mock.method_1(1, 2, 3) + waitable_mock.method_1.wait_until_any_call_with(1, 2, 3) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with(2, 3, 1) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with() + + def test_wait_until_any_call_kw(self): + waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT) + waitable_mock.method_1(a=1, b=2) + waitable_mock.method_1.wait_until_any_call_with(a=1, b=2) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with(a=2, b=1) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with() + + def test_magic_methods_success(self): + waitable_mock = self._make_mock() + str(waitable_mock) + waitable_mock.__str__.wait_until_called() + waitable_mock.__str__.assert_called() + + def test_reset_mock_resets_wait(self): + m = self._make_mock(timeout=VERY_SHORT_TIMEOUT) + + with self.assertRaises(AssertionError): + m.wait_until_called() + with self.assertRaises(AssertionError): + m.wait_until_any_call_with() + m() + m.wait_until_called() + m.wait_until_any_call_with() + m.assert_called_once() + + m.reset_mock() + + with self.assertRaises(AssertionError): + m.wait_until_called() + with self.assertRaises(AssertionError): + m.wait_until_any_call_with() + m() + m.wait_until_called() + m.wait_until_any_call_with() + m.assert_called_once() + + +if __name__ == "__main__": + unittest.main() diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index aa7812b3..26c63a2d 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -1,19 +1,17 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - +import unittest from warnings import catch_warnings -import unittest2 as unittest - from mock.tests.support import is_instance from mock import MagicMock, Mock, patch, sentinel, mock_open, call + something = sentinel.Something something_else = sentinel.SomethingElse +class SampleException(Exception): pass + class WithTest(unittest.TestCase): @@ -24,14 +22,10 @@ def test_with_statement(self): def test_with_statement_exception(self): - try: + with self.assertRaises(SampleException): with patch('%s.something' % __name__, sentinel.Something2): self.assertEqual(something, sentinel.Something2, "unpatched") - raise Exception('pow') - except Exception: - pass - else: - self.fail("patch swallowed exception") + raise SampleException() self.assertEqual(something, sentinel.Something) @@ -54,11 +48,10 @@ class Foo(object): def test_with_statement_nested(self): with catch_warnings(record=True): - with patch('%s.something' % __name__) as mock_something: - with patch('%s.something_else' % __name__) as mock_something_else: - self.assertEqual(something, mock_something, "unpatched") - self.assertEqual(something_else, mock_something_else, - "unpatched") + with patch('%s.something' % __name__) as mock_something, patch('%s.something_else' % __name__) as mock_something_else: + self.assertEqual(something, mock_something, "unpatched") + self.assertEqual(something_else, mock_something_else, + "unpatched") self.assertEqual(something, sentinel.Something) self.assertEqual(something_else, sentinel.SomethingElse) @@ -131,6 +124,19 @@ def test_dict_context_manager(self): self.assertEqual(foo, {}) + def test_double_patch_instance_method(self): + class C: + def f(self): pass + + c = C() + + with patch.object(c, 'f') as patch1: + with patch.object(c, 'f') as patch2: + c.f() + self.assertEqual(patch2.call_count, 1) + self.assertEqual(patch1.call_count, 0) + c.f() + self.assertEqual(patch1.call_count, 1) class TestMockOpen(unittest.TestCase): @@ -152,7 +158,7 @@ def test_mock_open_context_manager(self): f.read() expected_calls = [call('foo'), call().__enter__(), call().read(), - call().__exit__(None, None, None)] + call().__exit__(None, None, None), call().close()] self.assertEqual(mock.mock_calls, expected_calls) self.assertIs(f, handle) @@ -166,9 +172,9 @@ def test_mock_open_context_manager_multiple_times(self): expected_calls = [ call('foo'), call().__enter__(), call().read(), - call().__exit__(None, None, None), + call().__exit__(None, None, None), call().close(), call('bar'), call().__enter__(), call().read(), - call().__exit__(None, None, None)] + call().__exit__(None, None, None), call().close()] self.assertEqual(mock.mock_calls, expected_calls) def test_explicit_mock(self): @@ -193,6 +199,7 @@ def test_read_data(self): def test_readline_data(self): # Check that readline will return all the lines from the fake file + # And that once fully consumed, readline will return an empty string. mock = mock_open(read_data='foo\nbar\nbaz\n') with patch('%s.open' % __name__, mock, create=True): h = open('bar') @@ -202,6 +209,7 @@ def test_readline_data(self): self.assertEqual(line1, 'foo\n') self.assertEqual(line2, 'bar\n') self.assertEqual(line3, 'baz\n') + self.assertEqual(h.readline(), '') # Check that we properly emulate a file that doesn't end in a newline mock = mock_open(read_data='foo') @@ -209,7 +217,35 @@ def test_readline_data(self): h = open('bar') result = h.readline() self.assertEqual(result, 'foo') + self.assertEqual(h.readline(), '') + + def test_dunder_iter_data(self): + # Check that dunder_iter will return all the lines from the fake file. + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + lines = [l for l in h] + self.assertEqual(lines[0], 'foo\n') + self.assertEqual(lines[1], 'bar\n') + self.assertEqual(lines[2], 'baz\n') + self.assertEqual(h.readline(), '') + with self.assertRaises(StopIteration): + next(h) + + def test_next_data(self): + # Check that next will correctly return the next available + # line and plays well with the dunder_iter part. + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = next(h) + line2 = next(h) + lines = [l for l in h] + self.assertEqual(line1, 'foo\n') + self.assertEqual(line2, 'bar\n') + self.assertEqual(lines[0], 'baz\n') + self.assertEqual(h.readline(), '') def test_readlines_data(self): # Test that emulating a file that ends in a newline character works @@ -262,7 +298,12 @@ def test_mock_open_read_with_argument(self): # for mocks returned by mock_open some_data = 'foo\nbar\nbaz' mock = mock_open(read_data=some_data) - self.assertEqual(mock().read(10), some_data) + self.assertEqual(mock().read(10), some_data[:10]) + self.assertEqual(mock().read(10), some_data[:10]) + + f = mock() + self.assertEqual(f.read(10), some_data[:10]) + self.assertEqual(f.read(10), some_data[10:]) def test_interleaved_reads(self): diff --git a/release.py b/release.py new file mode 100644 index 00000000..a44dece0 --- /dev/null +++ b/release.py @@ -0,0 +1,94 @@ +import re +from glob import glob +from os.path import join +from subprocess import call + +import blurb as blurb_module +from argparse import ArgumentParser +from mock import version_info + +VERSION_TYPES = ['major', 'minor', 'bugfix'] + + +def incremented_version(version_info, type_): + type_index = VERSION_TYPES.index(type_) + version_info = tuple(0 if i>type_index else (e+(1 if i==type_index else 0)) + for i, e in enumerate(version_info)) + return '.'.join(str(p) for p in version_info) + + +def text_from_news(): + # hack: + blurb_module.sections.append('NEWS.d') + + blurbs = blurb_module.Blurbs() + for path in glob(join('NEWS.d', '*')): + blurbs.load_next(path) + + text = [] + for metadata, body in blurbs: + bpo = metadata.get('bpo') + gh = metadata.get('gh-issue') + issue = f'bpo-{bpo}' if bpo else f'gh-{gh}' + body = f"- {issue}: " + body + text.append(blurb_module.textwrap_body(body, subsequent_indent=' ')) + + return '\n'.join(text) + + +def news_to_changelog(version): + with open('CHANGELOG.rst') as source: + current_changelog = source.read() + + text = [version] + text.append('-'*len(version)) + text.append('') + text.append(text_from_news()) + text.append(current_changelog) + + new_changelog = '\n'.join(text) + with open('CHANGELOG.rst', 'w') as target: + target.write(new_changelog) + + +def update_version(new_version): + path = join('mock', '__init__.py') + with open(path) as source: + text = source.read() + + text = re.sub("(__version__ = ')[^']+(')", + r"\g<1>"+new_version+r"\2", + text) + + with open(path, 'w') as target: + target.write(text) + + +def git(command): + return call('git '+command, shell=True) + + +def git_commit(new_version): + git('rm NEWS.d/*') + git('add CHANGELOG.rst') + git('add mock/__init__.py') + git(f'commit -m "Preparing for {new_version} release."') + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('type', choices=VERSION_TYPES) + return parser.parse_args() + + +def main(): + args = parse_args() + new_version = incremented_version(version_info, args.type) + news_to_changelog(new_version) + update_version(new_version) + git_commit(new_version) + print(f'{new_version} ready to push, please check the HEAD commit first!') + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 31bbe5d1..00000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -funcsigs>=1;python_version<"3.3" -# For runtime needs this is correct. For setup_requires needs, 1.2.0 is needed -# but setuptools can't cope with conflicts in setup_requires, so thats -# unversioned. -pbr>=0.11 -six>=1.9 diff --git a/setup.cfg b/setup.cfg index 21e538e5..e702275a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,45 +1,52 @@ [metadata] name = mock summary = Rolling backport of unittest.mock for all Pythons -home-page = https://github.com/testing-cabal/mock +home-page = http://mock.readthedocs.org/en/latest/ description-file = README.rst author = Testing Cabal author-email = testing-in-python@lists.idyll.org -classifier = +classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.6 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.2 - Programming Language :: Python :: 3.3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython - Programming Language :: Python :: Implementation :: Jython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Testing +project_urls = + Source = https://github.com/testing-cabal/mock keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles -[extras] -test = - unittest2>=1.1.0 -docs = - jinja2<2.7:python_version<"3.3" and python_version>="3" - Pygments<2:python_version<"3.3" and python_version>="3" - sphinx<1.3:python_version<"3.3" and python_version>="3" - sphinx:python_version<"3" or python_version>="3.3" - -[files] +[options] +python_requires=>=3.6 packages = mock -[bdist_wheel] -universal = 1 +[options.extras_require] +docs = + sphinx +test = + pytest + pytest-cov +build = + twine + wheel + blurb + +[tool:pytest] +python_files=test*.py +filterwarnings = + error::RuntimeWarning + ignore::DeprecationWarning diff --git a/setup.py b/setup.py index 5f727ae7..6f5ff41d 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,10 @@ -#!/usr/bin/env python +import re +from os.path import join + import setuptools setuptools.setup( - setup_requires=['pbr>=1.3', 'setuptools>=17.1'], - pbr=True) + version=re.search("__version__ = '([^']+)'", + open(join('mock', '__init__.py')).read()).group(1), + long_description=open('README.rst').read(), +) diff --git a/tools/applypatch-transform b/tools/applypatch-transform deleted file mode 100755 index 52fcd939..00000000 --- a/tools/applypatch-transform +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# -# An example hook script to transform a patch taken from an email -# by git am. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the patch file. -# -# To enable this hook, rename this file to "applypatch-transform". -# -# This example changes the path of Lib/unittest/mock.py to mock.py -# Lib/unittest/tests/testmock to tests and Misc/NEWS to NEWS, and -# finally skips any patches that did not alter mock.py or its tests. - -set -eux - -patch_path=$1 - -# Pull out mock.py -filterdiff --clean --strip 3 --addprefix=a/mock/ -i 'a/Lib/unittest/mock.py' -i 'b/Lib/unittest/mock.py' $patch_path > $patch_path.mock -# And the tests -filterdiff --clean --strip 5 --addprefix=a/mock/tests/ -i 'a/Lib/unittest/test/testmock/*.py' -i 'b/Lib/unittest/test/testmock/*.py' $patch_path > $patch_path.tests -# Lastly we want to pick up any NEWS entries. -filterdiff --strip 2 --addprefix=a/ -i a/Misc/NEWS -i b/Misc/NEWS $patch_path > $patch_path.NEWS -cp $patch_path $patch_path.orig -# bash -cat $patch_path.mock $patch_path.tests > $patch_path -filtered=$(cat $patch_path) -if [ -n "${filtered}" ]; then - cat $patch_path.NEWS >> $patch_path - exitcode=0 -else - exitcode=1 -fi - -rm $patch_path.mock $patch_path.tests $patch_path.NEWS -exit $exitcode diff --git a/tools/pre-applypatch b/tools/pre-applypatch deleted file mode 100755 index 31985340..00000000 --- a/tools/pre-applypatch +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -set -eu - -#. git-sh-setup -echo "** in hook **" - -function test_version { - version=$1 - host=$(ls ~/.virtualenvs/mock-$version-* -d | sed -e "s/^.*mock-$version-//") - if [ -z "$host" ]; then - echo "No host found for $version" - return 1 - fi - echo testing $version in virtualenv mock-$version-$host on ssh host $host - ssh $host "cd work/mock && . ~/.virtualenvs/mock-$version-$host/bin/activate && pip install .[test] && unit2" -} - -find . -name "*.pyc" -exec rm "{}" \; - -test_version 2.6 -test_version 2.7 -test_version 3.3 -test_version 3.4 -test_version 3.5 -test_version cpython -test_version pypy - -echo '** pre-apply complete and successful **' diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 58e29d2b..00000000 --- a/tox.ini +++ /dev/null @@ -1,40 +0,0 @@ -[tox] -envlist = py25,py26,py27,py31,pypy,py32,py33,jython - -[testenv] -deps=unittest2 -commands={envbindir}/unit2 discover [] - -[testenv:py26] -commands= - {envbindir}/unit2 discover [] - {envbindir}/sphinx-build -E -b doctest docs html - {envbindir}/sphinx-build -E docs html -deps = - unittest2 - sphinx - -[testenv:py27] -commands= - {envbindir}/unit2 discover [] - {envbindir}/sphinx-build -E -b doctest docs html -deps = - unittest2 - sphinx - -[testenv:py31] -deps = - unittest2py3k - -[testenv:py32] -commands= - {envbindir}/python -m unittest discover [] -deps = - -[testenv:py33] -commands= - {envbindir}/python -m unittest discover [] -deps = - -# note for jython. Execute in tests directory: -# rm `find . -name '*$py.class'` \ No newline at end of file diff --git a/unittest.cfg b/unittest.cfg deleted file mode 100644 index b2d6f674..00000000 --- a/unittest.cfg +++ /dev/null @@ -1,95 +0,0 @@ - -[unittest] -plugins = - unittest2.plugins.debugger - unittest2.plugins.checker - unittest2.plugins.doctestloader - unittest2.plugins.matchregexp - unittest2.plugins.moduleloading - unittest2.plugins.testcoverage - unittest2.plugins.growl - unittest2.plugins.filtertests - unittest2.plugins.junitxml - unittest2.plugins.timed - unittest2.plugins.counttests - unittest2.plugins.logchannels - -excluded-plugins = - -# 0, 1 or 2 (default is 1) -# quiet, normal or verbose -# can be overriden at command line -verbosity = normal - -# true or false -# even if false can be switched on at command line -catch = -buffer = -failfast = - - -[matchregexp] -always-on = False -full-path = True - -[debugger] -always-on = False -errors-only = True - -[coverage] -always-on = False -config = -report-html = False -# only used if report-html is false -annotate = False -# defaults to './htmlcov/' -html-directory = -# if unset will output to console -text-file = -branch = False -timid = False -cover-pylib = False -exclude-lines = - # Have to re-enable the standard pragma - pragma: no cover - - # Don't complain about missing debug-only code: - def __repr__ - if self\.debug - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: - if __name__ == .__main__. - -ignore-errors = False -modules = - -[growl] -always-on = False - -[doctest] -always-on = False - -[module-loading] -always-on = False - -[checker] -always-on = False -pep8 = False -pyflakes = True - -[junit-xml] -always-on = False -path = junit.xml - -[timed] -always-on = True -threshold = 0.01 - -[count] -always-on = True -enhanced = False