|
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