diff --git a/changelog/4667.bugfix.rst b/changelog/4667.bugfix.rst new file mode 100644 index 000000000..ac2d8567c --- /dev/null +++ b/changelog/4667.bugfix.rst @@ -0,0 +1 @@ +``pytest_terminal_summary`` uses result from ``pytest_report_teststatus`` hook, rather than hardcoded strings. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 1a06ea6d1..8b9af2a88 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -213,7 +213,7 @@ class _NodeReporter(object): self._add_simple(Junit.skipped, "collection skipped", report.longrepr) def append_error(self, report): - if getattr(report, "when", None) == "teardown": + if report.when == "teardown": msg = "test teardown failure" else: msg = "test setup failure" diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 4b1564791..9cadd2f9d 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -305,13 +305,10 @@ class HookRecorder(object): """return a testreport whose dotted import path matches""" values = [] for rep in self.getreports(names=names): - try: - if not when and rep.when != "call" and rep.passed: - # setup/teardown passing reports - let's ignore those - continue - except AttributeError: - pass - if when and getattr(rep, "when", None) != when: + if not when and rep.when != "call" and rep.passed: + # setup/teardown passing reports - let's ignore those + continue + if when and rep.when != when: continue if not inamepart or inamepart in rep.nodeid.split("::"): values.append(rep) @@ -338,7 +335,7 @@ class HookRecorder(object): failed = [] for rep in self.getreports("pytest_collectreport pytest_runtest_logreport"): if rep.passed: - if getattr(rep, "when", None) == "call": + if rep.when == "call": passed.append(rep) elif rep.skipped: skipped.append(rep) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index b2010cc2e..3bdbce791 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -19,6 +19,8 @@ def getslaveinfoline(node): class BaseReport(object): + when = None + def __init__(self, **kw): self.__dict__.update(kw) @@ -169,6 +171,8 @@ class TeardownErrorReport(BaseReport): class CollectReport(BaseReport): + when = "collect" + def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra): self.nodeid = nodeid self.outcome = outcome diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index f755fc4eb..49676aa80 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -180,9 +180,9 @@ def pytest_runtest_makereport(item, call): def pytest_report_teststatus(report): if hasattr(report, "wasxfail"): if report.skipped: - return "xfailed", "x", "xfail" + return "xfailed", "x", "XFAIL" elif report.passed: - return "xpassed", "X", ("XPASS", {"yellow": True}) + return "xpassed", "X", "XPASS" # called by the terminalreporter instance/plugin @@ -191,11 +191,6 @@ def pytest_report_teststatus(report): def pytest_terminal_summary(terminalreporter): tr = terminalreporter if not tr.reportchars: - # for name in "xfailed skipped failed xpassed": - # if not tr.stats.get(name, 0): - # tr.write_line("HINT: use '-r' option to see extra " - # "summary info about tests") - # break return lines = [] @@ -209,21 +204,23 @@ def pytest_terminal_summary(terminalreporter): tr._tw.line(line) -def show_simple(terminalreporter, lines, stat, format): +def show_simple(terminalreporter, lines, stat): failed = terminalreporter.stats.get(stat) if failed: for rep in failed: + verbose_word = _get_report_str(terminalreporter, rep) pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) - lines.append(format % (pos,)) + lines.append("%s %s" % (verbose_word, pos)) def show_xfailed(terminalreporter, lines): xfailed = terminalreporter.stats.get("xfailed") if xfailed: for rep in xfailed: + verbose_word = _get_report_str(terminalreporter, rep) pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) reason = rep.wasxfail - lines.append("XFAIL %s" % (pos,)) + lines.append("%s %s" % (verbose_word, pos)) if reason: lines.append(" " + str(reason)) @@ -232,9 +229,10 @@ def show_xpassed(terminalreporter, lines): xpassed = terminalreporter.stats.get("xpassed") if xpassed: for rep in xpassed: + verbose_word = _get_report_str(terminalreporter, rep) pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) reason = rep.wasxfail - lines.append("XPASS %s %s" % (pos, reason)) + lines.append("%s %s %s" % (verbose_word, pos, reason)) def folded_skips(skipped): @@ -246,8 +244,11 @@ def folded_skips(skipped): # folding reports with global pytestmark variable # this is workaround, because for now we cannot identify the scope of a skip marker # TODO: revisit after marks scope would be fixed - when = getattr(event, "when", None) - if when == "setup" and "skip" in keywords and "pytestmark" not in keywords: + if ( + event.when == "setup" + and "skip" in keywords + and "pytestmark" not in keywords + ): key = (key[0], None, key[2]) d.setdefault(key, []).append(event) values = [] @@ -260,39 +261,42 @@ def show_skipped(terminalreporter, lines): tr = terminalreporter skipped = tr.stats.get("skipped", []) if skipped: - # if not tr.hasopt('skipped'): - # tr.write_line( - # "%d skipped tests, specify -rs for more info" % - # len(skipped)) - # return + verbose_word = _get_report_str(terminalreporter, report=skipped[0]) fskips = folded_skips(skipped) if fskips: - # tr.write_sep("_", "skipped test summary") for num, fspath, lineno, reason in fskips: if reason.startswith("Skipped: "): reason = reason[9:] if lineno is not None: lines.append( - "SKIP [%d] %s:%d: %s" % (num, fspath, lineno + 1, reason) + "%s [%d] %s:%d: %s" + % (verbose_word, num, fspath, lineno + 1, reason) ) else: - lines.append("SKIP [%d] %s: %s" % (num, fspath, reason)) + lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) -def shower(stat, format): +def shower(stat): def show_(terminalreporter, lines): - return show_simple(terminalreporter, lines, stat, format) + return show_simple(terminalreporter, lines, stat) return show_ +def _get_report_str(terminalreporter, report): + _category, _short, verbose = terminalreporter.config.hook.pytest_report_teststatus( + report=report + ) + return verbose + + REPORTCHAR_ACTIONS = { "x": show_xfailed, "X": show_xpassed, - "f": shower("failed", "FAIL %s"), - "F": shower("failed", "FAIL %s"), + "f": shower("failed"), + "F": shower("failed"), "s": show_skipped, "S": show_skipped, - "p": shower("passed", "PASSED %s"), - "E": shower("error", "ERROR %s"), + "p": shower("passed"), + "E": shower("error"), } diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index bea02306b..9e1d1cb62 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -376,8 +376,11 @@ class TerminalReporter(object): return running_xdist = hasattr(rep, "node") if markup is None: - if rep.passed: + was_xfail = hasattr(report, "wasxfail") + if rep.passed and not was_xfail: markup = {"green": True} + elif rep.passed and was_xfail: + markup = {"yellow": True} elif rep.failed: markup = {"red": True} elif rep.skipped: @@ -806,8 +809,7 @@ class TerminalReporter(object): self.write_sep("=", "ERRORS") for rep in self.stats["error"]: msg = self._getfailureheadline(rep) - if not hasattr(rep, "when"): - # collect + if rep.when == "collect": msg = "ERROR collecting " + msg elif rep.when == "setup": msg = "ERROR at setup of " + msg diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index b7f914335..af30f2123 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -804,8 +804,8 @@ class TestInvocationVariants(object): result = testdir.runpytest("-rf") lines = result.stdout.str().splitlines() for line in lines: - if line.startswith("FAIL "): - testid = line[5:].strip() + if line.startswith(("FAIL ", "FAILED ")): + _fail, _sep, testid = line.partition(" ") break result = testdir.runpytest(testid, "-rf") result.stdout.fnmatch_lines([line, "*1 failed*"]) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 6b18011b6..b2a515f11 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -770,6 +770,7 @@ def test_skip_reasons_folding(): # ev3 might be a collection report ev3 = X() + ev3.when = "collect" ev3.longrepr = longrepr ev3.skipped = True @@ -1202,6 +1203,6 @@ def test_summary_list_after_errors(testdir): [ "=* FAILURES *=", "*= short test summary info =*", - "FAIL test_summary_list_after_errors.py::test_fail", + "FAILED test_summary_list_after_errors.py::test_fail", ] ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 06345f88d..6837da5e0 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -625,7 +625,7 @@ class TestTerminalFunctional(object): "*test_verbose_reporting.py::test_fail *FAIL*", "*test_verbose_reporting.py::test_pass *PASS*", "*test_verbose_reporting.py::TestClass::test_skip *SKIP*", - "*test_verbose_reporting.py::test_gen *xfail*", + "*test_verbose_reporting.py::test_gen *XFAIL*", ] ) assert result.ret == 1