diff --git a/py/_plugin/hookspec.py b/py/_plugin/hookspec.py index 456c1ba9d..f9ec47ada 100644 --- a/py/_plugin/hookspec.py +++ b/py/_plugin/hookspec.py @@ -102,14 +102,16 @@ def pytest_generate_tests(metafunc): # ------------------------------------------------------------------------- # generic runtest related hooks # ------------------------------------------------------------------------- - def pytest_itemstart(item, node=None): - """ test item starts running. """ + """ (deprecated, use pytest_runtest_logstart). """ def pytest_runtest_protocol(item): """ implement fixture, run and report about the given test item. """ pytest_runtest_protocol.firstresult = True +def pytest_runtest_logstart(nodeid, location): + """ signal the start of a test run. """ + def pytest_runtest_setup(item): """ called before pytest_runtest_call(). """ @@ -160,7 +162,7 @@ def pytest_terminal_summary(terminalreporter): """ add additional section in terminal summary reporting. """ 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. fspath,lineno: file and linenumber of source of item definition. domainpath: custom id - e.g. for python: dotted import address diff --git a/py/_plugin/pytest_runner.py b/py/_plugin/pytest_runner.py index 733d9b6ce..a9989af2a 100644 --- a/py/_plugin/pytest_runner.py +++ b/py/_plugin/pytest_runner.py @@ -29,7 +29,31 @@ def pytest_sessionfinish(session, exitstatus): if 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): + nodeinfo = getitemnodeinfo(item) + item.ihook.pytest_runtest_logstart( + nodeid=nodeinfo.nodeid, + location=nodeinfo.location + ) runtestprotocol(item) return True @@ -117,11 +141,7 @@ class BaseReport(object): def pytest_runtest_makereport(item, call): - 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 + nodeinfo = getitemnodeinfo(item) when = call.when keywords = dict([(x,1) for x in item.keywords]) excinfo = call.excinfo @@ -141,7 +161,8 @@ def pytest_runtest_makereport(item, call): longrepr = item.repr_failure(excinfo) else: # exception in setup or teardown 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) class TestReport(BaseReport): diff --git a/py/_plugin/pytest_terminal.py b/py/_plugin/pytest_terminal.py index d205e91a2..db463d601 100644 --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -84,12 +84,12 @@ class TerminalReporter: return char in self.reportchars def write_fspath_result(self, fspath, res): - fspath = self.curdir.bestrelpath(fspath) if fspath != self.currentfspath: + self.currentfspath = fspath + fspath = self.curdir.bestrelpath(fspath) self._tw.line() relpath = self.curdir.bestrelpath(fspath) self._tw.write(relpath + " ") - self.currentfspath = fspath self._tw.write(res) def write_ensure_prefix(self, prefix, extra="", **kwargs): @@ -135,18 +135,18 @@ class TerminalReporter: def pytest_deselected(self, 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): 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): rep = report res = self.config.hook.pytest_report_teststatus(report=rep) @@ -162,9 +162,10 @@ class TerminalReporter: if not self.config.option.verbose: self.write_fspath_result(rep.fspath, letter) else: - line = self._locationline(rep) + line = self._locationline(*rep.location) if not hasattr(rep, 'node'): self.write_ensure_prefix(line, word, **markup) + #self._tw.write(word, **markup) else: self.ensure_newline() if hasattr(rep, 'node'): @@ -226,21 +227,20 @@ class TerminalReporter: else: excrepr.reprcrash.toterminal(self._tw) - def _locationline(self, rep): + def _locationline(self, fspath, lineno, domain): #collect_fspath = self._getfspath(item) - fspath, lineno, msg = rep.location #if fspath and fspath != collect_fspath: # fspath = "%s <- %s" % ( # self.curdir.bestrelpath(collect_fspath), # self.curdir.bestrelpath(fspath)) if fspath: - fspath = self.curdir.bestrelpath(fspath) + fspath = self.curdir.bestrelpath(py.path.local(fspath)) if lineno is not None: lineno += 1 - if fspath and lineno and msg: - line = "%(fspath)s:%(lineno)s: %(msg)s" - elif fspath and msg: - line = "%(fspath)s: %(msg)s" + if fspath and lineno and domain: + line = "%(fspath)s:%(lineno)s: %(domain)s" + elif fspath and domain: + line = "%(fspath)s: %(domain)s" elif fspath and lineno: line = "%(fspath)s:%(lineno)s %(extrapath)s" else: diff --git a/testing/plugin/test_pytest_python.py b/testing/plugin/test_pytest_python.py index cfc5a054f..4b6d717a0 100644 --- a/testing/plugin/test_pytest_python.py +++ b/testing/plugin/test_pytest_python.py @@ -1109,3 +1109,28 @@ def test_funcarg_lookup_error(testdir): "*1 error*", ]) 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 diff --git a/testing/plugin/test_pytest_terminal.py b/testing/plugin/test_pytest_terminal.py index d6af1450c..feada5178 100644 --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -89,47 +89,27 @@ class TestTerminal: assert lines[1].endswith("xy.py .") assert lines[2] == "hello world" - @py.test.mark.xfail(reason="re-implement ItemStart events") - def test_show_path_before_running_test(self, testdir, linecomp): + def test_show_runtest_logstart(self, testdir, linecomp): item = testdir.getitem("def test_func(): pass") tr = TerminalReporter(item.config, file=linecomp.stringio) 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([ - "*test_show_path_before_running_test.py*" + "*test_show_runtest_logstart.py*" ]) - @py.test.mark.xfail(reason="re-implement ItemStart events") - def test_itemreport_reportinfo(self, testdir, linecomp): - testdir.makeconftest(""" - import py - class Function(py.test.collect.Function): - def reportinfo(self): - return "ABCDE", 42, "custom" + def test_runtest_location_shown_before_test_starts(self, testdir): + p1 = testdir.makepyfile(""" + def test_1(): + import time + time.sleep(20) """) - item = testdir.getitem("def test_func(): pass") - 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([ - "*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*" - ]) + child = testdir.spawn_pytest("") + child.expect(".*test_runtest_location.*py") + child.sendeof() + child.kill(15) @py.test.mark.xfail(reason="re-implement subclassing precision reporting") def test_itemreport_subclasses_show_subclassed_file(self, testdir):