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:
holger krekel 2010-09-26 16:23:45 +02:00
parent 1c020c3d32
commit a2fe6714f8
5 changed files with 90 additions and 62 deletions

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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):