majorly refactor collection process

- get rid of py.test.collect.Directory alltogether.
- introduce direct node.nodeid attribute
- remove now superflous attributes on collect and test reports
This commit is contained in:
holger krekel 2010-11-06 09:58:04 +01:00
parent f181c70d8e
commit 6dac77433e
24 changed files with 698 additions and 751 deletions

View File

@ -26,7 +26,8 @@ Changes between 1.3.4 and 2.0.0dev0
output on assertion failures for comparisons and other cases (Floris Bruynooghe) output on assertion failures for comparisons and other cases (Floris Bruynooghe)
- nose-plugin: pass through type-signature failures in setup/teardown - nose-plugin: pass through type-signature failures in setup/teardown
functions instead of not calling them (Ed Singleton) functions instead of not calling them (Ed Singleton)
- major refactoring of internal collection handling - remove py.test.collect.Directory (follows from a major refactoring
and simplification of the collection process)
- majorly reduce py.test core code, shift function/python testing to own plugin - majorly reduce py.test core code, shift function/python testing to own plugin
- fix issue88 (finding custom test nodes from command line arg) - fix issue88 (finding custom test nodes from command line arg)
- refine 'tmpdir' creation, will now create basenames better associated - refine 'tmpdir' creation, will now create basenames better associated

View File

@ -5,7 +5,7 @@ see http://pytest.org for documentation and details
(c) Holger Krekel and others, 2004-2010 (c) Holger Krekel and others, 2004-2010
""" """
__version__ = '2.0.0.dev18' __version__ = '2.0.0.dev19'
__all__ = ['config', 'cmdline'] __all__ = ['config', 'cmdline']

View File

@ -126,7 +126,7 @@ def pytest_runtest_protocol(item):
""" """
pytest_runtest_protocol.firstresult = True pytest_runtest_protocol.firstresult = True
def pytest_runtest_logstart(nodeid, location, fspath): def pytest_runtest_logstart(nodeid, location):
""" signal the start of a test run. """ """ signal the start of a test run. """
def pytest_runtest_setup(item): def pytest_runtest_setup(item):

View File

@ -67,7 +67,10 @@ class PluginManager(object):
self._hints = [] self._hints = []
self.trace = TagTracer().get("pluginmanage") self.trace = TagTracer().get("pluginmanage")
if os.environ.get('PYTEST_DEBUG'): if os.environ.get('PYTEST_DEBUG'):
self.trace.root.setwriter(sys.stderr.write) err = sys.stderr
if hasattr(os, 'dup'):
err = py.io.dupfile(err)
self.trace.root.setwriter(err.write)
self.hook = HookRelay([hookspec], pm=self) self.hook = HookRelay([hookspec], pm=self)
self.register(self) self.register(self)
if load: if load:
@ -370,6 +373,7 @@ class HookCaller:
self.hookrelay = hookrelay self.hookrelay = hookrelay
self.name = name self.name = name
self.firstresult = firstresult self.firstresult = firstresult
self.trace = self.hookrelay.trace
def __repr__(self): def __repr__(self):
return "<HookCaller %r>" %(self.name,) return "<HookCaller %r>" %(self.name,)
@ -380,10 +384,15 @@ class HookCaller:
return mc.execute() return mc.execute()
def pcall(self, plugins, **kwargs): def pcall(self, plugins, **kwargs):
self.hookrelay.trace(self.name, kwargs) self.trace(self.name, kwargs)
self.trace.root.indent += 1
methods = self.hookrelay._pm.listattr(self.name, plugins=plugins) methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
mc = MultiCall(methods, kwargs, firstresult=self.firstresult) mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
return mc.execute() res = mc.execute()
if res:
self.trace(res)
self.trace.root.indent -= 1
return res
_preinit = [PluginManager(load=True)] # triggers default plugin importing _preinit = [PluginManager(load=True)] # triggers default plugin importing

View File

@ -133,7 +133,11 @@ class CaptureManager:
def pytest_make_collect_report(self, __multicall__, collector): def pytest_make_collect_report(self, __multicall__, collector):
method = self._getmethod(collector.config, collector.fspath) method = self._getmethod(collector.config, collector.fspath)
try:
self.resumecapture(method) self.resumecapture(method)
except ValueError:
return # recursive collect, XXX refactor capturing
# to allow for more lightweight recursive capturing
try: try:
rep = __multicall__.execute() rep = __multicall__.execute()
finally: finally:

View File

@ -255,7 +255,7 @@ class Config(object):
) )
#: a pluginmanager instance #: a pluginmanager instance
self.pluginmanager = pluginmanager or PluginManager(load=True) self.pluginmanager = pluginmanager or PluginManager(load=True)
self.trace = self.pluginmanager.trace.get("config") self.trace = self.pluginmanager.trace.root.get("config")
self._conftest = Conftest(onimport=self._onimportconftest) self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook self.hook = self.pluginmanager.hook

View File

@ -17,7 +17,7 @@ def pytest_addoption(parser):
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
config = parent.config config = parent.config
if path.ext == ".py": if path.ext == ".py":
if config.getvalue("doctestmodules"): if config.option.doctestmodules:
return DoctestModule(path, parent) return DoctestModule(path, parent)
elif path.check(fnmatch=config.getvalue("doctestglob")): elif path.check(fnmatch=config.getvalue("doctestglob")):
return DoctestTextfile(path, parent) return DoctestTextfile(path, parent)

View File

@ -3,6 +3,7 @@
""" """
import py import py
import os
import time import time
def pytest_addoption(parser): def pytest_addoption(parser):
@ -36,7 +37,9 @@ class LogXML(object):
self._durations = {} self._durations = {}
def _opentestcase(self, report): def _opentestcase(self, report):
names = report.nodenames names = report.nodeid.split("::")
names[0] = names[0].replace(os.sep, '.')
names = tuple(names)
d = {'time': self._durations.pop(names, "0")} d = {'time': self._durations.pop(names, "0")}
names = [x.replace(".py", "") for x in names if x != "()"] names = [x.replace(".py", "") for x in names if x != "()"]
classnames = names[:-1] classnames = names[:-1]

View File

@ -105,6 +105,7 @@ class HookRecorder:
return l return l
def contains(self, entries): def contains(self, entries):
__tracebackhide__ = True
from py.builtin import print_ from py.builtin import print_
i = 0 i = 0
entries = list(entries) entries = list(entries)
@ -123,8 +124,7 @@ class HookRecorder:
break break
print_("NONAMEMATCH", name, "with", call) print_("NONAMEMATCH", name, "with", call)
else: else:
raise AssertionError("could not find %r in %r" %( py.test.fail("could not find %r check %r" % (name, check))
name, self.calls[i:]))
def popcall(self, name): def popcall(self, name):
for i, call in enumerate(self.calls): for i, call in enumerate(self.calls):
@ -278,7 +278,16 @@ class TmpTestdir:
Collection = Collection Collection = Collection
def getnode(self, config, arg): def getnode(self, config, arg):
collection = Collection(config) collection = Collection(config)
return collection.getbyid(collection._normalizearg(arg))[0] assert '::' not in str(arg)
p = py.path.local(arg)
x = collection.fspath.bestrelpath(p)
return collection.perform_collect([x], genitems=False)[0]
def getpathnode(self, path):
config = self.parseconfig(path)
collection = Collection(config)
x = collection.fspath.bestrelpath(path)
return collection.perform_collect([x], genitems=False)[0]
def genitems(self, colitems): def genitems(self, colitems):
collection = colitems[0].collection collection = colitems[0].collection
@ -291,8 +300,9 @@ class TmpTestdir:
#config = self.parseconfig(*args) #config = self.parseconfig(*args)
config = self.parseconfigure(*args) config = self.parseconfigure(*args)
rec = self.getreportrecorder(config) rec = self.getreportrecorder(config)
items = Collection(config).perform_collect() collection = Collection(config)
return items, rec collection.perform_collect()
return collection.items, rec
def runitem(self, source): def runitem(self, source):
# used from runner functional tests # used from runner functional tests
@ -469,11 +479,12 @@ class TmpTestdir:
p = py.path.local.make_numbered_dir(prefix="runpytest-", p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir) keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args args = ('--basetemp=%s' % p, ) + args
for x in args: #for x in args:
if '--confcutdir' in str(x): # if '--confcutdir' in str(x):
break # break
else: #else:
args = ('--confcutdir=.',) + args # pass
# args = ('--confcutdir=.',) + args
plugins = [x for x in self.plugins if isinstance(x, str)] plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins: if plugins:
args = ('-p', plugins[0]) + args args = ('-p', plugins[0]) + args
@ -530,7 +541,7 @@ class ReportRecorder(object):
""" return a testreport whose dotted import path matches """ """ return a testreport whose dotted import path matches """
l = [] l = []
for rep in self.getreports(names=names): for rep in self.getreports(names=names):
if not inamepart or inamepart in rep.nodenames: if not inamepart or inamepart in rep.nodeid.split("::"):
l.append(rep) l.append(rep)
if not l: if not l:
raise ValueError("could not find test report matching %r: no test reports at all!" % raise ValueError("could not find test report matching %r: no test reports at all!" %
@ -616,6 +627,8 @@ class LineMatcher:
raise ValueError("line %r not found in output" % line) raise ValueError("line %r not found in output" % line)
def fnmatch_lines(self, lines2): def fnmatch_lines(self, lines2):
def show(arg1, arg2):
py.builtin.print_(arg1, arg2, file=py.std.sys.stderr)
lines2 = self._getlines(lines2) lines2 = self._getlines(lines2)
lines1 = self.lines[:] lines1 = self.lines[:]
nextline = None nextline = None
@ -626,17 +639,17 @@ class LineMatcher:
while lines1: while lines1:
nextline = lines1.pop(0) nextline = lines1.pop(0)
if line == nextline: if line == nextline:
print_("exact match:", repr(line)) show("exact match:", repr(line))
break break
elif fnmatch(nextline, line): elif fnmatch(nextline, line):
print_("fnmatch:", repr(line)) show("fnmatch:", repr(line))
print_(" with:", repr(nextline)) show(" with:", repr(nextline))
break break
else: else:
if not nomatchprinted: if not nomatchprinted:
print_("nomatch:", repr(line)) show("nomatch:", repr(line))
nomatchprinted = True nomatchprinted = True
print_(" and:", repr(nextline)) show(" and:", repr(nextline))
extralines.append(nextline) extralines.append(nextline)
else: else:
assert line == nextline py.test.fail("remains unmatched: %r, see stderr" % (line,))

View File

@ -48,9 +48,8 @@ def pytest_pyfunc_call(__multicall__, pyfuncitem):
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
ext = path.ext ext = path.ext
pb = path.purebasename pb = path.purebasename
if pb.startswith("test_") or pb.endswith("_test") or \ if ext == ".py" and (pb.startswith("test_") or pb.endswith("_test") or
path in parent.collection._argfspaths: parent.collection.isinitpath(path)):
if ext == ".py":
return parent.ihook.pytest_pycollect_makemodule( return parent.ihook.pytest_pycollect_makemodule(
path=path, parent=parent) path=path, parent=parent)
@ -713,11 +712,13 @@ class FuncargRequest:
def showfuncargs(config): def showfuncargs(config):
from pytest.plugin.session import Collection from pytest.plugin.session import Collection
collection = Collection(config) collection = Collection(config)
firstid = collection._normalizearg(config.args[0]) collection.perform_collect()
colitem = collection.getbyid(firstid)[0] if collection.items:
plugins = getplugins(collection.items[0])
else:
plugins = getplugins(collection)
curdir = py.path.local() curdir = py.path.local()
tw = py.io.TerminalWriter() tw = py.io.TerminalWriter()
plugins = getplugins(colitem, withpy=True)
verbose = config.getvalue("verbose") verbose = config.getvalue("verbose")
for plugin in plugins: for plugin in plugins:
available = [] available = []

View File

@ -29,30 +29,12 @@ def pytest_sessionfinish(session, exitstatus):
session.exitstatus = 1 session.exitstatus = 1
class NodeInfo: class NodeInfo:
def __init__(self, nodeid, nodenames, fspath, location): def __init__(self, location):
self.nodeid = nodeid
self.nodenames = nodenames
self.fspath = fspath
self.location = location self.location = location
def getitemnodeinfo(item):
try:
return item._nodeinfo
except AttributeError:
location = item.reportinfo()
location = (str(location[0]), location[1], str(location[2]))
nodenames = tuple(item.listnames())
nodeid = item.collection.getid(item)
fspath = item.fspath
item._nodeinfo = n = NodeInfo(nodeid, nodenames, fspath, location)
return n
def pytest_runtest_protocol(item): def pytest_runtest_protocol(item):
nodeinfo = getitemnodeinfo(item)
item.ihook.pytest_runtest_logstart( item.ihook.pytest_runtest_logstart(
nodeid=nodeinfo.nodeid, nodeid=item.nodeid, location=item.location,
location=nodeinfo.location,
fspath=str(item.fspath),
) )
runtestprotocol(item) runtestprotocol(item)
return True return True
@ -142,16 +124,18 @@ class BaseReport(object):
failed = property(lambda x: x.outcome == "failed") failed = property(lambda x: x.outcome == "failed")
skipped = property(lambda x: x.outcome == "skipped") skipped = property(lambda x: x.outcome == "skipped")
@property
def fspath(self):
return self.nodeid.split("::")[0]
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
nodeinfo = getitemnodeinfo(item)
when = call.when when = call.when
keywords = dict([(x,1) for x in item.keywords]) keywords = dict([(x,1) for x in item.keywords])
excinfo = call.excinfo
if not call.excinfo: if not call.excinfo:
outcome = "passed" outcome = "passed"
longrepr = None longrepr = None
else: else:
excinfo = call.excinfo
if not isinstance(excinfo, py.code.ExceptionInfo): if not isinstance(excinfo, py.code.ExceptionInfo):
outcome = "failed" outcome = "failed"
longrepr = excinfo longrepr = excinfo
@ -164,25 +148,18 @@ def pytest_runtest_makereport(item, call):
longrepr = item.repr_failure(excinfo) longrepr = item.repr_failure(excinfo)
else: # exception in setup or teardown else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo) longrepr = item._repr_failure_py(excinfo)
return TestReport(nodeinfo.nodeid, nodeinfo.nodenames, return TestReport(item.nodeid, item.location,
nodeinfo.fspath, nodeinfo.location,
keywords, outcome, longrepr, when) keywords, outcome, longrepr, when)
class TestReport(BaseReport): class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if """ Basic test report object (also used for setup and teardown calls if
they fail). they fail).
""" """
def __init__(self, nodeid, nodenames, fspath, location, def __init__(self, nodeid, location,
keywords, outcome, longrepr, when): keywords, outcome, longrepr, when):
#: normalized collection node id #: normalized collection node id
self.nodeid = nodeid self.nodeid = nodeid
#: list of names indicating position in collection tree.
self.nodenames = nodenames
#: the collected path of the file containing the test.
self.fspath = fspath # where the test was collected
#: a (filesystempath, lineno, domaininfo) tuple indicating the #: a (filesystempath, lineno, domaininfo) tuple indicating the
#: actual location of a test item - it might be different from the #: actual location of a test item - it might be different from the
#: collected one e.g. if a method is inherited from a different module. #: collected one e.g. if a method is inherited from a different module.
@ -212,39 +189,27 @@ class TeardownErrorReport(BaseReport):
self.longrepr = longrepr self.longrepr = longrepr
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
result = excinfo = None call = CallInfo(collector._memocollect, "memocollect")
try:
result = collector._memocollect()
except KeyboardInterrupt:
raise
except:
excinfo = py.code.ExceptionInfo()
nodenames = tuple(collector.listnames())
nodeid = collector.collection.getid(collector)
fspath = str(collector.fspath)
reason = longrepr = None reason = longrepr = None
if not excinfo: if not call.excinfo:
outcome = "passed" outcome = "passed"
else: else:
if excinfo.errisinstance(py.test.skip.Exception): if call.excinfo.errisinstance(py.test.skip.Exception):
outcome = "skipped" outcome = "skipped"
reason = str(excinfo.value) reason = str(call.excinfo.value)
longrepr = collector._repr_failure_py(excinfo, "line") longrepr = collector._repr_failure_py(call.excinfo, "line")
else: else:
outcome = "failed" outcome = "failed"
errorinfo = collector.repr_failure(excinfo) errorinfo = collector.repr_failure(call.excinfo)
if not hasattr(errorinfo, "toterminal"): if not hasattr(errorinfo, "toterminal"):
errorinfo = CollectErrorRepr(errorinfo) errorinfo = CollectErrorRepr(errorinfo)
longrepr = errorinfo longrepr = errorinfo
return CollectReport(nodenames, nodeid, fspath, return CollectReport(collector.nodeid, outcome, longrepr,
outcome, longrepr, result, reason) getattr(call, 'result', None), reason)
class CollectReport(BaseReport): class CollectReport(BaseReport):
def __init__(self, nodenames, nodeid, fspath, outcome, def __init__(self, nodeid, outcome, longrepr, result, reason):
longrepr, result, reason):
self.nodenames = nodenames
self.nodeid = nodeid self.nodeid = nodeid
self.fspath = fspath
self.outcome = outcome self.outcome = outcome
self.longrepr = longrepr self.longrepr = longrepr
self.result = result or [] self.result = result or []
@ -255,7 +220,8 @@ class CollectReport(BaseReport):
return (self.fspath, None, self.fspath) return (self.fspath, None, self.fspath)
def __repr__(self): def __repr__(self):
return "<CollectReport %r outcome=%r>" % (self.nodeid, self.outcome) return "<CollectReport %r lenresult=%s outcome=%r>" % (
self.nodeid, len(self.result), self.outcome)
class CollectErrorRepr(TerminalRepr): class CollectErrorRepr(TerminalRepr):
def __init__(self, msg): def __init__(self, msg):

View File

@ -14,11 +14,15 @@ EXIT_OK = 0
EXIT_TESTSFAILED = 1 EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2 EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3 EXIT_INTERNALERROR = 3
EXIT_NOHOSTS = 4
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion", parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=('.*', 'CVS', '_darcs', '{arch}')) type="args", default=('.*', 'CVS', '_darcs', '{arch}'))
#parser.addini("dirpatterns",
# "patterns specifying possible locations of test files",
# type="linelist", default=["**/test_*.txt",
# "**/test_*.py", "**/*_test.py"]
#)
group = parser.getgroup("general", "running and selection options") group = parser.getgroup("general", "running and selection options")
group._addoption('-x', '--exitfirst', action="store_true", default=False, group._addoption('-x', '--exitfirst', action="store_true", default=False,
dest="exitfirst", dest="exitfirst",
@ -44,12 +48,11 @@ def pytest_addoption(parser):
def pytest_namespace(): def pytest_namespace():
return dict(collect=dict(Item=Item, Collector=Collector, return dict(collect=dict(Item=Item, Collector=Collector, File=File))
File=File, Directory=Directory))
def pytest_configure(config): def pytest_configure(config):
py.test.config = config # compatibiltiy py.test.config = config # compatibiltiy
if config.getvalue("exitfirst"): if config.option.exitfirst:
config.option.maxfail = 1 config.option.maxfail = 1
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
@ -84,8 +87,10 @@ def pytest_cmdline_main(config):
def pytest_collection(session): def pytest_collection(session):
collection = session.collection collection = session.collection
assert not hasattr(collection, 'items') assert not hasattr(collection, 'items')
collection.perform_collect()
hook = session.config.hook hook = session.config.hook
collection.items = items = collection.perform_collect() items = collection.items
hook.pytest_collection_modifyitems(config=session.config, items=items) hook.pytest_collection_modifyitems(config=session.config, items=items)
hook.pytest_collection_finish(collection=collection) hook.pytest_collection_finish(collection=collection)
return True return True
@ -108,18 +113,6 @@ def pytest_ignore_collect(path, config):
ignore_paths.extend([py.path.local(x) for x in excludeopt]) ignore_paths.extend([py.path.local(x) for x in excludeopt])
return path in ignore_paths return path in ignore_paths
def pytest_collect_directory(path, parent):
# check if cmdline specified this dir or a subdir directly
for arg in parent.collection._argfspaths:
if path == arg or arg.relto(path):
break
else:
patterns = parent.config.getini("norecursedirs")
for pat in patterns or []:
if path.check(fnmatch=pat):
return
return Directory(path, parent=parent)
class Session(object): class Session(object):
class Interrupted(KeyboardInterrupt): class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """ """ signals an interrupted test run. """
@ -145,160 +138,17 @@ class Session(object):
self._testsfailed) self._testsfailed)
pytest_collectreport = pytest_runtest_logreport pytest_collectreport = pytest_runtest_logreport
class Collection:
def __init__(self, config):
self.config = config
self.topdir = gettopdir(self.config.args)
self._argfspaths = [py.path.local(decodearg(x)[0])
for x in self.config.args]
x = pytest.collect.Directory(fspath=self.topdir,
config=config, collection=self)
self._topcollector = x.consider_dir(self.topdir)
self._topcollector.parent = None
def _normalizearg(self, arg):
return "::".join(self._parsearg(arg))
def _parsearg(self, arg, base=None):
""" return normalized name list for a command line specified id
which might be of the form x/y/z::name1::name2
and should result into the form x::y::z::name1::name2
"""
if base is None:
base = py.path.local()
parts = str(arg).split("::")
path = base.join(parts[0], abs=True)
if not path.check():
raise pytest.UsageError("file not found: %s" %(path,))
topdir = self.topdir
if path != topdir and not path.relto(topdir):
raise pytest.UsageError("path %r is not relative to %r" %
(str(path), str(topdir)))
topparts = path.relto(topdir).split(path.sep)
return topparts + parts[1:]
def getid(self, node):
""" return id for node, relative to topdir. """
path = node.fspath
chain = [x for x in node.listchain() if x.fspath == path]
chain = chain[1:]
names = [x.name for x in chain if x.name != "()"]
relpath = path.relto(self.topdir)
if not relpath:
assert path == self.topdir
path = ''
else:
path = relpath
if os.sep != "/":
path = str(path).replace(os.sep, "/")
names.insert(0, path)
return "::".join(names)
def getbyid(self, id):
""" return one or more nodes matching the id. """
names = [x for x in id.split("::") if x]
if names and '/' in names[0]:
names[:1] = names[0].split("/")
return list(self.matchnodes([self._topcollector], names))
def perform_collect(self):
items = []
for arg in self.config.args:
names = self._parsearg(arg)
try:
for node in self.matchnodes([self._topcollector], names):
items.extend(self.genitems(node))
except NoMatch:
raise pytest.UsageError("can't collect: %s" % (arg,))
return items
def matchnodes(self, matching, names):
if not matching:
return
if not names:
for x in matching:
yield x
return
name = names[0]
names = names[1:]
for node in matching:
if isinstance(node, pytest.collect.Item):
if not name:
yield node
continue
assert isinstance(node, pytest.collect.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
#print "matching", rep.result, "against name", name
if rep.passed:
if not name:
for x in rep.result:
yield x
else:
matched = False
for x in rep.result:
try:
if x.name == name or x.fspath.basename == name:
for x in self.matchnodes([x], names):
yield x
matched = True
elif x.name == "()": # XXX special Instance() case
for x in self.matchnodes([x], [name] + names):
yield x
matched = True
except NoMatch:
pass
if not matched:
node.ihook.pytest_collectreport(report=rep)
raise NoMatch(name)
node.ihook.pytest_collectreport(report=rep)
def genitems(self, node):
if isinstance(node, pytest.collect.Item):
node.ihook.pytest_itemcollected(item=node)
yield node
else:
assert isinstance(node, pytest.collect.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
for subnode in rep.result:
for x in self.genitems(subnode):
yield x
node.ihook.pytest_collectreport(report=rep)
class NoMatch(Exception): class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """ """ raised if matching cannot locate a matching names. """
def gettopdir(args):
""" return the top directory for the given paths.
if the common base dir resides in a python package
parent directory of the root package is returned.
"""
fsargs = [py.path.local(decodearg(arg)[0]) for arg in args]
p = fsargs and fsargs[0] or None
for x in fsargs[1:]:
p = p.common(x)
assert p, "cannot determine common basedir of %s" %(fsargs,)
pkgdir = p.pypkgpath()
if pkgdir is None:
if p.check(file=1):
p = p.dirpath()
return p
else:
return pkgdir.dirpath()
def decodearg(arg):
arg = str(arg)
return arg.split("::")
class HookProxy: class HookProxy:
def __init__(self, node): def __init__(self, fspath, config):
self.node = node self.fspath = fspath
self.config = config
def __getattr__(self, name): def __getattr__(self, name):
hookmethod = getattr(self.node.config.hook, name) hookmethod = getattr(self.config.hook, name)
def call_matching_hooks(**kwargs): def call_matching_hooks(**kwargs):
plugins = self.node.config._getmatchingplugins(self.node.fspath) plugins = self.config._getmatchingplugins(self.fspath)
return hookmethod.pcall(plugins, **kwargs) return hookmethod.pcall(plugins, **kwargs)
return call_matching_hooks return call_matching_hooks
@ -329,7 +179,7 @@ class Node(object):
#: the file where this item is contained/collected from. #: the file where this item is contained/collected from.
self.fspath = getattr(parent, 'fspath', None) self.fspath = getattr(parent, 'fspath', None)
self.ihook = HookProxy(self) self.ihook = self.collection.gethookproxy(self.fspath)
self.keywords = {self.name: True} self.keywords = {self.name: True}
Module = compatproperty("Module") Module = compatproperty("Module")
@ -339,14 +189,19 @@ class Node(object):
Item = compatproperty("Item") Item = compatproperty("Item")
def __repr__(self): def __repr__(self):
if getattr(self.config.option, 'debug', False): return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None))
return "<%s %r %0x>" %(self.__class__.__name__,
getattr(self, 'name', None), id(self))
else:
return "<%s %r>" %(self.__class__.__name__,
getattr(self, 'name', None))
# methods for ordering nodes # methods for ordering nodes
@property
def nodeid(self):
try:
return self._nodeid
except AttributeError:
self._nodeid = x = self._makeid()
return x
def _makeid(self):
return self.parent.nodeid + "::" + self.name
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, Node): if not isinstance(other, Node):
@ -447,7 +302,7 @@ class Collector(Node):
def _memocollect(self): def _memocollect(self):
""" internal helper method to cache results of calling collect(). """ """ internal helper method to cache results of calling collect(). """
return self._memoizedcall('_collected', self.collect) return self._memoizedcall('_collected', lambda: list(self.collect()))
def _prunetraceback(self, excinfo): def _prunetraceback(self, excinfo):
if hasattr(self, 'fspath'): if hasattr(self, 'fspath'):
@ -460,55 +315,177 @@ class Collector(Node):
class FSCollector(Collector): class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, collection=None): def __init__(self, fspath, parent=None, config=None, collection=None):
fspath = py.path.local(fspath) fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
super(FSCollector, self).__init__(fspath.basename, name = parent and fspath.relto(parent.fspath) or fspath.basename
parent, config, collection) super(FSCollector, self).__init__(name, parent, config, collection)
self.fspath = fspath self.fspath = fspath
def _makeid(self):
if self == self.collection:
return "."
relpath = self.collection.fspath.bestrelpath(self.fspath)
if os.sep != "/":
relpath = str(path).replace(os.sep, "/")
return relpath
class File(FSCollector): class File(FSCollector):
""" base class for collecting tests from a file. """ """ base class for collecting tests from a file. """
class Directory(FSCollector):
def collect(self):
l = []
for path in self.fspath.listdir(sort=True):
res = self.consider(path)
if res is not None:
if isinstance(res, (list, tuple)):
l.extend(res)
else:
l.append(res)
return l
def consider(self, path):
if self.ihook.pytest_ignore_collect(path=path, config=self.config):
return
if path.check(file=1):
res = self.consider_file(path)
elif path.check(dir=1):
res = self.consider_dir(path)
else:
res = None
if isinstance(res, list):
# throw out identical results
l = []
for x in res:
if x not in l:
assert x.parent == self, (x.parent, self)
assert x.fspath == path, (x.fspath, path)
l.append(x)
res = l
return res
def consider_file(self, path):
return self.ihook.pytest_collect_file(path=path, parent=self)
def consider_dir(self, path):
return self.ihook.pytest_collect_directory(path=path, parent=self)
class Item(Node): class Item(Node):
""" a basic test invocation item. Note that for a single function """ a basic test invocation item. Note that for a single function
there might be multiple test invocation items. there might be multiple test invocation items.
""" """
def reportinfo(self): def reportinfo(self):
return self.fspath, None, "" return self.fspath, None, ""
@property
def location(self):
try:
return self._location
except AttributeError:
location = self.reportinfo()
location = (str(location[0]), location[1], str(location[2]))
self._location = location
return location
class Collection(FSCollector):
def __init__(self, config):
super(Collection, self).__init__(py.path.local(), parent=None,
config=config, collection=self)
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
def isinitpath(self, path):
return path in self._initialpaths
def gethookproxy(self, fspath):
return HookProxy(fspath, self.config)
def perform_collect(self, args=None, genitems=True):
if args is None:
args = self.config.args
self.trace("perform_collect", self, args)
self.trace.root.indent += 1
self._notfound = []
self._initialpaths = set()
self._initialargs = args
for arg in args:
parts = self._parsearg(arg)
self._initialpaths.add(parts[0])
self.ihook.pytest_collectstart(collector=self)
rep = self.ihook.pytest_make_collect_report(collector=self)
self.ihook.pytest_collectreport(report=rep)
self.trace.root.indent -= 1
if self._notfound:
for arg, exc in self._notfound:
line = "no name %r in any of %r" % (exc.args[1], exc.args[0])
raise pytest.UsageError("not found: %s\n%s" %(arg, line))
if not genitems:
return rep.result
else:
self.items = items = []
if rep.passed:
for node in rep.result:
self.items.extend(self.genitems(node))
return items
def collect(self):
for arg in self._initialargs:
self.trace("processing arg", arg)
self.trace.root.indent += 1
try:
for x in self._collect(arg):
yield x
except NoMatch:
# we are inside a make_report hook so
# we cannot directly pass through the exception
self._notfound.append((arg, sys.exc_info()[1]))
self.trace.root.indent -= 1
break
self.trace.root.indent -= 1
def _collect(self, arg):
names = self._parsearg(arg)
path = names.pop(0)
if path.check(dir=1):
assert not names, "invalid arg %r" %(arg,)
for path in path.visit(rec=self._recurse, bf=True, sort=True):
for x in self._collectfile(path):
yield x
else:
assert path.check(file=1)
for x in self.matchnodes(self._collectfile(path), names):
yield x
def _collectfile(self, path):
ihook = self.gethookproxy(path)
if ihook.pytest_ignore_collect(path=path, config=self.config):
return ()
return ihook.pytest_collect_file(path=path, parent=self)
def _recurse(self, path):
ihook = self.gethookproxy(path)
if ihook.pytest_ignore_collect(path=path, config=self.config):
return
for pat in self._norecursepatterns:
if path.check(fnmatch=pat):
return False
ihook.pytest_collect_directory(path=path, parent=self)
return True
def _parsearg(self, arg):
""" return (fspath, names) tuple after checking the file exists. """
parts = str(arg).split("::")
path = self.fspath.join(parts[0], abs=True)
if not path.check():
raise pytest.UsageError("file not found: %s" %(path,))
parts[0] = path
return parts
def matchnodes(self, matching, names):
self.trace("matchnodes", matching, names)
self.trace.root.indent += 1
nodes = self._matchnodes(matching, names)
num = len(nodes)
self.trace("matchnodes finished -> ", num, "nodes")
self.trace.root.indent -= 1
if num == 0:
raise NoMatch(matching, names[:1])
return nodes
def _matchnodes(self, matching, names):
if not matching or not names:
return matching
name = names[0]
assert name
nextnames = names[1:]
resultnodes = []
for node in matching:
if isinstance(node, pytest.collect.Item):
resultnodes.append(node)
continue
assert isinstance(node, pytest.collect.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
for x in rep.result:
if x.name == name:
resultnodes.extend(self.matchnodes([x], nextnames))
node.ihook.pytest_collectreport(report=rep)
return resultnodes
def genitems(self, node):
self.trace("genitems", node)
if isinstance(node, pytest.collect.Item):
node.ihook.pytest_itemcollected(item=node)
yield node
else:
assert isinstance(node, pytest.collect.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
for subnode in rep.result:
for x in self.genitems(subnode):
yield x
node.ihook.pytest_collectreport(report=rep)

View File

@ -115,10 +115,10 @@ class TerminalReporter:
def write_fspath_result(self, fspath, res): def write_fspath_result(self, fspath, res):
if fspath != self.currentfspath: if fspath != self.currentfspath:
self.currentfspath = fspath self.currentfspath = fspath
fspath = self.curdir.bestrelpath(fspath) #fspath = self.curdir.bestrelpath(fspath)
self._tw.line() self._tw.line()
relpath = self.curdir.bestrelpath(fspath) #relpath = self.curdir.bestrelpath(fspath)
self._tw.write(relpath + " ") self._tw.write(fspath + " ")
self._tw.write(res) self._tw.write(res)
def write_ensure_prefix(self, prefix, extra="", **kwargs): def write_ensure_prefix(self, prefix, extra="", **kwargs):
@ -163,14 +163,15 @@ class TerminalReporter:
def pytest__teardown_final_logerror(self, report): def pytest__teardown_final_logerror(self, report):
self.stats.setdefault("error", []).append(report) self.stats.setdefault("error", []).append(report)
def pytest_runtest_logstart(self, nodeid, location, fspath): def pytest_runtest_logstart(self, nodeid, location):
# ensure that the path is printed before the # ensure that the path is printed before the
# 1st test of a module starts running # 1st test of a module starts running
fspath = nodeid.split("::")[0]
if self.showlongtestinfo: if self.showlongtestinfo:
line = self._locationline(fspath, *location) line = self._locationline(fspath, *location)
self.write_ensure_prefix(line, "") self.write_ensure_prefix(line, "")
elif self.showfspath: elif self.showfspath:
self.write_fspath_result(py.path.local(fspath), "") self.write_fspath_result(fspath, "")
def pytest_runtest_logreport(self, report): def pytest_runtest_logreport(self, report):
rep = report rep = report

View File

@ -22,7 +22,7 @@ def main():
name='pytest', name='pytest',
description='py.test: simple powerful testing with Python', description='py.test: simple powerful testing with Python',
long_description = long_description, long_description = long_description,
version='2.0.0.dev18', version='2.0.0.dev19',
url='http://pytest.org', url='http://pytest.org',
license='MIT license', license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -76,14 +76,13 @@ class TestGeneralUsage:
p1 = testdir.makepyfile("") p1 = testdir.makepyfile("")
p2 = testdir.makefile(".pyc", "123") p2 = testdir.makefile(".pyc", "123")
result = testdir.runpytest(p1, p2) result = testdir.runpytest(p1, p2)
assert result.ret != 0 assert result.ret
result.stderr.fnmatch_lines([ result.stderr.fnmatch_lines([
"*ERROR: can't collect:*%s" %(p2.basename,) "*ERROR: not found:*%s" %(p2.basename,)
]) ])
@py.test.mark.xfail
def test_early_skip(self, testdir): def test_early_skip(self, testdir):
testdir.mkdir("xyz") testdir.mkdir("xyz")
testdir.makeconftest(""" testdir.makeconftest("""
@ -97,7 +96,6 @@ class TestGeneralUsage:
"*1 skip*" "*1 skip*"
]) ])
def test_issue88_initial_file_multinodes(self, testdir): def test_issue88_initial_file_multinodes(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
import py import py
@ -145,7 +143,7 @@ class TestGeneralUsage:
print (py.__file__) print (py.__file__)
print (py.__path__) print (py.__path__)
os.chdir(os.path.dirname(os.getcwd())) os.chdir(os.path.dirname(os.getcwd()))
print (py.log.Producer) print (py.log)
""")) """))
result = testdir.runpython(p, prepend=False) result = testdir.runpython(p, prepend=False)
assert not result.ret assert not result.ret
@ -211,6 +209,27 @@ class TestGeneralUsage:
assert res.ret == 0 assert res.ret == 0
res.stdout.fnmatch_lines(["*1 skipped*"]) res.stdout.fnmatch_lines(["*1 skipped*"])
def test_direct_addressing_selects(self, testdir):
p = testdir.makepyfile("""
def pytest_generate_tests(metafunc):
metafunc.addcall({'i': 1}, id="1")
metafunc.addcall({'i': 2}, id="2")
def test_func(i):
pass
""")
res = testdir.runpytest(p.basename + "::" + "test_func[1]")
assert res.ret == 0
res.stdout.fnmatch_lines(["*1 passed*"])
def test_direct_addressing_notfound(self, testdir):
p = testdir.makepyfile("""
def test_func():
pass
""")
res = testdir.runpytest(p.basename + "::" + "test_notfound")
assert res.ret
res.stderr.fnmatch_lines(["*ERROR*not found*"])
class TestInvocationVariants: class TestInvocationVariants:
def test_earlyinit(self, testdir): def test_earlyinit(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""

View File

@ -9,11 +9,13 @@ def runandparse(testdir, *args):
return result, xmldoc return result, xmldoc
def assert_attr(node, **kwargs): def assert_attr(node, **kwargs):
__tracebackhide__ = True
for name, expected in kwargs.items(): for name, expected in kwargs.items():
anode = node.getAttributeNode(name) anode = node.getAttributeNode(name)
assert anode, "node %r has no attribute %r" %(node, name) assert anode, "node %r has no attribute %r" %(node, name)
val = anode.value val = anode.value
assert val == str(expected) if val != str(expected):
py.test.fail("%r != %r" %(str(val), str(expected)))
class TestPython: class TestPython:
def test_summing_simple(self, testdir): def test_summing_simple(self, testdir):
@ -50,7 +52,7 @@ class TestPython:
assert_attr(node, errors=1, tests=0) assert_attr(node, errors=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0] tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, assert_attr(tnode,
classname="test_setup_error.test_setup_error", classname="test_setup_error",
name="test_function") name="test_function")
fnode = tnode.getElementsByTagName("error")[0] fnode = tnode.getElementsByTagName("error")[0]
assert_attr(fnode, message="test setup failure") assert_attr(fnode, message="test setup failure")
@ -68,9 +70,21 @@ class TestPython:
assert_attr(node, failures=1) assert_attr(node, failures=1)
tnode = node.getElementsByTagName("testcase")[0] tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, assert_attr(tnode,
classname="test_classname_instance.test_classname_instance.TestClass", classname="test_classname_instance.TestClass",
name="test_method") name="test_method")
def test_classname_nested_dir(self, testdir):
p = testdir.tmpdir.ensure("sub", "test_hello.py")
p.write("def test_func(): 0/0")
result, dom = runandparse(testdir)
assert result.ret
node = dom.getElementsByTagName("testsuite")[0]
assert_attr(node, failures=1)
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode,
classname="sub.test_hello",
name="test_func")
def test_internal_error(self, testdir): def test_internal_error(self, testdir):
testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0") testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0")
testdir.makepyfile("def test_function(): pass") testdir.makepyfile("def test_function(): pass")
@ -92,7 +106,7 @@ class TestPython:
assert_attr(node, failures=1, tests=1) assert_attr(node, failures=1, tests=1)
tnode = node.getElementsByTagName("testcase")[0] tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, assert_attr(tnode,
classname="test_failure_function.test_failure_function", classname="test_failure_function",
name="test_fail") name="test_fail")
fnode = tnode.getElementsByTagName("failure")[0] fnode = tnode.getElementsByTagName("failure")[0]
assert_attr(fnode, message="test failure") assert_attr(fnode, message="test failure")
@ -112,11 +126,11 @@ class TestPython:
assert_attr(node, failures=2, tests=2) assert_attr(node, failures=2, tests=2)
tnode = node.getElementsByTagName("testcase")[0] tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, assert_attr(tnode,
classname="test_failure_escape.test_failure_escape", classname="test_failure_escape",
name="test_func[<]") name="test_func[<]")
tnode = node.getElementsByTagName("testcase")[1] tnode = node.getElementsByTagName("testcase")[1]
assert_attr(tnode, assert_attr(tnode,
classname="test_failure_escape.test_failure_escape", classname="test_failure_escape",
name="test_func[&]") name="test_func[&]")
def test_junit_prefixing(self, testdir): def test_junit_prefixing(self, testdir):
@ -133,11 +147,11 @@ class TestPython:
assert_attr(node, failures=1, tests=2) assert_attr(node, failures=1, tests=2)
tnode = node.getElementsByTagName("testcase")[0] tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, assert_attr(tnode,
classname="xyz.test_junit_prefixing.test_junit_prefixing", classname="xyz.test_junit_prefixing",
name="test_func") name="test_func")
tnode = node.getElementsByTagName("testcase")[1] tnode = node.getElementsByTagName("testcase")[1]
assert_attr(tnode, assert_attr(tnode,
classname="xyz.test_junit_prefixing.test_junit_prefixing." classname="xyz.test_junit_prefixing."
"TestHello", "TestHello",
name="test_hello") name="test_hello")
@ -153,7 +167,7 @@ class TestPython:
assert_attr(node, skips=1, tests=0) assert_attr(node, skips=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0] tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, assert_attr(tnode,
classname="test_xfailure_function.test_xfailure_function", classname="test_xfailure_function",
name="test_xfail") name="test_xfail")
fnode = tnode.getElementsByTagName("skipped")[0] fnode = tnode.getElementsByTagName("skipped")[0]
assert_attr(fnode, message="expected test failure") assert_attr(fnode, message="expected test failure")
@ -172,7 +186,7 @@ class TestPython:
assert_attr(node, skips=1, tests=0) assert_attr(node, skips=1, tests=0)
tnode = node.getElementsByTagName("testcase")[0] tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, assert_attr(tnode,
classname="test_xfailure_xpass.test_xfailure_xpass", classname="test_xfailure_xpass",
name="test_xpass") name="test_xpass")
fnode = tnode.getElementsByTagName("skipped")[0] fnode = tnode.getElementsByTagName("skipped")[0]
assert_attr(fnode, message="xfail-marked test passes unexpectedly") assert_attr(fnode, message="xfail-marked test passes unexpectedly")

View File

@ -235,7 +235,7 @@ class TestFunction:
param = 1 param = 1
funcargs = {} funcargs = {}
id = "world" id = "world"
collection = object() collection = testdir.Collection(config)
f5 = py.test.collect.Function(name="name", config=config, f5 = py.test.collect.Function(name="name", config=config,
callspec=callspec1, callobj=isinstance, collection=collection) callspec=callspec1, callobj=isinstance, collection=collection)
f5b = py.test.collect.Function(name="name", config=config, f5b = py.test.collect.Function(name="name", config=config,
@ -395,8 +395,8 @@ def test_generate_tests_only_done_in_subdir(testdir):
def test_modulecol_roundtrip(testdir): def test_modulecol_roundtrip(testdir):
modcol = testdir.getmodulecol("pass", withinit=True) modcol = testdir.getmodulecol("pass", withinit=True)
trail = modcol.collection.getid(modcol) trail = modcol.nodeid
newcol = modcol.collection.getbyid(trail)[0] newcol = modcol.collection.perform_collect([trail], genitems=0)[0]
assert modcol.name == newcol.name assert modcol.name == newcol.name
@ -1058,8 +1058,7 @@ class TestReportInfo:
""") """)
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
runner = item.config.pluginmanager.getplugin("runner") runner = item.config.pluginmanager.getplugin("runner")
nodeinfo = runner.getitemnodeinfo(item) assert item.location == ("ABCDE", 42, "custom")
assert nodeinfo.location == ("ABCDE", 42, "custom")
def test_func_reportinfo(self, testdir): def test_func_reportinfo(self, testdir):
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")

View File

@ -257,9 +257,9 @@ class TestCollectionReports:
assert not rep.skipped assert not rep.skipped
assert rep.passed assert rep.passed
locinfo = rep.location locinfo = rep.location
assert locinfo[0] == col.fspath assert locinfo[0] == col.fspath.basename
assert not locinfo[1] assert not locinfo[1]
assert locinfo[2] == col.fspath assert locinfo[2] == col.fspath.basename
res = rep.result res = rep.result
assert len(res) == 2 assert len(res) == 2
assert res[0].name == "test_func1" assert res[0].name == "test_func1"

View File

@ -17,13 +17,14 @@ class SessionTests:
assert len(skipped) == 0 assert len(skipped) == 0
assert len(passed) == 1 assert len(passed) == 1
assert len(failed) == 3 assert len(failed) == 3
assert failed[0].nodenames[-1] == "test_one_one" end = lambda x: x.nodeid.split("::")[-1]
assert failed[1].nodenames[-1] == "test_other" assert end(failed[0]) == "test_one_one"
assert failed[2].nodenames[-1] == "test_two" assert end(failed[1]) == "test_other"
assert end(failed[2]) == "test_two"
itemstarted = reprec.getcalls("pytest_itemcollected") itemstarted = reprec.getcalls("pytest_itemcollected")
assert len(itemstarted) == 4 assert len(itemstarted) == 4
colstarted = reprec.getcalls("pytest_collectstart") colstarted = reprec.getcalls("pytest_collectstart")
assert len(colstarted) == 1 + 1 # XXX ExtraTopCollector assert len(colstarted) == 1 + 1
col = colstarted[1].collector col = colstarted[1].collector
assert isinstance(col, py.test.collect.Module) assert isinstance(col, py.test.collect.Module)
@ -186,7 +187,7 @@ class TestNewSession(SessionTests):
started = reprec.getcalls("pytest_collectstart") started = reprec.getcalls("pytest_collectstart")
finished = reprec.getreports("pytest_collectreport") finished = reprec.getreports("pytest_collectreport")
assert len(started) == len(finished) assert len(started) == len(finished)
assert len(started) == 8 + 1 # XXX extra TopCollector assert len(started) == 8 # XXX extra TopCollector
colfail = [x for x in finished if x.failed] colfail = [x for x in finished if x.failed]
colskipped = [x for x in finished if x.skipped] colskipped = [x for x in finished if x.skipped]
assert len(colfail) == 1 assert len(colfail) == 1

View File

@ -1,14 +1,9 @@
""" """
terminal reporting of the full testing process. terminal reporting of the full testing process.
""" """
import py import pytest,py
import sys import sys
# ===============================================================================
# plugin tests
#
# ===============================================================================
from pytest.plugin.terminal import TerminalReporter, \ from pytest.plugin.terminal import TerminalReporter, \
CollectonlyReporter, repr_pythonversion, getreportopt CollectonlyReporter, repr_pythonversion, getreportopt
from pytest.plugin import runner from pytest.plugin import runner
@ -95,9 +90,8 @@ class TestTerminal:
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
tr = TerminalReporter(item.config, file=linecomp.stringio) tr = TerminalReporter(item.config, file=linecomp.stringio)
item.config.pluginmanager.register(tr) item.config.pluginmanager.register(tr)
nodeid = item.collection.getid(item)
location = item.reportinfo() location = item.reportinfo()
tr.config.hook.pytest_runtest_logstart(nodeid=nodeid, tr.config.hook.pytest_runtest_logstart(nodeid=item.nodeid,
location=location, fspath=str(item.fspath)) location=location, fspath=str(item.fspath))
linecomp.assert_contains_lines([ linecomp.assert_contains_lines([
"*test_show_runtest_logstart.py*" "*test_show_runtest_logstart.py*"
@ -424,6 +418,7 @@ class TestTerminalFunctional:
"*test_verbose_reporting.py:10: test_gen*FAIL*", "*test_verbose_reporting.py:10: test_gen*FAIL*",
]) ])
assert result.ret == 1 assert result.ret == 1
pytest.xfail("repair xdist")
pytestconfig.pluginmanager.skipifmissing("xdist") pytestconfig.pluginmanager.skipifmissing("xdist")
result = testdir.runpytest(p1, '-v', '-n 1') result = testdir.runpytest(p1, '-v', '-n 1')
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([

View File

@ -1,288 +0,0 @@
import py
class TestCollector:
def test_collect_versus_item(self):
from pytest.collect import Collector, Item
assert not issubclass(Collector, Item)
assert not issubclass(Item, Collector)
def test_compat_attributes(self, testdir, recwarn):
modcol = testdir.getmodulecol("""
def test_pass(): pass
def test_fail(): assert 0
""")
recwarn.clear()
assert modcol.Module == py.test.collect.Module
recwarn.pop(DeprecationWarning)
assert modcol.Class == py.test.collect.Class
recwarn.pop(DeprecationWarning)
assert modcol.Item == py.test.collect.Item
recwarn.pop(DeprecationWarning)
assert modcol.File == py.test.collect.File
recwarn.pop(DeprecationWarning)
assert modcol.Function == py.test.collect.Function
recwarn.pop(DeprecationWarning)
def test_check_equality(self, testdir):
modcol = testdir.getmodulecol("""
def test_pass(): pass
def test_fail(): assert 0
""")
fn1 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn1, py.test.collect.Function)
fn2 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn2, py.test.collect.Function)
assert fn1 == fn2
assert fn1 != modcol
if py.std.sys.version_info < (3, 0):
assert cmp(fn1, fn2) == 0
assert hash(fn1) == hash(fn2)
fn3 = testdir.collect_by_name(modcol, "test_fail")
assert isinstance(fn3, py.test.collect.Function)
assert not (fn1 == fn3)
assert fn1 != fn3
for fn in fn1,fn2,fn3:
assert fn != 3
assert fn != modcol
assert fn != [1,2,3]
assert [1,2,3] != fn
assert modcol != fn
def test_getparent(self, testdir):
modcol = testdir.getmodulecol("""
class TestClass:
def test_foo():
pass
""")
cls = testdir.collect_by_name(modcol, "TestClass")
fn = testdir.collect_by_name(
testdir.collect_by_name(cls, "()"), "test_foo")
parent = fn.getparent(py.test.collect.Module)
assert parent is modcol
parent = fn.getparent(py.test.collect.Function)
assert parent is fn
parent = fn.getparent(py.test.collect.Class)
assert parent is cls
def test_getcustomfile_roundtrip(self, testdir):
hello = testdir.makefile(".xxx", hello="world")
testdir.makepyfile(conftest="""
import py
class CustomFile(py.test.collect.File):
pass
def pytest_collect_file(path, parent):
if path.ext == ".xxx":
return CustomFile(path, parent=parent)
""")
config = testdir.parseconfig(hello)
node = testdir.getnode(config, hello)
assert isinstance(node, py.test.collect.File)
assert node.name == "hello.xxx"
id = node.collection.getid(node)
nodes = node.collection.getbyid(id)
assert len(nodes) == 1
assert isinstance(nodes[0], py.test.collect.File)
class TestCollectFS:
def test_ignored_certain_directories(self, testdir):
tmpdir = testdir.tmpdir
tmpdir.ensure("_darcs", 'test_notfound.py')
tmpdir.ensure("CVS", 'test_notfound.py')
tmpdir.ensure("{arch}", 'test_notfound.py')
tmpdir.ensure(".whatever", 'test_notfound.py')
tmpdir.ensure(".bzr", 'test_notfound.py')
tmpdir.ensure("normal", 'test_found.py')
result = testdir.runpytest("--collectonly")
s = result.stdout.str()
assert "test_notfound" not in s
assert "test_found" in s
def test_custom_norecursedirs(self, testdir):
testdir.makeini("""
[pytest]
norecursedirs = mydir xyz*
""")
tmpdir = testdir.tmpdir
tmpdir.ensure("mydir", "test_hello.py").write("def test_1(): pass")
tmpdir.ensure("xyz123", "test_2.py").write("def test_2(): 0/0")
tmpdir.ensure("xy", "test_ok.py").write("def test_3(): pass")
rec = testdir.inline_run()
rec.assertoutcome(passed=1)
rec = testdir.inline_run("xyz123/test_2.py")
rec.assertoutcome(failed=1)
def test_found_certain_testfiles(self, testdir):
p1 = testdir.makepyfile(test_found = "pass", found_test="pass")
col = testdir.getnode(testdir.parseconfig(p1), p1.dirpath())
items = col.collect() # Directory collect returns files sorted by name
assert len(items) == 2
assert items[1].name == 'test_found.py'
assert items[0].name == 'found_test.py'
def test_directory_file_sorting(self, testdir):
p1 = testdir.makepyfile(test_one="hello")
p1.dirpath().mkdir("x")
p1.dirpath().mkdir("dir1")
testdir.makepyfile(test_two="hello")
p1.dirpath().mkdir("dir2")
config = testdir.parseconfig()
col = testdir.getnode(config, p1.dirpath())
names = [x.name for x in col.collect()]
assert names == ["dir1", "dir2", "test_one.py", "test_two.py", "x"]
class TestCollectPluginHookRelay:
def test_pytest_collect_file(self, testdir):
tmpdir = testdir.tmpdir
wascalled = []
class Plugin:
def pytest_collect_file(self, path, parent):
wascalled.append(path)
config = testdir.Config()
config.pluginmanager.register(Plugin())
config.parse([tmpdir])
col = testdir.getnode(config, tmpdir)
testdir.makefile(".abc", "xyz")
res = col.collect()
assert len(wascalled) == 1
assert wascalled[0].ext == '.abc'
def test_pytest_collect_directory(self, testdir):
tmpdir = testdir.tmpdir
wascalled = []
class Plugin:
def pytest_collect_directory(self, path, parent):
wascalled.append(path.basename)
return py.test.collect.Directory(path, parent)
testdir.plugins.append(Plugin())
testdir.mkdir("hello")
testdir.mkdir("world")
reprec = testdir.inline_run()
assert "hello" in wascalled
assert "world" in wascalled
# make sure the directories do not get double-appended
colreports = reprec.getreports("pytest_collectreport")
names = [rep.nodenames[-1] for rep in colreports]
assert names.count("hello") == 1
class TestPrunetraceback:
def test_collection_error(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
result = testdir.runpytest(p)
assert "__import__" not in result.stdout.str(), "too long traceback"
result.stdout.fnmatch_lines([
"*ERROR collecting*",
"*mport*not_exists*"
])
def test_custom_repr_failure(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
testdir.makeconftest("""
import py
def pytest_collect_file(path, parent):
return MyFile(path, parent)
class MyError(Exception):
pass
class MyFile(py.test.collect.File):
def collect(self):
raise MyError()
def repr_failure(self, excinfo):
if excinfo.errisinstance(MyError):
return "hello world"
return py.test.collect.File.repr_failure(self, excinfo)
""")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
"*ERROR collecting*",
"*hello world*",
])
@py.test.mark.xfail(reason="other mechanism for adding to reporting needed")
def test_collect_report_postprocessing(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
testdir.makeconftest("""
import py
def pytest_make_collect_report(__multicall__):
rep = __multicall__.execute()
rep.headerlines += ["header1"]
return rep
""")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
"*ERROR collecting*",
"*header1*",
])
class TestCustomConftests:
def test_ignore_collect_path(self, testdir):
testdir.makeconftest("""
def pytest_ignore_collect(path, config):
return path.basename.startswith("x") or \
path.basename == "test_one.py"
""")
testdir.mkdir("xy123").ensure("test_hello.py").write(
"syntax error"
)
testdir.makepyfile("def test_hello(): pass")
testdir.makepyfile(test_one="syntax error")
result = testdir.runpytest()
assert result.ret == 0
result.stdout.fnmatch_lines(["*1 passed*"])
def test_collectignore_exclude_on_option(self, testdir):
testdir.makeconftest("""
collect_ignore = ['hello', 'test_world.py']
def pytest_addoption(parser):
parser.addoption("--XX", action="store_true", default=False)
def pytest_configure(config):
if config.getvalue("XX"):
collect_ignore[:] = []
""")
testdir.mkdir("hello")
testdir.makepyfile(test_world="#")
reprec = testdir.inline_run(testdir.tmpdir)
names = [rep.nodenames[-1]
for rep in reprec.getreports("pytest_collectreport")]
assert 'hello' not in names
assert 'test_world.py' not in names
reprec = testdir.inline_run(testdir.tmpdir, "--XX")
names = [rep.nodenames[-1]
for rep in reprec.getreports("pytest_collectreport")]
assert 'hello' in names
assert 'test_world.py' in names
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
conf = 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)
""")
sub = testdir.mkdir("sub")
p = testdir.makepyfile("def test_x(): pass")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*MyDirectory*",
"*MyModule*",
"*test_x*"
])

View File

@ -1,6 +1,283 @@
import py import py
from pytest.plugin.session import Collection, gettopdir from pytest.plugin.session import Collection
class TestCollector:
def test_collect_versus_item(self):
from pytest.collect import Collector, Item
assert not issubclass(Collector, Item)
assert not issubclass(Item, Collector)
def test_compat_attributes(self, testdir, recwarn):
modcol = testdir.getmodulecol("""
def test_pass(): pass
def test_fail(): assert 0
""")
recwarn.clear()
assert modcol.Module == py.test.collect.Module
recwarn.pop(DeprecationWarning)
assert modcol.Class == py.test.collect.Class
recwarn.pop(DeprecationWarning)
assert modcol.Item == py.test.collect.Item
recwarn.pop(DeprecationWarning)
assert modcol.File == py.test.collect.File
recwarn.pop(DeprecationWarning)
assert modcol.Function == py.test.collect.Function
recwarn.pop(DeprecationWarning)
def test_check_equality(self, testdir):
modcol = testdir.getmodulecol("""
def test_pass(): pass
def test_fail(): assert 0
""")
fn1 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn1, py.test.collect.Function)
fn2 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn2, py.test.collect.Function)
assert fn1 == fn2
assert fn1 != modcol
if py.std.sys.version_info < (3, 0):
assert cmp(fn1, fn2) == 0
assert hash(fn1) == hash(fn2)
fn3 = testdir.collect_by_name(modcol, "test_fail")
assert isinstance(fn3, py.test.collect.Function)
assert not (fn1 == fn3)
assert fn1 != fn3
for fn in fn1,fn2,fn3:
assert fn != 3
assert fn != modcol
assert fn != [1,2,3]
assert [1,2,3] != fn
assert modcol != fn
def test_getparent(self, testdir):
modcol = testdir.getmodulecol("""
class TestClass:
def test_foo():
pass
""")
cls = testdir.collect_by_name(modcol, "TestClass")
fn = testdir.collect_by_name(
testdir.collect_by_name(cls, "()"), "test_foo")
parent = fn.getparent(py.test.collect.Module)
assert parent is modcol
parent = fn.getparent(py.test.collect.Function)
assert parent is fn
parent = fn.getparent(py.test.collect.Class)
assert parent is cls
def test_getcustomfile_roundtrip(self, testdir):
hello = testdir.makefile(".xxx", hello="world")
testdir.makepyfile(conftest="""
import py
class CustomFile(py.test.collect.File):
pass
def pytest_collect_file(path, parent):
if path.ext == ".xxx":
return CustomFile(path, parent=parent)
""")
node = testdir.getpathnode(hello)
assert isinstance(node, py.test.collect.File)
assert node.name == "hello.xxx"
nodes = node.collection.perform_collect([node.nodeid], genitems=False)
assert len(nodes) == 1
assert isinstance(nodes[0], py.test.collect.File)
class TestCollectFS:
def test_ignored_certain_directories(self, testdir):
tmpdir = testdir.tmpdir
tmpdir.ensure("_darcs", 'test_notfound.py')
tmpdir.ensure("CVS", 'test_notfound.py')
tmpdir.ensure("{arch}", 'test_notfound.py')
tmpdir.ensure(".whatever", 'test_notfound.py')
tmpdir.ensure(".bzr", 'test_notfound.py')
tmpdir.ensure("normal", 'test_found.py')
result = testdir.runpytest("--collectonly")
s = result.stdout.str()
assert "test_notfound" not in s
assert "test_found" in s
def test_custom_norecursedirs(self, testdir):
testdir.makeini("""
[pytest]
norecursedirs = mydir xyz*
""")
tmpdir = testdir.tmpdir
tmpdir.ensure("mydir", "test_hello.py").write("def test_1(): pass")
tmpdir.ensure("xyz123", "test_2.py").write("def test_2(): 0/0")
tmpdir.ensure("xy", "test_ok.py").write("def test_3(): pass")
rec = testdir.inline_run()
rec.assertoutcome(passed=1)
rec = testdir.inline_run("xyz123/test_2.py")
rec.assertoutcome(failed=1)
class TestCollectPluginHookRelay:
def test_pytest_collect_file(self, testdir):
wascalled = []
class Plugin:
def pytest_collect_file(self, path, parent):
wascalled.append(path)
testdir.makefile(".abc", "xyz")
testdir.pytestmain([testdir.tmpdir], plugins=[Plugin()])
assert len(wascalled) == 1
assert wascalled[0].ext == '.abc'
def test_pytest_collect_directory(self, testdir):
wascalled = []
class Plugin:
def pytest_collect_directory(self, path, parent):
wascalled.append(path.basename)
testdir.mkdir("hello")
testdir.mkdir("world")
testdir.pytestmain(testdir.tmpdir, plugins=[Plugin()])
assert "hello" in wascalled
assert "world" in wascalled
class TestPrunetraceback:
def test_collection_error(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
result = testdir.runpytest(p)
assert "__import__" not in result.stdout.str(), "too long traceback"
result.stdout.fnmatch_lines([
"*ERROR collecting*",
"*mport*not_exists*"
])
def test_custom_repr_failure(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
testdir.makeconftest("""
import py
def pytest_collect_file(path, parent):
return MyFile(path, parent)
class MyError(Exception):
pass
class MyFile(py.test.collect.File):
def collect(self):
raise MyError()
def repr_failure(self, excinfo):
if excinfo.errisinstance(MyError):
return "hello world"
return py.test.collect.File.repr_failure(self, excinfo)
""")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
"*ERROR collecting*",
"*hello world*",
])
@py.test.mark.xfail(reason="other mechanism for adding to reporting needed")
def test_collect_report_postprocessing(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
testdir.makeconftest("""
import py
def pytest_make_collect_report(__multicall__):
rep = __multicall__.execute()
rep.headerlines += ["header1"]
return rep
""")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
"*ERROR collecting*",
"*header1*",
])
class TestCustomConftests:
def test_ignore_collect_path(self, testdir):
testdir.makeconftest("""
def pytest_ignore_collect(path, config):
return path.basename.startswith("x") or \
path.basename == "test_one.py"
""")
testdir.mkdir("xy123").ensure("test_hello.py").write(
"syntax error"
)
testdir.makepyfile("def test_hello(): pass")
testdir.makepyfile(test_one="syntax error")
result = testdir.runpytest()
assert result.ret == 0
result.stdout.fnmatch_lines(["*1 passed*"])
def test_collectignore_exclude_on_option(self, testdir):
testdir.makeconftest("""
collect_ignore = ['hello', 'test_world.py']
def pytest_addoption(parser):
parser.addoption("--XX", action="store_true", default=False)
def pytest_configure(config):
if config.getvalue("XX"):
collect_ignore[:] = []
""")
testdir.mkdir("hello")
testdir.makepyfile(test_world="def test_hello(): pass")
result = testdir.runpytest()
assert result.ret == 0
assert "passed" not in result.stdout.str()
result = testdir.runpytest("--XX")
assert result.ret == 0
assert "passed" in result.stdout.str()
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
conf = testdir.makeconftest("""
import py
class MyModule(py.test.collect.Module):
pass
def pytest_collect_file(path, parent):
if path.ext == ".py":
return MyModule(path, parent)
""")
sub = testdir.mkdir("sub")
p = testdir.makepyfile("def test_x(): pass")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*MyModule*",
"*test_x*"
])
def test_pytest_collect_file_from_sister_dir(self, testdir):
sub1 = testdir.mkpydir("sub1")
sub2 = testdir.mkpydir("sub2")
conf1 = testdir.makeconftest("""
import py
class MyModule1(py.test.collect.Module):
pass
def pytest_collect_file(path, parent):
if path.ext == ".py":
return MyModule1(path, parent)
""")
conf1.move(sub1.join(conf1.basename))
conf2 = testdir.makeconftest("""
import py
class MyModule2(py.test.collect.Module):
pass
def pytest_collect_file(path, parent):
if path.ext == ".py":
return MyModule2(path, parent)
""")
conf2.move(sub2.join(conf2.basename))
p = testdir.makepyfile("def test_x(): pass")
p.copy(sub1.join(p.basename))
p.copy(sub2.join(p.basename))
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*MyModule1*",
"*MyModule2*",
"*test_x*"
])
class TestCollection: class TestCollection:
def test_parsearg(self, testdir): def test_parsearg(self, testdir):
@ -13,16 +290,15 @@ class TestCollection:
subdir.chdir() subdir.chdir()
config = testdir.parseconfig(p.basename) config = testdir.parseconfig(p.basename)
rcol = Collection(config=config) rcol = Collection(config=config)
assert rcol.topdir == testdir.tmpdir assert rcol.fspath == subdir
parts = rcol._parsearg(p.basename) parts = rcol._parsearg(p.basename)
assert parts[0] == "sub"
assert parts[1] == p.basename assert parts[0] == target
assert len(parts) == 2 assert len(parts) == 1
parts = rcol._parsearg(p.basename + "::test_func") parts = rcol._parsearg(p.basename + "::test_func")
assert parts[0] == "sub" assert parts[0] == target
assert parts[1] == p.basename assert parts[1] == "test_func"
assert parts[2] == "test_func" assert len(parts) == 2
assert len(parts) == 3
def test_collect_topdir(self, testdir): def test_collect_topdir(self, testdir):
p = testdir.makepyfile("def test_func(): pass") p = testdir.makepyfile("def test_func(): pass")
@ -30,14 +306,14 @@ class TestCollection:
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
topdir = testdir.tmpdir topdir = testdir.tmpdir
rcol = Collection(config) rcol = Collection(config)
assert topdir == rcol.topdir assert topdir == rcol.fspath
hookrec = testdir.getreportrecorder(config) rootid = rcol.nodeid
items = rcol.perform_collect() #root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0]
assert len(items) == 1 #assert root2 == rcol, rootid
root = items[0].listchain()[0] colitems = rcol.perform_collect([rcol.nodeid], genitems=False)
root_id = rcol.getid(root) assert len(colitems) == 1
root2 = rcol.getbyid(root_id)[0] assert colitems[0].fspath == p
assert root2.fspath == root.fspath
def test_collect_protocol_single_function(self, testdir): def test_collect_protocol_single_function(self, testdir):
p = testdir.makepyfile("def test_func(): pass") p = testdir.makepyfile("def test_func(): pass")
@ -45,13 +321,14 @@ class TestCollection:
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
topdir = testdir.tmpdir topdir = testdir.tmpdir
rcol = Collection(config) rcol = Collection(config)
assert topdir == rcol.topdir assert topdir == rcol.fspath
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
items = rcol.perform_collect() rcol.perform_collect()
items = rcol.items
assert len(items) == 1 assert len(items) == 1
item = items[0] item = items[0]
assert item.name == "test_func" assert item.name == "test_func"
newid = rcol.getid(item) newid = item.nodeid
assert newid == id assert newid == id
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.hookrecorder.calls)
hookrec.hookrecorder.contains([ hookrec.hookrecorder.contains([
@ -60,8 +337,8 @@ class TestCollection:
("pytest_collectstart", "collector.fspath == p"), ("pytest_collectstart", "collector.fspath == p"),
("pytest_make_collect_report", "collector.fspath == p"), ("pytest_make_collect_report", "collector.fspath == p"),
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.fspath == p"), ("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
("pytest_collectreport", "report.fspath == topdir") ("pytest_collectreport", "report.nodeid == '.'")
]) ])
def test_collect_protocol_method(self, testdir): def test_collect_protocol_method(self, testdir):
@ -70,19 +347,19 @@ class TestCollection:
def test_method(self): def test_method(self):
pass pass
""") """)
normid = p.basename + "::TestClass::test_method" normid = p.basename + "::TestClass::()::test_method"
for id in [p.basename, for id in [p.basename,
p.basename + "::TestClass", p.basename + "::TestClass",
p.basename + "::TestClass::()", p.basename + "::TestClass::()",
p.basename + "::TestClass::()::test_method",
normid, normid,
]: ]:
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
rcol = Collection(config=config) rcol = Collection(config=config)
nodes = rcol.perform_collect() rcol.perform_collect()
assert len(nodes) == 1 items = rcol.items
assert nodes[0].name == "test_method" assert len(items) == 1
newid = rcol.getid(nodes[0]) assert items[0].name == "test_method"
newid = items[0].nodeid
assert newid == normid assert newid == normid
def test_collect_custom_nodes_multi_id(self, testdir): def test_collect_custom_nodes_multi_id(self, testdir):
@ -104,20 +381,21 @@ class TestCollection:
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
rcol = Collection(config) rcol = Collection(config)
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
items = rcol.perform_collect() rcol.perform_collect()
items = rcol.items
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.hookrecorder.calls)
assert len(items) == 2 assert len(items) == 2
hookrec.hookrecorder.contains([ hookrec.hookrecorder.contains([
("pytest_collectstart", ("pytest_collectstart",
"collector.fspath == collector.collection.topdir"), "collector.fspath == collector.collection.fspath"),
("pytest_collectstart", ("pytest_collectstart",
"collector.__class__.__name__ == 'SpecialFile'"), "collector.__class__.__name__ == 'SpecialFile'"),
("pytest_collectstart", ("pytest_collectstart",
"collector.__class__.__name__ == 'Module'"), "collector.__class__.__name__ == 'Module'"),
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.fspath == p"), ("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
("pytest_collectreport", #("pytest_collectreport",
"report.fspath == %r" % str(rcol.topdir)), # "report.fspath == %r" % str(rcol.fspath)),
]) ])
def test_collect_subdir_event_ordering(self, testdir): def test_collect_subdir_event_ordering(self, testdir):
@ -128,134 +406,87 @@ class TestCollection:
config = testdir.parseconfig() config = testdir.parseconfig()
rcol = Collection(config) rcol = Collection(config)
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
items = rcol.perform_collect() rcol.perform_collect()
items = rcol.items
assert len(items) == 1 assert len(items) == 1
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.hookrecorder.calls)
hookrec.hookrecorder.contains([ hookrec.hookrecorder.contains([
("pytest_collectstart", "collector.fspath == aaa"),
("pytest_collectstart", "collector.fspath == test_aaa"), ("pytest_collectstart", "collector.fspath == test_aaa"),
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.fspath == test_aaa"), ("pytest_collectreport",
("pytest_collectreport", "report.fspath == aaa"), "report.nodeid.startswith('aaa/test_aaa.py')"),
]) ])
def test_collect_two_commandline_args(self, testdir): def test_collect_two_commandline_args(self, testdir):
p = testdir.makepyfile("def test_func(): pass") p = testdir.makepyfile("def test_func(): pass")
aaa = testdir.mkpydir("aaa") aaa = testdir.mkpydir("aaa")
bbb = testdir.mkpydir("bbb") bbb = testdir.mkpydir("bbb")
p.copy(aaa.join("test_aaa.py")) test_aaa = aaa.join("test_aaa.py")
p.move(bbb.join("test_bbb.py")) p.copy(test_aaa)
test_bbb = bbb.join("test_bbb.py")
p.move(test_bbb)
id = "." id = "."
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
rcol = Collection(config) rcol = Collection(config)
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
items = rcol.perform_collect() rcol.perform_collect()
items = rcol.items
assert len(items) == 2 assert len(items) == 2
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.hookrecorder.calls)
hookrec.hookrecorder.contains([ hookrec.hookrecorder.contains([
("pytest_collectstart", "collector.fspath == aaa"), ("pytest_collectstart", "collector.fspath == test_aaa"),
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.fspath == aaa"), ("pytest_collectreport", "report.nodeid == 'aaa/test_aaa.py'"),
("pytest_collectstart", "collector.fspath == bbb"), ("pytest_collectstart", "collector.fspath == test_bbb"),
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.fspath == bbb"), ("pytest_collectreport", "report.nodeid == 'bbb/test_bbb.py'"),
]) ])
def test_serialization_byid(self, testdir): def test_serialization_byid(self, testdir):
p = testdir.makepyfile("def test_func(): pass") p = testdir.makepyfile("def test_func(): pass")
config = testdir.parseconfig() config = testdir.parseconfig()
rcol = Collection(config) rcol = Collection(config)
items = rcol.perform_collect() rcol.perform_collect()
items = rcol.items
assert len(items) == 1 assert len(items) == 1
item, = items item, = items
id = rcol.getid(item)
newcol = Collection(config) newcol = Collection(config)
item2, = newcol.getbyid(id) item2, = newcol.perform_collect([item.nodeid], genitems=False)
assert item2.name == item.name assert item2.name == item.name
assert item2.fspath == item.fspath assert item2.fspath == item.fspath
item2b, = newcol.getbyid(id) item2b, = newcol.perform_collect([item.nodeid], genitems=False)
assert item2b is item2 assert item2b == item2
class Test_gettopdir:
def test_gettopdir(self, testdir):
tmp = testdir.tmpdir
assert gettopdir([tmp]) == tmp
topdir = gettopdir([tmp.join("hello"), tmp.join("world")])
assert topdir == tmp
somefile = tmp.ensure("somefile.py")
assert gettopdir([somefile]) == tmp
def test_gettopdir_pypkg(self, testdir):
tmp = testdir.tmpdir
a = tmp.ensure('a', dir=1)
b = tmp.ensure('a', 'b', '__init__.py')
c = tmp.ensure('a', 'b', 'c.py')
Z = tmp.ensure('Z', dir=1)
assert gettopdir([c]) == a
assert gettopdir([c, Z]) == tmp
assert gettopdir(["%s::xyc" % c]) == a
assert gettopdir(["%s::xyc::abc" % c]) == a
assert gettopdir(["%s::xyc" % c, "%s::abc" % Z]) == tmp
def getargnode(collection, arg): def getargnode(collection, arg):
return collection.getbyid(collection._normalizearg(str(arg)))[0] argpath = arg.relto(collection.fspath)
return collection.perform_collect([argpath], genitems=False)[0]
class Test_getinitialnodes: class Test_getinitialnodes:
def test_onedir(self, testdir):
config = testdir.reparseconfig([testdir.tmpdir])
c = Collection(config)
col = getargnode(c, testdir.tmpdir)
assert isinstance(col, py.test.collect.Directory)
for col in col.listchain():
assert col.config is config
t2 = getargnode(c, testdir.tmpdir)
assert col == t2
def test_curdir_and_subdir(self, testdir, tmpdir):
a = tmpdir.ensure("a", dir=1)
config = testdir.reparseconfig([tmpdir, a])
c = Collection(config)
col1 = getargnode(c, tmpdir)
col2 = getargnode(c, a)
assert col1.name == tmpdir.basename
assert col2.name == 'a'
for col in (col1, col2):
for subcol in col.listchain():
assert col.config is config
def test_global_file(self, testdir, tmpdir): def test_global_file(self, testdir, tmpdir):
x = tmpdir.ensure("x.py") x = tmpdir.ensure("x.py")
config = testdir.reparseconfig([x]) config = testdir.reparseconfig([x])
col = getargnode(Collection(config), x) col = testdir.getnode(config, x)
assert isinstance(col, py.test.collect.Module) assert isinstance(col, py.test.collect.Module)
assert col.name == 'x.py' assert col.name == 'x.py'
assert col.parent.name == tmpdir.basename assert col.parent.name == testdir.tmpdir.basename
assert col.parent.parent is None assert col.parent.parent is None
for col in col.listchain(): for col in col.listchain():
assert col.config is config assert col.config is config
def test_global_dir(self, testdir, tmpdir): def test_pkgfile(self, testdir):
x = tmpdir.ensure("a", dir=1) testdir.chdir()
tmpdir = testdir.tmpdir
subdir = tmpdir.join("subdir")
x = subdir.ensure("x.py")
subdir.ensure("__init__.py")
config = testdir.reparseconfig([x]) config = testdir.reparseconfig([x])
col = getargnode(Collection(config), x) col = testdir.getnode(config, x)
assert isinstance(col, py.test.collect.Directory)
print(col.listchain())
assert col.name == 'a'
assert col.parent is None
assert col.config is config
def test_pkgfile(self, testdir, tmpdir):
tmpdir = tmpdir.join("subdir")
x = tmpdir.ensure("x.py")
tmpdir.ensure("__init__.py")
config = testdir.reparseconfig([x])
col = getargnode(Collection(config), x)
assert isinstance(col, py.test.collect.Module) assert isinstance(col, py.test.collect.Module)
assert col.name == 'x.py' print col.obj
assert col.parent.name == x.dirpath().basename print col.listchain()
assert col.parent.parent.parent is None assert col.name == 'subdir/x.py'
assert col.parent.parent is None
for col in col.listchain(): for col in col.listchain():
assert col.config is config assert col.config is config

View File

@ -81,7 +81,7 @@ class TestConfigAPI:
config.trace.root.setwriter(l.append) config.trace.root.setwriter(l.append)
config.trace("hello") config.trace("hello")
assert len(l) == 1 assert len(l) == 1
assert l[0] == "[pytest:config] hello\n" assert l[0] == "[pytest] hello\n"
def test_config_getvalue_honours_conftest(self, testdir): def test_config_getvalue_honours_conftest(self, testdir):
testdir.makepyfile(conftest="x=1") testdir.makepyfile(conftest="x=1")

View File

@ -52,4 +52,5 @@ commands=
[pytest] [pytest]
minversion=2.0 minversion=2.0
plugins=pytester plugins=pytester
#addargs=-rf addargs=-rfx
rsyncdirs=pytest testing