Skip to content

Commit 13380e3

Browse files
refactor CallInfo constructor magic into named constructor
1 parent 9dec146 commit 13380e3

File tree

3 files changed

+43
-36
lines changed

3 files changed

+43
-36
lines changed

src/_pytest/nose.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ def get_skip_exceptions():
2323
def pytest_runtest_makereport(item, call):
2424
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
2525
# let's substitute the excinfo with a pytest.skip one
26-
call2 = call.__class__(lambda: runner.skip(str(call.excinfo.value)), call.when)
26+
call2 = runner.CallInfo.from_call(
27+
lambda: runner.skip(str(call.excinfo.value)), call.when
28+
)
2729
call.excinfo = call2.excinfo
2830

2931

src/_pytest/runner.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import sys
99
from time import time
1010

11+
import attr
1112
import six
1213

1314
from .reports import CollectErrorRepr
@@ -189,43 +190,57 @@ def check_interactive_exception(call, report):
189190
def call_runtest_hook(item, when, **kwds):
190191
hookname = "pytest_runtest_" + when
191192
ihook = getattr(item.ihook, hookname)
192-
return CallInfo(
193+
return CallInfo.from_call(
193194
lambda: ihook(item=item, **kwds),
194195
when=when,
195-
treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"),
196+
reraise=KeyboardInterrupt if not item.config.getvalue("usepdb") else (),
196197
)
197198

198199

200+
@attr.s(repr=False)
199201
class CallInfo(object):
200202
""" Result/Exception info a function invocation. """
201203

202-
#: None or ExceptionInfo object.
203-
excinfo = None
204-
205-
def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False):
204+
_result = attr.ib()
205+
# type: Optional[ExceptionInfo]
206+
excinfo = attr.ib()
207+
start = attr.ib()
208+
stop = attr.ib()
209+
when = attr.ib()
210+
211+
@property
212+
def result(self):
213+
if self.excinfo is not None:
214+
raise AttributeError("{!r} has no valid result".format(self))
215+
return self._result
216+
217+
@classmethod
218+
def from_call(cls, func, when, reraise=None):
206219
#: context of invocation: one of "setup", "call",
207220
#: "teardown", "memocollect"
208-
self.when = when
209-
self.start = time()
221+
start = time()
222+
excinfo = None
210223
try:
211-
self.result = func()
212-
except KeyboardInterrupt:
213-
if treat_keyboard_interrupt_as_exception:
214-
self.excinfo = ExceptionInfo()
215-
else:
216-
self.stop = time()
217-
raise
224+
result = func()
218225
except: # noqa
219-
self.excinfo = ExceptionInfo()
220-
self.stop = time()
226+
excinfo = ExceptionInfo()
227+
if reraise is not None and excinfo.errisinstance(reraise):
228+
raise
229+
result = None
230+
stop = time()
231+
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
221232

222233
def __repr__(self):
223-
if self.excinfo:
224-
status = "exception: %s" % str(self.excinfo.value)
234+
if self.excinfo is not None:
235+
status = "exception"
236+
value = self.excinfo.value
225237
else:
226-
result = getattr(self, "result", "<NOTSET>")
227-
status = "result: %r" % (result,)
228-
return "<CallInfo when=%r %s>" % (self.when, status)
238+
# TODO: investigate unification
239+
value = repr(self._result)
240+
status = "result"
241+
return "<CallInfo when={when!r} {status}: {value}>".format(
242+
when=self.when, value=value, status=status
243+
)
229244

230245

231246
def pytest_runtest_makereport(item, call):
@@ -269,7 +284,7 @@ def pytest_runtest_makereport(item, call):
269284

270285

271286
def pytest_make_collect_report(collector):
272-
call = CallInfo(lambda: list(collector.collect()), "collect")
287+
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
273288
longrepr = None
274289
if not call.excinfo:
275290
outcome = "passed"

testing/test_runner.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -487,30 +487,20 @@ def test_report_extra_parameters(reporttype):
487487

488488

489489
def test_callinfo():
490-
ci = runner.CallInfo(lambda: 0, "123")
490+
ci = runner.CallInfo.from_call(lambda: 0, "123")
491491
assert ci.when == "123"
492492
assert ci.result == 0
493493
assert "result" in repr(ci)
494494
assert repr(ci) == "<CallInfo when='123' result: 0>"
495495

496-
ci = runner.CallInfo(lambda: 0 / 0, "123")
496+
ci = runner.CallInfo.from_call(lambda: 0 / 0, "123")
497497
assert ci.when == "123"
498498
assert not hasattr(ci, "result")
499499
assert repr(ci) == "<CallInfo when='123' exception: division by zero>"
500500
assert ci.excinfo
501501
assert "exc" in repr(ci)
502502

503503

504-
def test_callinfo_repr_while_running():
505-
def repr_while_running():
506-
f = sys._getframe().f_back
507-
assert "func" in f.f_locals
508-
assert repr(f.f_locals["self"]) == "<CallInfo when='when' result: '<NOTSET>'>"
509-
510-
ci = runner.CallInfo(repr_while_running, "when")
511-
assert repr(ci) == "<CallInfo when='when' result: None>"
512-
513-
514504
# design question: do we want general hooks in python files?
515505
# then something like the following functional tests makes sense
516506

0 commit comments

Comments
 (0)