Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/_pytest/reports.py : 11%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from io import StringIO
2from pprint import pprint
3from typing import Any
4from typing import Dict
5from typing import Iterable
6from typing import Iterator
7from typing import List
8from typing import Optional
9from typing import Tuple
10from typing import TypeVar
11from typing import Union
13import attr
14import py
16from _pytest._code.code import ExceptionChainRepr
17from _pytest._code.code import ExceptionInfo
18from _pytest._code.code import ReprEntry
19from _pytest._code.code import ReprEntryNative
20from _pytest._code.code import ReprExceptionInfo
21from _pytest._code.code import ReprFileLocation
22from _pytest._code.code import ReprFuncArgs
23from _pytest._code.code import ReprLocals
24from _pytest._code.code import ReprTraceback
25from _pytest._code.code import TerminalRepr
26from _pytest._io import TerminalWriter
27from _pytest.compat import TYPE_CHECKING
28from _pytest.config import Config
29from _pytest.nodes import Collector
30from _pytest.nodes import Item
31from _pytest.outcomes import skip
32from _pytest.pathlib import Path
34if TYPE_CHECKING:
35 from typing import NoReturn
36 from typing_extensions import Type
37 from typing_extensions import Literal
39 from _pytest.runner import CallInfo
42def getworkerinfoline(node):
43 try:
44 return node._workerinfocache
45 except AttributeError:
46 d = node.workerinfo
47 ver = "%s.%s.%s" % d["version_info"][:3]
48 node._workerinfocache = s = "[{}] {} -- Python {} {}".format(
49 d["id"], d["sysplatform"], ver, d["executable"]
50 )
51 return s
54_R = TypeVar("_R", bound="BaseReport")
57class BaseReport:
58 when = None # type: Optional[str]
59 location = None # type: Optional[Tuple[str, Optional[int], str]]
60 # TODO: Improve this Any.
61 longrepr = None # type: Optional[Any]
62 sections = [] # type: List[Tuple[str, str]]
63 nodeid = None # type: str
65 def __init__(self, **kw: Any) -> None:
66 self.__dict__.update(kw)
68 if TYPE_CHECKING:
69 # Can have arbitrary fields given to __init__().
70 def __getattr__(self, key: str) -> Any:
71 raise NotImplementedError()
73 def toterminal(self, out: TerminalWriter) -> None:
74 if hasattr(self, "node"):
75 out.line(getworkerinfoline(self.node))
77 longrepr = self.longrepr
78 if longrepr is None:
79 return
81 if hasattr(longrepr, "toterminal"):
82 longrepr.toterminal(out)
83 else:
84 try:
85 s = str(longrepr)
86 except UnicodeEncodeError:
87 s = "<unprintable longrepr>"
88 out.line(s)
90 def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]:
91 for name, content in self.sections:
92 if name.startswith(prefix):
93 yield prefix, content
95 @property
96 def longreprtext(self) -> str:
97 """
98 Read-only property that returns the full string representation
99 of ``longrepr``.
101 .. versionadded:: 3.0
102 """
103 file = StringIO()
104 tw = TerminalWriter(file)
105 tw.hasmarkup = False
106 self.toterminal(tw)
107 exc = file.getvalue()
108 return exc.strip()
110 @property
111 def caplog(self) -> str:
112 """Return captured log lines, if log capturing is enabled
114 .. versionadded:: 3.5
115 """
116 return "\n".join(
117 content for (prefix, content) in self.get_sections("Captured log")
118 )
120 @property
121 def capstdout(self) -> str:
122 """Return captured text from stdout, if capturing is enabled
124 .. versionadded:: 3.0
125 """
126 return "".join(
127 content for (prefix, content) in self.get_sections("Captured stdout")
128 )
130 @property
131 def capstderr(self) -> str:
132 """Return captured text from stderr, if capturing is enabled
134 .. versionadded:: 3.0
135 """
136 return "".join(
137 content for (prefix, content) in self.get_sections("Captured stderr")
138 )
140 passed = property(lambda x: x.outcome == "passed")
141 failed = property(lambda x: x.outcome == "failed")
142 skipped = property(lambda x: x.outcome == "skipped")
144 @property
145 def fspath(self) -> str:
146 return self.nodeid.split("::")[0]
148 @property
149 def count_towards_summary(self) -> bool:
150 """
151 **Experimental**
153 ``True`` if this report should be counted towards the totals shown at the end of the
154 test session: "1 passed, 1 failure, etc".
156 .. note::
158 This function is considered **experimental**, so beware that it is subject to changes
159 even in patch releases.
160 """
161 return True
163 @property
164 def head_line(self) -> Optional[str]:
165 """
166 **Experimental**
168 Returns the head line shown with longrepr output for this report, more commonly during
169 traceback representation during failures::
171 ________ Test.foo ________
174 In the example above, the head_line is "Test.foo".
176 .. note::
178 This function is considered **experimental**, so beware that it is subject to changes
179 even in patch releases.
180 """
181 if self.location is not None:
182 fspath, lineno, domain = self.location
183 return domain
184 return None
186 def _get_verbose_word(self, config: Config):
187 _category, _short, verbose = config.hook.pytest_report_teststatus(
188 report=self, config=config
189 )
190 return verbose
192 def _to_json(self) -> Dict[str, Any]:
193 """
194 This was originally the serialize_report() function from xdist (ca03269).
196 Returns the contents of this report as a dict of builtin entries, suitable for
197 serialization.
199 Experimental method.
200 """
201 return _report_to_json(self)
203 @classmethod
204 def _from_json(cls: "Type[_R]", reportdict: Dict[str, object]) -> _R:
205 """
206 This was originally the serialize_report() function from xdist (ca03269).
208 Factory method that returns either a TestReport or CollectReport, depending on the calling
209 class. It's the callers responsibility to know which class to pass here.
211 Experimental method.
212 """
213 kwargs = _report_kwargs_from_json(reportdict)
214 return cls(**kwargs)
217def _report_unserialization_failure(
218 type_name: str, report_class: "Type[BaseReport]", reportdict
219) -> "NoReturn":
220 url = "https://github.com/pytest-dev/pytest/issues"
221 stream = StringIO()
222 pprint("-" * 100, stream=stream)
223 pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream)
224 pprint("report_name: %s" % report_class, stream=stream)
225 pprint(reportdict, stream=stream)
226 pprint("Please report this bug at %s" % url, stream=stream)
227 pprint("-" * 100, stream=stream)
228 raise RuntimeError(stream.getvalue())
231class TestReport(BaseReport):
232 """ Basic test report object (also used for setup and teardown calls if
233 they fail).
234 """
236 __test__ = False
238 def __init__(
239 self,
240 nodeid: str,
241 location: Tuple[str, Optional[int], str],
242 keywords,
243 outcome: "Literal['passed', 'failed', 'skipped']",
244 longrepr,
245 when: "Literal['setup', 'call', 'teardown']",
246 sections: Iterable[Tuple[str, str]] = (),
247 duration: float = 0,
248 user_properties: Optional[Iterable[Tuple[str, object]]] = None,
249 **extra
250 ) -> None:
251 #: normalized collection node id
252 self.nodeid = nodeid
254 #: a (filesystempath, lineno, domaininfo) tuple indicating the
255 #: actual location of a test item - it might be different from the
256 #: collected one e.g. if a method is inherited from a different module.
257 self.location = location # type: Tuple[str, Optional[int], str]
259 #: a name -> value dictionary containing all keywords and
260 #: markers associated with a test invocation.
261 self.keywords = keywords
263 #: test outcome, always one of "passed", "failed", "skipped".
264 self.outcome = outcome
266 #: None or a failure representation.
267 self.longrepr = longrepr
269 #: one of 'setup', 'call', 'teardown' to indicate runtest phase.
270 self.when = when
272 #: user properties is a list of tuples (name, value) that holds user
273 #: defined properties of the test
274 self.user_properties = list(user_properties or [])
276 #: list of pairs ``(str, str)`` of extra information which needs to
277 #: marshallable. Used by pytest to add captured text
278 #: from ``stdout`` and ``stderr``, but may be used by other plugins
279 #: to add arbitrary information to reports.
280 self.sections = list(sections)
282 #: time it took to run just the test
283 self.duration = duration
285 self.__dict__.update(extra)
287 def __repr__(self) -> str:
288 return "<{} {!r} when={!r} outcome={!r}>".format(
289 self.__class__.__name__, self.nodeid, self.when, self.outcome
290 )
292 @classmethod
293 def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
294 """
295 Factory method to create and fill a TestReport with standard item and call info.
296 """
297 when = call.when
298 # Remove "collect" from the Literal type -- only for collection calls.
299 assert when != "collect"
300 duration = call.duration
301 keywords = {x: 1 for x in item.keywords}
302 excinfo = call.excinfo
303 sections = []
304 if not call.excinfo:
305 outcome = "passed" # type: Literal["passed", "failed", "skipped"]
306 # TODO: Improve this Any.
307 longrepr = None # type: Optional[Any]
308 else:
309 if not isinstance(excinfo, ExceptionInfo):
310 outcome = "failed"
311 longrepr = excinfo
312 elif isinstance(excinfo.value, skip.Exception):
313 outcome = "skipped"
314 r = excinfo._getreprcrash()
315 longrepr = (str(r.path), r.lineno, r.message)
316 else:
317 outcome = "failed"
318 if call.when == "call":
319 longrepr = item.repr_failure(excinfo)
320 else: # exception in setup or teardown
321 longrepr = item._repr_failure_py(
322 excinfo, style=item.config.getoption("tbstyle", "auto")
323 )
324 for rwhen, key, content in item._report_sections:
325 sections.append(("Captured {} {}".format(key, rwhen), content))
326 return cls(
327 item.nodeid,
328 item.location,
329 keywords,
330 outcome,
331 longrepr,
332 when,
333 sections,
334 duration,
335 user_properties=item.user_properties,
336 )
339class CollectReport(BaseReport):
340 """Collection report object."""
342 when = "collect"
344 def __init__(
345 self,
346 nodeid: str,
347 outcome: "Literal['passed', 'skipped', 'failed']",
348 longrepr,
349 result: Optional[List[Union[Item, Collector]]],
350 sections: Iterable[Tuple[str, str]] = (),
351 **extra
352 ) -> None:
353 #: normalized collection node id
354 self.nodeid = nodeid
356 #: test outcome, always one of "passed", "failed", "skipped".
357 self.outcome = outcome
359 #: None or a failure representation.
360 self.longrepr = longrepr
362 #: The collected items and collection nodes.
363 self.result = result or []
365 #: list of pairs ``(str, str)`` of extra information which needs to
366 #: marshallable. Used by pytest to add captured text
367 #: from ``stdout`` and ``stderr``, but may be used by other plugins
368 #: to add arbitrary information to reports.
369 self.sections = list(sections)
371 self.__dict__.update(extra)
373 @property
374 def location(self):
375 return (self.fspath, None, self.fspath)
377 def __repr__(self) -> str:
378 return "<CollectReport {!r} lenresult={} outcome={!r}>".format(
379 self.nodeid, len(self.result), self.outcome
380 )
383class CollectErrorRepr(TerminalRepr):
384 def __init__(self, msg) -> None:
385 self.longrepr = msg
387 def toterminal(self, out: TerminalWriter) -> None:
388 out.line(self.longrepr, red=True)
391def pytest_report_to_serializable(
392 report: Union[CollectReport, TestReport]
393) -> Optional[Dict[str, Any]]:
394 if isinstance(report, (TestReport, CollectReport)):
395 data = report._to_json()
396 data["$report_type"] = report.__class__.__name__
397 return data
398 return None
401def pytest_report_from_serializable(
402 data: Dict[str, Any],
403) -> Optional[Union[CollectReport, TestReport]]:
404 if "$report_type" in data:
405 if data["$report_type"] == "TestReport":
406 return TestReport._from_json(data)
407 elif data["$report_type"] == "CollectReport":
408 return CollectReport._from_json(data)
409 assert False, "Unknown report_type unserialize data: {}".format(
410 data["$report_type"]
411 )
412 return None
415def _report_to_json(report: BaseReport) -> Dict[str, Any]:
416 """
417 This was originally the serialize_report() function from xdist (ca03269).
419 Returns the contents of this report as a dict of builtin entries, suitable for
420 serialization.
421 """
423 def serialize_repr_entry(
424 entry: Union[ReprEntry, ReprEntryNative]
425 ) -> Dict[str, Any]:
426 data = attr.asdict(entry)
427 for key, value in data.items():
428 if hasattr(value, "__dict__"):
429 data[key] = attr.asdict(value)
430 entry_data = {"type": type(entry).__name__, "data": data}
431 return entry_data
433 def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]:
434 result = attr.asdict(reprtraceback)
435 result["reprentries"] = [
436 serialize_repr_entry(x) for x in reprtraceback.reprentries
437 ]
438 return result
440 def serialize_repr_crash(
441 reprcrash: Optional[ReprFileLocation],
442 ) -> Optional[Dict[str, Any]]:
443 if reprcrash is not None:
444 return attr.asdict(reprcrash)
445 else:
446 return None
448 def serialize_longrepr(rep: BaseReport) -> Dict[str, Any]:
449 assert rep.longrepr is not None
450 result = {
451 "reprcrash": serialize_repr_crash(rep.longrepr.reprcrash),
452 "reprtraceback": serialize_repr_traceback(rep.longrepr.reprtraceback),
453 "sections": rep.longrepr.sections,
454 } # type: Dict[str, Any]
455 if isinstance(rep.longrepr, ExceptionChainRepr):
456 result["chain"] = []
457 for repr_traceback, repr_crash, description in rep.longrepr.chain:
458 result["chain"].append(
459 (
460 serialize_repr_traceback(repr_traceback),
461 serialize_repr_crash(repr_crash),
462 description,
463 )
464 )
465 else:
466 result["chain"] = None
467 return result
469 d = report.__dict__.copy()
470 if hasattr(report.longrepr, "toterminal"):
471 if hasattr(report.longrepr, "reprtraceback") and hasattr(
472 report.longrepr, "reprcrash"
473 ):
474 d["longrepr"] = serialize_longrepr(report)
475 else:
476 d["longrepr"] = str(report.longrepr)
477 else:
478 d["longrepr"] = report.longrepr
479 for name in d:
480 if isinstance(d[name], (py.path.local, Path)):
481 d[name] = str(d[name])
482 elif name == "result":
483 d[name] = None # for now
484 return d
487def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
488 """
489 This was originally the serialize_report() function from xdist (ca03269).
491 Returns **kwargs that can be used to construct a TestReport or CollectReport instance.
492 """
494 def deserialize_repr_entry(entry_data):
495 data = entry_data["data"]
496 entry_type = entry_data["type"]
497 if entry_type == "ReprEntry":
498 reprfuncargs = None
499 reprfileloc = None
500 reprlocals = None
501 if data["reprfuncargs"]:
502 reprfuncargs = ReprFuncArgs(**data["reprfuncargs"])
503 if data["reprfileloc"]:
504 reprfileloc = ReprFileLocation(**data["reprfileloc"])
505 if data["reprlocals"]:
506 reprlocals = ReprLocals(data["reprlocals"]["lines"])
508 reprentry = ReprEntry(
509 lines=data["lines"],
510 reprfuncargs=reprfuncargs,
511 reprlocals=reprlocals,
512 reprfileloc=reprfileloc,
513 style=data["style"],
514 ) # type: Union[ReprEntry, ReprEntryNative]
515 elif entry_type == "ReprEntryNative":
516 reprentry = ReprEntryNative(data["lines"])
517 else:
518 _report_unserialization_failure(entry_type, TestReport, reportdict)
519 return reprentry
521 def deserialize_repr_traceback(repr_traceback_dict):
522 repr_traceback_dict["reprentries"] = [
523 deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"]
524 ]
525 return ReprTraceback(**repr_traceback_dict)
527 def deserialize_repr_crash(repr_crash_dict: Optional[dict]):
528 if repr_crash_dict is not None:
529 return ReprFileLocation(**repr_crash_dict)
530 else:
531 return None
533 if (
534 reportdict["longrepr"]
535 and "reprcrash" in reportdict["longrepr"]
536 and "reprtraceback" in reportdict["longrepr"]
537 ):
539 reprtraceback = deserialize_repr_traceback(
540 reportdict["longrepr"]["reprtraceback"]
541 )
542 reprcrash = deserialize_repr_crash(reportdict["longrepr"]["reprcrash"])
543 if reportdict["longrepr"]["chain"]:
544 chain = []
545 for repr_traceback_data, repr_crash_data, description in reportdict[
546 "longrepr"
547 ]["chain"]:
548 chain.append(
549 (
550 deserialize_repr_traceback(repr_traceback_data),
551 deserialize_repr_crash(repr_crash_data),
552 description,
553 )
554 )
555 exception_info = ExceptionChainRepr(
556 chain
557 ) # type: Union[ExceptionChainRepr,ReprExceptionInfo]
558 else:
559 exception_info = ReprExceptionInfo(reprtraceback, reprcrash)
561 for section in reportdict["longrepr"]["sections"]:
562 exception_info.addsection(*section)
563 reportdict["longrepr"] = exception_info
565 return reportdict