only consider matching conftest plugins for discovering hooks related to collection nodes.
--HG-- branch : trunk
This commit is contained in:
parent
9d01975c78
commit
631dfe9f13
|
@ -9,6 +9,10 @@ Changes between 1.X and 1.1.1
|
||||||
|
|
||||||
- new "pytestconfig" funcarg allows access to test config object
|
- new "pytestconfig" funcarg allows access to test config object
|
||||||
|
|
||||||
|
- collection/item node specific runtest/collect hooks are only called exactly
|
||||||
|
on matching conftest.py files, i.e. ones which are exactly below
|
||||||
|
the filesystem path of an item
|
||||||
|
|
||||||
- robustify capturing to survive if custom pytest_runtest_setup
|
- robustify capturing to survive if custom pytest_runtest_setup
|
||||||
code failed and prevented the capturing setup code from running.
|
code failed and prevented the capturing setup code from running.
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,18 @@ def configproperty(name):
|
||||||
return self.config._getcollectclass(name, self.fspath)
|
return self.config._getcollectclass(name, self.fspath)
|
||||||
return property(fget)
|
return property(fget)
|
||||||
|
|
||||||
|
class HookProxy:
|
||||||
|
def __init__(self, node):
|
||||||
|
self.node = node
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name[0] == "_":
|
||||||
|
raise AttributeError(name)
|
||||||
|
hookmethod = getattr(self.node.config.hook, name)
|
||||||
|
def call_matching_hooks(**kwargs):
|
||||||
|
plugins = self.node.config.getmatchingplugins(self.node.fspath)
|
||||||
|
return hookmethod.pcall(plugins, **kwargs)
|
||||||
|
return call_matching_hooks
|
||||||
|
|
||||||
class Node(object):
|
class Node(object):
|
||||||
""" base class for Nodes in the collection tree.
|
""" base class for Nodes in the collection tree.
|
||||||
Collector nodes have children and
|
Collector nodes have children and
|
||||||
|
@ -29,6 +41,7 @@ class Node(object):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.config = getattr(parent, 'config', None)
|
self.config = getattr(parent, 'config', None)
|
||||||
self.fspath = getattr(parent, 'fspath', None)
|
self.fspath = getattr(parent, 'fspath', None)
|
||||||
|
self.ihook = HookProxy(self)
|
||||||
|
|
||||||
def _checkcollectable(self):
|
def _checkcollectable(self):
|
||||||
if not hasattr(self, 'fspath'):
|
if not hasattr(self, 'fspath'):
|
||||||
|
@ -426,13 +439,12 @@ class Directory(FSCollector):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def consider_file(self, path):
|
def consider_file(self, path):
|
||||||
return self.config.hook.pytest_collect_file(path=path, parent=self)
|
return self.ihook.pytest_collect_file(path=path, parent=self)
|
||||||
|
|
||||||
def consider_dir(self, path, usefilters=None):
|
def consider_dir(self, path, usefilters=None):
|
||||||
if usefilters is not None:
|
if usefilters is not None:
|
||||||
py.log._apiwarn("0.99", "usefilters argument not needed")
|
py.log._apiwarn("0.99", "usefilters argument not needed")
|
||||||
return self.config.hook.pytest_collect_directory(
|
return self.ihook.pytest_collect_directory(path=path, parent=self)
|
||||||
path=path, parent=self)
|
|
||||||
|
|
||||||
class Item(Node):
|
class Item(Node):
|
||||||
""" a basic test item. """
|
""" a basic test item. """
|
||||||
|
|
|
@ -45,6 +45,13 @@ class Config(object):
|
||||||
self.trace("loaded conftestmodule %r" %(conftestmodule,))
|
self.trace("loaded conftestmodule %r" %(conftestmodule,))
|
||||||
self.pluginmanager.consider_conftest(conftestmodule)
|
self.pluginmanager.consider_conftest(conftestmodule)
|
||||||
|
|
||||||
|
def getmatchingplugins(self, fspath):
|
||||||
|
conftests = self._conftest._conftestpath2mod.values()
|
||||||
|
plugins = [x for x in self.pluginmanager.getplugins()
|
||||||
|
if x not in conftests]
|
||||||
|
plugins += self._conftest.getconftestmodules(fspath)
|
||||||
|
return plugins
|
||||||
|
|
||||||
def trace(self, msg):
|
def trace(self, msg):
|
||||||
if getattr(self.option, 'traceconfig', None):
|
if getattr(self.option, 'traceconfig', None):
|
||||||
self.hook.pytest_trace(category="config", msg=msg)
|
self.hook.pytest_trace(category="config", msg=msg)
|
||||||
|
|
|
@ -11,6 +11,7 @@ class Conftest(object):
|
||||||
def __init__(self, onimport=None):
|
def __init__(self, onimport=None):
|
||||||
self._path2confmods = {}
|
self._path2confmods = {}
|
||||||
self._onimport = onimport
|
self._onimport = onimport
|
||||||
|
self._conftestpath2mod = {}
|
||||||
|
|
||||||
def setinitial(self, args):
|
def setinitial(self, args):
|
||||||
""" try to find a first anchor path for looking up global values
|
""" try to find a first anchor path for looking up global values
|
||||||
|
@ -65,17 +66,20 @@ class Conftest(object):
|
||||||
raise KeyError(name)
|
raise KeyError(name)
|
||||||
|
|
||||||
def importconftest(self, conftestpath):
|
def importconftest(self, conftestpath):
|
||||||
# Using caching here looks redundant since ultimately
|
|
||||||
# sys.modules caches already
|
|
||||||
assert conftestpath.check(), conftestpath
|
assert conftestpath.check(), conftestpath
|
||||||
if not conftestpath.dirpath('__init__.py').check(file=1):
|
try:
|
||||||
# HACK: we don't want any "globally" imported conftest.py,
|
return self._conftestpath2mod[conftestpath]
|
||||||
# prone to conflicts and subtle problems
|
except KeyError:
|
||||||
modname = str(conftestpath).replace('.', conftestpath.sep)
|
if not conftestpath.dirpath('__init__.py').check(file=1):
|
||||||
mod = conftestpath.pyimport(modname=modname)
|
# HACK: we don't want any "globally" imported conftest.py,
|
||||||
else:
|
# prone to conflicts and subtle problems
|
||||||
mod = conftestpath.pyimport()
|
modname = str(conftestpath).replace('.', conftestpath.sep)
|
||||||
return self._postimport(mod)
|
mod = conftestpath.pyimport(modname=modname)
|
||||||
|
else:
|
||||||
|
mod = conftestpath.pyimport()
|
||||||
|
self._postimport(mod)
|
||||||
|
self._conftestpath2mod[conftestpath] = mod
|
||||||
|
return mod
|
||||||
|
|
||||||
def _postimport(self, mod):
|
def _postimport(self, mod):
|
||||||
if self._onimport:
|
if self._onimport:
|
||||||
|
|
|
@ -223,7 +223,7 @@ class DSession(Session):
|
||||||
nodes = self.item2nodes.setdefault(item, [])
|
nodes = self.item2nodes.setdefault(item, [])
|
||||||
assert node not in nodes
|
assert node not in nodes
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
self.config.hook.pytest_itemstart(item=item, node=node)
|
item.ihook.pytest_itemstart(item=item, node=node)
|
||||||
tosend[:] = tosend[room:] # update inplace
|
tosend[:] = tosend[room:] # update inplace
|
||||||
if tosend:
|
if tosend:
|
||||||
# we have some left, give it to the main loop
|
# we have some left, give it to the main loop
|
||||||
|
@ -242,7 +242,7 @@ class DSession(Session):
|
||||||
# "sending same item %r to multiple "
|
# "sending same item %r to multiple "
|
||||||
# "not implemented" %(item,))
|
# "not implemented" %(item,))
|
||||||
self.item2nodes.setdefault(item, []).append(node)
|
self.item2nodes.setdefault(item, []).append(node)
|
||||||
self.config.hook.pytest_itemstart(item=item, node=node)
|
item.ihook.pytest_itemstart(item=item, node=node)
|
||||||
pending.extend(sending)
|
pending.extend(sending)
|
||||||
tosend[:] = tosend[room:] # update inplace
|
tosend[:] = tosend[room:] # update inplace
|
||||||
if not tosend:
|
if not tosend:
|
||||||
|
@ -267,7 +267,7 @@ class DSession(Session):
|
||||||
info = "!!! Node %r crashed during running of test %r" %(node, item)
|
info = "!!! Node %r crashed during running of test %r" %(node, item)
|
||||||
rep = runner.ItemTestReport(item=item, excinfo=info, when="???")
|
rep = runner.ItemTestReport(item=item, excinfo=info, when="???")
|
||||||
rep.node = node
|
rep.node = node
|
||||||
self.config.hook.pytest_runtest_logreport(report=rep)
|
item.ihook.pytest_runtest_logreport(report=rep)
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
""" setup any neccessary resources ahead of the test run. """
|
""" setup any neccessary resources ahead of the test run. """
|
||||||
|
|
|
@ -93,7 +93,7 @@ class FuncargRequest:
|
||||||
self.fspath = pyfuncitem.fspath
|
self.fspath = pyfuncitem.fspath
|
||||||
if hasattr(pyfuncitem, '_requestparam'):
|
if hasattr(pyfuncitem, '_requestparam'):
|
||||||
self.param = pyfuncitem._requestparam
|
self.param = pyfuncitem._requestparam
|
||||||
self._plugins = self.config.pluginmanager.getplugins()
|
self._plugins = self.config.getmatchingplugins(self.fspath)
|
||||||
self._plugins.append(self.module)
|
self._plugins.append(self.module)
|
||||||
if self.instance is not None:
|
if self.instance is not None:
|
||||||
self._plugins.append(self.instance)
|
self._plugins.append(self.instance)
|
||||||
|
|
|
@ -136,8 +136,8 @@ class PluginManager(object):
|
||||||
# API for interacting with registered and instantiated plugin objects
|
# API for interacting with registered and instantiated plugin objects
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
def listattr(self, attrname, plugins=None, extra=()):
|
def listattr(self, attrname, plugins=None):
|
||||||
return self.registry.listattr(attrname, plugins=plugins, extra=extra)
|
return self.registry.listattr(attrname, plugins=plugins)
|
||||||
|
|
||||||
def notify_exception(self, excinfo=None):
|
def notify_exception(self, excinfo=None):
|
||||||
if excinfo is None:
|
if excinfo is None:
|
||||||
|
@ -271,12 +271,11 @@ class Registry:
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self._plugins)
|
return iter(self._plugins)
|
||||||
|
|
||||||
def listattr(self, attrname, plugins=None, extra=(), reverse=False):
|
def listattr(self, attrname, plugins=None, reverse=False):
|
||||||
l = []
|
l = []
|
||||||
if plugins is None:
|
if plugins is None:
|
||||||
plugins = self._plugins
|
plugins = self._plugins
|
||||||
candidates = list(plugins) + list(extra)
|
for plugin in plugins:
|
||||||
for plugin in candidates:
|
|
||||||
try:
|
try:
|
||||||
l.append(getattr(plugin, attrname))
|
l.append(getattr(plugin, attrname))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -291,32 +290,29 @@ class HookRelay:
|
||||||
self._registry = registry
|
self._registry = registry
|
||||||
for name, method in vars(hookspecs).items():
|
for name, method in vars(hookspecs).items():
|
||||||
if name[:1] != "_":
|
if name[:1] != "_":
|
||||||
setattr(self, name, self._makecall(name))
|
firstresult = getattr(method, 'firstresult', False)
|
||||||
|
hc = HookCaller(self, name, firstresult=firstresult)
|
||||||
def _makecall(self, name, extralookup=None):
|
setattr(self, name, hc)
|
||||||
hookspecmethod = getattr(self._hookspecs, name)
|
|
||||||
firstresult = getattr(hookspecmethod, 'firstresult', False)
|
|
||||||
return HookCaller(self, name, firstresult=firstresult,
|
|
||||||
extralookup=extralookup)
|
|
||||||
|
|
||||||
def _getmethods(self, name, extralookup=()):
|
|
||||||
return self._registry.listattr(name, extra=extralookup)
|
|
||||||
|
|
||||||
def _performcall(self, name, multicall):
|
def _performcall(self, name, multicall):
|
||||||
return multicall.execute()
|
return multicall.execute()
|
||||||
|
|
||||||
class HookCaller:
|
class HookCaller:
|
||||||
def __init__(self, hookrelay, name, firstresult, extralookup=None):
|
def __init__(self, hookrelay, name, firstresult):
|
||||||
self.hookrelay = hookrelay
|
self.hookrelay = hookrelay
|
||||||
self.name = name
|
self.name = name
|
||||||
self.firstresult = firstresult
|
self.firstresult = firstresult
|
||||||
self.extralookup = extralookup and [extralookup] or ()
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<HookCaller %r>" %(self.name,)
|
return "<HookCaller %r>" %(self.name,)
|
||||||
|
|
||||||
def __call__(self, **kwargs):
|
def __call__(self, **kwargs):
|
||||||
methods = self.hookrelay._getmethods(self.name, self.extralookup)
|
methods = self.hookrelay._registry.listattr(self.name)
|
||||||
|
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
|
||||||
|
return self.hookrelay._performcall(self.name, mc)
|
||||||
|
|
||||||
|
def pcall(self, plugins, **kwargs):
|
||||||
|
methods = self.hookrelay._registry.listattr(self.name, plugins=plugins)
|
||||||
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
|
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
|
||||||
return self.hookrelay._performcall(self.name, mc)
|
return self.hookrelay._performcall(self.name, mc)
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
||||||
return self.join(name)
|
return self.join(name)
|
||||||
|
|
||||||
def makeitem(self, name, obj):
|
def makeitem(self, name, obj):
|
||||||
return self.config.hook.pytest_pycollect_makeitem(
|
return self.ihook.pytest_pycollect_makeitem(
|
||||||
collector=self, name=name, obj=obj)
|
collector=self, name=name, obj=obj)
|
||||||
|
|
||||||
def _istestclasscandidate(self, name, obj):
|
def _istestclasscandidate(self, name, obj):
|
||||||
|
@ -137,9 +137,9 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
||||||
cls = clscol and clscol.obj or None
|
cls = clscol and clscol.obj or None
|
||||||
metafunc = funcargs.Metafunc(funcobj, config=self.config,
|
metafunc = funcargs.Metafunc(funcobj, config=self.config,
|
||||||
cls=cls, module=module)
|
cls=cls, module=module)
|
||||||
gentesthook = self.config.hook._makecall(
|
gentesthook = self.config.hook.pytest_generate_tests
|
||||||
"pytest_generate_tests", extralookup=module)
|
plugins = self.config.getmatchingplugins(self.fspath) + [module]
|
||||||
gentesthook(metafunc=metafunc)
|
gentesthook.pcall(plugins, metafunc=metafunc)
|
||||||
if not metafunc._calls:
|
if not metafunc._calls:
|
||||||
return self.Function(name, parent=self)
|
return self.Function(name, parent=self)
|
||||||
return funcargs.FunctionCollector(name=name,
|
return funcargs.FunctionCollector(name=name,
|
||||||
|
@ -338,7 +338,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
|
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
""" execute the underlying test function. """
|
""" execute the underlying test function. """
|
||||||
self.config.hook.pytest_pyfunc_call(pyfuncitem=self)
|
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
super(Function, self).setup()
|
super(Function, self).setup()
|
||||||
|
|
|
@ -39,7 +39,7 @@ def pytest_runtest_protocol(item):
|
||||||
if item.config.getvalue("boxed"):
|
if item.config.getvalue("boxed"):
|
||||||
reports = forked_run_report(item)
|
reports = forked_run_report(item)
|
||||||
for rep in reports:
|
for rep in reports:
|
||||||
item.config.hook.pytest_runtest_logreport(report=rep)
|
item.ihook.pytest_runtest_logreport(report=rep)
|
||||||
else:
|
else:
|
||||||
runtestprotocol(item)
|
runtestprotocol(item)
|
||||||
return True
|
return True
|
||||||
|
@ -85,7 +85,7 @@ def pytest_report_teststatus(report):
|
||||||
|
|
||||||
def call_and_report(item, when, log=True):
|
def call_and_report(item, when, log=True):
|
||||||
call = call_runtest_hook(item, when)
|
call = call_runtest_hook(item, when)
|
||||||
hook = item.config.hook
|
hook = item.ihook
|
||||||
report = hook.pytest_runtest_makereport(item=item, call=call)
|
report = hook.pytest_runtest_makereport(item=item, call=call)
|
||||||
if log and (when == "call" or not report.passed):
|
if log and (when == "call" or not report.passed):
|
||||||
hook.pytest_runtest_logreport(report=report)
|
hook.pytest_runtest_logreport(report=report)
|
||||||
|
@ -93,8 +93,8 @@ def call_and_report(item, when, log=True):
|
||||||
|
|
||||||
def call_runtest_hook(item, when):
|
def call_runtest_hook(item, when):
|
||||||
hookname = "pytest_runtest_" + when
|
hookname = "pytest_runtest_" + when
|
||||||
hook = getattr(item.config.hook, hookname)
|
ihook = getattr(item.ihook, hookname)
|
||||||
return CallInfo(lambda: hook(item=item), when=when)
|
return CallInfo(lambda: ihook(item=item), when=when)
|
||||||
|
|
||||||
class CallInfo:
|
class CallInfo:
|
||||||
excinfo = None
|
excinfo = None
|
||||||
|
|
|
@ -482,3 +482,25 @@ class TestGenfuncFunctional:
|
||||||
"*test_myfunc*world*FAIL*",
|
"*test_myfunc*world*FAIL*",
|
||||||
"*1 failed, 1 passed*"
|
"*1 failed, 1 passed*"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_conftest_funcargs_only_available_in_subdir(testdir):
|
||||||
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
sub2 = testdir.mkpydir("sub2")
|
||||||
|
sub1.join("conftest.py").write(py.code.Source("""
|
||||||
|
import py
|
||||||
|
def pytest_funcarg__arg1(request):
|
||||||
|
py.test.raises(Exception, "request.getfuncargvalue('arg2')")
|
||||||
|
"""))
|
||||||
|
sub2.join("conftest.py").write(py.code.Source("""
|
||||||
|
import py
|
||||||
|
def pytest_funcarg__arg2(request):
|
||||||
|
py.test.raises(Exception, "request.getfuncargvalue('arg1')")
|
||||||
|
"""))
|
||||||
|
|
||||||
|
sub1.join("test_in_sub1.py").write("def test_1(arg1): pass")
|
||||||
|
sub2.join("test_in_sub2.py").write("def test_2(arg2): pass")
|
||||||
|
result = testdir.runpytest("-v")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*2 passed*"
|
||||||
|
])
|
||||||
|
|
|
@ -395,12 +395,6 @@ class TestRegistry:
|
||||||
l = list(plugins.listattr('x', reverse=True))
|
l = list(plugins.listattr('x', reverse=True))
|
||||||
assert l == [43, 42, 41]
|
assert l == [43, 42, 41]
|
||||||
|
|
||||||
class api4:
|
|
||||||
x = 44
|
|
||||||
l = list(plugins.listattr('x', extra=(api4,)))
|
|
||||||
assert l == [41,42,43,44]
|
|
||||||
assert len(list(plugins)) == 3 # otherwise extra added
|
|
||||||
|
|
||||||
class TestHookRelay:
|
class TestHookRelay:
|
||||||
def test_happypath(self):
|
def test_happypath(self):
|
||||||
registry = Registry()
|
registry = Registry()
|
||||||
|
@ -441,23 +435,3 @@ class TestHookRelay:
|
||||||
res = mcm.hello(arg=3)
|
res = mcm.hello(arg=3)
|
||||||
assert res == 4
|
assert res == 4
|
||||||
|
|
||||||
def test_hooks_extra_plugins(self):
|
|
||||||
registry = Registry()
|
|
||||||
class Api:
|
|
||||||
def hello(self, arg):
|
|
||||||
pass
|
|
||||||
hookrelay = HookRelay(hookspecs=Api, registry=registry)
|
|
||||||
hook_hello = hookrelay.hello
|
|
||||||
class Plugin:
|
|
||||||
def hello(self, arg):
|
|
||||||
return arg + 1
|
|
||||||
registry.register(Plugin())
|
|
||||||
class Plugin2:
|
|
||||||
def hello(self, arg):
|
|
||||||
return arg + 2
|
|
||||||
newhook = hookrelay._makecall("hello", extralookup=Plugin2())
|
|
||||||
l = newhook(arg=3)
|
|
||||||
assert l == [5, 4]
|
|
||||||
l2 = hook_hello(arg=3)
|
|
||||||
assert l2 == [4]
|
|
||||||
|
|
||||||
|
|
|
@ -461,3 +461,49 @@ class TestReportinfo:
|
||||||
def test_method(self):
|
def test_method(self):
|
||||||
pass
|
pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def test_setup_only_available_in_subdir(testdir):
|
||||||
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
sub2 = testdir.mkpydir("sub2")
|
||||||
|
sub1.join("conftest.py").write(py.code.Source("""
|
||||||
|
import py
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub1"
|
||||||
|
def pytest_runtest_call(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub1"
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub1"
|
||||||
|
"""))
|
||||||
|
sub2.join("conftest.py").write(py.code.Source("""
|
||||||
|
import py
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub2"
|
||||||
|
def pytest_runtest_call(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub2"
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub2"
|
||||||
|
"""))
|
||||||
|
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||||
|
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||||
|
result = testdir.runpytest("-v", "-s")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*2 passed*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_generate_tests_only_done_in_subdir(testdir):
|
||||||
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
sub2 = testdir.mkpydir("sub2")
|
||||||
|
sub1.join("conftest.py").write(py.code.Source("""
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
assert metafunc.function.__name__ == "test_1"
|
||||||
|
"""))
|
||||||
|
sub2.join("conftest.py").write(py.code.Source("""
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
assert metafunc.function.__name__ == "test_2"
|
||||||
|
"""))
|
||||||
|
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||||
|
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||||
|
result = testdir.runpytest("-v", "-s", sub1, sub2, sub1)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*3 passed*"
|
||||||
|
])
|
||||||
|
|
Loading…
Reference in New Issue