Merge pull request #1711 from nicoddemus/invocation-scoped-fixtures
Invocation scoped fixtures
This commit is contained in:
		
						commit
						ae0798522f
					
				|  | @ -135,9 +135,17 @@ time or change existing behaviors in order to make them less surprising/more use | |||
|   never fail because tuples are always truthy and are usually a mistake | ||||
|   (see `#1562`_). Thanks `@kvas-it`_, for the PR. | ||||
| 
 | ||||
| * Experimentally introduce new ``"invocation"`` fixture scope. At invocation scope a | ||||
|   fixture function is cached in the same way as the fixture or test function that requests it. | ||||
|   You can now use the builtin ``monkeypatch`` fixture from ``session``-scoped fixtures | ||||
|   where previously you would get an error that you can not use a ``function``-scoped fixture from a | ||||
|   ``session``-scoped one.* | ||||
|   Thanks `@nicoddemus`_ for the PR. | ||||
| 
 | ||||
| * Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``). | ||||
|   Thanks to `@anntzer`_ for the PR. | ||||
| 
 | ||||
| 
 | ||||
| * | ||||
| 
 | ||||
| * | ||||
|  |  | |||
|  | @ -161,7 +161,7 @@ def capsys(request): | |||
|     captured output available via ``capsys.readouterr()`` method calls | ||||
|     which return a ``(out, err)`` tuple. | ||||
|     """ | ||||
|     if "capfd" in request._funcargs: | ||||
|     if "capfd" in request.fixturenames: | ||||
|         raise request.raiseerror(error_capsysfderror) | ||||
|     request.node._capfuncarg = c = CaptureFixture(SysCapture, request) | ||||
|     return c | ||||
|  | @ -172,7 +172,7 @@ def capfd(request): | |||
|     captured output available via ``capfd.readouterr()`` method calls | ||||
|     which return a ``(out, err)`` tuple. | ||||
|     """ | ||||
|     if "capsys" in request._funcargs: | ||||
|     if "capsys" in request.fixturenames: | ||||
|         request.raiseerror(error_capsysfderror) | ||||
|     if not hasattr(os, 'dup'): | ||||
|         pytest.skip("capfd funcarg needs os.dup") | ||||
|  |  | |||
|  | @ -260,8 +260,6 @@ class FuncFixtureInfo: | |||
|         self.name2fixturedefs = name2fixturedefs | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class FixtureRequest(FuncargnamesCompatAttr): | ||||
|     """ A request for a fixture from a test or fixture function. | ||||
| 
 | ||||
|  | @ -276,34 +274,51 @@ class FixtureRequest(FuncargnamesCompatAttr): | |||
|         self.fixturename = None | ||||
|         #: Scope string, one of "function", "class", "module", "session" | ||||
|         self.scope = "function" | ||||
|         self._funcargs  = {} | ||||
|         self._fixturedefs = {} | ||||
|         # rename both attributes below because their key has changed; better an attribute error | ||||
|         # than subtle key misses; also backward incompatibility | ||||
|         self._fixture_values = {}  # (argname, scope) -> fixture value | ||||
|         self._fixture_defs = {}  # (argname, scope) -> FixtureDef | ||||
|         fixtureinfo = pyfuncitem._fixtureinfo | ||||
|         self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() | ||||
|         self._arg2index = {} | ||||
|         self.fixturenames = fixtureinfo.names_closure | ||||
|         self._fixturemanager = pyfuncitem.session._fixturemanager | ||||
| 
 | ||||
|     @property | ||||
|     def fixturenames(self): | ||||
|         # backward incompatible note: now a readonly property | ||||
|         return list(self._pyfuncitem._fixtureinfo.names_closure) | ||||
| 
 | ||||
|     @property | ||||
|     def node(self): | ||||
|         """ underlying collection node (depends on current request scope)""" | ||||
|         return self._getscopeitem(self.scope) | ||||
| 
 | ||||
| 
 | ||||
|     def _getnextfixturedef(self, argname): | ||||
|         fixturedefs = self._arg2fixturedefs.get(argname, None) | ||||
|     def _getnextfixturedef(self, argname, scope): | ||||
|         def trygetfixturedefs(argname): | ||||
|             fixturedefs = self._arg2fixturedefs.get(argname, None) | ||||
|             if fixturedefs is None: | ||||
|                 fixturedefs = self._arg2fixturedefs.get(argname + ':' + scope, None) | ||||
|             return fixturedefs | ||||
| 
 | ||||
|         fixturedefs = trygetfixturedefs(argname) | ||||
|         if fixturedefs is None: | ||||
|             # we arrive here because of a  a dynamic call to | ||||
|             # getfixturevalue(argname) usage which was naturally | ||||
|             # not known at parsing/collection time | ||||
|             fixturedefs = self._fixturemanager.getfixturedefs( | ||||
|                             argname, self._pyfuncitem.parent.nodeid) | ||||
|             self._arg2fixturedefs[argname] = fixturedefs | ||||
|             parentid = self._pyfuncitem.parent.nodeid | ||||
|             fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) | ||||
|             if fixturedefs: | ||||
|                 self._arg2fixturedefs[argname] = fixturedefs | ||||
|             fixturedefs_by_argname = self._fixturemanager.getfixturedefs_multiple_scopes(argname, parentid) | ||||
|             if fixturedefs_by_argname: | ||||
|                 self._arg2fixturedefs.update(fixturedefs_by_argname) | ||||
|             fixturedefs = trygetfixturedefs(argname) | ||||
|         # fixturedefs list is immutable so we maintain a decreasing index | ||||
|         index = self._arg2index.get(argname, 0) - 1 | ||||
|         index = self._arg2index.get((argname, scope), 0) - 1 | ||||
|         if fixturedefs is None or (-index > len(fixturedefs)): | ||||
|             raise FixtureLookupError(argname, self) | ||||
|         self._arg2index[argname] = index | ||||
|         self._arg2index[(argname, scope)] = index | ||||
|         return fixturedefs[index] | ||||
| 
 | ||||
|     @property | ||||
|  | @ -442,10 +457,10 @@ class FixtureRequest(FuncargnamesCompatAttr): | |||
| 
 | ||||
|     def _get_active_fixturedef(self, argname): | ||||
|         try: | ||||
|             return self._fixturedefs[argname] | ||||
|             return self._fixture_defs[(argname, self.scope)] | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 fixturedef = self._getnextfixturedef(argname) | ||||
|                 fixturedef = self._getnextfixturedef(argname, self.scope) | ||||
|             except FixtureLookupError: | ||||
|                 if argname == "request": | ||||
|                     class PseudoFixtureDef: | ||||
|  | @ -456,8 +471,8 @@ class FixtureRequest(FuncargnamesCompatAttr): | |||
|         # remove indent to prevent the python3 exception | ||||
|         # from leaking into the call | ||||
|         result = self._getfixturevalue(fixturedef) | ||||
|         self._funcargs[argname] = result | ||||
|         self._fixturedefs[argname] = fixturedef | ||||
|         self._fixture_values[(argname, self.scope)] = result | ||||
|         self._fixture_defs[(argname, self.scope)] = fixturedef | ||||
|         return fixturedef | ||||
| 
 | ||||
|     def _get_fixturestack(self): | ||||
|  | @ -578,11 +593,10 @@ class SubRequest(FixtureRequest): | |||
|         self._fixturedef = fixturedef | ||||
|         self.addfinalizer = fixturedef.addfinalizer | ||||
|         self._pyfuncitem = request._pyfuncitem | ||||
|         self._funcargs  = request._funcargs | ||||
|         self._fixturedefs = request._fixturedefs | ||||
|         self._fixture_values  = request._fixture_values | ||||
|         self._fixture_defs = request._fixture_defs | ||||
|         self._arg2fixturedefs = request._arg2fixturedefs | ||||
|         self._arg2index = request._arg2index | ||||
|         self.fixturenames = request.fixturenames | ||||
|         self._fixturemanager = request._fixturemanager | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|  | @ -622,7 +636,7 @@ class FixtureLookupError(LookupError): | |||
|             fspath, lineno = getfslineno(function) | ||||
|             try: | ||||
|                 lines, _ = inspect.getsourcelines(get_real_func(function)) | ||||
|             except (IOError, IndexError): | ||||
|             except (IOError, IndexError, TypeError): | ||||
|                 error_msg = "file %s, line %s: source code not available" | ||||
|                 addline(error_msg % (fspath, lineno+1)) | ||||
|             else: | ||||
|  | @ -636,9 +650,9 @@ class FixtureLookupError(LookupError): | |||
|         if msg is None: | ||||
|             fm = self.request._fixturemanager | ||||
|             available = [] | ||||
|             for name, fixturedef in fm._arg2fixturedefs.items(): | ||||
|                 parentid = self.request._pyfuncitem.parent.nodeid | ||||
|                 faclist = list(fm._matchfactories(fixturedef, parentid)) | ||||
|             parentid = self.request._pyfuncitem.parent.nodeid | ||||
|             for name, fixturedefs in fm._arg2fixturedefs.items(): | ||||
|                 faclist = list(fm._matchfactories(fixturedefs, parentid)) | ||||
|                 if faclist: | ||||
|                     available.append(name) | ||||
|             msg = "fixture %r not found" % (self.argname,) | ||||
|  | @ -749,7 +763,7 @@ class FixtureDef: | |||
|             assert not hasattr(self, "cached_result") | ||||
| 
 | ||||
|         ihook = self._fixturemanager.session.ihook | ||||
|         ihook.pytest_fixture_setup(fixturedef=self, request=request) | ||||
|         return ihook.pytest_fixture_setup(fixturedef=self, request=request) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return ("<FixtureDef name=%r scope=%r baseid=%r >" % | ||||
|  | @ -984,10 +998,12 @@ class FixtureManager: | |||
| 
 | ||||
|         parentid = parentnode.nodeid | ||||
|         fixturenames_closure = self._getautousenames(parentid) | ||||
| 
 | ||||
|         def merge(otherlist): | ||||
|             for arg in otherlist: | ||||
|                 if arg not in fixturenames_closure: | ||||
|                     fixturenames_closure.append(arg) | ||||
| 
 | ||||
|         merge(fixturenames) | ||||
|         arg2fixturedefs = {} | ||||
|         lastlen = -1 | ||||
|  | @ -1000,6 +1016,11 @@ class FixtureManager: | |||
|                 if fixturedefs: | ||||
|                     arg2fixturedefs[argname] = fixturedefs | ||||
|                     merge(fixturedefs[-1].argnames) | ||||
|                 fixturedefs_by_argname = self.getfixturedefs_multiple_scopes(argname, parentid) | ||||
|                 if fixturedefs_by_argname: | ||||
|                     arg2fixturedefs.update(fixturedefs_by_argname) | ||||
|                     for fixturedefs in fixturedefs_by_argname.values(): | ||||
|                         merge(fixturedefs[-1].argnames) | ||||
|         return fixturenames_closure, arg2fixturedefs | ||||
| 
 | ||||
|     def pytest_generate_tests(self, metafunc): | ||||
|  | @ -1018,7 +1039,7 @@ class FixtureManager: | |||
|                                              indirect=True, scope=fixturedef.scope, | ||||
|                                              ids=fixturedef.ids) | ||||
|             else: | ||||
|                 continue # will raise FixtureLookupError at setup time | ||||
|                 continue  # will raise FixtureLookupError at setup time | ||||
| 
 | ||||
|     def pytest_collection_modifyitems(self, items): | ||||
|         # separate parametrized setups | ||||
|  | @ -1057,25 +1078,43 @@ class FixtureManager: | |||
|                 msg = 'fixtures cannot have "pytest_funcarg__" prefix ' \ | ||||
|                       'and be decorated with @pytest.fixture:\n%s' % name | ||||
|                 assert not name.startswith(self._argprefix), msg | ||||
|             fixturedef = FixtureDef(self, nodeid, name, obj, | ||||
|                                     marker.scope, marker.params, | ||||
|                                     unittest=unittest, ids=marker.ids) | ||||
|             faclist = self._arg2fixturedefs.setdefault(name, []) | ||||
|             if fixturedef.has_location: | ||||
|                 faclist.append(fixturedef) | ||||
| 
 | ||||
|             def new_fixture_def(name, scope): | ||||
|                 """Create and registers a new FixtureDef with given name and scope.""" | ||||
|                 fixture_def = FixtureDef(self, nodeid, name, obj, | ||||
|                                          scope, marker.params, | ||||
|                                          unittest=unittest, ids=marker.ids) | ||||
| 
 | ||||
|                 faclist = self._arg2fixturedefs.setdefault(name, []) | ||||
|                 if fixture_def.has_location: | ||||
|                     faclist.append(fixture_def) | ||||
|                 else: | ||||
|                     # fixturedefs with no location are at the front | ||||
|                     # so this inserts the current fixturedef after the | ||||
|                     # existing fixturedefs from external plugins but | ||||
|                     # before the fixturedefs provided in conftests. | ||||
|                     i = len([f for f in faclist if not f.has_location]) | ||||
|                     faclist.insert(i, fixture_def) | ||||
|                 if marker.autouse: | ||||
|                     autousenames.append(name) | ||||
| 
 | ||||
|             if marker.scope == 'invocation': | ||||
|                 for new_scope in scopes: | ||||
|                     new_fixture_def(name + ':{0}'.format(new_scope), new_scope) | ||||
|             else: | ||||
|                 # fixturedefs with no location are at the front | ||||
|                 # so this inserts the current fixturedef after the | ||||
|                 # existing fixturedefs from external plugins but | ||||
|                 # before the fixturedefs provided in conftests. | ||||
|                 i = len([f for f in faclist if not f.has_location]) | ||||
|                 faclist.insert(i, fixturedef) | ||||
|             if marker.autouse: | ||||
|                 autousenames.append(name) | ||||
|                 new_fixture_def(name, marker.scope) | ||||
| 
 | ||||
|         if autousenames: | ||||
|             self._nodeid_and_autousenames.append((nodeid or '', autousenames)) | ||||
| 
 | ||||
|     def getfixturedefs(self, argname, nodeid): | ||||
|         """ | ||||
|         Gets a list of fixtures which are applicable to the given node id. | ||||
| 
 | ||||
|         :param str argname: name of the fixture to search for | ||||
|         :param str nodeid: full node id of the requesting test. | ||||
|         :return: list[FixtureDef] | ||||
|         """ | ||||
|         try: | ||||
|             fixturedefs = self._arg2fixturedefs[argname] | ||||
|         except KeyError: | ||||
|  | @ -1087,3 +1126,24 @@ class FixtureManager: | |||
|         for fixturedef in fixturedefs: | ||||
|             if nodeid.startswith(fixturedef.baseid): | ||||
|                 yield fixturedef | ||||
| 
 | ||||
|     def getfixturedefs_multiple_scopes(self, argname, nodeid): | ||||
|         """ | ||||
|         Gets multiple scoped fixtures which are applicable to the given nodeid. Multiple scoped | ||||
|         fixtures are created by "invocation" scoped fixtures and have argnames in | ||||
|         the form: "<argname>:<scope>" (for example "tmpdir:session"). | ||||
| 
 | ||||
|         :return: dict of "argname" -> [FixtureDef]. | ||||
| 
 | ||||
|         Arguments similar to ``getfixturedefs``. | ||||
|         """ | ||||
|         prefix = argname + ':' | ||||
|         fixturedefs_by_argname = dict((k, v) for k, v in self._arg2fixturedefs.items() | ||||
|                                       if k.startswith(prefix)) | ||||
|         if fixturedefs_by_argname: | ||||
|             result = {} | ||||
|             for argname, fixturedefs in fixturedefs_by_argname.items(): | ||||
|                 result[argname] = tuple(self._matchfactories(fixturedefs, nodeid)) | ||||
|             return result | ||||
|         else: | ||||
|             return None | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ import pytest | |||
| RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| @pytest.fixture(scope='invocation') | ||||
| def monkeypatch(request): | ||||
|     """The returned ``monkeypatch`` fixture provides these | ||||
|     helper methods to modify objects, dictionaries or os.environ:: | ||||
|  | @ -25,9 +25,11 @@ def monkeypatch(request): | |||
|         monkeypatch.chdir(path) | ||||
| 
 | ||||
|     All modifications will be undone after the requesting | ||||
|     test function has finished. The ``raising`` | ||||
|     test function or fixture has finished. The ``raising`` | ||||
|     parameter determines if a KeyError or AttributeError | ||||
|     will be raised if the set/deletion operation has no target. | ||||
| 
 | ||||
|     This fixture is ``invocation``-scoped. | ||||
|     """ | ||||
|     mpatch = MonkeyPatch() | ||||
|     request.addfinalizer(mpatch.undo) | ||||
|  | @ -97,7 +99,8 @@ notset = Notset() | |||
| 
 | ||||
| 
 | ||||
| class MonkeyPatch: | ||||
|     """ Object keeping a record of setattr/item/env/syspath changes. """ | ||||
|     """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self._setattr = [] | ||||
|  |  | |||
|  | @ -1506,4 +1506,3 @@ class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr): | |||
|         super(Function, self).setup() | ||||
|         fixtures.fillfixtures(self) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -603,7 +603,7 @@ first execute with one instance and then finalizers are called | |||
| before the next fixture instance is created.  Among other things, | ||||
| this eases testing of applications which create and use global state. | ||||
| 
 | ||||
| The following example uses two parametrized funcargs, one of which is | ||||
| The following example uses two parametrized fixture, one of which is | ||||
| scoped on a per-module basis, and all the functions perform ``print`` calls | ||||
| to show the setup/teardown flow:: | ||||
| 
 | ||||
|  | @ -863,6 +863,14 @@ All test methods in this TestClass will use the transaction fixture while | |||
| other test classes or functions in the module will not use it unless | ||||
| they also add a ``transact`` reference. | ||||
| 
 | ||||
| invocation-scoped fixtures | ||||
| -------------------------- | ||||
| 
 | ||||
| pytest 3.0 introduced a new advanced scope for fixtures: ``"invocation"``. Fixtures marked with | ||||
| this scope can be requested from any other scope, providing a version of the fixture for that scope. | ||||
| 
 | ||||
| See more in :ref:`invocation_scoped_fixture`. | ||||
| 
 | ||||
| Shifting (visibility of) fixture functions | ||||
| ---------------------------------------------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,63 @@ | |||
| .. _invocation_scoped_fixture: | ||||
| 
 | ||||
| Invocation-scoped fixtures | ||||
| ========================== | ||||
| 
 | ||||
| .. versionadded:: 3.0 | ||||
| 
 | ||||
| .. note:: | ||||
|     This feature is experimental, so if decided that it brings too much problems | ||||
|     or considered too complicated it might be removed in pytest ``3.1``. | ||||
| 
 | ||||
| Fixtures can be defined with ``invocation`` scope, meaning that the fixture | ||||
| can be requested by fixtures from any scope, but when they do they assume | ||||
| the same scope as the fixture requesting it. | ||||
| 
 | ||||
| An ``invocation``-scoped fixture can be requested from different scopes | ||||
| in the same test session, in which case each scope will have its own copy. | ||||
| 
 | ||||
| Example | ||||
| ------- | ||||
| 
 | ||||
| Consider a fixture which manages external process execution: | ||||
| this fixture provides auxiliary methods for tests and fixtures to start external | ||||
| processes while making sure the | ||||
| processes terminate at the appropriate time. Because it makes sense | ||||
| to start a webserver for the entire session and also to execute a numerical | ||||
| simulation for a single test function, the ``process_manager`` | ||||
| fixture can be declared as ``invocation``, so each scope gets its own | ||||
| value and can manage processes which will live for the duration of the scope. | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     import pytest | ||||
| 
 | ||||
|     @pytest.fixture(scope='invocation') | ||||
|     def process_manager(): | ||||
|         """ | ||||
|         Return a ProcessManager instance which can be used to start | ||||
|         long-lived processes and ensures they are terminated at the | ||||
|         appropriate scope. | ||||
|         """ | ||||
|         m = ProcessManager() | ||||
|         yield m | ||||
|         m.shutdown_all() | ||||
| 
 | ||||
| 
 | ||||
|     @pytest.fixture(scope='session') | ||||
|     def server(process_manager): | ||||
|         process_manager.start(sys.executable, 'server.py') | ||||
| 
 | ||||
| 
 | ||||
|     @pytest.fixture(scope='function') | ||||
|     def start_simulation(process_manager): | ||||
|         import functools | ||||
|         return functools.partial(process_manager.start, | ||||
|                                  sys.executable, 'simulator.py') | ||||
| 
 | ||||
| 
 | ||||
| In the above code, simulators started using ``start_simulation`` will be | ||||
| terminated when the test function exits, while the server will be kept | ||||
| active for the entire simulation run, being terminated when the test session | ||||
| finishes. | ||||
| 
 | ||||
|  | @ -6,7 +6,7 @@ Monkeypatching/mocking modules and environments | |||
| 
 | ||||
| Sometimes tests need to invoke functionality which depends | ||||
| on global settings or which invokes code which cannot be easily | ||||
| tested such as network access.  The ``monkeypatch`` function argument | ||||
| tested such as network access.  The ``monkeypatch`` fixture | ||||
| helps you to safely set/delete an attribute, dictionary item or | ||||
| environment variable or to modify ``sys.path`` for importing. | ||||
| See the `monkeypatch blog post`_ for some introduction material | ||||
|  | @ -14,6 +14,9 @@ and a discussion of its motivation. | |||
| 
 | ||||
| .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ | ||||
| 
 | ||||
| As of pytest-3.0, the ``monkeypatch`` fixture is :ref:`invocation-scoped <invocation_scoped_fixture>` | ||||
| meaning it can be requested from fixtures of any scope. | ||||
| 
 | ||||
| Simple example: monkeypatching functions | ||||
| --------------------------------------------------- | ||||
| 
 | ||||
|  | @ -53,27 +56,31 @@ This autouse fixture will be executed for each test function and it | |||
| will delete the method ``request.session.Session.request``  | ||||
| so that any attempts within tests to create http requests will fail. | ||||
| 
 | ||||
| example: setting an attribute on some class | ||||
| ------------------------------------------------------ | ||||
| example: setting an environment variable for the test session | ||||
| ------------------------------------------------------------- | ||||
| 
 | ||||
| If you need to patch out ``os.getcwd()`` to return an artificial | ||||
| value:: | ||||
| If you would like for an environment variable to be | ||||
| configured for the entire test session, you can add this to your | ||||
| top-level ``conftest.py`` file: | ||||
| 
 | ||||
|     def test_some_interaction(monkeypatch): | ||||
|         monkeypatch.setattr("os.getcwd", lambda: "/") | ||||
| .. code-block:: python | ||||
| 
 | ||||
| which is equivalent to the long form:: | ||||
|     # content of conftest.py | ||||
|     @pytest.fixture(scope='session', autouse=True) | ||||
|     def enable_debugging(monkeypatch): | ||||
|         monkeypatch.setenv("DEBUGGING_VERBOSITY", "4") | ||||
| 
 | ||||
|     def test_some_interaction(monkeypatch): | ||||
|         import os | ||||
|         monkeypatch.setattr(os, "getcwd", lambda: "/") | ||||
| This auto-use fixture will set the ``DEBUGGING_VERBOSITY`` environment variable for | ||||
| the entire test session. | ||||
| 
 | ||||
| Note that the ability to use a ``monkeypatch`` fixture from a ``session``-scoped | ||||
| fixture was added in pytest-3.0. | ||||
| 
 | ||||
| 
 | ||||
| Method reference of the monkeypatch fixture | ||||
| ------------------------------------------- | ||||
| 
 | ||||
| Method reference of the monkeypatch function argument | ||||
| ----------------------------------------------------- | ||||
| 
 | ||||
| .. autoclass:: monkeypatch | ||||
| .. autoclass:: MonkeyPatch | ||||
|     :members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo | ||||
| 
 | ||||
| ``monkeypatch.setattr/delattr/delitem/delenv()`` all | ||||
|  |  | |||
|  | @ -0,0 +1,214 @@ | |||
| 
 | ||||
| def test_invocation_request(testdir): | ||||
|     """ | ||||
|     Simple test case with session and module scopes requesting an | ||||
|     invocation-scoped fixture. | ||||
|     """ | ||||
|     testdir.makeconftest(""" | ||||
|         import pytest | ||||
| 
 | ||||
|         @pytest.fixture(scope='invocation') | ||||
|         def my_name(request): | ||||
|             if request.scope == 'function': | ||||
|                 return request.function.__name__ | ||||
|             elif request.scope == 'module': | ||||
|                 return request.module.__name__ | ||||
|             elif request.scope == 'session': | ||||
|                 return '<session>' | ||||
| 
 | ||||
|         @pytest.fixture(scope='session') | ||||
|         def session_name(my_name): | ||||
|             return my_name | ||||
| 
 | ||||
|         @pytest.fixture(scope='module') | ||||
|         def module_name(my_name): | ||||
|             return my_name | ||||
|     """) | ||||
|     testdir.makepyfile(test_module_foo=""" | ||||
|         def test_foo(my_name, module_name, session_name): | ||||
|             assert my_name == 'test_foo' | ||||
|             assert module_name == 'test_module_foo' | ||||
|             assert session_name == '<session>' | ||||
|     """) | ||||
|     testdir.makepyfile(test_module_bar=""" | ||||
|         def test_bar(my_name, module_name, session_name): | ||||
|             assert my_name == 'test_bar' | ||||
|             assert module_name == 'test_module_bar' | ||||
|             assert session_name == '<session>' | ||||
|     """) | ||||
|     result = testdir.runpytest() | ||||
|     result.stdout.fnmatch_lines(['*2 passed*']) | ||||
| 
 | ||||
| 
 | ||||
| def test_override_invocation_scoped(testdir): | ||||
|     """Test that it's possible to override invocation-scoped fixtures.""" | ||||
|     testdir.makeconftest(""" | ||||
|         import pytest | ||||
| 
 | ||||
|         @pytest.fixture(scope='invocation') | ||||
|         def magic_value(request): | ||||
|             if request.scope == 'function': | ||||
|                 return 1 | ||||
|             elif request.scope == 'module': | ||||
|                 return 100 | ||||
| 
 | ||||
|         @pytest.fixture(scope='module') | ||||
|         def module_magic_value(magic_value): | ||||
|             return magic_value * 2 | ||||
|     """) | ||||
|     testdir.makepyfile(test_module_override=""" | ||||
|         import pytest | ||||
| 
 | ||||
|         @pytest.fixture(scope='module') | ||||
|         def magic_value(): | ||||
|             return 42 | ||||
| 
 | ||||
|         def test_override(magic_value, module_magic_value): | ||||
|             assert magic_value == 42 | ||||
|             assert module_magic_value == 42 * 2 | ||||
|     """) | ||||
|     testdir.makepyfile(test_normal=""" | ||||
|         def test_normal(magic_value, module_magic_value): | ||||
|             assert magic_value == 1 | ||||
|             assert module_magic_value == 200 | ||||
|     """) | ||||
|     result = testdir.runpytest() | ||||
|     result.stdout.fnmatch_lines(['*2 passed*']) | ||||
| 
 | ||||
| 
 | ||||
| class TestAcceptance: | ||||
|     """ | ||||
|     Complete acceptance test for a invocation-scoped fixture. | ||||
|     """ | ||||
| 
 | ||||
|     def test_acceptance(self, testdir): | ||||
|         """ | ||||
|         Tests a "stack" fixture which provides a separate list to each scope which uses it. | ||||
| 
 | ||||
|         Some notes: | ||||
| 
 | ||||
|         - For each scope, define 2 fixtures of the same scope which use the "stack" fixture, | ||||
|           to ensure they get the same "stack" instance for that scope. | ||||
|         - Creates multiple test files, which tests on each modifying and checking fixtures to | ||||
|           ensure things are working properly. | ||||
|         """ | ||||
|         testdir.makeconftest(""" | ||||
|             import pytest | ||||
| 
 | ||||
|             @pytest.fixture(scope='invocation') | ||||
|             def stack(): | ||||
|                 return [] | ||||
| 
 | ||||
|             @pytest.fixture(scope='session') | ||||
|             def session1_fix(stack): | ||||
|                 stack.append('session1_fix') | ||||
|                 return stack | ||||
| 
 | ||||
|             @pytest.fixture(scope='session') | ||||
|             def session2_fix(stack): | ||||
|                 stack.append('session2_fix') | ||||
|                 return stack | ||||
| 
 | ||||
|             @pytest.fixture(scope='module') | ||||
|             def module1_fix(stack): | ||||
|                 stack.append('module1_fix') | ||||
|                 return stack | ||||
| 
 | ||||
|             @pytest.fixture(scope='module') | ||||
|             def module2_fix(stack): | ||||
|                 stack.append('module2_fix') | ||||
|                 return stack | ||||
| 
 | ||||
|             @pytest.fixture(scope='class') | ||||
|             def class1_fix(stack): | ||||
|                 stack.append('class1_fix') | ||||
|                 return stack | ||||
| 
 | ||||
|             @pytest.fixture(scope='class') | ||||
|             def class2_fix(stack): | ||||
|                 stack.append('class2_fix') | ||||
|                 return stack | ||||
|         """) | ||||
|         testdir.makepyfile(test_0=""" | ||||
|             import pytest | ||||
| 
 | ||||
|             @pytest.fixture | ||||
|             def func_stack(stack): | ||||
|                 return stack | ||||
| 
 | ||||
|             def test_scoped_instances(session1_fix, session2_fix, module1_fix, module2_fix, | ||||
|                                       class1_fix, class2_fix, stack, func_stack): | ||||
|                 assert session1_fix is session2_fix | ||||
|                 assert module1_fix is module2_fix | ||||
|                 assert class1_fix is class2_fix | ||||
|                 assert stack is func_stack | ||||
| 
 | ||||
|                 assert session1_fix is not module2_fix | ||||
|                 assert module2_fix is not class1_fix | ||||
|                 assert class1_fix is not stack | ||||
|         """) | ||||
|         testdir.makepyfile(test_1=""" | ||||
|             def test_func_1(request, session1_fix, session2_fix, module1_fix, module2_fix, stack): | ||||
|                 assert stack == [] | ||||
| 
 | ||||
|                 assert session1_fix == ['session1_fix', 'session2_fix'] | ||||
|                 session1_fix.append('test_1::test_func_1') | ||||
| 
 | ||||
|                 assert module1_fix == ['module1_fix', 'module2_fix'] | ||||
|                 module1_fix.append('test_1::test_func_1') | ||||
| 
 | ||||
| 
 | ||||
|             class Test: | ||||
| 
 | ||||
|                 def test_1(self, request, session1_fix, module1_fix, class1_fix, class2_fix, stack): | ||||
|                     assert stack == [] | ||||
| 
 | ||||
|                     assert session1_fix == ['session1_fix', 'session2_fix', 'test_1::test_func_1'] | ||||
|                     session1_fix.append('test_1::Test::test_1') | ||||
| 
 | ||||
|                     assert module1_fix == ['module1_fix', 'module2_fix', 'test_1::test_func_1'] | ||||
|                     module1_fix.append('test_1::test_func_1') | ||||
| 
 | ||||
|                     assert class1_fix == ['class1_fix', 'class2_fix'] | ||||
|                     class1_fix.append('test_1::Test::test_1') | ||||
| 
 | ||||
|                 def test_2(self, request, class1_fix, class2_fix): | ||||
|                     assert class1_fix == ['class1_fix', 'class2_fix', 'test_1::Test::test_1'] | ||||
|                     class1_fix.append('Test.test_2') | ||||
| 
 | ||||
| 
 | ||||
|             def test_func_2(request, session1_fix, session2_fix, module1_fix, class1_fix, class2_fix, stack): | ||||
|                 assert stack == [] | ||||
|                 assert session1_fix == ['session1_fix', 'session2_fix', 'test_1::test_func_1', | ||||
|                                         'test_1::Test::test_1'] | ||||
|                 session1_fix.append('test_1::test_func_2') | ||||
| 
 | ||||
|                 assert module1_fix == ['module1_fix', 'module2_fix', 'test_1::test_func_1', 'test_1::test_func_1'] | ||||
| 
 | ||||
|                 assert class1_fix == ['class1_fix', 'class2_fix'] | ||||
|         """) | ||||
|         testdir.makepyfile(test_2=""" | ||||
|             import pytest | ||||
| 
 | ||||
|             @pytest.fixture(scope='session') | ||||
|             def another_session_stack(stack): | ||||
|                 stack.append('other_session_stack') | ||||
|                 return stack | ||||
| 
 | ||||
|             def test_func_2(request, another_session_stack, module1_fix, stack): | ||||
|                 assert stack == [] | ||||
|                 assert another_session_stack == [ | ||||
|                     'session1_fix', | ||||
|                     'session2_fix', | ||||
|                     'test_1::test_func_1', | ||||
|                     'test_1::Test::test_1', | ||||
|                     'test_1::test_func_2', | ||||
|                     'other_session_stack', | ||||
|                     ] | ||||
|                 assert module1_fix == ['module1_fix'] | ||||
|         """) | ||||
|         result = testdir.runpytest() | ||||
|         assert result.ret == 0 | ||||
|         result.stdout.fnmatch_lines('* 6 passed in *') | ||||
| 
 | ||||
| 
 | ||||
|  | @ -421,6 +421,25 @@ class TestCaptureFixture: | |||
|             "*capsys*capfd*same*time*", | ||||
|             "*2 error*"]) | ||||
| 
 | ||||
|     def test_capturing_getfixturevalue(self, testdir): | ||||
|         """Test that asking for "capfd" and "capsys" using request.getfixturevalue | ||||
|         in the same test is an error. | ||||
|         """ | ||||
|         testdir.makepyfile(""" | ||||
|             def test_one(capsys, request): | ||||
|                 request.getfixturevalue("capfd") | ||||
|             def test_two(capfd, request): | ||||
|                 request.getfixturevalue("capsys") | ||||
|         """) | ||||
|         result = testdir.runpytest() | ||||
|         result.stdout.fnmatch_lines([ | ||||
|             "*test_one*", | ||||
|             "*capsys*capfd*same*time*", | ||||
|             "*test_two*", | ||||
|             "*capsys*capfd*same*time*", | ||||
|             "*2 failed in*", | ||||
|         ]) | ||||
| 
 | ||||
|     @pytest.mark.parametrize("method", ["sys", "fd"]) | ||||
|     def test_capture_is_represented_on_failure_issue128(self, testdir, method): | ||||
|         p = testdir.makepyfile(""" | ||||
|  |  | |||
|  | @ -329,3 +329,35 @@ def test_issue1338_name_resolving(): | |||
|          monkeypatch.delattr('requests.sessions.Session.request') | ||||
|     finally: | ||||
|         monkeypatch.undo() | ||||
| 
 | ||||
| 
 | ||||
| def test_invocation_scoped_monkeypatch(testdir): | ||||
|     testdir.makeconftest(""" | ||||
|         import pytest | ||||
|         import sys | ||||
| 
 | ||||
|         @pytest.fixture(scope='module') | ||||
|         def stamp_sys(monkeypatch): | ||||
|             monkeypatch.setattr(sys, 'module_stamped', True, raising=False) | ||||
|     """) | ||||
|     testdir.makepyfile(test_inv_mokeypatch_1=""" | ||||
|         import sys | ||||
| 
 | ||||
|         def test_stamp_1(monkeypatch, stamp_sys): | ||||
|             assert sys.module_stamped | ||||
|             monkeypatch.setattr(sys, 'function_stamped', True, raising=False) | ||||
|             assert sys.function_stamped | ||||
| 
 | ||||
|         def test_stamp_2(monkeypatch): | ||||
|             assert sys.module_stamped | ||||
|             assert not hasattr(sys, 'function_stamped') | ||||
|     """) | ||||
|     testdir.makepyfile(test_inv_mokeypatch_2=""" | ||||
|         import sys | ||||
| 
 | ||||
|         def test_no_stamps(): | ||||
|             assert not hasattr(sys, 'module_stamped') | ||||
|             assert not hasattr(sys, 'function_stamped') | ||||
|     """) | ||||
|     result = testdir.runpytest() | ||||
|     result.stdout.fnmatch_lines(['*3 passed*']) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue