Merge pull request #4299 from RonnyPfannschmidt/callinfo-sanity

refactor callinfo to simplify ctor magic
This commit is contained in:
Bruno Oliveira 2018-11-30 14:07:40 -02:00 committed by GitHub
commit e0ba1cbf8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 35 deletions

View File

@ -23,7 +23,9 @@ def get_skip_exceptions():
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
# let's substitute the excinfo with a pytest.skip one # let's substitute the excinfo with a pytest.skip one
call2 = call.__class__(lambda: runner.skip(str(call.excinfo.value)), call.when) call2 = runner.CallInfo.from_call(
lambda: runner.skip(str(call.excinfo.value)), call.when
)
call.excinfo = call2.excinfo call.excinfo = call2.excinfo

View File

@ -8,6 +8,7 @@ import os
import sys import sys
from time import time from time import time
import attr
import six import six
from .reports import CollectErrorRepr from .reports import CollectErrorRepr
@ -189,43 +190,57 @@ def check_interactive_exception(call, report):
def call_runtest_hook(item, when, **kwds): def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname) ihook = getattr(item.ihook, hookname)
return CallInfo( return CallInfo.from_call(
lambda: ihook(item=item, **kwds), lambda: ihook(item=item, **kwds),
when=when, when=when,
treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"), reraise=KeyboardInterrupt if not item.config.getvalue("usepdb") else (),
) )
@attr.s(repr=False)
class CallInfo(object): class CallInfo(object):
""" Result/Exception info a function invocation. """ """ Result/Exception info a function invocation. """
#: None or ExceptionInfo object. _result = attr.ib()
excinfo = None # type: Optional[ExceptionInfo]
excinfo = attr.ib()
start = attr.ib()
stop = attr.ib()
when = attr.ib()
def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False): @property
def result(self):
if self.excinfo is not None:
raise AttributeError("{!r} has no valid result".format(self))
return self._result
@classmethod
def from_call(cls, func, when, reraise=None):
#: context of invocation: one of "setup", "call", #: context of invocation: one of "setup", "call",
#: "teardown", "memocollect" #: "teardown", "memocollect"
self.when = when start = time()
self.start = time() excinfo = None
try: try:
self.result = func() result = func()
except KeyboardInterrupt:
if treat_keyboard_interrupt_as_exception:
self.excinfo = ExceptionInfo.from_current()
else:
self.stop = time()
raise
except: # noqa except: # noqa
self.excinfo = ExceptionInfo.from_current() excinfo = ExceptionInfo.from_current()
self.stop = time() if reraise is not None and excinfo.errisinstance(reraise):
raise
result = None
stop = time()
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
def __repr__(self): def __repr__(self):
if self.excinfo: if self.excinfo is not None:
status = "exception: %s" % str(self.excinfo.value) status = "exception"
value = self.excinfo.value
else: else:
result = getattr(self, "result", "<NOTSET>") # TODO: investigate unification
status = "result: %r" % (result,) value = repr(self._result)
return "<CallInfo when=%r %s>" % (self.when, status) status = "result"
return "<CallInfo when={when!r} {status}: {value}>".format(
when=self.when, value=value, status=status
)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
@ -269,7 +284,7 @@ def pytest_runtest_makereport(item, call):
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
call = CallInfo(lambda: list(collector.collect()), "collect") call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
longrepr = None longrepr = None
if not call.excinfo: if not call.excinfo:
outcome = "passed" outcome = "passed"

View File

@ -487,13 +487,13 @@ def test_report_extra_parameters(reporttype):
def test_callinfo(): def test_callinfo():
ci = runner.CallInfo(lambda: 0, "123") ci = runner.CallInfo.from_call(lambda: 0, "123")
assert ci.when == "123" assert ci.when == "123"
assert ci.result == 0 assert ci.result == 0
assert "result" in repr(ci) assert "result" in repr(ci)
assert repr(ci) == "<CallInfo when='123' result: 0>" assert repr(ci) == "<CallInfo when='123' result: 0>"
ci = runner.CallInfo(lambda: 0 / 0, "123") ci = runner.CallInfo.from_call(lambda: 0 / 0, "123")
assert ci.when == "123" assert ci.when == "123"
assert not hasattr(ci, "result") assert not hasattr(ci, "result")
assert repr(ci) == "<CallInfo when='123' exception: division by zero>" assert repr(ci) == "<CallInfo when='123' exception: division by zero>"
@ -501,16 +501,6 @@ def test_callinfo():
assert "exc" in repr(ci) assert "exc" in repr(ci)
def test_callinfo_repr_while_running():
def repr_while_running():
f = sys._getframe().f_back
assert "func" in f.f_locals
assert repr(f.f_locals["self"]) == "<CallInfo when='when' result: '<NOTSET>'>"
ci = runner.CallInfo(repr_while_running, "when")
assert repr(ci) == "<CallInfo when='when' result: None>"
# design question: do we want general hooks in python files? # design question: do we want general hooks in python files?
# then something like the following functional tests makes sense # then something like the following functional tests makes sense