Merge pull request #5069 from blueyed/cleanup-summary-to-terminal
cleanup: move terminal summary code to terminal plugin
This commit is contained in:
commit
f5d2b199e2
|
@ -0,0 +1 @@
|
||||||
|
The code for the short test summary in the terminal was moved to the terminal plugin.
|
|
@ -183,128 +183,3 @@ def pytest_report_teststatus(report):
|
||||||
return "xfailed", "x", "XFAIL"
|
return "xfailed", "x", "XFAIL"
|
||||||
elif report.passed:
|
elif report.passed:
|
||||||
return "xpassed", "X", "XPASS"
|
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"),
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import collections
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import pluggy
|
import pluggy
|
||||||
|
@ -681,6 +682,7 @@ class TerminalReporter(object):
|
||||||
self.summary_failures()
|
self.summary_failures()
|
||||||
self.summary_warnings()
|
self.summary_warnings()
|
||||||
yield
|
yield
|
||||||
|
self.short_test_summary()
|
||||||
self.summary_passes()
|
self.summary_passes()
|
||||||
# Display any extra warnings from teardown here (if any).
|
# Display any extra warnings from teardown here (if any).
|
||||||
self.summary_warnings()
|
self.summary_warnings()
|
||||||
|
@ -876,6 +878,106 @@ class TerminalReporter(object):
|
||||||
if self.verbosity == -1:
|
if self.verbosity == -1:
|
||||||
self.write_line(msg, **markup)
|
self.write_line(msg, **markup)
|
||||||
|
|
||||||
|
def short_test_summary(self):
|
||||||
|
if not self.reportchars:
|
||||||
|
return
|
||||||
|
|
||||||
|
def show_simple(stat, lines):
|
||||||
|
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", [])
|
||||||
|
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", [])
|
||||||
|
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", [])
|
||||||
|
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(
|
||||||
|
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": partial(show_simple, "failed"),
|
||||||
|
"F": partial(show_simple, "failed"),
|
||||||
|
"s": show_skipped,
|
||||||
|
"S": show_skipped,
|
||||||
|
"p": partial(show_simple, "passed"),
|
||||||
|
"E": partial(show_simple, "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
|
||||||
|
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 build_summary_stats_line(stats):
|
def build_summary_stats_line(stats):
|
||||||
known_types = (
|
known_types = (
|
||||||
|
|
|
@ -6,7 +6,6 @@ import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.runner import runtestprotocol
|
from _pytest.runner import runtestprotocol
|
||||||
from _pytest.skipping import folded_skips
|
|
||||||
from _pytest.skipping import MarkEvaluator
|
from _pytest.skipping import MarkEvaluator
|
||||||
from _pytest.skipping import pytest_runtest_setup
|
from _pytest.skipping import pytest_runtest_setup
|
||||||
|
|
||||||
|
@ -749,40 +748,6 @@ def test_skipif_class(testdir):
|
||||||
result.stdout.fnmatch_lines(["*2 skipped*"])
|
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):
|
def test_skipped_reasons_functional(testdir):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
test_one="""
|
test_one="""
|
||||||
|
|
|
@ -16,6 +16,7 @@ import py
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||||
from _pytest.reports import BaseReport
|
from _pytest.reports import BaseReport
|
||||||
|
from _pytest.terminal import _folded_skips
|
||||||
from _pytest.terminal import _plugin_nameversions
|
from _pytest.terminal import _plugin_nameversions
|
||||||
from _pytest.terminal import build_summary_stats_line
|
from _pytest.terminal import build_summary_stats_line
|
||||||
from _pytest.terminal import getreportopt
|
from _pytest.terminal import getreportopt
|
||||||
|
@ -1524,3 +1525,37 @@ class TestProgressWithTeardown(object):
|
||||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||||
output = testdir.runpytest("-n2")
|
output = testdir.runpytest("-n2")
|
||||||
output.stdout.re_match_lines([r"[\.E]{40} \s+ \[100%\]"])
|
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
|
||||||
|
|
Loading…
Reference in New Issue