test_ok2/py/_test/session.py

263 lines
8.9 KiB
Python

""" basic test session implementation.
* drives collection of tests
* triggers executions of tests
* produces events used by reporting
"""
import py
import sys
#
# main entry point
#
def main(args=None):
if args is None:
args = sys.argv[1:]
config = py.test.config
try:
config.parse(args)
config.pluginmanager.do_configure(config)
exitstatus = config.hook.pytest_cmdline_main(config=config)
config.pluginmanager.do_unconfigure(config)
except config.Error:
e = sys.exc_info()[1]
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
exitstatus = EXIT_INTERNALERROR
py.test.config = py.test.config.__class__()
return exitstatus
# exitcodes for the command line
EXIT_OK = 0
EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3
EXIT_NOHOSTS = 4
# imports used for genitems()
Item = py.test.collect.Item
Collector = py.test.collect.Collector
class Session(object):
nodeid = ""
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
def __init__(self, config, collection):
self.config = config
self.pluginmanager = config.pluginmanager # shortcut
self.pluginmanager.register(self)
self._testsfailed = 0
self.shouldstop = False
self.collection = collection
def sessionstarts(self):
""" setup any neccessary resources ahead of the test run. """
self.config.hook.pytest_sessionstart(session=self)
def pytest_runtest_logreport(self, report):
if report.failed:
self._testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self._testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self._testsfailed)
self.collection.shouldstop = self.shouldstop
pytest_collectreport = pytest_runtest_logreport
def sessionfinishes(self, exitstatus):
""" teardown any resources after a test run. """
self.config.hook.pytest_sessionfinish(
session=self,
exitstatus=exitstatus,
)
def main(self):
""" main loop for running tests. """
self.shouldstop = False
self.sessionstarts()
exitstatus = EXIT_OK
try:
self._mainloop()
if self._testsfailed:
exitstatus = EXIT_TESTSFAILED
self.sessionfinishes(exitstatus=exitstatus)
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
exitstatus = EXIT_INTERRUPTED
except:
excinfo = py.code.ExceptionInfo()
self.config.pluginmanager.notify_exception(excinfo)
exitstatus = EXIT_INTERNALERROR
if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED):
self.sessionfinishes(exitstatus=exitstatus)
return exitstatus
def _mainloop(self):
if self.config.option.collectonly:
return
for item in self.collection.items:
item.config.hook.pytest_runtest_protocol(item=item)
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
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 = py.test.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):
""" 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
"""
parts = str(arg).split("::")
path = py.path.local(parts[0])
if not path.check():
raise self.config.Error("file not found: %s" %(path,))
topdir = self.topdir
if path != topdir and not path.relto(topdir):
raise self.config.Error("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, relative=True):
""" 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 != "()"]
if relative:
relpath = path.relto(self.topdir)
if relpath:
path = relpath
names = relpath.split(node.fspath.sep) + names
return "::".join(names)
def getbyid(self, id):
""" return one or more nodes matching the id. """
matching = [self._topcollector]
if not id:
return matching
names = id.split("::")
while names:
name = names.pop(0)
l = []
for current in matching:
for x in current._memocollect():
if x.name == name:
l.append(x)
elif x.name == "()":
names.insert(0, name)
l.append(x)
break
if not l:
raise ValueError("no node named %r below %r" %(name, current))
matching = l
return matching
def do_collection(self):
assert not hasattr(self, 'items')
hook = self.config.hook
hook.pytest_log_startcollection(collection=self)
try:
self.items = self.perform_collect()
except self.config.Error:
raise
except Exception:
self.config.pluginmanager.notify_exception()
return EXIT_INTERNALERROR
else:
hook.pytest_collection_modifyitems(collection=self)
res = hook.pytest_log_finishcollection(collection=self)
return res and max(res) or 0 # returncode
def getinitialnodes(self):
idlist = [self._normalizearg(arg) for arg in self.config.args]
nodes = []
for id in idlist:
nodes.extend(self.getbyid(id))
return nodes
def perform_collect(self):
idlist = [self._parsearg(arg) for arg in self.config.args]
nodes = []
for names in idlist:
self.genitems([self._topcollector], names, nodes)
return nodes
def genitems(self, matching, names, result):
names = list(names)
name = names and names.pop(0) or None
for node in matching:
if isinstance(node, Item):
if name is None:
self.config.hook.pytest_log_itemcollect(item=node)
result.append(node)
else:
assert isinstance(node, 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 name:
matched = False
for subcol in rep.result:
if subcol.name != name and subcol.name == "()":
names.insert(0, name)
name = "()"
# see doctests/custom naming XXX
if subcol.name == name or subcol.fspath.basename == name:
self.genitems([subcol], names, result)
matched = True
if not matched:
raise self.config.Error(
"can't collect: %s" % (name,))
else:
self.genitems(rep.result, [], result)
node.ihook.pytest_collectreport(report=rep)
x = getattr(self, 'shouldstop', None)
if x:
raise self.Interrupted(x)
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("::")