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)
|
||||
Ian Bicking
|
||||
Ian Lesperance
|
||||
Ionuț Turturică
|
||||
Jaap Broekhuizen
|
||||
Jan Balster
|
||||
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*.
|
||||
|
||||
|
||||
``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
|
||||
---------------------------------------------
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function
|
|||
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from collections import OrderedDict, deque, defaultdict
|
||||
|
@ -45,6 +46,7 @@ def pytest_sessionstart(session):
|
|||
|
||||
scopename2class.update(
|
||||
{
|
||||
"package": _pytest.python.Package,
|
||||
"class": _pytest.python.Class,
|
||||
"module": _pytest.python.Module,
|
||||
"function": _pytest.nodes.Item,
|
||||
|
@ -58,6 +60,7 @@ scopename2class = {}
|
|||
|
||||
|
||||
scope2props = dict(session=())
|
||||
scope2props["package"] = ("fspath",)
|
||||
scope2props["module"] = ("fspath", "module")
|
||||
scope2props["class"] = scope2props["module"] + ("cls",)
|
||||
scope2props["instance"] = scope2props["class"] + ("instance",)
|
||||
|
@ -80,6 +83,21 @@ def scopeproperty(name=None, doc=None):
|
|||
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):
|
||||
cls = scopename2class.get(scope)
|
||||
if cls is None:
|
||||
|
@ -173,9 +191,11 @@ def get_parametrized_fixture_keys(item, scopenum):
|
|||
continue
|
||||
if scopenum == 0: # session
|
||||
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)
|
||||
elif scopenum == 2: # class
|
||||
elif scopenum == 3: # class
|
||||
key = (argname, param_index, item.fspath, item.cls)
|
||||
yield key
|
||||
|
||||
|
@ -612,7 +632,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
|||
if scope == "function":
|
||||
# this might also be a non-function Item despite its attribute name
|
||||
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":
|
||||
# fallback to function item itself
|
||||
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")
|
||||
|
||||
|
||||
|
@ -937,16 +960,27 @@ class FixtureFunctionMarker(object):
|
|||
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
|
||||
"""Decorator to mark a fixture factory function.
|
||||
|
||||
This decorator can be used (with or without parameters) to define a
|
||||
fixture function. The name of the fixture function can later be
|
||||
referenced to cause its 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
|
||||
This decorator can be used, with or without parameters, to define a
|
||||
fixture function.
|
||||
|
||||
The name of the fixture function can later be referenced to cause its
|
||||
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
|
||||
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
|
||||
"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
|
||||
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
|
||||
``fixture_<fixturename>`` and then use
|
||||
``@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:
|
||||
# direct decoration
|
||||
|
|
|
@ -383,6 +383,8 @@ class Session(nodes.FSCollector):
|
|||
self.trace = config.trace.root.get("collection")
|
||||
self._norecursepatterns = config.getini("norecursedirs")
|
||||
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")
|
||||
|
||||
|
@ -481,18 +483,61 @@ class Session(nodes.FSCollector):
|
|||
|
||||
def _collect(self, arg):
|
||||
names = self._parsearg(arg)
|
||||
path = names.pop(0)
|
||||
if path.check(dir=1):
|
||||
argpath = names.pop(0)
|
||||
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,)
|
||||
for path in path.visit(
|
||||
for path in argpath.visit(
|
||||
fil=lambda x: x.check(file=1), rec=self._recurse, bf=True, sort=True
|
||||
):
|
||||
for x in self._collectfile(path):
|
||||
yield x
|
||||
pkginit = path.dirpath().join("__init__.py")
|
||||
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:
|
||||
assert path.check(file=1)
|
||||
for x in self.matchnodes(self._collectfile(path), names):
|
||||
yield x
|
||||
assert argpath.check(file=1)
|
||||
|
||||
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):
|
||||
ihook = self.gethookproxy(path)
|
||||
|
@ -577,7 +622,11 @@ class Session(nodes.FSCollector):
|
|||
resultnodes.append(node)
|
||||
continue
|
||||
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:
|
||||
has_matched = False
|
||||
for x in rep.result:
|
||||
|
|
|
@ -358,7 +358,7 @@ class FSCollector(Collector):
|
|||
|
||||
if not nodeid:
|
||||
nodeid = _check_initialpaths_for_relpath(session, fspath)
|
||||
if os.sep != SEP:
|
||||
if nodeid and os.sep != SEP:
|
||||
nodeid = nodeid.replace(os.sep, SEP)
|
||||
|
||||
super(FSCollector, self).__init__(
|
||||
|
|
|
@ -13,6 +13,7 @@ from itertools import count
|
|||
|
||||
import py
|
||||
import six
|
||||
from _pytest.main import FSHookProxy
|
||||
from _pytest.mark import MarkerError
|
||||
from _pytest.config import hookimpl
|
||||
|
||||
|
@ -201,7 +202,7 @@ def pytest_collect_file(path, parent):
|
|||
ext = path.ext
|
||||
if ext == ".py":
|
||||
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):
|
||||
break
|
||||
else:
|
||||
|
@ -211,9 +212,23 @@ def pytest_collect_file(path, parent):
|
|||
|
||||
|
||||
def pytest_pycollect_makemodule(path, parent):
|
||||
if path.basename == "__init__.py":
|
||||
return Package(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)
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
outcome = yield
|
||||
|
@ -531,6 +546,66 @@ class Module(nodes.File, PyCollector):
|
|||
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):
|
||||
"""
|
||||
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.nodes import Item, Collector, File
|
||||
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
|
||||
|
||||
|
@ -50,6 +50,7 @@ __all__ = [
|
|||
"Item",
|
||||
"File",
|
||||
"Collector",
|
||||
"Package",
|
||||
"Session",
|
||||
"Module",
|
||||
"Class",
|
||||
|
|
|
@ -1078,7 +1078,7 @@ def test_setup_only_available_in_subdir(testdir):
|
|||
|
||||
|
||||
def test_modulecol_roundtrip(testdir):
|
||||
modcol = testdir.getmodulecol("pass", withinit=True)
|
||||
modcol = testdir.getmodulecol("pass", withinit=False)
|
||||
trail = modcol.nodeid
|
||||
newcol = modcol.session.perform_collect([trail], genitems=0)[0]
|
||||
assert modcol.name == newcol.name
|
||||
|
|
|
@ -1658,6 +1658,97 @@ class TestFixtureManagerParseFactories(object):
|
|||
reprec = testdir.inline_run("..")
|
||||
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):
|
||||
@pytest.fixture
|
||||
|
@ -3833,6 +3924,10 @@ class TestScopeOrdering(object):
|
|||
def s1():
|
||||
FIXTURE_ORDER.append('s1')
|
||||
|
||||
@pytest.fixture(scope="package")
|
||||
def p1():
|
||||
FIXTURE_ORDER.append('p1')
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def m1():
|
||||
FIXTURE_ORDER.append('m1')
|
||||
|
@ -3853,16 +3948,20 @@ class TestScopeOrdering(object):
|
|||
def 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()
|
||||
request = FixtureRequest(items[0])
|
||||
# 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()
|
||||
# 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):
|
||||
testdir.makepyfile(
|
||||
|
@ -3931,9 +4030,13 @@ class TestScopeOrdering(object):
|
|||
"sub/conftest.py": """
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='package', autouse=True)
|
||||
def p_sub(): pass
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def m_sub(): pass
|
||||
""",
|
||||
"sub/__init__.py": "",
|
||||
"sub/test_func.py": """
|
||||
import pytest
|
||||
|
||||
|
@ -3950,7 +4053,7 @@ class TestScopeOrdering(object):
|
|||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
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):
|
||||
"""Complex test involving all scopes and mixing autouse with normal fixtures"""
|
||||
|
@ -3960,8 +4063,12 @@ class TestScopeOrdering(object):
|
|||
|
||||
@pytest.fixture(scope='session')
|
||||
def s1(): pass
|
||||
|
||||
@pytest.fixture(scope='package', autouse=True)
|
||||
def p1(): pass
|
||||
"""
|
||||
)
|
||||
testdir.makepyfile(**{"__init__.py": ""})
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
@ -3990,4 +4097,4 @@ class TestScopeOrdering(object):
|
|||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
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)
|
||||
assert isinstance(col, pytest.Module)
|
||||
assert col.name == "x.py"
|
||||
assert col.parent.parent is None
|
||||
assert col.parent.parent.parent is None
|
||||
for col in col.listchain():
|
||||
assert col.config is config
|
||||
|
||||
|
@ -904,7 +904,7 @@ def test_continue_on_collection_errors_maxfail(testdir):
|
|||
|
||||
def test_fixture_scope_sibling_conftests(testdir):
|
||||
"""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(
|
||||
_pytest._code.Source(
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue