Skip to content

Commit fbf251f

Browse files
committed
Improve typing of reports' longrepr field
1 parent f0eb82f commit fbf251f

File tree

6 files changed

+104
-60
lines changed

6 files changed

+104
-60
lines changed

src/_pytest/junitxml.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from _pytest import nodes
2626
from _pytest import timing
2727
from _pytest._code.code import ExceptionRepr
28+
from _pytest._code.code import ReprFileLocation
2829
from _pytest.config import Config
2930
from _pytest.config import filename_arg
3031
from _pytest.config.argparsing import Parser
@@ -200,8 +201,11 @@ def append_failure(self, report: TestReport) -> None:
200201
self._add_simple("skipped", "xfail-marked test passes unexpectedly")
201202
else:
202203
assert report.longrepr is not None
203-
if getattr(report.longrepr, "reprcrash", None) is not None:
204-
message = report.longrepr.reprcrash.message
204+
reprcrash = getattr(
205+
report.longrepr, "reprcrash", None
206+
) # type: Optional[ReprFileLocation]
207+
if reprcrash is not None:
208+
message = reprcrash.message
205209
else:
206210
message = str(report.longrepr)
207211
message = bin_xml_escape(message)
@@ -217,8 +221,11 @@ def append_collect_skipped(self, report: TestReport) -> None:
217221

218222
def append_error(self, report: TestReport) -> None:
219223
assert report.longrepr is not None
220-
if getattr(report.longrepr, "reprcrash", None) is not None:
221-
reason = report.longrepr.reprcrash.message
224+
reprcrash = getattr(
225+
report.longrepr, "reprcrash", None
226+
) # type: Optional[ReprFileLocation]
227+
if reprcrash is not None:
228+
reason = reprcrash.message
222229
else:
223230
reason = str(report.longrepr)
224231

@@ -237,7 +244,7 @@ def append_skipped(self, report: TestReport) -> None:
237244
skipped = ET.Element("skipped", type="pytest.xfail", message=xfailreason)
238245
self.append(skipped)
239246
else:
240-
assert report.longrepr is not None
247+
assert isinstance(report.longrepr, tuple)
241248
filename, lineno, skipreason = report.longrepr
242249
if skipreason.startswith("Skipped: "):
243250
skipreason = skipreason[9:]

src/_pytest/reports.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from io import StringIO
22
from pprint import pprint
33
from typing import Any
4+
from typing import cast
45
from typing import Dict
56
from typing import Iterable
67
from typing import Iterator
@@ -15,6 +16,7 @@
1516

1617
from _pytest._code.code import ExceptionChainRepr
1718
from _pytest._code.code import ExceptionInfo
19+
from _pytest._code.code import ExceptionRepr
1820
from _pytest._code.code import ReprEntry
1921
from _pytest._code.code import ReprEntryNative
2022
from _pytest._code.code import ReprExceptionInfo
@@ -57,8 +59,9 @@ def getworkerinfoline(node):
5759
class BaseReport:
5860
when = None # type: Optional[str]
5961
location = None # type: Optional[Tuple[str, Optional[int], str]]
60-
# TODO: Improve this Any.
61-
longrepr = None # type: Optional[Any]
62+
longrepr = (
63+
None
64+
) # type: Union[None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr]
6265
sections = [] # type: List[Tuple[str, str]]
6366
nodeid = None # type: str
6467

@@ -79,7 +82,8 @@ def toterminal(self, out: TerminalWriter) -> None:
7982
return
8083

8184
if hasattr(longrepr, "toterminal"):
82-
longrepr.toterminal(out)
85+
longrepr_terminal = cast(TerminalRepr, longrepr)
86+
longrepr_terminal.toterminal(out)
8387
else:
8488
try:
8589
s = str(longrepr)
@@ -233,7 +237,9 @@ def __init__(
233237
location: Tuple[str, Optional[int], str],
234238
keywords,
235239
outcome: "Literal['passed', 'failed', 'skipped']",
236-
longrepr,
240+
longrepr: Union[
241+
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
242+
],
237243
when: "Literal['setup', 'call', 'teardown']",
238244
sections: Iterable[Tuple[str, str]] = (),
239245
duration: float = 0,
@@ -293,8 +299,9 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
293299
sections = []
294300
if not call.excinfo:
295301
outcome = "passed" # type: Literal["passed", "failed", "skipped"]
296-
# TODO: Improve this Any.
297-
longrepr = None # type: Optional[Any]
302+
longrepr = (
303+
None
304+
) # type: Union[None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr]
298305
else:
299306
if not isinstance(excinfo, ExceptionInfo):
300307
outcome = "failed"
@@ -372,7 +379,7 @@ def __repr__(self) -> str:
372379

373380

374381
class CollectErrorRepr(TerminalRepr):
375-
def __init__(self, msg) -> None:
382+
def __init__(self, msg: str) -> None:
376383
self.longrepr = msg
377384

378385
def toterminal(self, out: TerminalWriter) -> None:
@@ -436,16 +443,18 @@ def serialize_repr_crash(
436443
else:
437444
return None
438445

439-
def serialize_longrepr(rep: BaseReport) -> Dict[str, Any]:
446+
def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]:
440447
assert rep.longrepr is not None
448+
# TODO: Investigate whether the duck typing is really necessary here.
449+
longrepr = cast(ExceptionRepr, rep.longrepr)
441450
result = {
442-
"reprcrash": serialize_repr_crash(rep.longrepr.reprcrash),
443-
"reprtraceback": serialize_repr_traceback(rep.longrepr.reprtraceback),
444-
"sections": rep.longrepr.sections,
451+
"reprcrash": serialize_repr_crash(longrepr.reprcrash),
452+
"reprtraceback": serialize_repr_traceback(longrepr.reprtraceback),
453+
"sections": longrepr.sections,
445454
} # type: Dict[str, Any]
446-
if isinstance(rep.longrepr, ExceptionChainRepr):
455+
if isinstance(longrepr, ExceptionChainRepr):
447456
result["chain"] = []
448-
for repr_traceback, repr_crash, description in rep.longrepr.chain:
457+
for repr_traceback, repr_crash, description in longrepr.chain:
449458
result["chain"].append(
450459
(
451460
serialize_repr_traceback(repr_traceback),
@@ -462,7 +471,7 @@ def serialize_longrepr(rep: BaseReport) -> Dict[str, Any]:
462471
if hasattr(report.longrepr, "reprtraceback") and hasattr(
463472
report.longrepr, "reprcrash"
464473
):
465-
d["longrepr"] = serialize_longrepr(report)
474+
d["longrepr"] = serialize_exception_longrepr(report)
466475
else:
467476
d["longrepr"] = str(report.longrepr)
468477
else:

src/_pytest/resultlog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
8585
elif report.passed:
8686
longrepr = ""
8787
elif report.skipped:
88-
assert report.longrepr is not None
88+
assert isinstance(report.longrepr, tuple)
8989
longrepr = str(report.longrepr[2])
9090
else:
9191
longrepr = str(report.longrepr)

src/_pytest/runner.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import bdb
33
import os
44
import sys
5-
from typing import Any
65
from typing import Callable
76
from typing import cast
87
from typing import Dict
@@ -22,6 +21,7 @@
2221
from _pytest import timing
2322
from _pytest._code.code import ExceptionChainRepr
2423
from _pytest._code.code import ExceptionInfo
24+
from _pytest._code.code import TerminalRepr
2525
from _pytest.compat import TYPE_CHECKING
2626
from _pytest.config.argparsing import Parser
2727
from _pytest.nodes import Collector
@@ -327,8 +327,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport:
327327

328328
def pytest_make_collect_report(collector: Collector) -> CollectReport:
329329
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
330-
# TODO: Better typing for longrepr.
331-
longrepr = None # type: Optional[Any]
330+
longrepr = None # type: Union[None, Tuple[str, int, str], str, TerminalRepr]
332331
if not call.excinfo:
333332
outcome = "passed" # type: Literal["passed", "skipped", "failed"]
334333
else:
@@ -348,6 +347,7 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
348347
outcome = "failed"
349348
errorinfo = collector.repr_failure(call.excinfo)
350349
if not hasattr(errorinfo, "toterminal"):
350+
assert isinstance(errorinfo, str)
351351
errorinfo = CollectErrorRepr(errorinfo)
352352
longrepr = errorinfo
353353
result = call.result if not call.excinfo else None

src/_pytest/terminal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,7 @@ def _folded_skips(
12471247
d = {} # type: Dict[Tuple[str, Optional[int], str], List[CollectReport]]
12481248
for event in skipped:
12491249
assert event.longrepr is not None
1250+
assert isinstance(event.longrepr, tuple), (event, event.longrepr)
12501251
assert len(event.longrepr) == 3, (event, event.longrepr)
12511252
fspath, lineno, reason = event.longrepr
12521253
# For consistency, report all fspaths in relative form.

0 commit comments

Comments
 (0)