deprecate direct definition of Directory, Module, ... in conftest.py's,

add some pytest collect related tests + some refinements.

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-12-30 16:18:59 +01:00
parent d3b20e8d24
commit f5ea19858c
10 changed files with 177 additions and 101 deletions

View File

@ -14,11 +14,16 @@ Changes between 1.X and 1.1.1
- allow pytest_generate_tests to be defined in classes as well - allow pytest_generate_tests to be defined in classes as well
- deprecate usage of 'disabled' attribute in favour of pytestmark - deprecate usage of 'disabled' attribute in favour of pytestmark
- deprecate definition of Directory, Module, Class and Function nodes
in conftest.py files. Use pytest collect hooks instead.
- collection/item node specific runtest/collect hooks are only called exactly - collection/item node specific runtest/collect hooks are only called exactly
on matching conftest.py files, i.e. ones which are exactly below on matching conftest.py files, i.e. ones which are exactly below
the filesystem path of an item the filesystem path of an item
- change: the first pytest_collect_directory hook to return something
will now prevent further hooks to be called.
- 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.

View File

@ -36,10 +36,10 @@ class Node(object):
- configuration/options for setup/teardown - configuration/options for setup/teardown
stdout/stderr capturing and execution of test items stdout/stderr capturing and execution of test items
""" """
def __init__(self, name, parent=None): def __init__(self, name, parent=None, config=None):
self.name = name self.name = name
self.parent = parent self.parent = parent
self.config = getattr(parent, 'config', None) self.config = config or parent.config
self.fspath = getattr(parent, 'fspath', None) self.fspath = getattr(parent, 'fspath', None)
self.ihook = HookProxy(self) self.ihook = HookProxy(self)
@ -353,9 +353,9 @@ class Collector(Node):
return traceback return traceback
class FSCollector(Collector): class FSCollector(Collector):
def __init__(self, fspath, parent=None): def __init__(self, fspath, parent=None, config=None):
fspath = py.path.local(fspath) fspath = py.path.local(fspath)
super(FSCollector, self).__init__(fspath.basename, parent) super(FSCollector, self).__init__(fspath.basename, parent, config=config)
self.fspath = fspath self.fspath = fspath
def __getstate__(self): def __getstate__(self):

View File

@ -156,16 +156,20 @@ class Config(object):
pkgpath = path.pypkgpath() pkgpath = path.pypkgpath()
if pkgpath is None: if pkgpath is None:
pkgpath = path.check(file=1) and path.dirpath() or path pkgpath = path.check(file=1) and path.dirpath() or path
Dir = self._getcollectclass("Directory", pkgpath) tmpcol = py.test.collect.Directory(pkgpath, config=self)
col = Dir(pkgpath) col = tmpcol.ihook.pytest_collect_directory(path=pkgpath, parent=tmpcol)
col.config = self col.parent = None
return col._getfsnode(path) return col._getfsnode(path)
def _getcollectclass(self, name, path): def _getcollectclass(self, name, path):
try: try:
return self.getvalue(name, path) cls = self.getvalue(name, path)
except KeyError: except KeyError:
return getattr(py.test.collect, name) return getattr(py.test.collect, name)
else:
py.log._apiwarn(">1.1", "%r was found in a conftest.py file, "
"use pytest_collect hooks instead." % (cls,))
return cls
def getconftest_pathlist(self, name, path=None): def getconftest_pathlist(self, name, path=None):
""" return a matching value, which needs to be sequence """ return a matching value, which needs to be sequence

View File

@ -315,9 +315,9 @@ class Function(FunctionMixin, py.test.collect.Item):
and executing a Python callable test object. and executing a Python callable test object.
""" """
_genid = None _genid = None
def __init__(self, name, parent=None, args=None, def __init__(self, name, parent=None, args=None, config=None,
callspec=None, callobj=_dummy): callspec=None, callobj=_dummy):
super(Function, self).__init__(name, parent) super(Function, self).__init__(name, parent, config=config)
self._args = args self._args = args
if self._isyieldedfunction(): if self._isyieldedfunction():
assert not callspec, "yielded functions (deprecated) cannot have funcargs" assert not callspec, "yielded functions (deprecated) cannot have funcargs"

View File

@ -26,6 +26,7 @@ def pytest_unconfigure(config):
def pytest_collect_directory(path, parent): def pytest_collect_directory(path, parent):
""" return Collection node or None for the given path. """ """ return Collection node or None for the given path. """
pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
""" return Collection node or None for the given path. """ """ return Collection node or None for the given path. """

View File

@ -3,8 +3,9 @@ import os
from py.plugin.pytest_resultlog import generic_path, ResultLog from py.plugin.pytest_resultlog import generic_path, ResultLog
from py.impl.test.collect import Node, Item, FSCollector from py.impl.test.collect import Node, Item, FSCollector
def test_generic_path(): def test_generic_path(testdir):
p1 = Node('a') config = testdir.Config()
p1 = Node('a', config=config)
assert p1.fspath is None assert p1.fspath is None
p2 = Node('B', parent=p1) p2 = Node('B', parent=p1)
p3 = Node('()', parent = p2) p3 = Node('()', parent = p2)
@ -13,7 +14,7 @@ def test_generic_path():
res = generic_path(item) res = generic_path(item)
assert res == 'a.B().c' assert res == 'a.B().c'
p0 = FSCollector('proj/test') p0 = FSCollector('proj/test', config=config)
p1 = FSCollector('proj/test/a', parent=p0) p1 = FSCollector('proj/test/a', parent=p0)
p2 = Node('B', parent=p1) p2 = Node('B', parent=p1)
p3 = Node('()', parent = p2) p3 = Node('()', parent = p2)

View File

@ -193,31 +193,6 @@ class TestPrunetraceback:
]) ])
class TestCustomConftests: class TestCustomConftests:
def test_non_python_files(self, testdir):
testdir.makepyfile(conftest="""
import py
class CustomItem(py.test.collect.Item):
def run(self):
pass
class Directory(py.test.collect.Directory):
def consider_file(self, fspath):
if fspath.ext == ".xxx":
return CustomItem(fspath.basename, parent=self)
""")
checkfile = testdir.makefile(ext="xxx", hello="world")
testdir.makepyfile(x="")
testdir.maketxtfile(x="")
config = testdir.parseconfig()
dircol = config.getfsnode(checkfile.dirpath())
colitems = dircol.collect()
assert len(colitems) == 1
assert colitems[0].name == "hello.xxx"
assert colitems[0].__class__.__name__ == "CustomItem"
item = config.getfsnode(checkfile)
assert item.name == "hello.xxx"
assert item.__class__.__name__ == "CustomItem"
def test_collectignore_exclude_on_option(self, testdir): def test_collectignore_exclude_on_option(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
collect_ignore = ['hello', 'test_world.py'] collect_ignore = ['hello', 'test_world.py']
@ -237,3 +212,23 @@ class TestCustomConftests:
names = [rep.collector.name for rep in reprec.getreports("pytest_collectreport")] names = [rep.collector.name for rep in reprec.getreports("pytest_collectreport")]
assert 'hello' in names assert 'hello' in names
assert 'test_world.py' in names assert 'test_world.py' in names
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
testdir.makeconftest("""
import py
class MyDirectory(py.test.collect.Directory):
pass
class MyModule(py.test.collect.Module):
pass
def pytest_collect_directory(path, parent):
return MyDirectory(path, parent)
def pytest_collect_file(path, parent):
return MyModule(path, parent)
""")
testdir.makepyfile("def test_x(): pass")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*MyDirectory*",
"*MyModule*",
"*test_x*"
])

View File

@ -48,12 +48,6 @@ class TestConftestValueAccessGlobal:
conftest.getconftestmodules(basedir.join('b')) conftest.getconftestmodules(basedir.join('b'))
assert len(conftest._path2confmods) == snap1 + 2 assert len(conftest._path2confmods) == snap1 + 2
def test_default_Module_setting_is_visible_always(self, basedir, testdir):
basedir.copy(testdir.tmpdir)
config = testdir.Config()
colclass = config._getcollectclass("Module", testdir.tmpdir)
assert colclass == py.test.collect.Module
def test_default_has_lower_prio(self, basedir): def test_default_has_lower_prio(self, basedir):
conftest = ConftestWithSetinitial(basedir.join("adir")) conftest = ConftestWithSetinitial(basedir.join("adir"))
assert conftest.rget('Directory') == 3 assert conftest.rget('Directory') == 3

View File

@ -37,15 +37,19 @@ class TestCollectDeprecated:
def join(self, name): def join(self, name):
if name == "somefile.py": if name == "somefile.py":
return self.Module(self.fspath.join(name), parent=self) return self.Module(self.fspath.join(name), parent=self)
Directory = MyDirectory
def pytest_collect_directory(path, parent):
return MyDirectory(path, parent)
""") """)
p = testdir.makepyfile(somefile=""" subconf = testdir.mkpydir("subconf")
somefile = subconf.join("somefile.py")
somefile.write(py.code.Source("""
def check(): pass def check(): pass
class Cls: class Cls:
def check2(self): pass def check2(self): pass
""") """))
config = testdir.parseconfig() config = testdir.parseconfig(somefile)
dirnode = config.getfsnode(p.dirpath()) dirnode = config.getfsnode(somefile.dirpath())
colitems = dirnode.collect() colitems = dirnode.collect()
w = recwarn.pop(DeprecationWarning) w = recwarn.pop(DeprecationWarning)
assert w.filename.find("conftest.py") != -1 assert w.filename.find("conftest.py") != -1
@ -120,6 +124,7 @@ class TestCollectDeprecated:
""") """)
modcol = testdir.getmodulecol("def test_func2(): pass") modcol = testdir.getmodulecol("def test_func2(): pass")
funcitem = modcol.collect()[0] funcitem = modcol.collect()[0]
w = recwarn.pop(DeprecationWarning) # for defining conftest.Function
assert funcitem.name == 'test_func2' assert funcitem.name == 'test_func2'
funcitem._deprecated_testexecution() funcitem._deprecated_testexecution()
w = recwarn.pop(DeprecationWarning) w = recwarn.pop(DeprecationWarning)
@ -136,6 +141,8 @@ class TestCollectDeprecated:
""") """)
modcol = testdir.getmodulecol("def test_some2(): pass") modcol = testdir.getmodulecol("def test_some2(): pass")
funcitem = modcol.collect()[0] funcitem = modcol.collect()[0]
w = recwarn.pop(DeprecationWarning)
assert "conftest.py" in str(w.message)
recwarn.clear() recwarn.clear()
funcitem._deprecated_testexecution() funcitem._deprecated_testexecution()
@ -169,6 +176,7 @@ class TestCollectDeprecated:
col = config.getfsnode(testme) col = config.getfsnode(testme)
assert col.collect() == [] assert col.collect() == []
class TestDisabled: class TestDisabled:
def test_disabled_module(self, recwarn, testdir): def test_disabled_module(self, recwarn, testdir):
@ -211,6 +219,31 @@ class TestDisabled:
""") """)
reprec.assertoutcome(skipped=2) reprec.assertoutcome(skipped=2)
@py.test.mark.multi(name="Directory Module Class Function".split())
def test_function_deprecated_run_execute(self, name, testdir, recwarn):
testdir.makeconftest("""
import py
class %s(py.test.collect.%s):
pass
""" % (name, name))
p = testdir.makepyfile("""
class TestClass:
def test_method(self):
pass
def test_function():
pass
""")
config = testdir.parseconfig()
if name == "Directory":
config.getfsnode(testdir.tmpdir)
elif name in ("Module", "File"):
config.getfsnode(p)
else:
fnode = config.getfsnode(p)
recwarn.clear()
fnode.collect()
w = recwarn.pop(DeprecationWarning)
assert "conftest.py" in str(w.message)
def test_config_cmdline_options(recwarn, testdir): def test_config_cmdline_options(recwarn, testdir):
testdir.makepyfile(conftest=""" testdir.makepyfile(conftest="""
@ -267,3 +300,80 @@ def test_dist_conftest_options(testdir):
"*1 passed*", "*1 passed*",
]) ])
def test_conftest_non_python_items(recwarn, testdir):
testdir.makepyfile(conftest="""
import py
class CustomItem(py.test.collect.Item):
def run(self):
pass
class Directory(py.test.collect.Directory):
def consider_file(self, fspath):
if fspath.ext == ".xxx":
return CustomItem(fspath.basename, parent=self)
""")
checkfile = testdir.makefile(ext="xxx", hello="world")
testdir.makepyfile(x="")
testdir.maketxtfile(x="")
config = testdir.parseconfig()
recwarn.clear()
dircol = config.getfsnode(checkfile.dirpath())
w = recwarn.pop(DeprecationWarning)
assert str(w.message).find("conftest.py") != -1
colitems = dircol.collect()
assert len(colitems) == 1
assert colitems[0].name == "hello.xxx"
assert colitems[0].__class__.__name__ == "CustomItem"
item = config.getfsnode(checkfile)
assert item.name == "hello.xxx"
assert item.__class__.__name__ == "CustomItem"
def test_extra_python_files_and_functions(testdir):
testdir.makepyfile(conftest="""
import py
class MyFunction(py.test.collect.Function):
pass
class Directory(py.test.collect.Directory):
def consider_file(self, path):
if path.check(fnmatch="check_*.py"):
return self.Module(path, parent=self)
return super(Directory, self).consider_file(path)
class myfuncmixin:
Function = MyFunction
def funcnamefilter(self, name):
return name.startswith('check_')
class Module(myfuncmixin, py.test.collect.Module):
def classnamefilter(self, name):
return name.startswith('CustomTestClass')
class Instance(myfuncmixin, py.test.collect.Instance):
pass
""")
checkfile = testdir.makepyfile(check_file="""
def check_func():
assert 42 == 42
class CustomTestClass:
def check_method(self):
assert 23 == 23
""")
# check that directory collects "check_" files
config = testdir.parseconfig()
col = config.getfsnode(checkfile.dirpath())
colitems = col.collect()
assert len(colitems) == 1
assert isinstance(colitems[0], py.test.collect.Module)
# check that module collects "check_" functions and methods
config = testdir.parseconfig(checkfile)
col = config.getfsnode(checkfile)
assert isinstance(col, py.test.collect.Module)
colitems = col.collect()
assert len(colitems) == 2
funccol = colitems[0]
assert isinstance(funccol, py.test.collect.Function)
assert funccol.name == "check_func"
clscol = colitems[1]
assert isinstance(clscol, py.test.collect.Class)
colitems = clscol.collect()[0].collect()
assert len(colitems) == 1
assert colitems[0].name == "check_method"

View File

@ -4,7 +4,7 @@ class TestModule:
def test_module_file_not_found(self, testdir): def test_module_file_not_found(self, testdir):
tmpdir = testdir.tmpdir tmpdir = testdir.tmpdir
fn = tmpdir.join('nada','no') fn = tmpdir.join('nada','no')
col = py.test.collect.Module(fn) col = py.test.collect.Module(fn, config=testdir.Config())
col.config = testdir.parseconfig(tmpdir) col.config = testdir.parseconfig(tmpdir)
py.test.raises(py.error.ENOENT, col.collect) py.test.raises(py.error.ENOENT, col.collect)
@ -213,13 +213,13 @@ class TestFunction:
def test_function_equality(self, testdir, tmpdir): def test_function_equality(self, testdir, tmpdir):
config = testdir.reparseconfig() config = testdir.reparseconfig()
f1 = py.test.collect.Function(name="name", f1 = py.test.collect.Function(name="name", config=config,
args=(1,), callobj=isinstance) args=(1,), callobj=isinstance)
f2 = py.test.collect.Function(name="name", f2 = py.test.collect.Function(name="name",config=config,
args=(1,), callobj=py.builtin.callable) args=(1,), callobj=py.builtin.callable)
assert not f1 == f2 assert not f1 == f2
assert f1 != f2 assert f1 != f2
f3 = py.test.collect.Function(name="name", f3 = py.test.collect.Function(name="name", config=config,
args=(1,2), callobj=py.builtin.callable) args=(1,2), callobj=py.builtin.callable)
assert not f3 == f2 assert not f3 == f2
assert f3 != f2 assert f3 != f2
@ -227,7 +227,7 @@ class TestFunction:
assert not f3 == f1 assert not f3 == f1
assert f3 != f1 assert f3 != f1
f1_b = py.test.collect.Function(name="name", f1_b = py.test.collect.Function(name="name", config=config,
args=(1,), callobj=isinstance) args=(1,), callobj=isinstance)
assert f1 == f1_b assert f1 == f1_b
assert not f1 != f1_b assert not f1 != f1_b
@ -242,9 +242,9 @@ class TestFunction:
param = 1 param = 1
funcargs = {} funcargs = {}
id = "world" id = "world"
f5 = py.test.collect.Function(name="name", f5 = py.test.collect.Function(name="name", config=config,
callspec=callspec1, callobj=isinstance) callspec=callspec1, callobj=isinstance)
f5b = py.test.collect.Function(name="name", f5b = py.test.collect.Function(name="name", config=config,
callspec=callspec2, callobj=isinstance) callspec=callspec2, callobj=isinstance)
assert f5 != f5b assert f5 != f5b
assert not (f5 == f5b) assert not (f5 == f5b)
@ -313,55 +313,21 @@ class TestSorting:
class TestConftestCustomization: class TestConftestCustomization:
def test_extra_python_files_and_functions(self, testdir): def test_pytest_pycollect_makeitem(self, testdir):
testdir.makepyfile(conftest=""" testdir.makeconftest("""
import py import py
class MyFunction(py.test.collect.Function): class MyFunction(py.test.collect.Function):
pass pass
class Directory(py.test.collect.Directory): def pytest_pycollect_makeitem(collector, name, obj):
def consider_file(self, path): if name == "some":
if path.check(fnmatch="check_*.py"): return MyFunction(name, collector)
return self.Module(path, parent=self)
return super(Directory, self).consider_file(path)
class myfuncmixin:
Function = MyFunction
def funcnamefilter(self, name):
return name.startswith('check_')
class Module(myfuncmixin, py.test.collect.Module):
def classnamefilter(self, name):
return name.startswith('CustomTestClass')
class Instance(myfuncmixin, py.test.collect.Instance):
pass
""") """)
checkfile = testdir.makepyfile(check_file=""" testdir.makepyfile("def some(): pass")
def check_func(): result = testdir.runpytest("--collectonly")
assert 42 == 42 result.stdout.fnmatch_lines([
class CustomTestClass: "*MyFunction*some*",
def check_method(self): ])
assert 23 == 23
""")
# check that directory collects "check_" files
config = testdir.parseconfig()
col = config.getfsnode(checkfile.dirpath())
colitems = col.collect()
assert len(colitems) == 1
assert isinstance(colitems[0], py.test.collect.Module)
# check that module collects "check_" functions and methods
config = testdir.parseconfig(checkfile)
col = config.getfsnode(checkfile)
assert isinstance(col, py.test.collect.Module)
colitems = col.collect()
assert len(colitems) == 2
funccol = colitems[0]
assert isinstance(funccol, py.test.collect.Function)
assert funccol.name == "check_func"
clscol = colitems[1]
assert isinstance(clscol, py.test.collect.Class)
colitems = clscol.collect()[0].collect()
assert len(colitems) == 1
assert colitems[0].name == "check_method"
def test_makeitem_non_underscore(self, testdir, monkeypatch): def test_makeitem_non_underscore(self, testdir, monkeypatch):
modcol = testdir.getmodulecol("def _hello(): pass") modcol = testdir.getmodulecol("def _hello(): pass")
l = [] l = []