Skip to content

Commit

Permalink
Fix performance when raising ValueError when used as context-manager
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoddemus committed Jun 4, 2020
1 parent e6a1d0a commit 55c11a9
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 18 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
@@ -1,3 +1,11 @@
3.1.1 (2020-05-31)
------------------

* Fixed performance regression caused by the ``ValueError`` raised
when ``mocker`` is used as context manager (`#191`_).

.. _#191: https://github.com/pytest-dev/pytest-mock/issues/191

3.1.0 (2020-04-18)
------------------

Expand Down
24 changes: 7 additions & 17 deletions src/pytest_mock/plugin.py
Expand Up @@ -158,30 +158,20 @@ def _start_patch(self, mock_func, *args, **kwargs):
module, registering the patch to stop it later and returns the
mock object resulting from the mock call.
"""
self._enforce_no_with_context(inspect.stack())
p = mock_func(*args, **kwargs)
mocked = p.start()
self._patches.append(p)
if hasattr(mocked, "reset_mock"):
self._mocks.append(mocked)
# check if `mocked` is actually a mock object, as depending on autospec or target
# parameters `mocked` can be anything
if hasattr(mocked, "__enter__"):
mocked.__enter__.side_effect = ValueError(
"Using mocker in a with context is not supported. "
"https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager"
)
return mocked

def _enforce_no_with_context(self, stack):
"""raises a ValueError if mocker is used in a with context"""
caller = stack[2]
frame = caller[0]
info = inspect.getframeinfo(frame)
if info.code_context is None:
# no source code available (#169)
return
code_context = " ".join(info.code_context).strip()

if code_context.startswith("with mocker."):
raise ValueError(
"Using mocker in a with context is not supported. "
"https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager"
)

def object(self, *args, **kwargs):
"""API to mock.patch.object"""
return self._start_patch(self.mock_module.patch.object, *args, **kwargs)
Expand Down
22 changes: 21 additions & 1 deletion tests/test_pytest_mock.py
Expand Up @@ -773,7 +773,7 @@ def doIt(self):

def test_abort_patch_context_manager(mocker):
with pytest.raises(ValueError) as excinfo:
with mocker.patch("some_package"):
with mocker.patch("json.loads"):
pass

expected_error_msg = (
Expand All @@ -784,6 +784,26 @@ def test_abort_patch_context_manager(mocker):
assert str(excinfo.value) == expected_error_msg


def test_context_manager_patch_example(mocker):
"""Our message about misusing mocker as a context manager should not affect mocking
context managers (see #192)"""

class dummy_module:
class MyContext:
def __enter__(self, *args, **kwargs):
return 10

def __exit__(self, *args, **kwargs):
pass

def my_func():
with dummy_module.MyContext() as v:
return v

m = mocker.patch.object(dummy_module, "MyContext")
assert isinstance(my_func(), mocker.MagicMock)


def test_abort_patch_context_manager_with_stale_pyc(testdir):
"""Ensure we don't trigger an error in case the frame where mocker.patch is being
used doesn't have a 'context' (#169)"""
Expand Down

0 comments on commit 55c11a9

Please sign in to comment.