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