clean up and simplify startup test protocols and objects
introduce some new experimental hooks pytest_runtest_mainloop to better integrate distributed testing --HG-- branch : trunk
This commit is contained in:
parent
2cf22e3124
commit
7d1585215d
|
@ -24,6 +24,10 @@ def pytest_cmdline_main(config):
|
||||||
""" called for performing the main (cmdline) action. """
|
""" called for performing the main (cmdline) action. """
|
||||||
pytest_cmdline_main.firstresult = True
|
pytest_cmdline_main.firstresult = True
|
||||||
|
|
||||||
|
def pytest_runtest_mainloop(session):
|
||||||
|
""" called for performing the main runtest loop (after collection. """
|
||||||
|
pytest_runtest_mainloop.firstresult = True
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
def pytest_unconfigure(config):
|
||||||
""" called before test process is exited. """
|
""" called before test process is exited. """
|
||||||
|
|
||||||
|
@ -31,10 +35,11 @@ def pytest_unconfigure(config):
|
||||||
# collection hooks
|
# collection hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
def pytest_log_startcollection(collection):
|
def pytest_perform_collection(session):
|
||||||
""" called before collection.perform_collection() is called. """
|
""" perform the collection protocol for the given session. """
|
||||||
|
pytest_perform_collection.firstresult = True
|
||||||
|
|
||||||
def pytest_collection_modifyitems(collection):
|
def pytest_collection_modifyitems(config, items):
|
||||||
""" called to allow filtering and selecting of test items (inplace). """
|
""" called to allow filtering and selecting of test items (inplace). """
|
||||||
|
|
||||||
def pytest_log_finishcollection(collection):
|
def pytest_log_finishcollection(collection):
|
||||||
|
@ -139,6 +144,7 @@ def pytest_sessionstart(session):
|
||||||
def pytest_sessionfinish(session, exitstatus):
|
def pytest_sessionfinish(session, exitstatus):
|
||||||
""" whole test run finishes. """
|
""" whole test run finishes. """
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# hooks for influencing reporting (invoked from pytest_terminal)
|
# hooks for influencing reporting (invoked from pytest_terminal)
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
|
@ -4,15 +4,26 @@ import sys
|
||||||
import py
|
import py
|
||||||
|
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
from py._test.session import Session, Collection
|
from py._test.session import Session
|
||||||
collection = Collection(config)
|
return Session(config).main()
|
||||||
# instantiate session already because it
|
|
||||||
# records failures and implements maxfail handling
|
def pytest_perform_collection(session):
|
||||||
session = Session(config, collection)
|
collection = session.collection
|
||||||
exitstatus = collection.do_collection()
|
assert not hasattr(collection, 'items')
|
||||||
if not exitstatus:
|
hook = session.config.hook
|
||||||
exitstatus = session.main()
|
collection.items = items = collection.perform_collect()
|
||||||
return exitstatus
|
hook.pytest_collection_modifyitems(config=session.config, items=items)
|
||||||
|
hook.pytest_log_finishcollection(collection=collection)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def pytest_runtest_mainloop(session):
|
||||||
|
if session.config.option.collectonly:
|
||||||
|
return True
|
||||||
|
for item in session.collection.items:
|
||||||
|
item.config.hook.pytest_runtest_protocol(item=item)
|
||||||
|
if session.shouldstop:
|
||||||
|
raise session.Interrupted(session.shouldstop)
|
||||||
|
return True
|
||||||
|
|
||||||
def pytest_ignore_collect(path, config):
|
def pytest_ignore_collect(path, config):
|
||||||
ignore_paths = config.getconftest_pathlist("collect_ignore", path=path)
|
ignore_paths = config.getconftest_pathlist("collect_ignore", path=path)
|
||||||
|
@ -21,11 +32,6 @@ def pytest_ignore_collect(path, config):
|
||||||
if excludeopt:
|
if excludeopt:
|
||||||
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
|
||||||
# XXX more refined would be:
|
|
||||||
if ignore_paths:
|
|
||||||
for p in ignore_paths:
|
|
||||||
if path == p or path.relto(p):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def pytest_collect_directory(path, parent):
|
def pytest_collect_directory(path, parent):
|
||||||
# XXX reconsider the following comment
|
# XXX reconsider the following comment
|
||||||
|
|
|
@ -11,7 +11,7 @@ def pytest_addoption(parser):
|
||||||
dest="genscript", metavar="path",
|
dest="genscript", metavar="path",
|
||||||
help="create standalone py.test script at given target path.")
|
help="create standalone py.test script at given target path.")
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_cmdline_main(config):
|
||||||
genscript = config.getvalue("genscript")
|
genscript = config.getvalue("genscript")
|
||||||
if genscript:
|
if genscript:
|
||||||
import py
|
import py
|
||||||
|
@ -20,7 +20,7 @@ def pytest_configure(config):
|
||||||
pybasedir = py.path.local(py.__file__).dirpath().dirpath()
|
pybasedir = py.path.local(py.__file__).dirpath().dirpath()
|
||||||
genscript = py.path.local(genscript)
|
genscript = py.path.local(genscript)
|
||||||
main(pybasedir, outfile=genscript, infile=infile)
|
main(pybasedir, outfile=genscript, infile=infile)
|
||||||
raise SystemExit(0)
|
return 0
|
||||||
|
|
||||||
def main(pybasedir, outfile, infile):
|
def main(pybasedir, outfile, infile):
|
||||||
import base64
|
import base64
|
||||||
|
|
|
@ -23,15 +23,18 @@ def pytest_addoption(parser):
|
||||||
help="show available conftest.py and ENV-variable names.")
|
help="show available conftest.py and ENV-variable names.")
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(__multicall__, config):
|
def pytest_cmdline_main(config):
|
||||||
if config.option.version:
|
if config.option.version:
|
||||||
p = py.path.local(py.__file__).dirpath()
|
p = py.path.local(py.__file__).dirpath()
|
||||||
sys.stderr.write("This is py.test version %s, imported from %s\n" %
|
sys.stderr.write("This is py.test version %s, imported from %s\n" %
|
||||||
(py.__version__, p))
|
(py.__version__, p))
|
||||||
sys.exit(0)
|
return 0
|
||||||
if not config.option.helpconfig:
|
elif config.option.helpconfig:
|
||||||
return
|
config.pluginmanager.do_configure(config)
|
||||||
__multicall__.execute()
|
showpluginhelp(config)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def showpluginhelp(config):
|
||||||
options = []
|
options = []
|
||||||
for group in config._parser._groups:
|
for group in config._parser._groups:
|
||||||
options.extend(group.options)
|
options.extend(group.options)
|
||||||
|
@ -65,9 +68,7 @@ def pytest_configure(__multicall__, config):
|
||||||
help,
|
help,
|
||||||
)
|
)
|
||||||
tw.line(line[:tw.fullwidth])
|
tw.line(line[:tw.fullwidth])
|
||||||
|
|
||||||
tw.sep("-")
|
tw.sep("-")
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
conftest_options = (
|
conftest_options = (
|
||||||
('pytest_plugins', 'list of plugin names to load'),
|
('pytest_plugins', 'list of plugin names to load'),
|
||||||
|
|
|
@ -8,8 +8,7 @@ def pytest_addoption(parser):
|
||||||
"Terminate the expression with ':' to treat a match as a signal "
|
"Terminate the expression with ':' to treat a match as a signal "
|
||||||
"to run all subsequent tests. ")
|
"to run all subsequent tests. ")
|
||||||
|
|
||||||
def pytest_collection_modifyitems(collection):
|
def pytest_collection_modifyitems(items, config):
|
||||||
config = collection.config
|
|
||||||
keywordexpr = config.option.keyword
|
keywordexpr = config.option.keyword
|
||||||
if not keywordexpr:
|
if not keywordexpr:
|
||||||
return
|
return
|
||||||
|
@ -20,7 +19,7 @@ def pytest_collection_modifyitems(collection):
|
||||||
|
|
||||||
remaining = []
|
remaining = []
|
||||||
deselected = []
|
deselected = []
|
||||||
for colitem in collection.items:
|
for colitem in items:
|
||||||
if keywordexpr and skipbykeyword(colitem, keywordexpr):
|
if keywordexpr and skipbykeyword(colitem, keywordexpr):
|
||||||
deselected.append(colitem)
|
deselected.append(colitem)
|
||||||
else:
|
else:
|
||||||
|
@ -30,7 +29,7 @@ def pytest_collection_modifyitems(collection):
|
||||||
|
|
||||||
if deselected:
|
if deselected:
|
||||||
config.hook.pytest_deselected(items=deselected)
|
config.hook.pytest_deselected(items=deselected)
|
||||||
collection.items[:] = remaining
|
items[:] = remaining
|
||||||
|
|
||||||
def skipbykeyword(colitem, keywordexpr):
|
def skipbykeyword(colitem, keywordexpr):
|
||||||
""" return True if they given keyword expression means to
|
""" return True if they given keyword expression means to
|
||||||
|
|
|
@ -193,9 +193,9 @@ class TmpTestdir:
|
||||||
args = ("-s", ) + args # otherwise FD leakage
|
args = ("-s", ) + args # otherwise FD leakage
|
||||||
config = self.parseconfig(*args)
|
config = self.parseconfig(*args)
|
||||||
reprec = self.getreportrecorder(config)
|
reprec = self.getreportrecorder(config)
|
||||||
config.pluginmanager.do_configure(config)
|
#config.pluginmanager.do_configure(config)
|
||||||
config.hook.pytest_cmdline_main(config=config)
|
config.hook.pytest_cmdline_main(config=config)
|
||||||
config.pluginmanager.do_unconfigure(config)
|
#config.pluginmanager.do_unconfigure(config)
|
||||||
return reprec
|
return reprec
|
||||||
|
|
||||||
def config_preparse(self):
|
def config_preparse(self):
|
||||||
|
|
|
@ -29,21 +29,13 @@ def pytest_addoption(parser):
|
||||||
help="don't cut any tracebacks (default is to cut).")
|
help="don't cut any tracebacks (default is to cut).")
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
|
if config.option.showfuncargs:
|
||||||
|
return
|
||||||
if config.option.collectonly:
|
if config.option.collectonly:
|
||||||
reporter = CollectonlyReporter(config)
|
reporter = CollectonlyReporter(config)
|
||||||
elif config.option.showfuncargs:
|
|
||||||
reporter = None
|
|
||||||
else:
|
else:
|
||||||
reporter = TerminalReporter(config)
|
reporter = TerminalReporter(config)
|
||||||
if reporter:
|
config.pluginmanager.register(reporter, 'terminalreporter')
|
||||||
# XXX see remote.py's XXX
|
|
||||||
for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
|
|
||||||
if hasattr(config, attr):
|
|
||||||
#print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
|
|
||||||
name = attr.split("_")[-1]
|
|
||||||
assert hasattr(self.reporter._tw, name), name
|
|
||||||
setattr(reporter._tw, name, getattr(config, attr))
|
|
||||||
config.pluginmanager.register(reporter, 'terminalreporter')
|
|
||||||
|
|
||||||
def getreportopt(config):
|
def getreportopt(config):
|
||||||
reportopts = ""
|
reportopts = ""
|
||||||
|
@ -192,7 +184,10 @@ class TerminalReporter:
|
||||||
markup = {}
|
markup = {}
|
||||||
self.stats.setdefault(cat, []).append(rep)
|
self.stats.setdefault(cat, []).append(rep)
|
||||||
if not self.config.option.verbose:
|
if not self.config.option.verbose:
|
||||||
self.write_fspath_result(self._getfspath(rep.item), letter)
|
fspath = getattr(rep, 'fspath', None)
|
||||||
|
if not fspath:
|
||||||
|
fspath = self._getfspath(rep.item)
|
||||||
|
self.write_fspath_result(fspath, letter)
|
||||||
else:
|
else:
|
||||||
line = self._reportinfoline(rep.item)
|
line = self._reportinfoline(rep.item)
|
||||||
if not hasattr(rep, 'node'):
|
if not hasattr(rep, 'node'):
|
||||||
|
@ -217,17 +212,19 @@ class TerminalReporter:
|
||||||
def pytest_sessionstart(self, session):
|
def pytest_sessionstart(self, session):
|
||||||
self.write_sep("=", "test session starts", bold=True)
|
self.write_sep("=", "test session starts", bold=True)
|
||||||
self._sessionstarttime = py.std.time.time()
|
self._sessionstarttime = py.std.time.time()
|
||||||
|
|
||||||
verinfo = ".".join(map(str, sys.version_info[:3]))
|
verinfo = ".".join(map(str, sys.version_info[:3]))
|
||||||
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
|
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
|
||||||
msg += " -- pytest-%s" % (py.__version__)
|
msg += " -- pytest-%s" % (py.__version__)
|
||||||
if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None):
|
if self.config.option.verbose or self.config.option.debug or \
|
||||||
|
getattr(self.config.option, 'pastebin', None):
|
||||||
msg += " -- " + str(sys.executable)
|
msg += " -- " + str(sys.executable)
|
||||||
self.write_line(msg)
|
self.write_line(msg)
|
||||||
lines = self.config.hook.pytest_report_header(config=self.config)
|
lines = self.config.hook.pytest_report_header(config=self.config)
|
||||||
lines.reverse()
|
lines.reverse()
|
||||||
for line in flatten(lines):
|
for line in flatten(lines):
|
||||||
self.write_line(line)
|
self.write_line(line)
|
||||||
|
|
||||||
|
def pytest_log_finishcollection(self):
|
||||||
for i, testarg in enumerate(self.config.args):
|
for i, testarg in enumerate(self.config.args):
|
||||||
self.write_line("test path %d: %s" %(i+1, testarg))
|
self.write_line("test path %d: %s" %(i+1, testarg))
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PluginManager(object):
|
||||||
name = id(plugin)
|
name = id(plugin)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def register(self, plugin, name=None):
|
def register(self, plugin, name=None, prepend=False):
|
||||||
assert not self.isregistered(plugin), plugin
|
assert not self.isregistered(plugin), plugin
|
||||||
assert not self.registry.isregistered(plugin), plugin
|
assert not self.registry.isregistered(plugin), plugin
|
||||||
name = self._getpluginname(plugin, name)
|
name = self._getpluginname(plugin, name)
|
||||||
|
@ -41,7 +41,7 @@ class PluginManager(object):
|
||||||
self._name2plugin[name] = plugin
|
self._name2plugin[name] = plugin
|
||||||
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
||||||
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
||||||
self.registry.register(plugin)
|
self.registry.register(plugin, prepend=prepend)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def unregister(self, plugin):
|
def unregister(self, plugin):
|
||||||
|
@ -277,10 +277,13 @@ class Registry:
|
||||||
plugins = []
|
plugins = []
|
||||||
self._plugins = plugins
|
self._plugins = plugins
|
||||||
|
|
||||||
def register(self, plugin):
|
def register(self, plugin, prepend=False):
|
||||||
assert not isinstance(plugin, str)
|
assert not isinstance(plugin, str)
|
||||||
assert not plugin in self._plugins
|
assert not plugin in self._plugins
|
||||||
self._plugins.append(plugin)
|
if not prepend:
|
||||||
|
self._plugins.append(plugin)
|
||||||
|
else:
|
||||||
|
self._plugins.insert(0, plugin)
|
||||||
|
|
||||||
def unregister(self, plugin):
|
def unregister(self, plugin):
|
||||||
self._plugins.remove(plugin)
|
self._plugins.remove(plugin)
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import sys
|
import os, sys
|
||||||
|
|
||||||
#
|
#
|
||||||
# main entry point
|
# main entry point
|
||||||
|
@ -16,11 +16,9 @@ def main(args=None):
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
config = py.test.config
|
config = py.test.config
|
||||||
|
config.parse(args)
|
||||||
try:
|
try:
|
||||||
config.parse(args)
|
|
||||||
config.pluginmanager.do_configure(config)
|
|
||||||
exitstatus = config.hook.pytest_cmdline_main(config=config)
|
exitstatus = config.hook.pytest_cmdline_main(config=config)
|
||||||
config.pluginmanager.do_unconfigure(config)
|
|
||||||
except config.Error:
|
except config.Error:
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
||||||
|
@ -28,7 +26,6 @@ def main(args=None):
|
||||||
py.test.config = py.test.config.__class__()
|
py.test.config = py.test.config.__class__()
|
||||||
return exitstatus
|
return exitstatus
|
||||||
|
|
||||||
|
|
||||||
# exitcodes for the command line
|
# exitcodes for the command line
|
||||||
EXIT_OK = 0
|
EXIT_OK = 0
|
||||||
EXIT_TESTSFAILED = 1
|
EXIT_TESTSFAILED = 1
|
||||||
|
@ -36,27 +33,25 @@ EXIT_INTERRUPTED = 2
|
||||||
EXIT_INTERNALERROR = 3
|
EXIT_INTERNALERROR = 3
|
||||||
EXIT_NOHOSTS = 4
|
EXIT_NOHOSTS = 4
|
||||||
|
|
||||||
# imports used for genitems()
|
|
||||||
Item = py.test.collect.Item
|
|
||||||
Collector = py.test.collect.Collector
|
|
||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
nodeid = ""
|
nodeid = ""
|
||||||
class Interrupted(KeyboardInterrupt):
|
class Interrupted(KeyboardInterrupt):
|
||||||
""" signals an interrupted test run. """
|
""" signals an interrupted test run. """
|
||||||
__module__ = 'builtins' # for py3
|
__module__ = 'builtins' # for py3
|
||||||
|
|
||||||
def __init__(self, config, collection):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.pluginmanager = config.pluginmanager # shortcut
|
self.config.pluginmanager.register(self, name="session", prepend=True)
|
||||||
self.pluginmanager.register(self)
|
|
||||||
self._testsfailed = 0
|
self._testsfailed = 0
|
||||||
self.shouldstop = False
|
self.shouldstop = False
|
||||||
self.collection = collection
|
self.collection = Collection(config)
|
||||||
|
|
||||||
def sessionstarts(self):
|
def sessionfinishes(self, exitstatus):
|
||||||
""" setup any neccessary resources ahead of the test run. """
|
# XXX move to main loop / refactor mainloop
|
||||||
self.config.hook.pytest_sessionstart(session=self)
|
self.config.hook.pytest_sessionfinish(
|
||||||
|
session=self,
|
||||||
|
exitstatus=exitstatus,
|
||||||
|
)
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, report):
|
def pytest_runtest_logreport(self, report):
|
||||||
if report.failed:
|
if report.failed:
|
||||||
|
@ -68,24 +63,22 @@ class Session(object):
|
||||||
self.collection.shouldstop = self.shouldstop
|
self.collection.shouldstop = self.shouldstop
|
||||||
pytest_collectreport = pytest_runtest_logreport
|
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):
|
def main(self):
|
||||||
""" main loop for running tests. """
|
""" main loop for running tests. """
|
||||||
self.shouldstop = False
|
self.shouldstop = False
|
||||||
|
|
||||||
self.sessionstarts()
|
|
||||||
exitstatus = EXIT_OK
|
exitstatus = EXIT_OK
|
||||||
|
config = self.config
|
||||||
try:
|
try:
|
||||||
self._mainloop()
|
config.pluginmanager.do_configure(config)
|
||||||
|
config.hook.pytest_sessionstart(session=self)
|
||||||
|
config.hook.pytest_perform_collection(session=self)
|
||||||
|
config.hook.pytest_runtest_mainloop(session=self)
|
||||||
if self._testsfailed:
|
if self._testsfailed:
|
||||||
exitstatus = EXIT_TESTSFAILED
|
exitstatus = EXIT_TESTSFAILED
|
||||||
self.sessionfinishes(exitstatus=exitstatus)
|
self.sessionfinishes(exitstatus=exitstatus)
|
||||||
|
config.pluginmanager.do_unconfigure(config)
|
||||||
|
except self.config.Error:
|
||||||
|
raise
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||||
|
@ -94,19 +87,12 @@ class Session(object):
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
self.config.pluginmanager.notify_exception(excinfo)
|
self.config.pluginmanager.notify_exception(excinfo)
|
||||||
exitstatus = EXIT_INTERNALERROR
|
exitstatus = EXIT_INTERNALERROR
|
||||||
|
if excinfo.errisinstance(SystemExit):
|
||||||
|
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
|
||||||
if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED):
|
if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED):
|
||||||
self.sessionfinishes(exitstatus=exitstatus)
|
self.sessionfinishes(exitstatus=exitstatus)
|
||||||
return 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:
|
class Collection:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -121,13 +107,15 @@ class Collection:
|
||||||
def _normalizearg(self, arg):
|
def _normalizearg(self, arg):
|
||||||
return "::".join(self._parsearg(arg))
|
return "::".join(self._parsearg(arg))
|
||||||
|
|
||||||
def _parsearg(self, arg):
|
def _parsearg(self, arg, base=None):
|
||||||
""" return normalized name list for a command line specified id
|
""" return normalized name list for a command line specified id
|
||||||
which might be of the form x/y/z::name1::name2
|
which might be of the form x/y/z::name1::name2
|
||||||
and should result into 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("::")
|
parts = str(arg).split("::")
|
||||||
path = py.path.local(parts[0])
|
path = base.join(parts[0], abs=True)
|
||||||
if not path.check():
|
if not path.check():
|
||||||
raise self.config.Error("file not found: %s" %(path,))
|
raise self.config.Error("file not found: %s" %(path,))
|
||||||
topdir = self.topdir
|
topdir = self.topdir
|
||||||
|
@ -137,17 +125,21 @@ class Collection:
|
||||||
topparts = path.relto(topdir).split(path.sep)
|
topparts = path.relto(topdir).split(path.sep)
|
||||||
return topparts + parts[1:]
|
return topparts + parts[1:]
|
||||||
|
|
||||||
def getid(self, node, relative=True):
|
def getid(self, node):
|
||||||
""" return id for node, relative to topdir. """
|
""" return id for node, relative to topdir. """
|
||||||
path = node.fspath
|
path = node.fspath
|
||||||
chain = [x for x in node.listchain() if x.fspath == path]
|
chain = [x for x in node.listchain() if x.fspath == path]
|
||||||
chain = chain[1:]
|
chain = chain[1:]
|
||||||
names = [x.name for x in chain if x.name != "()"]
|
names = [x.name for x in chain if x.name != "()"]
|
||||||
if relative:
|
relpath = path.relto(self.topdir)
|
||||||
relpath = path.relto(self.topdir)
|
if not relpath:
|
||||||
if relpath:
|
assert path == self.topdir
|
||||||
path = relpath
|
path = ''
|
||||||
names = relpath.split(node.fspath.sep) + names
|
else:
|
||||||
|
path = relpath
|
||||||
|
if os.sep != "/":
|
||||||
|
path = str(path).replace(os.sep, "/")
|
||||||
|
names.insert(0, path)
|
||||||
return "::".join(names)
|
return "::".join(names)
|
||||||
|
|
||||||
def getbyid(self, id):
|
def getbyid(self, id):
|
||||||
|
@ -158,6 +150,9 @@ class Collection:
|
||||||
names = id.split("::")
|
names = id.split("::")
|
||||||
while names:
|
while names:
|
||||||
name = names.pop(0)
|
name = names.pop(0)
|
||||||
|
newnames = name.split("/")
|
||||||
|
name = newnames[0]
|
||||||
|
names[:0] = newnames[1:]
|
||||||
l = []
|
l = []
|
||||||
for current in matching:
|
for current in matching:
|
||||||
for x in current._memocollect():
|
for x in current._memocollect():
|
||||||
|
@ -172,22 +167,6 @@ class Collection:
|
||||||
matching = l
|
matching = l
|
||||||
return matching
|
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):
|
def getinitialnodes(self):
|
||||||
idlist = [self._normalizearg(arg) for arg in self.config.args]
|
idlist = [self._normalizearg(arg) for arg in self.config.args]
|
||||||
nodes = []
|
nodes = []
|
||||||
|
@ -206,36 +185,36 @@ class Collection:
|
||||||
names = list(names)
|
names = list(names)
|
||||||
name = names and names.pop(0) or None
|
name = names and names.pop(0) or None
|
||||||
for node in matching:
|
for node in matching:
|
||||||
if isinstance(node, Item):
|
if isinstance(node, py.test.collect.Item):
|
||||||
if name is None:
|
if name is None:
|
||||||
self.config.hook.pytest_log_itemcollect(item=node)
|
self.config.hook.pytest_log_itemcollect(item=node)
|
||||||
result.append(node)
|
result.append(node)
|
||||||
else:
|
continue
|
||||||
assert isinstance(node, Collector)
|
assert isinstance(node, py.test.collect.Collector)
|
||||||
node.ihook.pytest_collectstart(collector=node)
|
node.ihook.pytest_collectstart(collector=node)
|
||||||
rep = node.ihook.pytest_make_collect_report(collector=node)
|
rep = node.ihook.pytest_make_collect_report(collector=node)
|
||||||
#print "matching", rep.result, "against name", name
|
#print "matching", rep.result, "against name", name
|
||||||
if rep.passed:
|
if rep.passed:
|
||||||
if name:
|
if name:
|
||||||
matched = False
|
matched = False
|
||||||
for subcol in rep.result:
|
for subcol in rep.result:
|
||||||
if subcol.name != name and subcol.name == "()":
|
if subcol.name != name and subcol.name == "()":
|
||||||
names.insert(0, name)
|
names.insert(0, name)
|
||||||
name = "()"
|
name = "()"
|
||||||
# see doctests/custom naming XXX
|
# see doctests/custom naming XXX
|
||||||
if subcol.name == name or subcol.fspath.basename == name:
|
if subcol.name == name or subcol.fspath.basename == name:
|
||||||
self.genitems([subcol], names, result)
|
self.genitems([subcol], names, result)
|
||||||
matched = True
|
matched = True
|
||||||
if not matched:
|
if not matched:
|
||||||
raise self.config.Error(
|
raise self.config.Error(
|
||||||
"can't collect: %s" % (name,))
|
"can't collect: %s" % (name,))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.genitems(rep.result, [], result)
|
self.genitems(rep.result, [], result)
|
||||||
node.ihook.pytest_collectreport(report=rep)
|
node.ihook.pytest_collectreport(report=rep)
|
||||||
x = getattr(self, 'shouldstop', None)
|
x = getattr(self, 'shouldstop', None)
|
||||||
if x:
|
if x:
|
||||||
raise self.Interrupted(x)
|
raise Session.Interrupted(x)
|
||||||
|
|
||||||
def gettopdir(args):
|
def gettopdir(args):
|
||||||
""" return the top directory for the given paths.
|
""" return the top directory for the given paths.
|
||||||
|
|
|
@ -26,7 +26,7 @@ def test_gen(testdir, anypython, standalone):
|
||||||
"*imported from*mypytest"
|
"*imported from*mypytest"
|
||||||
])
|
])
|
||||||
|
|
||||||
@py.test.mark.xfail(reason="fix-dist")
|
@py.test.mark.xfail(reason="fix-dist", run=False)
|
||||||
def test_rundist(testdir, pytestconfig, standalone):
|
def test_rundist(testdir, pytestconfig, standalone):
|
||||||
pytestconfig.pluginmanager.skipifmissing("xdist")
|
pytestconfig.pluginmanager.skipifmissing("xdist")
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
|
|
|
@ -145,7 +145,7 @@ class TestCollection:
|
||||||
bbb = testdir.mkpydir("bbb")
|
bbb = testdir.mkpydir("bbb")
|
||||||
p.copy(aaa.join("test_aaa.py"))
|
p.copy(aaa.join("test_aaa.py"))
|
||||||
p.move(bbb.join("test_bbb.py"))
|
p.move(bbb.join("test_bbb.py"))
|
||||||
|
|
||||||
id = "."
|
id = "."
|
||||||
config = testdir.parseconfig(id)
|
config = testdir.parseconfig(id)
|
||||||
rcol = Collection(config)
|
rcol = Collection(config)
|
||||||
|
|
|
@ -160,6 +160,19 @@ class TestBootstrapping:
|
||||||
pp.unregister(a2)
|
pp.unregister(a2)
|
||||||
assert not pp.isregistered(a2)
|
assert not pp.isregistered(a2)
|
||||||
|
|
||||||
|
def test_registry_ordering(self):
|
||||||
|
pp = PluginManager()
|
||||||
|
class A: pass
|
||||||
|
a1, a2 = A(), A()
|
||||||
|
pp.register(a1)
|
||||||
|
pp.register(a2, "hello")
|
||||||
|
l = pp.getplugins()
|
||||||
|
assert l.index(a1) < l.index(a2)
|
||||||
|
a3 = A()
|
||||||
|
pp.register(a3, prepend=True)
|
||||||
|
l = pp.getplugins()
|
||||||
|
assert l.index(a3) == 0
|
||||||
|
|
||||||
def test_register_imported_modules(self):
|
def test_register_imported_modules(self):
|
||||||
pp = PluginManager()
|
pp = PluginManager()
|
||||||
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
||||||
|
|
Loading…
Reference in New Issue