Skip to content

Commit

Permalink
refactor CallInfo constructor magic into named constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
RonnyPfannschmidt committed Nov 30, 2018
1 parent b531f7d commit 847eace
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 36 deletions.
4 changes: 3 additions & 1 deletion src/_pytest/nose.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def get_skip_exceptions():
def pytest_runtest_makereport(item, call):
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
# 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


Expand Down
61 changes: 38 additions & 23 deletions src/_pytest/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sys
from time import time

import attr
import six

from .reports import CollectErrorRepr
Expand Down Expand Up @@ -189,43 +190,57 @@ def check_interactive_exception(call, report):
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
return CallInfo(
return CallInfo.from_call(
lambda: ihook(item=item, **kwds),
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):
""" Result/Exception info a function invocation. """

#: None or ExceptionInfo object.
excinfo = None

def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False):
_result = attr.ib()
# type: Optional[ExceptionInfo]
excinfo = attr.ib()
start = attr.ib()
stop = attr.ib()
when = attr.ib()

@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",
#: "teardown", "memocollect"
self.when = when
self.start = time()
start = time()
excinfo = None
try:
self.result = func()
except KeyboardInterrupt:
if treat_keyboard_interrupt_as_exception:
self.excinfo = ExceptionInfo.from_current()
else:
self.stop = time()
raise
result = func()
except: # noqa
self.excinfo = ExceptionInfo.from_current()
self.stop = time()
excinfo = ExceptionInfo.from_current()
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):
if self.excinfo:
status = "exception: %s" % str(self.excinfo.value)
if self.excinfo is not None:
status = "exception"
value = self.excinfo.value
else:
result = getattr(self, "result", "<NOTSET>")
status = "result: %r" % (result,)
return "<CallInfo when=%r %s>" % (self.when, status)
# TODO: investigate unification
value = repr(self._result)
status = "result"
return "<CallInfo when={when!r} {status}: {value}>".format(
when=self.when, value=value, status=status
)


def pytest_runtest_makereport(item, call):
Expand Down Expand Up @@ -269,7 +284,7 @@ def pytest_runtest_makereport(item, call):


def pytest_make_collect_report(collector):
call = CallInfo(lambda: list(collector.collect()), "collect")
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
longrepr = None
if not call.excinfo:
outcome = "passed"
Expand Down
14 changes: 2 additions & 12 deletions testing/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,30 +487,20 @@ def test_report_extra_parameters(reporttype):


def test_callinfo():
ci = runner.CallInfo(lambda: 0, "123")
ci = runner.CallInfo.from_call(lambda: 0, "123")
assert ci.when == "123"
assert ci.result == 0
assert "result" in repr(ci)
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 not hasattr(ci, "result")
assert repr(ci) == "<CallInfo when='123' exception: division by zero>"
assert ci.excinfo
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?
# then something like the following functional tests makes sense

Expand Down

0 comments on commit 847eace

Please sign in to comment.