Skip to content

Commit

Permalink
terminal: summary: handle (internal) CollectReport errors (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
blueyed authored Feb 28, 2020
1 parent f862e54 commit db03d16
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 30 deletions.
72 changes: 43 additions & 29 deletions src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Union

import attr
import pluggy
Expand All @@ -27,6 +28,7 @@

import pytest
from _pytest import nodes
from _pytest._code.code import ReprFileLocation
from _pytest.assertion.util import _running_on_ci
from _pytest.compat import shell_quote
from _pytest.config import Config
Expand Down Expand Up @@ -1211,43 +1213,55 @@ def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], s
return parts, main_color


def _get_pos(config: Config, rep: TestReport) -> str:
nodeid = config.cwd_relative_nodeid(rep.nodeid)
path, _, testname = nodeid.partition("::")
if not testname:
return nodeid

# Append location (line number).
def _get_rep_reprcrash(
rep: Union[CollectReport, TestReport], fulltrace: bool
) -> Optional[ReprFileLocation]:
if not rep.longrepr:
return nodeid
if config.option.fulltrace:
try:
testloc = rep.longrepr.reprcrash
except AttributeError:
return nodeid
else:
return None

if isinstance(rep, TestReport) and not fulltrace:
# This uses the first traceback entry for the location in the test itself
# (rather than reprcrash, which might be less relevant for going to
# directly, e.g. pexpect failures in pytest itself).
try:
testloc = rep.longrepr.reprtraceback.reprentries[0].reprfileloc
return rep.longrepr.reprtraceback.reprentries[0].reprfileloc
except AttributeError:
testloc = None
if testloc is None:
# Handle --tb=native, --tb=no.
try:
testloc = rep.longrepr.reprcrash
except AttributeError:
return nodeid
pass

assert isinstance(testloc.path, str), testloc.path
testloc_path = Path(testloc.path)
if testloc_path.is_absolute():
testloc_path = _shorten_path(testloc_path, Path(str(config.invocation_dir)))
# Handle --tb=native, --tb=no.
try:
return rep.longrepr.reprcrash
except AttributeError:
return None


def _get_pos(config: Config, rep: Union[CollectReport, TestReport]) -> str:
nodeid = config.cwd_relative_nodeid(rep.nodeid)
path, _, testname = nodeid.partition("::")

if str(testloc_path).replace("\\", nodes.SEP) == path:
return "%s:%d::%s" % (path, testloc.lineno, testname)
return "%s (%s:%d)" % (nodeid, testloc_path, testloc.lineno)
if isinstance(rep, CollectReport):
desc = "collecting"
if nodeid:
desc += " " + nodeid
path = rep.fspath
else:
desc = nodeid

# Append location (line number).
crashloc = _get_rep_reprcrash(rep, config.option.fulltrace)
if not crashloc:
return desc

assert isinstance(crashloc.path, str), crashloc.path
crash_path = Path(crashloc.path)
if crash_path.is_absolute():
crash_path = _shorten_path(crash_path, Path(str(config.invocation_dir)))

if str(crash_path).replace("\\", nodes.SEP) == path:
if not testname:
return "%s:%d" % (path, crashloc.lineno)
return "%s:%d::%s" % (path, crashloc.lineno, testname)
return "%s (%s:%d)" % (desc, crash_path, crashloc.lineno)


def _get_line_with_reprcrash_message(config, rep, termwidth):
Expand Down
3 changes: 3 additions & 0 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ def test_hello():
"test_strict_prohibits_unregistered_markers.py:2: in <module>",
" @pytest.mark.unregisteredmark",
"E Failed: 'unregisteredmark' not found in `markers` configuration option",
"*= short test summary info =*",
"ERROR test_strict_prohibits_unregistered_markers.py:2 - Failed:"
" 'unregisteredmark' not found in `markers` configuration option",
"*! Interrupted: 1 error during collection !*",
"*= 1 error in *",
]
Expand Down
41 changes: 40 additions & 1 deletion testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2257,6 +2257,45 @@ class longrepr:
assert check(config, rep) == "windows/path.py:2::testname"


def test_crash_during_collection(testdir, monkeypatch) -> None:
from pluggy import hooks

def crash(obj):
raise Exception("crash")

monkeypatch.setattr("_pytest.python.safe_isclass", crash)

p1 = testdir.makepyfile("def test_pass(): pass")
result = testdir.runpytest(str(p1))
result.stdout.fnmatch_lines(
[
"collected 0 items / 1 error",
"",
"*= ERRORS =*",
"*_ ERROR collecting test_crash_during_collection.py _*",
# XXX: shouldn't be cut off here.
"{}:*: in __call__".format(hooks.__file__),
" return self._hookexec(self, self.get_hookimpls(), kwargs)",
],
consecutive=True,
)

lnum = crash.__code__.co_firstlineno + 1
result.stdout.fnmatch_lines(
[
"{}:{}: in crash".format(__file__, lnum),
' raise Exception("crash")',
"E Exception: crash",
"*= short test summary info =*",
"ERROR collecting test_crash_during_collection.py ({}:{}) - Exception: crash".format(
__file__, lnum
),
"*! Interrupted: 1 error during collection !*",
"*= 1 error in *",
]
)


@pytest.mark.parametrize("ci", (None, "true"))
def test_summary_with_nonprintable(ci, testdir: Testdir) -> None:
testdir.monkeypatch.setattr("_pytest.terminal.get_terminal_width", lambda: 99)
Expand Down Expand Up @@ -2339,7 +2378,7 @@ def test_collecterror(testdir):
"*_ ERROR collecting test_collecterror.py _*",
"E SyntaxError: *",
"*= short test summary info =*",
"ERROR test_collecterror.py",
"ERROR collecting test_collecterror.py",
"*! Interrupted: 1 error during collection !*",
"*= 1 error in *",
]
Expand Down

0 comments on commit db03d16

Please sign in to comment.