Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3290 improve monkeypatch #3382

Merged
merged 6 commits into from
Apr 19, 2018
Merged

#3290 improve monkeypatch #3382

merged 6 commits into from
Apr 19, 2018

Conversation

feuillemorte
Copy link
Contributor

@feuillemorte feuillemorte commented Apr 10, 2018

Fixes #3290.

To handle #2180, we could just import open and other builtins explicitly at the module level of rewrite.py to shield us from users tampering with it.

I added to rewrite.py (not in this PR)

from builtins import open

but it has no effect :(

@feuillemorte feuillemorte added plugin: monkeypatch related to the monkeypatch builtin plugin type: bug problem that needs to be addressed labels Apr 10, 2018
@coveralls
Copy link

coveralls commented Apr 10, 2018

Coverage Status

Coverage increased (+0.04%) to 92.771% when pulling 283ac8b on feuillemorte:3290-improve-monkeypatch into 372bcdb on pytest-dev:features.



def test_context(testdir):
testdir.makepyfile("""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be constructed as unittest directly taking monkeypatch, no need to invoke a nested acceptance test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. @feuillemorte please take a look at the other tests in this module for inspiration. 😁

Copy link
Member

@nicoddemus nicoddemus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@feuillemorte please rebase on features because this is a new feature.

Thanks for all the hard work!

@@ -106,6 +108,14 @@ def __init__(self):
self._cwd = None
self._savesyspath = None

@contextmanager
def context(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a docstring here, something along the lines:

"""
Context manager that returns a new :class:`MonkeyPatch` object which
undoes any patching done inside the ``with`` block upon exit:

.. code-block:: python

    import functools
    def test_partial(monkeypatch):
        with monkeypatch.context() as m:
            m.setattr(functools, "partial", 3)

Useful in situations where it is desired to undo some patches before the test ends,
such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples
of this see `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_.
"""

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -61,6 +61,17 @@ so that any attempts within tests to create http requests will fail.
``compile``, etc., because it might break pytest's internals. If that's
unavoidable, passing ``--tb=native``, ``--assert=plain`` and ``--capture=no`` might
help although there's no guarantee.

To avoid damage of pytest from patching python stdlib functions use ``with``
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest here to change this to:

.. note::

    Mind that patching ``stdlib`` functions and some third-party libraries used by pytest might break pytest itself, therefore in those cases it is recommended to use :meth:`MonkeyPatch.context` to limit the patching to the block you want tested:

    .. code-block:: python
        import functools
        def test_partial(monkeypatch):
            with monkeypatch.context() as m:
                m.setattr(functools, "partial", 3)
                assert functools.partial == 3

    See issue `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_ for details.



def test_context(testdir):
testdir.makepyfile("""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. @feuillemorte please take a look at the other tests in this module for inspiration. 😁

@@ -0,0 +1,2 @@
Improved `monkeypatch` to support some form of with statement. Now you can use `with monkeypatch.context() as m:`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new feature so it should be named 3290.feature instead. Also I suggest using this text:

``monkeypatch`` now supports a ``context()`` function which acts as a context manager which undoes all patching done within the ``with`` block.

The fact that it can be used to avoid blowing up pytest itself is not the highlight of the new feature, just a recommended workaround.

@nicoddemus
Copy link
Member

but it has no effect :(

What do you mean?

@feuillemorte feuillemorte changed the base branch from master to features April 11, 2018 09:18
@feuillemorte
Copy link
Contributor Author

What do you mean?

I mean that need to fix #2180 problem in this PR.

you wrote:

To handle #2180, we could just import open and other builtins explicitly at the module level of rewrite.py to shield us from users tampering with it.

So, I get a test:

import unittest
import requests
from unittest.mock import patch, mock_open


class PyTestIssue(unittest.TestCase):
    def test_open(self):
        with patch('builtins.open', mock_open(read_data='')):
            requests.get('http://www.google.com')

And try to fix an exception by importing open to rewrite directly:

from builtins import open

And it has no effect, test failed with exception.

@feuillemorte
Copy link
Contributor Author

rebased to features

@RonnyPfannschmidt
Copy link
Member

@feuillemorte if you import/alias a name to a new place, then patching it at the old place has no effect to the new place

@feuillemorte
Copy link
Contributor Author

Sorry, I didn't get.

def test_open():
    with patch('builtins.open', mock_open(read_data='')): # here we mocking builtin function and go to context manager
        # here we have mocked open function 
        #and pytest also want to call open, but open is mocked 
        #and I have no idea how to split context manager and 
        #pytest functions. open() mocked globally in context 
        #manager and directly importing in pytest is not working here
        requests.get('http://www.google.com') 
        open() # here open() is also mocked and must to behave as mocked
    open() # here is normal behavior

@feuillemorte feuillemorte removed the type: bug problem that needs to be addressed label Apr 11, 2018
@nicoddemus
Copy link
Member

@feuillemorte I now noticed that #2180 definitely can't be fixed, the error occurs inside imp.py and there's nothing pytest can do to prevent that; the discussion in #2180 led to believe that it was a problem that could be solved in rewrite.py, but the error doesn't happen there (the traceback doesn't even contain any pytest related code).

So don't worry about it, I'm closing #2180 as "not fixable".

@feuillemorte
Copy link
Contributor Author

All comments are fixed

@nicoddemus nicoddemus merged commit 3318e53 into pytest-dev:features Apr 19, 2018
@nicoddemus
Copy link
Member

Thanks again @feuillemorte, awesome work! 👍

@feuillemorte feuillemorte deleted the 3290-improve-monkeypatch branch July 2, 2018 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin: monkeypatch related to the monkeypatch builtin plugin
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants