|
8 | 8 | import sys
|
9 | 9 | from time import time
|
10 | 10 |
|
| 11 | +import attr |
11 | 12 | import six
|
12 | 13 |
|
13 | 14 | from .reports import CollectErrorRepr
|
@@ -189,43 +190,57 @@ def check_interactive_exception(call, report):
|
189 | 190 | def call_runtest_hook(item, when, **kwds):
|
190 | 191 | hookname = "pytest_runtest_" + when
|
191 | 192 | ihook = getattr(item.ihook, hookname)
|
192 |
| - return CallInfo( |
| 193 | + return CallInfo.from_call( |
193 | 194 | lambda: ihook(item=item, **kwds),
|
194 | 195 | when=when,
|
195 |
| - treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"), |
| 196 | + reraise=KeyboardInterrupt if not item.config.getvalue("usepdb") else (), |
196 | 197 | )
|
197 | 198 |
|
198 | 199 |
|
| 200 | +@attr.s(repr=False) |
199 | 201 | class CallInfo(object):
|
200 | 202 | """ Result/Exception info a function invocation. """
|
201 | 203 |
|
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): |
206 | 219 | #: context of invocation: one of "setup", "call",
|
207 | 220 | #: "teardown", "memocollect"
|
208 |
| - self.when = when |
209 |
| - self.start = time() |
| 221 | + start = time() |
| 222 | + excinfo = None |
210 | 223 | 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() |
218 | 225 | 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) |
221 | 232 |
|
222 | 233 | 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 |
225 | 237 | 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 | + ) |
229 | 244 |
|
230 | 245 |
|
231 | 246 | def pytest_runtest_makereport(item, call):
|
@@ -269,7 +284,7 @@ def pytest_runtest_makereport(item, call):
|
269 | 284 |
|
270 | 285 |
|
271 | 286 | def pytest_make_collect_report(collector):
|
272 |
| - call = CallInfo(lambda: list(collector.collect()), "collect") |
| 287 | + call = CallInfo.from_call(lambda: list(collector.collect()), "collect") |
273 | 288 | longrepr = None
|
274 | 289 | if not call.excinfo:
|
275 | 290 | outcome = "passed"
|
|
0 commit comments