Merge pull request #3389 from jonozzz/features
Add package scoped fixtures #2283
This commit is contained in:
commit
1e94ac784f
1
AUTHORS
1
AUTHORS
|
@ -89,6 +89,7 @@ Hugo van Kemenade
|
||||||
Hui Wang (coldnight)
|
Hui Wang (coldnight)
|
||||||
Ian Bicking
|
Ian Bicking
|
||||||
Ian Lesperance
|
Ian Lesperance
|
||||||
|
Ionuț Turturică
|
||||||
Jaap Broekhuizen
|
Jaap Broekhuizen
|
||||||
Jan Balster
|
Jan Balster
|
||||||
Janne Vanhala
|
Janne Vanhala
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
New ``package`` fixture scope: fixtures are finalized when the last test of a *package* finishes. This feature is considered **experimental**, so use it sparingly.
|
|
@ -258,6 +258,22 @@ instance, you can simply declare it:
|
||||||
Finally, the ``class`` scope will invoke the fixture once per test *class*.
|
Finally, the ``class`` scope will invoke the fixture once per test *class*.
|
||||||
|
|
||||||
|
|
||||||
|
``package`` scope (experimental)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
In pytest 3.7 the ``package`` scope has been introduced. Package-scoped fixtures
|
||||||
|
are finalized when the last test of a *package* finishes.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
This functionality is considered **experimental** and may be removed in future
|
||||||
|
versions if hidden corner-cases or serious problems with this functionality
|
||||||
|
are discovered after it gets more usage in the wild.
|
||||||
|
|
||||||
|
Use this new feature sparingly and please make sure to report any issues you find.
|
||||||
|
|
||||||
|
|
||||||
Higher-scoped fixtures are instantiated first
|
Higher-scoped fixtures are instantiated first
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections import OrderedDict, deque, defaultdict
|
from collections import OrderedDict, deque, defaultdict
|
||||||
|
@ -45,6 +46,7 @@ def pytest_sessionstart(session):
|
||||||
|
|
||||||
scopename2class.update(
|
scopename2class.update(
|
||||||
{
|
{
|
||||||
|
"package": _pytest.python.Package,
|
||||||
"class": _pytest.python.Class,
|
"class": _pytest.python.Class,
|
||||||
"module": _pytest.python.Module,
|
"module": _pytest.python.Module,
|
||||||
"function": _pytest.nodes.Item,
|
"function": _pytest.nodes.Item,
|
||||||
|
@ -58,6 +60,7 @@ scopename2class = {}
|
||||||
|
|
||||||
|
|
||||||
scope2props = dict(session=())
|
scope2props = dict(session=())
|
||||||
|
scope2props["package"] = ("fspath",)
|
||||||
scope2props["module"] = ("fspath", "module")
|
scope2props["module"] = ("fspath", "module")
|
||||||
scope2props["class"] = scope2props["module"] + ("cls",)
|
scope2props["class"] = scope2props["module"] + ("cls",)
|
||||||
scope2props["instance"] = scope2props["class"] + ("instance",)
|
scope2props["instance"] = scope2props["class"] + ("instance",)
|
||||||
|
@ -80,6 +83,21 @@ def scopeproperty(name=None, doc=None):
|
||||||
return decoratescope
|
return decoratescope
|
||||||
|
|
||||||
|
|
||||||
|
def get_scope_package(node, fixturedef):
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
cls = pytest.Package
|
||||||
|
current = node
|
||||||
|
fixture_package_name = os.path.join(fixturedef.baseid, "__init__.py")
|
||||||
|
while current and (
|
||||||
|
type(current) is not cls or fixture_package_name != current.nodeid
|
||||||
|
):
|
||||||
|
current = current.parent
|
||||||
|
if current is None:
|
||||||
|
return node.session
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
def get_scope_node(node, scope):
|
def get_scope_node(node, scope):
|
||||||
cls = scopename2class.get(scope)
|
cls = scopename2class.get(scope)
|
||||||
if cls is None:
|
if cls is None:
|
||||||
|
@ -173,9 +191,11 @@ def get_parametrized_fixture_keys(item, scopenum):
|
||||||
continue
|
continue
|
||||||
if scopenum == 0: # session
|
if scopenum == 0: # session
|
||||||
key = (argname, param_index)
|
key = (argname, param_index)
|
||||||
elif scopenum == 1: # module
|
elif scopenum == 1: # package
|
||||||
|
key = (argname, param_index, item.fspath.dirpath())
|
||||||
|
elif scopenum == 2: # module
|
||||||
key = (argname, param_index, item.fspath)
|
key = (argname, param_index, item.fspath)
|
||||||
elif scopenum == 2: # class
|
elif scopenum == 3: # class
|
||||||
key = (argname, param_index, item.fspath, item.cls)
|
key = (argname, param_index, item.fspath, item.cls)
|
||||||
yield key
|
yield key
|
||||||
|
|
||||||
|
@ -612,7 +632,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
if scope == "function":
|
if scope == "function":
|
||||||
# this might also be a non-function Item despite its attribute name
|
# this might also be a non-function Item despite its attribute name
|
||||||
return self._pyfuncitem
|
return self._pyfuncitem
|
||||||
node = get_scope_node(self._pyfuncitem, scope)
|
if scope == "package":
|
||||||
|
node = get_scope_package(self._pyfuncitem, self._fixturedef)
|
||||||
|
else:
|
||||||
|
node = get_scope_node(self._pyfuncitem, scope)
|
||||||
if node is None and scope == "class":
|
if node is None and scope == "class":
|
||||||
# fallback to function item itself
|
# fallback to function item itself
|
||||||
node = self._pyfuncitem
|
node = self._pyfuncitem
|
||||||
|
@ -656,7 +679,7 @@ class ScopeMismatchError(Exception):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
scopes = "session module class function".split()
|
scopes = "session package module class function".split()
|
||||||
scopenum_function = scopes.index("function")
|
scopenum_function = scopes.index("function")
|
||||||
|
|
||||||
|
|
||||||
|
@ -937,16 +960,27 @@ class FixtureFunctionMarker(object):
|
||||||
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
|
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
|
||||||
"""Decorator to mark a fixture factory function.
|
"""Decorator to mark a fixture factory function.
|
||||||
|
|
||||||
This decorator can be used (with or without parameters) to define a
|
This decorator can be used, with or without parameters, to define a
|
||||||
fixture function. The name of the fixture function can later be
|
fixture function.
|
||||||
referenced to cause its invocation ahead of running tests: test
|
|
||||||
modules or classes can use the pytest.mark.usefixtures(fixturename)
|
The name of the fixture function can later be referenced to cause its
|
||||||
marker. Test functions can directly use fixture names as input
|
invocation ahead of running tests: test
|
||||||
|
modules or classes can use the ``pytest.mark.usefixtures(fixturename)``
|
||||||
|
marker.
|
||||||
|
|
||||||
|
Test functions can directly use fixture names as input
|
||||||
arguments in which case the fixture instance returned from the fixture
|
arguments in which case the fixture instance returned from the fixture
|
||||||
function will be injected.
|
function will be injected.
|
||||||
|
|
||||||
|
Fixtures can provide their values to test functions using ``return`` or ``yield``
|
||||||
|
statements. When using ``yield`` the code block after the ``yield`` statement is executed
|
||||||
|
as teardown code regardless of the test outcome, and must yield exactly once.
|
||||||
|
|
||||||
: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" or "session".
|
``"function"`` (default), ``"class"``, ``"module"``,
|
||||||
|
``"package"`` or ``"session"``.
|
||||||
|
|
||||||
|
``"package"`` is considered **experimental** at this time.
|
||||||
|
|
||||||
: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
|
||||||
|
@ -967,10 +1001,6 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
|
||||||
to resolve this is to name the decorated function
|
to resolve this is to name the decorated function
|
||||||
``fixture_<fixturename>`` and then use
|
``fixture_<fixturename>`` and then use
|
||||||
``@pytest.fixture(name='<fixturename>')``.
|
``@pytest.fixture(name='<fixturename>')``.
|
||||||
|
|
||||||
Fixtures can optionally provide their values to test functions using a ``yield`` statement,
|
|
||||||
instead of ``return``. In this case, the code block after the ``yield`` statement is executed
|
|
||||||
as teardown code regardless of the test outcome. A fixture function must yield exactly once.
|
|
||||||
"""
|
"""
|
||||||
if callable(scope) and params is None and autouse is False:
|
if callable(scope) and params is None and autouse is False:
|
||||||
# direct decoration
|
# direct decoration
|
||||||
|
|
|
@ -383,6 +383,8 @@ class Session(nodes.FSCollector):
|
||||||
self.trace = config.trace.root.get("collection")
|
self.trace = config.trace.root.get("collection")
|
||||||
self._norecursepatterns = config.getini("norecursedirs")
|
self._norecursepatterns = config.getini("norecursedirs")
|
||||||
self.startdir = py.path.local()
|
self.startdir = py.path.local()
|
||||||
|
# Keep track of any collected nodes in here, so we don't duplicate fixtures
|
||||||
|
self._node_cache = {}
|
||||||
|
|
||||||
self.config.pluginmanager.register(self, name="session")
|
self.config.pluginmanager.register(self, name="session")
|
||||||
|
|
||||||
|
@ -481,18 +483,61 @@ class Session(nodes.FSCollector):
|
||||||
|
|
||||||
def _collect(self, arg):
|
def _collect(self, arg):
|
||||||
names = self._parsearg(arg)
|
names = self._parsearg(arg)
|
||||||
path = names.pop(0)
|
argpath = names.pop(0)
|
||||||
if path.check(dir=1):
|
paths = []
|
||||||
|
|
||||||
|
root = self
|
||||||
|
# Start with a Session root, and delve to argpath item (dir or file)
|
||||||
|
# and stack all Packages found on the way.
|
||||||
|
# No point in finding packages when collecting doctests
|
||||||
|
if not self.config.option.doctestmodules:
|
||||||
|
for parent in argpath.parts():
|
||||||
|
pm = self.config.pluginmanager
|
||||||
|
if pm._confcutdir and pm._confcutdir.relto(parent):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if parent.isdir():
|
||||||
|
pkginit = parent.join("__init__.py")
|
||||||
|
if pkginit.isfile():
|
||||||
|
if pkginit in self._node_cache:
|
||||||
|
root = self._node_cache[pkginit]
|
||||||
|
else:
|
||||||
|
col = root._collectfile(pkginit)
|
||||||
|
if col:
|
||||||
|
root = col[0]
|
||||||
|
self._node_cache[root.fspath] = root
|
||||||
|
|
||||||
|
# If it's a directory argument, recurse and look for any Subpackages.
|
||||||
|
# Let the Package collector deal with subnodes, don't collect here.
|
||||||
|
if argpath.check(dir=1):
|
||||||
assert not names, "invalid arg %r" % (arg,)
|
assert not names, "invalid arg %r" % (arg,)
|
||||||
for path in path.visit(
|
for path in argpath.visit(
|
||||||
fil=lambda x: x.check(file=1), rec=self._recurse, bf=True, sort=True
|
fil=lambda x: x.check(file=1), rec=self._recurse, bf=True, sort=True
|
||||||
):
|
):
|
||||||
for x in self._collectfile(path):
|
pkginit = path.dirpath().join("__init__.py")
|
||||||
yield x
|
if pkginit.exists() and not any(x in pkginit.parts() for x in paths):
|
||||||
|
for x in root._collectfile(pkginit):
|
||||||
|
yield x
|
||||||
|
paths.append(x.fspath.dirpath())
|
||||||
|
|
||||||
|
if not any(x in path.parts() for x in paths):
|
||||||
|
for x in root._collectfile(path):
|
||||||
|
if (type(x), x.fspath) in self._node_cache:
|
||||||
|
yield self._node_cache[(type(x), x.fspath)]
|
||||||
|
else:
|
||||||
|
yield x
|
||||||
|
self._node_cache[(type(x), x.fspath)] = x
|
||||||
else:
|
else:
|
||||||
assert path.check(file=1)
|
assert argpath.check(file=1)
|
||||||
for x in self.matchnodes(self._collectfile(path), names):
|
|
||||||
yield x
|
if argpath in self._node_cache:
|
||||||
|
col = self._node_cache[argpath]
|
||||||
|
else:
|
||||||
|
col = root._collectfile(argpath)
|
||||||
|
if col:
|
||||||
|
self._node_cache[argpath] = col
|
||||||
|
for y in self.matchnodes(col, names):
|
||||||
|
yield y
|
||||||
|
|
||||||
def _collectfile(self, path):
|
def _collectfile(self, path):
|
||||||
ihook = self.gethookproxy(path)
|
ihook = self.gethookproxy(path)
|
||||||
|
@ -577,7 +622,11 @@ class Session(nodes.FSCollector):
|
||||||
resultnodes.append(node)
|
resultnodes.append(node)
|
||||||
continue
|
continue
|
||||||
assert isinstance(node, nodes.Collector)
|
assert isinstance(node, nodes.Collector)
|
||||||
rep = collect_one_node(node)
|
if node.nodeid in self._node_cache:
|
||||||
|
rep = self._node_cache[node.nodeid]
|
||||||
|
else:
|
||||||
|
rep = collect_one_node(node)
|
||||||
|
self._node_cache[node.nodeid] = rep
|
||||||
if rep.passed:
|
if rep.passed:
|
||||||
has_matched = False
|
has_matched = False
|
||||||
for x in rep.result:
|
for x in rep.result:
|
||||||
|
|
|
@ -358,7 +358,7 @@ class FSCollector(Collector):
|
||||||
|
|
||||||
if not nodeid:
|
if not nodeid:
|
||||||
nodeid = _check_initialpaths_for_relpath(session, fspath)
|
nodeid = _check_initialpaths_for_relpath(session, fspath)
|
||||||
if os.sep != SEP:
|
if nodeid and os.sep != SEP:
|
||||||
nodeid = nodeid.replace(os.sep, SEP)
|
nodeid = nodeid.replace(os.sep, SEP)
|
||||||
|
|
||||||
super(FSCollector, self).__init__(
|
super(FSCollector, self).__init__(
|
||||||
|
|
|
@ -13,6 +13,7 @@ from itertools import count
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
|
from _pytest.main import FSHookProxy
|
||||||
from _pytest.mark import MarkerError
|
from _pytest.mark import MarkerError
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
|
|
||||||
|
@ -201,7 +202,7 @@ def pytest_collect_file(path, parent):
|
||||||
ext = path.ext
|
ext = path.ext
|
||||||
if ext == ".py":
|
if ext == ".py":
|
||||||
if not parent.session.isinitpath(path):
|
if not parent.session.isinitpath(path):
|
||||||
for pat in parent.config.getini("python_files"):
|
for pat in parent.config.getini("python_files") + ["__init__.py"]:
|
||||||
if path.fnmatch(pat):
|
if path.fnmatch(pat):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -211,9 +212,23 @@ def pytest_collect_file(path, parent):
|
||||||
|
|
||||||
|
|
||||||
def pytest_pycollect_makemodule(path, parent):
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
|
if path.basename == "__init__.py":
|
||||||
|
return Package(path, parent)
|
||||||
return Module(path, parent)
|
return Module(path, parent)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_ignore_collect(path, config):
|
||||||
|
# Skip duplicate packages.
|
||||||
|
keepduplicates = config.getoption("keepduplicates")
|
||||||
|
if keepduplicates:
|
||||||
|
duplicate_paths = config.pluginmanager._duplicatepaths
|
||||||
|
if path.basename == "__init__.py":
|
||||||
|
if path in duplicate_paths:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
duplicate_paths.add(path)
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(hookwrapper=True)
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
outcome = yield
|
outcome = yield
|
||||||
|
@ -531,6 +546,66 @@ class Module(nodes.File, PyCollector):
|
||||||
self.addfinalizer(teardown_module)
|
self.addfinalizer(teardown_module)
|
||||||
|
|
||||||
|
|
||||||
|
class Package(Module):
|
||||||
|
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
|
||||||
|
session = parent.session
|
||||||
|
nodes.FSCollector.__init__(
|
||||||
|
self, fspath, parent=parent, config=config, session=session, nodeid=nodeid
|
||||||
|
)
|
||||||
|
self.name = fspath.dirname
|
||||||
|
self.trace = session.trace
|
||||||
|
self._norecursepatterns = session._norecursepatterns
|
||||||
|
for path in list(session.config.pluginmanager._duplicatepaths):
|
||||||
|
if path.dirname == fspath.dirname and path != fspath:
|
||||||
|
session.config.pluginmanager._duplicatepaths.remove(path)
|
||||||
|
|
||||||
|
def _recurse(self, path):
|
||||||
|
ihook = self.gethookproxy(path.dirpath())
|
||||||
|
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||||
|
return
|
||||||
|
for pat in self._norecursepatterns:
|
||||||
|
if path.check(fnmatch=pat):
|
||||||
|
return False
|
||||||
|
ihook = self.gethookproxy(path)
|
||||||
|
ihook.pytest_collect_directory(path=path, parent=self)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def gethookproxy(self, fspath):
|
||||||
|
# check if we have the common case of running
|
||||||
|
# hooks with all conftest.py filesall conftest.py
|
||||||
|
pm = self.config.pluginmanager
|
||||||
|
my_conftestmodules = pm._getconftestmodules(fspath)
|
||||||
|
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
|
||||||
|
if remove_mods:
|
||||||
|
# one or more conftests are not in use at this fspath
|
||||||
|
proxy = FSHookProxy(fspath, pm, remove_mods)
|
||||||
|
else:
|
||||||
|
# all plugis are active for this fspath
|
||||||
|
proxy = self.config.hook
|
||||||
|
return proxy
|
||||||
|
|
||||||
|
def _collectfile(self, path):
|
||||||
|
ihook = self.gethookproxy(path)
|
||||||
|
if not self.isinitpath(path):
|
||||||
|
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||||
|
return ()
|
||||||
|
return ihook.pytest_collect_file(path=path, parent=self)
|
||||||
|
|
||||||
|
def isinitpath(self, path):
|
||||||
|
return path in self.session._initialpaths
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
path = self.fspath.dirpath()
|
||||||
|
pkg_prefix = None
|
||||||
|
for path in path.visit(fil=lambda x: 1, rec=self._recurse, bf=True, sort=True):
|
||||||
|
if pkg_prefix and pkg_prefix in path.parts():
|
||||||
|
continue
|
||||||
|
for x in self._collectfile(path):
|
||||||
|
yield x
|
||||||
|
if isinstance(x, Package):
|
||||||
|
pkg_prefix = path.dirpath()
|
||||||
|
|
||||||
|
|
||||||
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
||||||
"""
|
"""
|
||||||
Return a callable to perform xunit-style setup or teardown if
|
Return a callable to perform xunit-style setup or teardown if
|
||||||
|
|
|
@ -18,7 +18,7 @@ from _pytest.mark import MARK_GEN as mark, param
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
from _pytest.nodes import Item, Collector, File
|
from _pytest.nodes import Item, Collector, File
|
||||||
from _pytest.fixtures import fillfixtures as _fillfuncargs
|
from _pytest.fixtures import fillfixtures as _fillfuncargs
|
||||||
from _pytest.python import Module, Class, Instance, Function, Generator
|
from _pytest.python import Package, Module, Class, Instance, Function, Generator
|
||||||
|
|
||||||
from _pytest.python_api import approx, raises
|
from _pytest.python_api import approx, raises
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ __all__ = [
|
||||||
"Item",
|
"Item",
|
||||||
"File",
|
"File",
|
||||||
"Collector",
|
"Collector",
|
||||||
|
"Package",
|
||||||
"Session",
|
"Session",
|
||||||
"Module",
|
"Module",
|
||||||
"Class",
|
"Class",
|
||||||
|
|
|
@ -1078,7 +1078,7 @@ def test_setup_only_available_in_subdir(testdir):
|
||||||
|
|
||||||
|
|
||||||
def test_modulecol_roundtrip(testdir):
|
def test_modulecol_roundtrip(testdir):
|
||||||
modcol = testdir.getmodulecol("pass", withinit=True)
|
modcol = testdir.getmodulecol("pass", withinit=False)
|
||||||
trail = modcol.nodeid
|
trail = modcol.nodeid
|
||||||
newcol = modcol.session.perform_collect([trail], genitems=0)[0]
|
newcol = modcol.session.perform_collect([trail], genitems=0)[0]
|
||||||
assert modcol.name == newcol.name
|
assert modcol.name == newcol.name
|
||||||
|
|
|
@ -1658,6 +1658,97 @@ class TestFixtureManagerParseFactories(object):
|
||||||
reprec = testdir.inline_run("..")
|
reprec = testdir.inline_run("..")
|
||||||
reprec.assertoutcome(passed=2)
|
reprec.assertoutcome(passed=2)
|
||||||
|
|
||||||
|
def test_package_xunit_fixture(self, testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
__init__="""\
|
||||||
|
values = []
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
package = testdir.mkdir("package")
|
||||||
|
package.join("__init__.py").write(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
from .. import values
|
||||||
|
def setup_module():
|
||||||
|
values.append("package")
|
||||||
|
def teardown_module():
|
||||||
|
values[:] = []
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
package.join("test_x.py").write(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
from .. import values
|
||||||
|
def test_x():
|
||||||
|
assert values == ["package"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
package = testdir.mkdir("package2")
|
||||||
|
package.join("__init__.py").write(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
from .. import values
|
||||||
|
def setup_module():
|
||||||
|
values.append("package2")
|
||||||
|
def teardown_module():
|
||||||
|
values[:] = []
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
package.join("test_x.py").write(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
from .. import values
|
||||||
|
def test_x():
|
||||||
|
assert values == ["package2"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(passed=2)
|
||||||
|
|
||||||
|
def test_package_fixture_complex(self, testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
__init__="""\
|
||||||
|
values = []
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
package = testdir.mkdir("package")
|
||||||
|
package.join("__init__.py").write("")
|
||||||
|
package.join("conftest.py").write(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
import pytest
|
||||||
|
from .. import values
|
||||||
|
@pytest.fixture(scope="package")
|
||||||
|
def one():
|
||||||
|
values.append("package")
|
||||||
|
yield values
|
||||||
|
values.pop()
|
||||||
|
@pytest.fixture(scope="package", autouse=True)
|
||||||
|
def two():
|
||||||
|
values.append("package-auto")
|
||||||
|
yield values
|
||||||
|
values.pop()
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
package.join("test_x.py").write(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
from .. import values
|
||||||
|
def test_package_autouse():
|
||||||
|
assert values == ["package-auto"]
|
||||||
|
def test_package(one):
|
||||||
|
assert values == ["package-auto", "package"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(passed=2)
|
||||||
|
|
||||||
|
|
||||||
class TestAutouseDiscovery(object):
|
class TestAutouseDiscovery(object):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -3833,6 +3924,10 @@ class TestScopeOrdering(object):
|
||||||
def s1():
|
def s1():
|
||||||
FIXTURE_ORDER.append('s1')
|
FIXTURE_ORDER.append('s1')
|
||||||
|
|
||||||
|
@pytest.fixture(scope="package")
|
||||||
|
def p1():
|
||||||
|
FIXTURE_ORDER.append('p1')
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def m1():
|
def m1():
|
||||||
FIXTURE_ORDER.append('m1')
|
FIXTURE_ORDER.append('m1')
|
||||||
|
@ -3853,16 +3948,20 @@ class TestScopeOrdering(object):
|
||||||
def f2():
|
def f2():
|
||||||
FIXTURE_ORDER.append('f2')
|
FIXTURE_ORDER.append('f2')
|
||||||
|
|
||||||
def test_foo(f1, m1, f2, s1): pass
|
def test_foo(f1, p1, m1, f2, s1): pass
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
items, _ = testdir.inline_genitems()
|
items, _ = testdir.inline_genitems()
|
||||||
request = FixtureRequest(items[0])
|
request = FixtureRequest(items[0])
|
||||||
# order of fixtures based on their scope and position in the parameter list
|
# order of fixtures based on their scope and position in the parameter list
|
||||||
assert request.fixturenames == "s1 my_tmpdir_factory m1 f1 f2 my_tmpdir".split()
|
assert (
|
||||||
|
request.fixturenames == "s1 my_tmpdir_factory p1 m1 f1 f2 my_tmpdir".split()
|
||||||
|
)
|
||||||
testdir.runpytest()
|
testdir.runpytest()
|
||||||
# actual fixture execution differs: dependent fixtures must be created first ("my_tmpdir")
|
# actual fixture execution differs: dependent fixtures must be created first ("my_tmpdir")
|
||||||
assert pytest.FIXTURE_ORDER == "s1 my_tmpdir_factory m1 my_tmpdir f1 f2".split()
|
assert (
|
||||||
|
pytest.FIXTURE_ORDER == "s1 my_tmpdir_factory p1 m1 my_tmpdir f1 f2".split()
|
||||||
|
)
|
||||||
|
|
||||||
def test_func_closure_module(self, testdir):
|
def test_func_closure_module(self, testdir):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
|
@ -3931,9 +4030,13 @@ class TestScopeOrdering(object):
|
||||||
"sub/conftest.py": """
|
"sub/conftest.py": """
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope='package', autouse=True)
|
||||||
|
def p_sub(): pass
|
||||||
|
|
||||||
@pytest.fixture(scope='module', autouse=True)
|
@pytest.fixture(scope='module', autouse=True)
|
||||||
def m_sub(): pass
|
def m_sub(): pass
|
||||||
""",
|
""",
|
||||||
|
"sub/__init__.py": "",
|
||||||
"sub/test_func.py": """
|
"sub/test_func.py": """
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -3950,7 +4053,7 @@ class TestScopeOrdering(object):
|
||||||
)
|
)
|
||||||
items, _ = testdir.inline_genitems()
|
items, _ = testdir.inline_genitems()
|
||||||
request = FixtureRequest(items[0])
|
request = FixtureRequest(items[0])
|
||||||
assert request.fixturenames == "m_conf m_sub m_test f1".split()
|
assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split()
|
||||||
|
|
||||||
def test_func_closure_all_scopes_complex(self, testdir):
|
def test_func_closure_all_scopes_complex(self, testdir):
|
||||||
"""Complex test involving all scopes and mixing autouse with normal fixtures"""
|
"""Complex test involving all scopes and mixing autouse with normal fixtures"""
|
||||||
|
@ -3960,8 +4063,12 @@ class TestScopeOrdering(object):
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def s1(): pass
|
def s1(): pass
|
||||||
|
|
||||||
|
@pytest.fixture(scope='package', autouse=True)
|
||||||
|
def p1(): pass
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
testdir.makepyfile(**{"__init__.py": ""})
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -3990,4 +4097,4 @@ class TestScopeOrdering(object):
|
||||||
)
|
)
|
||||||
items, _ = testdir.inline_genitems()
|
items, _ = testdir.inline_genitems()
|
||||||
request = FixtureRequest(items[0])
|
request = FixtureRequest(items[0])
|
||||||
assert request.fixturenames == "s1 m1 m2 c1 f2 f1".split()
|
assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split()
|
||||||
|
|
|
@ -647,7 +647,7 @@ class Test_getinitialnodes(object):
|
||||||
col = testdir.getnode(config, x)
|
col = testdir.getnode(config, x)
|
||||||
assert isinstance(col, pytest.Module)
|
assert isinstance(col, pytest.Module)
|
||||||
assert col.name == "x.py"
|
assert col.name == "x.py"
|
||||||
assert col.parent.parent is None
|
assert col.parent.parent.parent is None
|
||||||
for col in col.listchain():
|
for col in col.listchain():
|
||||||
assert col.config is config
|
assert col.config is config
|
||||||
|
|
||||||
|
@ -904,7 +904,7 @@ def test_continue_on_collection_errors_maxfail(testdir):
|
||||||
|
|
||||||
def test_fixture_scope_sibling_conftests(testdir):
|
def test_fixture_scope_sibling_conftests(testdir):
|
||||||
"""Regression test case for https://github.com/pytest-dev/pytest/issues/2836"""
|
"""Regression test case for https://github.com/pytest-dev/pytest/issues/2836"""
|
||||||
foo_path = testdir.mkpydir("foo")
|
foo_path = testdir.mkdir("foo")
|
||||||
foo_path.join("conftest.py").write(
|
foo_path.join("conftest.py").write(
|
||||||
_pytest._code.Source(
|
_pytest._code.Source(
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue