Merge pull request #1811 from nicoddemus/revert-invocation-fixtures
Revert invocation-fixtures code
This commit is contained in:
commit
c8ab79402c
|
@ -143,13 +143,6 @@ 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
|
never fail because tuples are always truthy and are usually a mistake
|
||||||
(see `#1562`_). Thanks `@kvas-it`_, for the PR.
|
(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``).
|
* Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``).
|
||||||
Thanks to `@anntzer`_ for the PR.
|
Thanks to `@anntzer`_ for the PR.
|
||||||
|
|
||||||
|
|
|
@ -274,10 +274,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
self.fixturename = None
|
self.fixturename = None
|
||||||
#: Scope string, one of "function", "class", "module", "session"
|
#: Scope string, one of "function", "class", "module", "session"
|
||||||
self.scope = "function"
|
self.scope = "function"
|
||||||
# rename both attributes below because their key has changed; better an attribute error
|
self._fixture_values = {} # argname -> fixture value
|
||||||
# than subtle key misses; also backward incompatibility
|
self._fixture_defs = {} # argname -> FixtureDef
|
||||||
self._fixture_values = {} # (argname, scope) -> fixture value
|
|
||||||
self._fixture_defs = {} # (argname, scope) -> FixtureDef
|
|
||||||
fixtureinfo = pyfuncitem._fixtureinfo
|
fixtureinfo = pyfuncitem._fixtureinfo
|
||||||
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
|
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
|
||||||
self._arg2index = {}
|
self._arg2index = {}
|
||||||
|
@ -294,31 +292,20 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
return self._getscopeitem(self.scope)
|
return self._getscopeitem(self.scope)
|
||||||
|
|
||||||
|
|
||||||
def _getnextfixturedef(self, argname, scope):
|
def _getnextfixturedef(self, argname):
|
||||||
def trygetfixturedefs(argname):
|
fixturedefs = self._arg2fixturedefs.get(argname, None)
|
||||||
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:
|
if fixturedefs is None:
|
||||||
# we arrive here because of a a dynamic call to
|
# we arrive here because of a a dynamic call to
|
||||||
# getfixturevalue(argname) usage which was naturally
|
# getfixturevalue(argname) usage which was naturally
|
||||||
# not known at parsing/collection time
|
# not known at parsing/collection time
|
||||||
parentid = self._pyfuncitem.parent.nodeid
|
parentid = self._pyfuncitem.parent.nodeid
|
||||||
fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
|
fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
|
||||||
if fixturedefs:
|
self._arg2fixturedefs[argname] = 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
|
# fixturedefs list is immutable so we maintain a decreasing index
|
||||||
index = self._arg2index.get((argname, scope), 0) - 1
|
index = self._arg2index.get(argname, 0) - 1
|
||||||
if fixturedefs is None or (-index > len(fixturedefs)):
|
if fixturedefs is None or (-index > len(fixturedefs)):
|
||||||
raise FixtureLookupError(argname, self)
|
raise FixtureLookupError(argname, self)
|
||||||
self._arg2index[(argname, scope)] = index
|
self._arg2index[argname] = index
|
||||||
return fixturedefs[index]
|
return fixturedefs[index]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -458,10 +445,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
|
|
||||||
def _get_active_fixturedef(self, argname):
|
def _get_active_fixturedef(self, argname):
|
||||||
try:
|
try:
|
||||||
return self._fixture_defs[(argname, self.scope)]
|
return self._fixture_defs[argname]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
try:
|
try:
|
||||||
fixturedef = self._getnextfixturedef(argname, self.scope)
|
fixturedef = self._getnextfixturedef(argname)
|
||||||
except FixtureLookupError:
|
except FixtureLookupError:
|
||||||
if argname == "request":
|
if argname == "request":
|
||||||
class PseudoFixtureDef:
|
class PseudoFixtureDef:
|
||||||
|
@ -472,8 +459,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
# remove indent to prevent the python3 exception
|
# remove indent to prevent the python3 exception
|
||||||
# from leaking into the call
|
# from leaking into the call
|
||||||
result = self._getfixturevalue(fixturedef)
|
result = self._getfixturevalue(fixturedef)
|
||||||
self._fixture_values[(argname, self.scope)] = result
|
self._fixture_values[argname] = result
|
||||||
self._fixture_defs[(argname, self.scope)] = fixturedef
|
self._fixture_defs[argname] = fixturedef
|
||||||
return fixturedef
|
return fixturedef
|
||||||
|
|
||||||
def _get_fixturestack(self):
|
def _get_fixturestack(self):
|
||||||
|
@ -615,16 +602,6 @@ def scopemismatch(currentscope, newscope):
|
||||||
return scopes.index(newscope) > scopes.index(currentscope)
|
return scopes.index(newscope) > scopes.index(currentscope)
|
||||||
|
|
||||||
|
|
||||||
def strip_invocation_scope_suffix(name):
|
|
||||||
"""Remove the invocation-scope suffix from the given name.
|
|
||||||
|
|
||||||
Invocation scope fixtures have their scope in the name of the fixture.
|
|
||||||
For example, "monkeypatch:session". This function strips the suffix
|
|
||||||
returning the user-frienldy name of the fixture.
|
|
||||||
"""
|
|
||||||
return name.split(':')[0]
|
|
||||||
|
|
||||||
|
|
||||||
class FixtureLookupError(LookupError):
|
class FixtureLookupError(LookupError):
|
||||||
""" could not return a requested Fixture (missing or invalid). """
|
""" could not return a requested Fixture (missing or invalid). """
|
||||||
def __init__(self, argname, request, msg=None):
|
def __init__(self, argname, request, msg=None):
|
||||||
|
@ -664,7 +641,6 @@ class FixtureLookupError(LookupError):
|
||||||
parentid = self.request._pyfuncitem.parent.nodeid
|
parentid = self.request._pyfuncitem.parent.nodeid
|
||||||
for name, fixturedefs in fm._arg2fixturedefs.items():
|
for name, fixturedefs in fm._arg2fixturedefs.items():
|
||||||
faclist = list(fm._matchfactories(fixturedefs, parentid))
|
faclist = list(fm._matchfactories(fixturedefs, parentid))
|
||||||
name = strip_invocation_scope_suffix(name)
|
|
||||||
if faclist and name not in available:
|
if faclist and name not in available:
|
||||||
available.append(name)
|
available.append(name)
|
||||||
msg = "fixture %r not found" % (self.argname,)
|
msg = "fixture %r not found" % (self.argname,)
|
||||||
|
@ -847,7 +823,7 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
|
||||||
function will be injected.
|
function will be injected.
|
||||||
|
|
||||||
:arg scope: the scope for which this fixture is shared, one of
|
:arg scope: the scope for which this fixture is shared, one of
|
||||||
"function" (default), "class", "module", "session" or "invocation".
|
"function" (default), "class", "module" or "session".
|
||||||
|
|
||||||
:arg params: an optional list of parameters which will cause multiple
|
:arg params: an optional list of parameters which will cause multiple
|
||||||
invocations of the fixture function and all of the tests
|
invocations of the fixture function and all of the tests
|
||||||
|
@ -1029,11 +1005,6 @@ class FixtureManager:
|
||||||
if fixturedefs:
|
if fixturedefs:
|
||||||
arg2fixturedefs[argname] = fixturedefs
|
arg2fixturedefs[argname] = fixturedefs
|
||||||
merge(fixturedefs[-1].argnames)
|
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
|
return fixturenames_closure, arg2fixturedefs
|
||||||
|
|
||||||
def pytest_generate_tests(self, metafunc):
|
def pytest_generate_tests(self, metafunc):
|
||||||
|
@ -1093,30 +1064,22 @@ class FixtureManager:
|
||||||
'and be decorated with @pytest.fixture:\n%s' % name
|
'and be decorated with @pytest.fixture:\n%s' % name
|
||||||
assert not name.startswith(self._argprefix), msg
|
assert not name.startswith(self._argprefix), msg
|
||||||
|
|
||||||
def new_fixture_def(name, scope):
|
fixture_def = FixtureDef(self, nodeid, name, obj,
|
||||||
"""Create and registers a new FixtureDef with given name and scope."""
|
marker.scope, marker.params,
|
||||||
fixture_def = FixtureDef(self, nodeid, name, obj,
|
unittest=unittest, ids=marker.ids)
|
||||||
scope, marker.params,
|
|
||||||
unittest=unittest, ids=marker.ids)
|
|
||||||
|
|
||||||
faclist = self._arg2fixturedefs.setdefault(name, [])
|
faclist = self._arg2fixturedefs.setdefault(name, [])
|
||||||
if fixture_def.has_location:
|
if fixture_def.has_location:
|
||||||
faclist.append(fixture_def)
|
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:
|
else:
|
||||||
new_fixture_def(name, marker.scope)
|
# 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 autousenames:
|
if autousenames:
|
||||||
self._nodeid_and_autousenames.append((nodeid or '', autousenames))
|
self._nodeid_and_autousenames.append((nodeid or '', autousenames))
|
||||||
|
@ -1141,23 +1104,3 @@ class FixtureManager:
|
||||||
if nodeid.startswith(fixturedef.baseid):
|
if nodeid.startswith(fixturedef.baseid):
|
||||||
yield fixturedef
|
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 (.*)$")
|
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='invocation')
|
@pytest.fixture
|
||||||
def monkeypatch(request):
|
def monkeypatch(request):
|
||||||
"""The returned ``monkeypatch`` fixture provides these
|
"""The returned ``monkeypatch`` fixture provides these
|
||||||
helper methods to modify objects, dictionaries or os.environ::
|
helper methods to modify objects, dictionaries or os.environ::
|
||||||
|
@ -28,8 +28,6 @@ def monkeypatch(request):
|
||||||
test function or fixture has finished. The ``raising``
|
test function or fixture has finished. The ``raising``
|
||||||
parameter determines if a KeyError or AttributeError
|
parameter determines if a KeyError or AttributeError
|
||||||
will be raised if the set/deletion operation has no target.
|
will be raised if the set/deletion operation has no target.
|
||||||
|
|
||||||
This fixture is ``invocation``-scoped.
|
|
||||||
"""
|
"""
|
||||||
mpatch = MonkeyPatch()
|
mpatch = MonkeyPatch()
|
||||||
request.addfinalizer(mpatch.undo)
|
request.addfinalizer(mpatch.undo)
|
||||||
|
|
|
@ -1008,7 +1008,6 @@ def showfixtures(config):
|
||||||
|
|
||||||
def _showfixtures_main(config, session):
|
def _showfixtures_main(config, session):
|
||||||
import _pytest.config
|
import _pytest.config
|
||||||
from _pytest.fixtures import strip_invocation_scope_suffix
|
|
||||||
session.perform_collect()
|
session.perform_collect()
|
||||||
curdir = py.path.local()
|
curdir = py.path.local()
|
||||||
tw = _pytest.config.create_terminal_writer(config)
|
tw = _pytest.config.create_terminal_writer(config)
|
||||||
|
@ -1025,16 +1024,13 @@ def _showfixtures_main(config, session):
|
||||||
continue
|
continue
|
||||||
for fixturedef in fixturedefs:
|
for fixturedef in fixturedefs:
|
||||||
loc = getlocation(fixturedef.func, curdir)
|
loc = getlocation(fixturedef.func, curdir)
|
||||||
# invocation-scoped fixtures have argname in the form
|
if (fixturedef.argname, loc) in seen:
|
||||||
# "<name>:<scope>" (for example: "monkeypatch:session").
|
|
||||||
fixture_argname = strip_invocation_scope_suffix(fixturedef.argname)
|
|
||||||
if (fixture_argname, loc) in seen:
|
|
||||||
continue
|
continue
|
||||||
seen.add((fixture_argname, loc))
|
seen.add((fixturedef.argname, loc))
|
||||||
available.append((len(fixturedef.baseid),
|
available.append((len(fixturedef.baseid),
|
||||||
fixturedef.func.__module__,
|
fixturedef.func.__module__,
|
||||||
curdir.bestrelpath(loc),
|
curdir.bestrelpath(loc),
|
||||||
fixture_argname, fixturedef))
|
fixturedef.argname, fixturedef))
|
||||||
|
|
||||||
available.sort()
|
available.sort()
|
||||||
currentmodule = None
|
currentmodule = None
|
||||||
|
|
|
@ -865,13 +865,6 @@ 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
|
other test classes or functions in the module will not use it unless
|
||||||
they also add a ``transact`` reference.
|
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
|
Shifting (visibility of) fixture functions
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
.. _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.
|
|
||||||
|
|
|
@ -14,8 +14,6 @@ and a discussion of its motivation.
|
||||||
|
|
||||||
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
.. _`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
|
Simple example: monkeypatching functions
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
|
@ -433,8 +433,7 @@ class TestFillFixtures:
|
||||||
"*1 error*",
|
"*1 error*",
|
||||||
])
|
])
|
||||||
assert "INTERNAL" not in result.stdout.str()
|
assert "INTERNAL" not in result.stdout.str()
|
||||||
# invocation-scoped fixture should appear with their friendly name only
|
|
||||||
assert 'monkeypatch:session' not in result.stdout.str()
|
|
||||||
|
|
||||||
def test_fixture_excinfo_leak(self, testdir):
|
def test_fixture_excinfo_leak(self, testdir):
|
||||||
# on python2 sys.excinfo would leak into fixture executions
|
# on python2 sys.excinfo would leak into fixture executions
|
||||||
|
@ -2743,13 +2742,6 @@ class TestShowFixtures:
|
||||||
Hi from test module
|
Hi from test module
|
||||||
''')
|
''')
|
||||||
|
|
||||||
def test_show_invocation_fixtures(self, testdir):
|
|
||||||
"""py.test --fixtures should display invocation-scoped fixtures once.
|
|
||||||
"""
|
|
||||||
result = testdir.runpytest("--fixtures")
|
|
||||||
result.stdout.fnmatch_lines('''monkeypatch''')
|
|
||||||
assert 'monkeypatch:session' not in result.stdout.str()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('flavor', ['fixture', 'yield_fixture'])
|
@pytest.mark.parametrize('flavor', ['fixture', 'yield_fixture'])
|
||||||
class TestContextManagerFixtureFuncs:
|
class TestContextManagerFixtureFuncs:
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
|
|
||||||
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 *')
|
|
||||||
|
|
||||||
|
|
|
@ -331,33 +331,3 @@ def test_issue1338_name_resolving():
|
||||||
monkeypatch.undo()
|
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