From c70ecd49caf44322c0ebd3a5bd56bf555d006b8c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 7 Apr 2019 18:21:37 +0200 Subject: [PATCH 1/5] cleanup: move terminal summary code to terminal plugin Fixes https://github.com/pytest-dev/pytest/issues/5067. --- src/_pytest/skipping.py | 125 --------------------------------------- src/_pytest/terminal.py | 122 ++++++++++++++++++++++++++++++++++++++ testing/test_skipping.py | 35 ----------- testing/test_terminal.py | 35 +++++++++++ 4 files changed, 157 insertions(+), 160 deletions(-) diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 22acafbddfb..3f488fad532 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -183,128 +183,3 @@ def pytest_report_teststatus(report): return "xfailed", "x", "XFAIL" elif report.passed: return "xpassed", "X", "XPASS" - - -# called by the terminalreporter instance/plugin - - -def pytest_terminal_summary(terminalreporter): - tr = terminalreporter - if not tr.reportchars: - return - - lines = [] - for char in tr.reportchars: - action = REPORTCHAR_ACTIONS.get(char, lambda tr, lines: None) - action(terminalreporter, lines) - - if lines: - tr._tw.sep("=", "short test summary info") - for line in lines: - tr._tw.line(line) - - -def show_simple(terminalreporter, lines, stat): - failed = terminalreporter.stats.get(stat) - if failed: - config = terminalreporter.config - for rep in failed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - lines.append("%s %s" % (verbose_word, pos)) - - -def show_xfailed(terminalreporter, lines): - xfailed = terminalreporter.stats.get("xfailed") - if xfailed: - config = terminalreporter.config - for rep in xfailed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - lines.append("%s %s" % (verbose_word, pos)) - reason = rep.wasxfail - if reason: - lines.append(" " + str(reason)) - - -def show_xpassed(terminalreporter, lines): - xpassed = terminalreporter.stats.get("xpassed") - if xpassed: - config = terminalreporter.config - for rep in xpassed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - reason = rep.wasxfail - lines.append("%s %s %s" % (verbose_word, pos, reason)) - - -def folded_skips(skipped): - d = {} - for event in skipped: - key = event.longrepr - assert len(key) == 3, (event, key) - keywords = getattr(event, "keywords", {}) - # 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 - 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 = [] - for key, events in d.items(): - values.append((len(events),) + key) - return values - - -def show_skipped(terminalreporter, lines): - tr = terminalreporter - skipped = tr.stats.get("skipped", []) - if skipped: - fskips = folded_skips(skipped) - if fskips: - verbose_word = _get_report_str(terminalreporter.config, report=skipped[0]) - for num, fspath, lineno, reason in fskips: - if reason.startswith("Skipped: "): - reason = reason[9:] - if lineno is not None: - lines.append( - "%s [%d] %s:%d: %s" - % (verbose_word, num, fspath, lineno + 1, reason) - ) - else: - lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) - - -def shower(stat): - def show_(terminalreporter, lines): - return show_simple(terminalreporter, lines, stat) - - return show_ - - -def _get_report_str(config, report): - _category, _short, verbose = config.hook.pytest_report_teststatus( - report=report, config=config - ) - return verbose - - -def _get_pos(config, rep): - nodeid = config.cwd_relative_nodeid(rep.nodeid) - return nodeid - - -REPORTCHAR_ACTIONS = { - "x": show_xfailed, - "X": show_xpassed, - "f": shower("failed"), - "F": shower("failed"), - "s": show_skipped, - "S": show_skipped, - "p": shower("passed"), - "E": shower("error"), -} diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 31c0da46d71..8d6abb17afe 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -874,6 +874,128 @@ def summary_stats(self): self.write_line(msg, **markup) +def pytest_terminal_summary(terminalreporter): + tr = terminalreporter + if not tr.reportchars: + return + + lines = [] + for char in tr.reportchars: + action = REPORTCHAR_ACTIONS.get(char, lambda tr, lines: None) + action(terminalreporter, lines) + + if lines: + tr._tw.sep("=", "short test summary info") + for line in lines: + tr._tw.line(line) + + +def show_simple(terminalreporter, lines, stat): + failed = terminalreporter.stats.get(stat) + if failed: + config = terminalreporter.config + for rep in failed: + verbose_word = _get_report_str(config, rep) + pos = _get_pos(config, rep) + lines.append("%s %s" % (verbose_word, pos)) + + +def show_xfailed(terminalreporter, lines): + xfailed = terminalreporter.stats.get("xfailed") + if xfailed: + config = terminalreporter.config + for rep in xfailed: + verbose_word = _get_report_str(config, rep) + pos = _get_pos(config, rep) + lines.append("%s %s" % (verbose_word, pos)) + reason = rep.wasxfail + if reason: + lines.append(" " + str(reason)) + + +def show_xpassed(terminalreporter, lines): + xpassed = terminalreporter.stats.get("xpassed") + if xpassed: + config = terminalreporter.config + for rep in xpassed: + verbose_word = _get_report_str(config, rep) + pos = _get_pos(config, rep) + reason = rep.wasxfail + lines.append("%s %s %s" % (verbose_word, pos, reason)) + + +def folded_skips(skipped): + d = {} + for event in skipped: + key = event.longrepr + assert len(key) == 3, (event, key) + keywords = getattr(event, "keywords", {}) + # 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 + 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 = [] + for key, events in d.items(): + values.append((len(events),) + key) + return values + + +def show_skipped(terminalreporter, lines): + tr = terminalreporter + skipped = tr.stats.get("skipped", []) + if skipped: + fskips = folded_skips(skipped) + if fskips: + verbose_word = _get_report_str(terminalreporter.config, report=skipped[0]) + for num, fspath, lineno, reason in fskips: + if reason.startswith("Skipped: "): + reason = reason[9:] + if lineno is not None: + lines.append( + "%s [%d] %s:%d: %s" + % (verbose_word, num, fspath, lineno + 1, reason) + ) + else: + lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) + + +def shower(stat): + def show_(terminalreporter, lines): + return show_simple(terminalreporter, lines, stat) + + return show_ + + +def _get_report_str(config, report): + _category, _short, verbose = config.hook.pytest_report_teststatus( + report=report, config=config + ) + return verbose + + +def _get_pos(config, rep): + nodeid = config.cwd_relative_nodeid(rep.nodeid) + return nodeid + + +REPORTCHAR_ACTIONS = { + "x": show_xfailed, + "X": show_xpassed, + "f": shower("failed"), + "F": shower("failed"), + "s": show_skipped, + "S": show_skipped, + "p": shower("passed"), + "E": shower("error"), +} + + def build_summary_stats_line(stats): known_types = ( "failed passed skipped deselected xfailed xpassed warnings error".split() diff --git a/testing/test_skipping.py b/testing/test_skipping.py index e5206a44ebf..cef0fe6ee18 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -6,7 +6,6 @@ import pytest from _pytest.runner import runtestprotocol -from _pytest.skipping import folded_skips from _pytest.skipping import MarkEvaluator from _pytest.skipping import pytest_runtest_setup @@ -749,40 +748,6 @@ def test_though(self): result.stdout.fnmatch_lines(["*2 skipped*"]) -def test_skip_reasons_folding(): - path = "xyz" - lineno = 3 - message = "justso" - longrepr = (path, lineno, message) - - class X(object): - pass - - ev1 = X() - ev1.when = "execute" - ev1.skipped = True - ev1.longrepr = longrepr - - ev2 = X() - ev2.when = "execute" - ev2.longrepr = longrepr - ev2.skipped = True - - # ev3 might be a collection report - ev3 = X() - ev3.when = "collect" - ev3.longrepr = longrepr - ev3.skipped = True - - values = folded_skips([ev1, ev2, ev3]) - assert len(values) == 1 - num, fspath, lineno, reason = values[0] - assert num == 3 - assert fspath == path - assert lineno == lineno - assert reason == message - - def test_skipped_reasons_functional(testdir): testdir.makepyfile( test_one=""" diff --git a/testing/test_terminal.py b/testing/test_terminal.py index d0fdce23eb6..888104bf393 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -18,6 +18,7 @@ from _pytest.reports import BaseReport from _pytest.terminal import _plugin_nameversions from _pytest.terminal import build_summary_stats_line +from _pytest.terminal import folded_skips from _pytest.terminal import getreportopt from _pytest.terminal import TerminalReporter @@ -1524,3 +1525,37 @@ def test_xdist_normal(self, many_files, testdir, monkeypatch): monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) output = testdir.runpytest("-n2") output.stdout.re_match_lines([r"[\.E]{40} \s+ \[100%\]"]) + + +def test_skip_reasons_folding(): + path = "xyz" + lineno = 3 + message = "justso" + longrepr = (path, lineno, message) + + class X(object): + pass + + ev1 = X() + ev1.when = "execute" + ev1.skipped = True + ev1.longrepr = longrepr + + ev2 = X() + ev2.when = "execute" + ev2.longrepr = longrepr + ev2.skipped = True + + # ev3 might be a collection report + ev3 = X() + ev3.when = "collect" + ev3.longrepr = longrepr + ev3.skipped = True + + values = folded_skips([ev1, ev2, ev3]) + assert len(values) == 1 + num, fspath, lineno, reason = values[0] + assert num == 3 + assert fspath == path + assert lineno == lineno + assert reason == message From 06029d11d337d8777ac3118dd0e96eda6c98125c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 7 Apr 2019 19:36:29 +0200 Subject: [PATCH 2/5] Refactor into TerminalReporter.short_test_summary --- src/_pytest/terminal.py | 195 +++++++++++++++++++-------------------- testing/test_terminal.py | 4 +- 2 files changed, 96 insertions(+), 103 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 8d6abb17afe..44b874af8bd 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -678,6 +678,7 @@ def pytest_terminal_summary(self): self.summary_failures() self.summary_warnings() yield + self.short_test_summary() self.summary_passes() # Display any extra warnings from teardown here (if any). self.summary_warnings() @@ -873,58 +874,100 @@ def summary_stats(self): if self.verbosity == -1: self.write_line(msg, **markup) + def short_test_summary(self): + if not self.reportchars: + return -def pytest_terminal_summary(terminalreporter): - tr = terminalreporter - if not tr.reportchars: - return - - lines = [] - for char in tr.reportchars: - action = REPORTCHAR_ACTIONS.get(char, lambda tr, lines: None) - action(terminalreporter, lines) - - if lines: - tr._tw.sep("=", "short test summary info") - for line in lines: - tr._tw.line(line) - - -def show_simple(terminalreporter, lines, stat): - failed = terminalreporter.stats.get(stat) - if failed: - config = terminalreporter.config - for rep in failed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - lines.append("%s %s" % (verbose_word, pos)) - - -def show_xfailed(terminalreporter, lines): - xfailed = terminalreporter.stats.get("xfailed") - if xfailed: - config = terminalreporter.config - for rep in xfailed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - lines.append("%s %s" % (verbose_word, pos)) - reason = rep.wasxfail - if reason: - lines.append(" " + str(reason)) - - -def show_xpassed(terminalreporter, lines): - xpassed = terminalreporter.stats.get("xpassed") - if xpassed: - config = terminalreporter.config - for rep in xpassed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - reason = rep.wasxfail - lines.append("%s %s %s" % (verbose_word, pos, reason)) - - -def folded_skips(skipped): + def show_simple(lines, stat): + failed = self.stats.get(stat) + if failed: + config = self.config + for rep in failed: + verbose_word = _get_report_str(config, rep) + pos = _get_pos(config, rep) + lines.append("%s %s" % (verbose_word, pos)) + + def show_xfailed(lines): + xfailed = self.stats.get("xfailed") + if xfailed: + config = self.config + for rep in xfailed: + verbose_word = _get_report_str(config, rep) + pos = _get_pos(config, rep) + lines.append("%s %s" % (verbose_word, pos)) + reason = rep.wasxfail + if reason: + lines.append(" " + str(reason)) + + def show_xpassed(lines): + xpassed = self.stats.get("xpassed") + if xpassed: + config = self.config + for rep in xpassed: + verbose_word = _get_report_str(config, rep) + pos = _get_pos(config, rep) + reason = rep.wasxfail + lines.append("%s %s %s" % (verbose_word, pos, reason)) + + def show_skipped(lines): + skipped = self.stats.get("skipped", []) + if skipped: + fskips = _folded_skips(skipped) + if fskips: + verbose_word = _get_report_str(self.config, report=skipped[0]) + for num, fspath, lineno, reason in fskips: + if reason.startswith("Skipped: "): + reason = reason[9:] + if lineno is not None: + lines.append( + "%s [%d] %s:%d: %s" + % (verbose_word, num, fspath, lineno + 1, reason) + ) + else: + lines.append( + "%s [%d] %s: %s" % (verbose_word, num, fspath, reason) + ) + + def shower(stat): + def show_(lines): + return show_simple(lines, stat) + + return show_ + + def _get_report_str(config, report): + _category, _short, verbose = config.hook.pytest_report_teststatus( + report=report, config=config + ) + return verbose + + def _get_pos(config, rep): + nodeid = config.cwd_relative_nodeid(rep.nodeid) + return nodeid + + REPORTCHAR_ACTIONS = { + "x": show_xfailed, + "X": show_xpassed, + "f": shower("failed"), + "F": shower("failed"), + "s": show_skipped, + "S": show_skipped, + "p": shower("passed"), + "E": shower("error"), + } + + lines = [] + for char in self.reportchars: + action = REPORTCHAR_ACTIONS.get(char) + if action: # skipping e.g. "P" (passed with output) here. + action(lines) + + if lines: + self.write_sep("=", "short test summary info") + for line in lines: + self.write_line(line) + + +def _folded_skips(skipped): d = {} for event in skipped: key = event.longrepr @@ -946,56 +989,6 @@ def folded_skips(skipped): return values -def show_skipped(terminalreporter, lines): - tr = terminalreporter - skipped = tr.stats.get("skipped", []) - if skipped: - fskips = folded_skips(skipped) - if fskips: - verbose_word = _get_report_str(terminalreporter.config, report=skipped[0]) - for num, fspath, lineno, reason in fskips: - if reason.startswith("Skipped: "): - reason = reason[9:] - if lineno is not None: - lines.append( - "%s [%d] %s:%d: %s" - % (verbose_word, num, fspath, lineno + 1, reason) - ) - else: - lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) - - -def shower(stat): - def show_(terminalreporter, lines): - return show_simple(terminalreporter, lines, stat) - - return show_ - - -def _get_report_str(config, report): - _category, _short, verbose = config.hook.pytest_report_teststatus( - report=report, config=config - ) - return verbose - - -def _get_pos(config, rep): - nodeid = config.cwd_relative_nodeid(rep.nodeid) - return nodeid - - -REPORTCHAR_ACTIONS = { - "x": show_xfailed, - "X": show_xpassed, - "f": shower("failed"), - "F": shower("failed"), - "s": show_skipped, - "S": show_skipped, - "p": shower("passed"), - "E": shower("error"), -} - - def build_summary_stats_line(stats): known_types = ( "failed passed skipped deselected xfailed xpassed warnings error".split() diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 888104bf393..12878d2b3fc 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -16,9 +16,9 @@ import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.reports import BaseReport +from _pytest.terminal import _folded_skips from _pytest.terminal import _plugin_nameversions from _pytest.terminal import build_summary_stats_line -from _pytest.terminal import folded_skips from _pytest.terminal import getreportopt from _pytest.terminal import TerminalReporter @@ -1552,7 +1552,7 @@ class X(object): ev3.longrepr = longrepr ev3.skipped = True - values = folded_skips([ev1, ev2, ev3]) + values = _folded_skips([ev1, ev2, ev3]) assert len(values) == 1 num, fspath, lineno, reason = values[0] assert num == 3 From d8d835c1f5a516decc7afb37048a2909041d86f0 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 7 Apr 2019 19:49:10 +0200 Subject: [PATCH 3/5] minor: use functools.partial --- src/_pytest/terminal.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 44b874af8bd..8b645f4c9cd 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -11,6 +11,7 @@ import platform import sys import time +from functools import partial import attr import pluggy @@ -878,7 +879,7 @@ def short_test_summary(self): if not self.reportchars: return - def show_simple(lines, stat): + def show_simple(stat, lines): failed = self.stats.get(stat) if failed: config = self.config @@ -928,12 +929,6 @@ def show_skipped(lines): "%s [%d] %s: %s" % (verbose_word, num, fspath, reason) ) - def shower(stat): - def show_(lines): - return show_simple(lines, stat) - - return show_ - def _get_report_str(config, report): _category, _short, verbose = config.hook.pytest_report_teststatus( report=report, config=config @@ -947,12 +942,12 @@ def _get_pos(config, rep): REPORTCHAR_ACTIONS = { "x": show_xfailed, "X": show_xpassed, - "f": shower("failed"), - "F": shower("failed"), + "f": partial(show_simple, "failed"), + "F": partial(show_simple, "failed"), "s": show_skipped, "S": show_skipped, - "p": shower("passed"), - "E": shower("error"), + "p": partial(show_simple, "passed"), + "E": partial(show_simple, "error"), } lines = [] From 2662c400ba9463aa9b9c4aaf8778736b1803b2e0 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 7 Apr 2019 20:04:31 +0200 Subject: [PATCH 4/5] dedent --- src/_pytest/terminal.py | 74 ++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 8b645f4c9cd..18011b66b36 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -880,54 +880,46 @@ def short_test_summary(self): return def show_simple(stat, lines): - failed = self.stats.get(stat) - if failed: - config = self.config - for rep in failed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - lines.append("%s %s" % (verbose_word, pos)) + failed = self.stats.get(stat, []) + for rep in failed: + verbose_word = _get_report_str(self.config, rep) + pos = _get_pos(self.config, rep) + lines.append("%s %s" % (verbose_word, pos)) def show_xfailed(lines): - xfailed = self.stats.get("xfailed") - if xfailed: - config = self.config - for rep in xfailed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - lines.append("%s %s" % (verbose_word, pos)) - reason = rep.wasxfail - if reason: - lines.append(" " + str(reason)) + xfailed = self.stats.get("xfailed", []) + for rep in xfailed: + verbose_word = _get_report_str(self.config, rep) + pos = _get_pos(self.config, rep) + lines.append("%s %s" % (verbose_word, pos)) + reason = rep.wasxfail + if reason: + lines.append(" " + str(reason)) def show_xpassed(lines): - xpassed = self.stats.get("xpassed") - if xpassed: - config = self.config - for rep in xpassed: - verbose_word = _get_report_str(config, rep) - pos = _get_pos(config, rep) - reason = rep.wasxfail - lines.append("%s %s %s" % (verbose_word, pos, reason)) + xpassed = self.stats.get("xpassed", []) + for rep in xpassed: + verbose_word = _get_report_str(self.config, rep) + pos = _get_pos(self.config, rep) + reason = rep.wasxfail + lines.append("%s %s %s" % (verbose_word, pos, reason)) def show_skipped(lines): skipped = self.stats.get("skipped", []) - if skipped: - fskips = _folded_skips(skipped) - if fskips: - verbose_word = _get_report_str(self.config, report=skipped[0]) - for num, fspath, lineno, reason in fskips: - if reason.startswith("Skipped: "): - reason = reason[9:] - if lineno is not None: - lines.append( - "%s [%d] %s:%d: %s" - % (verbose_word, num, fspath, lineno + 1, reason) - ) - else: - lines.append( - "%s [%d] %s: %s" % (verbose_word, num, fspath, reason) - ) + fskips = _folded_skips(skipped) if skipped else [] + if not fskips: + return + verbose_word = _get_report_str(self.config, report=skipped[0]) + for num, fspath, lineno, reason in fskips: + if reason.startswith("Skipped: "): + reason = reason[9:] + if lineno is not None: + lines.append( + "%s [%d] %s:%d: %s" + % (verbose_word, num, fspath, lineno + 1, reason) + ) + else: + lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) def _get_report_str(config, report): _category, _short, verbose = config.hook.pytest_report_teststatus( From dde27a2305d2e2cd80fd27ae0885bfa3f9206822 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 10 Apr 2019 21:39:11 +0200 Subject: [PATCH 5/5] changelog [ci skip] --- changelog/5069.trivial.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/5069.trivial.rst diff --git a/changelog/5069.trivial.rst b/changelog/5069.trivial.rst new file mode 100644 index 00000000000..dd6abd8b8ce --- /dev/null +++ b/changelog/5069.trivial.rst @@ -0,0 +1 @@ +The code for the short test summary in the terminal was moved to the terminal plugin.