Skip to content

Commit a2fe671

Browse files
committed
implement pytest_runtest_logstart(nodeid, location) hook
factor out a NodeInfo helper, and streamline terminal printing a bit --HG-- branch : trunk
1 parent 1c020c3 commit a2fe671

File tree

5 files changed

+90
-62
lines changed

5 files changed

+90
-62
lines changed

py/_plugin/hookspec.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,16 @@ def pytest_generate_tests(metafunc):
102102
# -------------------------------------------------------------------------
103103
# generic runtest related hooks
104104
# -------------------------------------------------------------------------
105-
106105
def pytest_itemstart(item, node=None):
107-
""" test item starts running. """
106+
""" (deprecated, use pytest_runtest_logstart). """
108107

109108
def pytest_runtest_protocol(item):
110109
""" implement fixture, run and report about the given test item. """
111110
pytest_runtest_protocol.firstresult = True
112111

112+
def pytest_runtest_logstart(nodeid, location):
113+
""" signal the start of a test run. """
114+
113115
def pytest_runtest_setup(item):
114116
""" called before pytest_runtest_call(). """
115117

@@ -160,7 +162,7 @@ def pytest_terminal_summary(terminalreporter):
160162
""" add additional section in terminal summary reporting. """
161163

162164
def pytest_report_iteminfo(item):
163-
""" return (fspath, lineno, domainpath) for the item.
165+
""" return (fspath, lineno, domainpath) location info for the item.
164166
the information is used for result display and to sort tests.
165167
fspath,lineno: file and linenumber of source of item definition.
166168
domainpath: custom id - e.g. for python: dotted import address

py/_plugin/pytest_runner.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,31 @@ def pytest_sessionfinish(session, exitstatus):
2929
if rep:
3030
hook.pytest__teardown_final_logerror(report=rep)
3131

32+
class NodeInfo:
33+
def __init__(self, nodeid, nodenames, fspath, location):
34+
self.nodeid = nodeid
35+
self.nodenames = nodenames
36+
self.fspath = fspath
37+
self.location = location
38+
39+
def getitemnodeinfo(item):
40+
try:
41+
return item._nodeinfo
42+
except AttributeError:
43+
location = item.ihook.pytest_report_iteminfo(item=item)
44+
location = (str(location[0]), location[1], str(location[2]))
45+
nodenames = tuple(item.listnames())
46+
nodeid = item.collection.getid(item)
47+
fspath = item.fspath
48+
item._nodeinfo = n = NodeInfo(nodeid, nodenames, fspath, location)
49+
return n
50+
3251
def pytest_runtest_protocol(item):
52+
nodeinfo = getitemnodeinfo(item)
53+
item.ihook.pytest_runtest_logstart(
54+
nodeid=nodeinfo.nodeid,
55+
location=nodeinfo.location
56+
)
3357
runtestprotocol(item)
3458
return True
3559

@@ -117,11 +141,7 @@ def toterminal(self, out):
117141

118142

119143
def pytest_runtest_makereport(item, call):
120-
location = item.ihook.pytest_report_iteminfo(item=item)
121-
location = (str(location[0]), location[1], str(location[2]))
122-
nodenames = tuple(item.listnames())
123-
nodeid = item.collection.getid(item)
124-
fspath = item.fspath
144+
nodeinfo = getitemnodeinfo(item)
125145
when = call.when
126146
keywords = dict([(x,1) for x in item.keywords])
127147
excinfo = call.excinfo
@@ -141,7 +161,8 @@ def pytest_runtest_makereport(item, call):
141161
longrepr = item.repr_failure(excinfo)
142162
else: # exception in setup or teardown
143163
longrepr = item._repr_failure_py(excinfo)
144-
return TestReport(nodeid, nodenames, fspath, location,
164+
return TestReport(nodeinfo.nodeid, nodeinfo.nodenames,
165+
nodeinfo.fspath, nodeinfo.location,
145166
keywords, outcome, longrepr, when)
146167

147168
class TestReport(BaseReport):

py/_plugin/pytest_terminal.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ def hasopt(self, char):
8484
return char in self.reportchars
8585

8686
def write_fspath_result(self, fspath, res):
87-
fspath = self.curdir.bestrelpath(fspath)
8887
if fspath != self.currentfspath:
88+
self.currentfspath = fspath
89+
fspath = self.curdir.bestrelpath(fspath)
8990
self._tw.line()
9091
relpath = self.curdir.bestrelpath(fspath)
9192
self._tw.write(relpath + " ")
92-
self.currentfspath = fspath
9393
self._tw.write(res)
9494

9595
def write_ensure_prefix(self, prefix, extra="", **kwargs):
@@ -135,18 +135,18 @@ def pytest_trace(self, category, msg):
135135
def pytest_deselected(self, items):
136136
self.stats.setdefault('deselected', []).extend(items)
137137

138-
#def pytest_itemstart(self, item, node=None):
139-
# if self.config.option.verbose:
140-
# line = self._locationline(rep)
141-
# self.write_ensure_prefix(line, "")
142-
# else:
143-
# # ensure that the path is printed before the
144-
# # 1st test of a module starts running
145-
# self.write_fspath_result(self._getfspath(item), "")
146-
147138
def pytest__teardown_final_logerror(self, report):
148139
self.stats.setdefault("error", []).append(report)
149140

141+
def pytest_runtest_logstart(self, nodeid, location):
142+
# ensure that the path is printed before the
143+
# 1st test of a module starts running
144+
if self.config.option.verbose:
145+
line = self._locationline(*location)
146+
self.write_ensure_prefix(line, "")
147+
else:
148+
self.write_fspath_result(py.path.local(location[0]), "")
149+
150150
def pytest_runtest_logreport(self, report):
151151
rep = report
152152
res = self.config.hook.pytest_report_teststatus(report=rep)
@@ -162,9 +162,10 @@ def pytest_runtest_logreport(self, report):
162162
if not self.config.option.verbose:
163163
self.write_fspath_result(rep.fspath, letter)
164164
else:
165-
line = self._locationline(rep)
165+
line = self._locationline(*rep.location)
166166
if not hasattr(rep, 'node'):
167167
self.write_ensure_prefix(line, word, **markup)
168+
#self._tw.write(word, **markup)
168169
else:
169170
self.ensure_newline()
170171
if hasattr(rep, 'node'):
@@ -226,21 +227,20 @@ def _report_keyboardinterrupt(self):
226227
else:
227228
excrepr.reprcrash.toterminal(self._tw)
228229

229-
def _locationline(self, rep):
230+
def _locationline(self, fspath, lineno, domain):
230231
#collect_fspath = self._getfspath(item)
231-
fspath, lineno, msg = rep.location
232232
#if fspath and fspath != collect_fspath:
233233
# fspath = "%s <- %s" % (
234234
# self.curdir.bestrelpath(collect_fspath),
235235
# self.curdir.bestrelpath(fspath))
236236
if fspath:
237-
fspath = self.curdir.bestrelpath(fspath)
237+
fspath = self.curdir.bestrelpath(py.path.local(fspath))
238238
if lineno is not None:
239239
lineno += 1
240-
if fspath and lineno and msg:
241-
line = "%(fspath)s:%(lineno)s: %(msg)s"
242-
elif fspath and msg:
243-
line = "%(fspath)s: %(msg)s"
240+
if fspath and lineno and domain:
241+
line = "%(fspath)s:%(lineno)s: %(domain)s"
242+
elif fspath and domain:
243+
line = "%(fspath)s: %(domain)s"
244244
elif fspath and lineno:
245245
line = "%(fspath)s:%(lineno)s %(extrapath)s"
246246
else:

testing/plugin/test_pytest_python.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,3 +1109,28 @@ def test_lookup_error(unknown):
11091109
"*1 error*",
11101110
])
11111111
assert "INTERNAL" not in result.stdout.str()
1112+
1113+
class TestReportInfo:
1114+
def test_itemreport_reportinfo(self, testdir, linecomp):
1115+
testdir.makeconftest("""
1116+
import py
1117+
class Function(py.test.collect.Function):
1118+
def reportinfo(self):
1119+
return "ABCDE", 42, "custom"
1120+
""")
1121+
item = testdir.getitem("def test_func(): pass")
1122+
runner = item.config.pluginmanager.getplugin("runner")
1123+
nodeinfo = runner.getitemnodeinfo(item)
1124+
assert nodeinfo.location == ("ABCDE", 42, "custom")
1125+
1126+
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
1127+
item = testdir.getitem("def test_func(): pass")
1128+
tup = "FGHJ", 42, "custom"
1129+
class Plugin:
1130+
def pytest_report_iteminfo(self, item):
1131+
return tup
1132+
item.config.pluginmanager.register(Plugin())
1133+
runner = runner = item.config.pluginmanager.getplugin("runner")
1134+
nodeinfo = runner.getitemnodeinfo(item)
1135+
location = nodeinfo.location
1136+
assert location == tup

testing/plugin/test_pytest_terminal.py

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -89,47 +89,27 @@ def test_writeline(self, testdir, linecomp):
8989
assert lines[1].endswith("xy.py .")
9090
assert lines[2] == "hello world"
9191

92-
@py.test.mark.xfail(reason="re-implement ItemStart events")
93-
def test_show_path_before_running_test(self, testdir, linecomp):
92+
def test_show_runtest_logstart(self, testdir, linecomp):
9493
item = testdir.getitem("def test_func(): pass")
9594
tr = TerminalReporter(item.config, file=linecomp.stringio)
9695
item.config.pluginmanager.register(tr)
97-
tr.config.hook.pytest_itemstart(item=item)
96+
nodeid = item.collection.getid(item)
97+
location = item.ihook.pytest_report_iteminfo(item=item)
98+
tr.config.hook.pytest_runtest_logstart(nodeid=nodeid, location=location)
9899
linecomp.assert_contains_lines([
99-
"*test_show_path_before_running_test.py*"
100+
"*test_show_runtest_logstart.py*"
100101
])
101102

102-
@py.test.mark.xfail(reason="re-implement ItemStart events")
103-
def test_itemreport_reportinfo(self, testdir, linecomp):
104-
testdir.makeconftest("""
105-
import py
106-
class Function(py.test.collect.Function):
107-
def reportinfo(self):
108-
return "ABCDE", 42, "custom"
103+
def test_runtest_location_shown_before_test_starts(self, testdir):
104+
p1 = testdir.makepyfile("""
105+
def test_1():
106+
import time
107+
time.sleep(20)
109108
""")
110-
item = testdir.getitem("def test_func(): pass")
111-
tr = TerminalReporter(item.config, file=linecomp.stringio)
112-
item.config.pluginmanager.register(tr)
113-
tr.config.option.verbose = True
114-
tr.config.hook.pytest_itemstart(item=item)
115-
linecomp.assert_contains_lines([
116-
"*ABCDE:43: custom*"
117-
])
118-
119-
@py.test.mark.xfail(reason="re-implement ItemStart events")
120-
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
121-
item = testdir.getitem("def test_func(): pass")
122-
class Plugin:
123-
def pytest_report_iteminfo(self, item):
124-
return "FGHJ", 42, "custom"
125-
item.config.pluginmanager.register(Plugin())
126-
tr = TerminalReporter(item.config, file=linecomp.stringio)
127-
item.config.pluginmanager.register(tr)
128-
tr.config.option.verbose = True
129-
tr.config.hook.pytest_itemstart(item=item)
130-
linecomp.assert_contains_lines([
131-
"*FGHJ:43: custom*"
132-
])
109+
child = testdir.spawn_pytest("")
110+
child.expect(".*test_runtest_location.*py")
111+
child.sendeof()
112+
child.kill(15)
133113

134114
@py.test.mark.xfail(reason="re-implement subclassing precision reporting")
135115
def test_itemreport_subclasses_show_subclassed_file(self, testdir):

0 commit comments

Comments
 (0)