From 847eacea19a02887fb6e63601908a37d7c2576a8 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 3 Nov 2018 21:50:13 +0100 Subject: [PATCH] refactor CallInfo constructor magic into named constructor --- src/_pytest/nose.py | 4 ++- src/_pytest/runner.py | 59 ++++++++++++++++++++++++++---------------- testing/test_runner.py | 14 ++-------- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index 416381bb5..6facc547f 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -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 diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 9ea1a07cd..27f244a80 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -8,6 +8,7 @@ import os import sys from time import time +import attr import six from .reports import CollectErrorRepr @@ -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 + _result = attr.ib() + # 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", #: "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", "") - status = "result: %r" % (result,) - return "" % (self.when, status) + # TODO: investigate unification + value = repr(self._result) + status = "result" + return "".format( + when=self.when, value=value, status=status + ) def pytest_runtest_makereport(item, call): @@ -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" diff --git a/testing/test_runner.py b/testing/test_runner.py index d76f3da9b..916c2ea4a 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -487,13 +487,13 @@ 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) == "" - 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) == "" @@ -501,16 +501,6 @@ def test_callinfo(): 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"]) == "'>" - - ci = runner.CallInfo(repr_while_running, "when") - assert repr(ci) == "" - - # design question: do we want general hooks in python files? # then something like the following functional tests makes sense