implement pytest_runtest_logstart(nodeid, location) hook
factor out a NodeInfo helper, and streamline terminal printing a bit --HG-- branch : trunk
This commit is contained in:
parent
1c020c3d32
commit
a2fe6714f8
|
@ -102,14 +102,16 @@ def pytest_generate_tests(metafunc):
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# generic runtest related hooks
|
# generic runtest related hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
def pytest_itemstart(item, node=None):
|
def pytest_itemstart(item, node=None):
|
||||||
""" test item starts running. """
|
""" (deprecated, use pytest_runtest_logstart). """
|
||||||
|
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item):
|
||||||
""" implement fixture, run and report about the given test item. """
|
""" implement fixture, run and report about the given test item. """
|
||||||
pytest_runtest_protocol.firstresult = True
|
pytest_runtest_protocol.firstresult = True
|
||||||
|
|
||||||
|
def pytest_runtest_logstart(nodeid, location):
|
||||||
|
""" signal the start of a test run. """
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
""" called before pytest_runtest_call(). """
|
""" called before pytest_runtest_call(). """
|
||||||
|
|
||||||
|
@ -160,7 +162,7 @@ def pytest_terminal_summary(terminalreporter):
|
||||||
""" add additional section in terminal summary reporting. """
|
""" add additional section in terminal summary reporting. """
|
||||||
|
|
||||||
def pytest_report_iteminfo(item):
|
def pytest_report_iteminfo(item):
|
||||||
""" return (fspath, lineno, domainpath) for the item.
|
""" return (fspath, lineno, domainpath) location info for the item.
|
||||||
the information is used for result display and to sort tests.
|
the information is used for result display and to sort tests.
|
||||||
fspath,lineno: file and linenumber of source of item definition.
|
fspath,lineno: file and linenumber of source of item definition.
|
||||||
domainpath: custom id - e.g. for python: dotted import address
|
domainpath: custom id - e.g. for python: dotted import address
|
||||||
|
|
|
@ -29,7 +29,31 @@ def pytest_sessionfinish(session, exitstatus):
|
||||||
if rep:
|
if rep:
|
||||||
hook.pytest__teardown_final_logerror(report=rep)
|
hook.pytest__teardown_final_logerror(report=rep)
|
||||||
|
|
||||||
|
class NodeInfo:
|
||||||
|
def __init__(self, nodeid, nodenames, fspath, location):
|
||||||
|
self.nodeid = nodeid
|
||||||
|
self.nodenames = nodenames
|
||||||
|
self.fspath = fspath
|
||||||
|
self.location = location
|
||||||
|
|
||||||
|
def getitemnodeinfo(item):
|
||||||
|
try:
|
||||||
|
return item._nodeinfo
|
||||||
|
except AttributeError:
|
||||||
|
location = item.ihook.pytest_report_iteminfo(item=item)
|
||||||
|
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(
|
||||||
|
nodeid=nodeinfo.nodeid,
|
||||||
|
location=nodeinfo.location
|
||||||
|
)
|
||||||
runtestprotocol(item)
|
runtestprotocol(item)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -117,11 +141,7 @@ class BaseReport(object):
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
location = item.ihook.pytest_report_iteminfo(item=item)
|
nodeinfo = getitemnodeinfo(item)
|
||||||
location = (str(location[0]), location[1], str(location[2]))
|
|
||||||
nodenames = tuple(item.listnames())
|
|
||||||
nodeid = item.collection.getid(item)
|
|
||||||
fspath = item.fspath
|
|
||||||
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
|
excinfo = call.excinfo
|
||||||
|
@ -141,7 +161,8 @@ 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(nodeid, nodenames, fspath, location,
|
return TestReport(nodeinfo.nodeid, nodeinfo.nodenames,
|
||||||
|
nodeinfo.fspath, nodeinfo.location,
|
||||||
keywords, outcome, longrepr, when)
|
keywords, outcome, longrepr, when)
|
||||||
|
|
||||||
class TestReport(BaseReport):
|
class TestReport(BaseReport):
|
||||||
|
|
|
@ -84,12 +84,12 @@ class TerminalReporter:
|
||||||
return char in self.reportchars
|
return char in self.reportchars
|
||||||
|
|
||||||
def write_fspath_result(self, fspath, res):
|
def write_fspath_result(self, fspath, res):
|
||||||
fspath = self.curdir.bestrelpath(fspath)
|
|
||||||
if fspath != self.currentfspath:
|
if fspath != self.currentfspath:
|
||||||
|
self.currentfspath = 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(relpath + " ")
|
||||||
self.currentfspath = 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):
|
||||||
|
@ -135,18 +135,18 @@ class TerminalReporter:
|
||||||
def pytest_deselected(self, items):
|
def pytest_deselected(self, items):
|
||||||
self.stats.setdefault('deselected', []).extend(items)
|
self.stats.setdefault('deselected', []).extend(items)
|
||||||
|
|
||||||
#def pytest_itemstart(self, item, node=None):
|
|
||||||
# if self.config.option.verbose:
|
|
||||||
# line = self._locationline(rep)
|
|
||||||
# self.write_ensure_prefix(line, "")
|
|
||||||
# else:
|
|
||||||
# # ensure that the path is printed before the
|
|
||||||
# # 1st test of a module starts running
|
|
||||||
# self.write_fspath_result(self._getfspath(item), "")
|
|
||||||
|
|
||||||
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):
|
||||||
|
# ensure that the path is printed before the
|
||||||
|
# 1st test of a module starts running
|
||||||
|
if self.config.option.verbose:
|
||||||
|
line = self._locationline(*location)
|
||||||
|
self.write_ensure_prefix(line, "")
|
||||||
|
else:
|
||||||
|
self.write_fspath_result(py.path.local(location[0]), "")
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, report):
|
def pytest_runtest_logreport(self, report):
|
||||||
rep = report
|
rep = report
|
||||||
res = self.config.hook.pytest_report_teststatus(report=rep)
|
res = self.config.hook.pytest_report_teststatus(report=rep)
|
||||||
|
@ -162,9 +162,10 @@ class TerminalReporter:
|
||||||
if not self.config.option.verbose:
|
if not self.config.option.verbose:
|
||||||
self.write_fspath_result(rep.fspath, letter)
|
self.write_fspath_result(rep.fspath, letter)
|
||||||
else:
|
else:
|
||||||
line = self._locationline(rep)
|
line = self._locationline(*rep.location)
|
||||||
if not hasattr(rep, 'node'):
|
if not hasattr(rep, 'node'):
|
||||||
self.write_ensure_prefix(line, word, **markup)
|
self.write_ensure_prefix(line, word, **markup)
|
||||||
|
#self._tw.write(word, **markup)
|
||||||
else:
|
else:
|
||||||
self.ensure_newline()
|
self.ensure_newline()
|
||||||
if hasattr(rep, 'node'):
|
if hasattr(rep, 'node'):
|
||||||
|
@ -226,21 +227,20 @@ class TerminalReporter:
|
||||||
else:
|
else:
|
||||||
excrepr.reprcrash.toterminal(self._tw)
|
excrepr.reprcrash.toterminal(self._tw)
|
||||||
|
|
||||||
def _locationline(self, rep):
|
def _locationline(self, fspath, lineno, domain):
|
||||||
#collect_fspath = self._getfspath(item)
|
#collect_fspath = self._getfspath(item)
|
||||||
fspath, lineno, msg = rep.location
|
|
||||||
#if fspath and fspath != collect_fspath:
|
#if fspath and fspath != collect_fspath:
|
||||||
# fspath = "%s <- %s" % (
|
# fspath = "%s <- %s" % (
|
||||||
# self.curdir.bestrelpath(collect_fspath),
|
# self.curdir.bestrelpath(collect_fspath),
|
||||||
# self.curdir.bestrelpath(fspath))
|
# self.curdir.bestrelpath(fspath))
|
||||||
if fspath:
|
if fspath:
|
||||||
fspath = self.curdir.bestrelpath(fspath)
|
fspath = self.curdir.bestrelpath(py.path.local(fspath))
|
||||||
if lineno is not None:
|
if lineno is not None:
|
||||||
lineno += 1
|
lineno += 1
|
||||||
if fspath and lineno and msg:
|
if fspath and lineno and domain:
|
||||||
line = "%(fspath)s:%(lineno)s: %(msg)s"
|
line = "%(fspath)s:%(lineno)s: %(domain)s"
|
||||||
elif fspath and msg:
|
elif fspath and domain:
|
||||||
line = "%(fspath)s: %(msg)s"
|
line = "%(fspath)s: %(domain)s"
|
||||||
elif fspath and lineno:
|
elif fspath and lineno:
|
||||||
line = "%(fspath)s:%(lineno)s %(extrapath)s"
|
line = "%(fspath)s:%(lineno)s %(extrapath)s"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1109,3 +1109,28 @@ def test_funcarg_lookup_error(testdir):
|
||||||
"*1 error*",
|
"*1 error*",
|
||||||
])
|
])
|
||||||
assert "INTERNAL" not in result.stdout.str()
|
assert "INTERNAL" not in result.stdout.str()
|
||||||
|
|
||||||
|
class TestReportInfo:
|
||||||
|
def test_itemreport_reportinfo(self, testdir, linecomp):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import py
|
||||||
|
class Function(py.test.collect.Function):
|
||||||
|
def reportinfo(self):
|
||||||
|
return "ABCDE", 42, "custom"
|
||||||
|
""")
|
||||||
|
item = testdir.getitem("def test_func(): pass")
|
||||||
|
runner = item.config.pluginmanager.getplugin("runner")
|
||||||
|
nodeinfo = runner.getitemnodeinfo(item)
|
||||||
|
assert nodeinfo.location == ("ABCDE", 42, "custom")
|
||||||
|
|
||||||
|
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
|
||||||
|
item = testdir.getitem("def test_func(): pass")
|
||||||
|
tup = "FGHJ", 42, "custom"
|
||||||
|
class Plugin:
|
||||||
|
def pytest_report_iteminfo(self, item):
|
||||||
|
return tup
|
||||||
|
item.config.pluginmanager.register(Plugin())
|
||||||
|
runner = runner = item.config.pluginmanager.getplugin("runner")
|
||||||
|
nodeinfo = runner.getitemnodeinfo(item)
|
||||||
|
location = nodeinfo.location
|
||||||
|
assert location == tup
|
||||||
|
|
|
@ -89,47 +89,27 @@ class TestTerminal:
|
||||||
assert lines[1].endswith("xy.py .")
|
assert lines[1].endswith("xy.py .")
|
||||||
assert lines[2] == "hello world"
|
assert lines[2] == "hello world"
|
||||||
|
|
||||||
@py.test.mark.xfail(reason="re-implement ItemStart events")
|
def test_show_runtest_logstart(self, testdir, linecomp):
|
||||||
def test_show_path_before_running_test(self, testdir, linecomp):
|
|
||||||
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)
|
||||||
tr.config.hook.pytest_itemstart(item=item)
|
nodeid = item.collection.getid(item)
|
||||||
|
location = item.ihook.pytest_report_iteminfo(item=item)
|
||||||
|
tr.config.hook.pytest_runtest_logstart(nodeid=nodeid, location=location)
|
||||||
linecomp.assert_contains_lines([
|
linecomp.assert_contains_lines([
|
||||||
"*test_show_path_before_running_test.py*"
|
"*test_show_runtest_logstart.py*"
|
||||||
])
|
])
|
||||||
|
|
||||||
@py.test.mark.xfail(reason="re-implement ItemStart events")
|
def test_runtest_location_shown_before_test_starts(self, testdir):
|
||||||
def test_itemreport_reportinfo(self, testdir, linecomp):
|
p1 = testdir.makepyfile("""
|
||||||
testdir.makeconftest("""
|
def test_1():
|
||||||
import py
|
import time
|
||||||
class Function(py.test.collect.Function):
|
time.sleep(20)
|
||||||
def reportinfo(self):
|
|
||||||
return "ABCDE", 42, "custom"
|
|
||||||
""")
|
""")
|
||||||
item = testdir.getitem("def test_func(): pass")
|
child = testdir.spawn_pytest("")
|
||||||
tr = TerminalReporter(item.config, file=linecomp.stringio)
|
child.expect(".*test_runtest_location.*py")
|
||||||
item.config.pluginmanager.register(tr)
|
child.sendeof()
|
||||||
tr.config.option.verbose = True
|
child.kill(15)
|
||||||
tr.config.hook.pytest_itemstart(item=item)
|
|
||||||
linecomp.assert_contains_lines([
|
|
||||||
"*ABCDE:43: custom*"
|
|
||||||
])
|
|
||||||
|
|
||||||
@py.test.mark.xfail(reason="re-implement ItemStart events")
|
|
||||||
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
|
|
||||||
item = testdir.getitem("def test_func(): pass")
|
|
||||||
class Plugin:
|
|
||||||
def pytest_report_iteminfo(self, item):
|
|
||||||
return "FGHJ", 42, "custom"
|
|
||||||
item.config.pluginmanager.register(Plugin())
|
|
||||||
tr = TerminalReporter(item.config, file=linecomp.stringio)
|
|
||||||
item.config.pluginmanager.register(tr)
|
|
||||||
tr.config.option.verbose = True
|
|
||||||
tr.config.hook.pytest_itemstart(item=item)
|
|
||||||
linecomp.assert_contains_lines([
|
|
||||||
"*FGHJ:43: custom*"
|
|
||||||
])
|
|
||||||
|
|
||||||
@py.test.mark.xfail(reason="re-implement subclassing precision reporting")
|
@py.test.mark.xfail(reason="re-implement subclassing precision reporting")
|
||||||
def test_itemreport_subclasses_show_subclassed_file(self, testdir):
|
def test_itemreport_subclasses_show_subclassed_file(self, testdir):
|
||||||
|
|
Loading…
Reference in New Issue