Skip to content

Commit 533aa0d

Browse files
authored
Merge pull request #3 from blueyed/mm-summary_stats-multi-color
pytest-dev#5061
2 parents 25b8750 + 4521608 commit 533aa0d

File tree

4 files changed

+172
-53
lines changed

4 files changed

+172
-53
lines changed

changelog/5061.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use multiple colors with terminal summary statistics.

src/_pytest/terminal.py

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -864,15 +864,41 @@ def _outrep_summary(self, rep):
864864
self._tw.line(content)
865865

866866
def summary_stats(self):
867-
session_duration = time.time() - self._sessionstarttime
868-
(line, color) = build_summary_stats_line(self.stats)
869-
msg = "{} in {}".format(line, format_session_duration(session_duration))
870-
markup = {color: True, "bold": True}
867+
if self.verbosity < -1:
868+
return
871869

872-
if self.verbosity >= 0:
873-
self.write_sep("=", msg, **markup)
874-
if self.verbosity == -1:
875-
self.write_line(msg, **markup)
870+
session_duration = time.time() - self._sessionstarttime
871+
(parts, main_color) = build_summary_stats_line(self.stats)
872+
line_parts = []
873+
874+
display_sep = self.verbosity >= 0
875+
if display_sep:
876+
fullwidth = self._tw.fullwidth
877+
for text, markup in parts:
878+
with_markup = self._tw.markup(text, **markup)
879+
if display_sep:
880+
fullwidth += len(with_markup) - len(text)
881+
line_parts.append(with_markup)
882+
msg = ", ".join(line_parts)
883+
884+
main_markup = {main_color: True}
885+
duration = " in {}".format(format_session_duration(session_duration))
886+
duration_with_markup = self._tw.markup(duration, **main_markup)
887+
if display_sep:
888+
fullwidth += len(duration_with_markup) - len(duration)
889+
msg += duration_with_markup
890+
891+
if display_sep:
892+
markup_for_end_sep = self._tw.markup("", **main_markup)
893+
if markup_for_end_sep.endswith("\x1b[0m"):
894+
markup_for_end_sep = markup_for_end_sep[:-4]
895+
fullwidth += len(markup_for_end_sep)
896+
msg += markup_for_end_sep
897+
898+
if display_sep:
899+
self.write_sep("=", msg, fullwidth=fullwidth, **main_markup)
900+
else:
901+
self.write_line(msg, **main_markup)
876902

877903
def short_test_summary(self):
878904
if not self.reportchars:
@@ -1011,6 +1037,15 @@ def _folded_skips(skipped):
10111037
return values
10121038

10131039

1040+
_color_for_type = {
1041+
"failed": "red",
1042+
"error": "red",
1043+
"warnings": "yellow",
1044+
"passed": "green",
1045+
}
1046+
_color_for_type_default = "yellow"
1047+
1048+
10141049
def build_summary_stats_line(stats):
10151050
known_types = (
10161051
"failed passed skipped deselected xfailed xpassed warnings error".split()
@@ -1021,30 +1056,32 @@ def build_summary_stats_line(stats):
10211056
if found_type: # setup/teardown reports have an empty key, ignore them
10221057
known_types.append(found_type)
10231058
unknown_type_seen = True
1059+
1060+
# main color
1061+
if "failed" in stats or "error" in stats:
1062+
main_color = "red"
1063+
elif "warnings" in stats or unknown_type_seen:
1064+
main_color = "yellow"
1065+
elif "passed" in stats:
1066+
main_color = "green"
1067+
else:
1068+
main_color = "yellow"
1069+
10241070
parts = []
10251071
for key in known_types:
10261072
reports = stats.get(key, None)
10271073
if reports:
10281074
count = sum(
10291075
1 for rep in reports if getattr(rep, "count_towards_summary", True)
10301076
)
1031-
parts.append("%d %s" % (count, key))
1032-
1033-
if parts:
1034-
line = ", ".join(parts)
1035-
else:
1036-
line = "no tests ran"
1077+
color = _color_for_type.get(key, _color_for_type_default)
1078+
markup = {color: True, "bold": color == main_color}
1079+
parts.append(("%d %s" % (count, key), markup))
10371080

1038-
if "failed" in stats or "error" in stats:
1039-
color = "red"
1040-
elif "warnings" in stats or unknown_type_seen:
1041-
color = "yellow"
1042-
elif "passed" in stats:
1043-
color = "green"
1044-
else:
1045-
color = "yellow"
1081+
if not parts:
1082+
parts = [("no tests ran", {_color_for_type_default: True})]
10461083

1047-
return line, color
1084+
return parts, main_color
10481085

10491086

10501087
def _plugin_nameversions(plugininfo):

testing/test_pdb.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def test_one(self):
193193
)
194194
child = testdir.spawn_pytest("-rs --pdb %s" % p1)
195195
child.expect("Skipping also with pdb active")
196-
child.expect("1 skipped in")
196+
child.expect_exact("= \x1b[33m\x1b[1m1 skipped\x1b[0m\x1b[33m in")
197197
child.sendeof()
198198
self.flush(child)
199199

@@ -221,7 +221,7 @@ def test_not_called_due_to_quit():
221221
child.sendeof()
222222
rest = child.read().decode("utf8")
223223
assert "Exit: Quitting debugger" in rest
224-
assert "= 1 failed in" in rest
224+
assert "= \x1b[31m\x1b[1m1 failed\x1b[0m\x1b[31m in" in rest
225225
assert "def test_1" not in rest
226226
assert "get rekt" not in rest
227227
self.flush(child)
@@ -703,7 +703,7 @@ def do_continue(self, arg):
703703
assert "> PDB continue (IO-capturing resumed) >" in rest
704704
else:
705705
assert "> PDB continue >" in rest
706-
assert "1 passed in" in rest
706+
assert "= \x1b[32m\x1b[1m1 passed\x1b[0m\x1b[32m in" in rest
707707

708708
def test_pdb_used_outside_test(self, testdir):
709709
p1 = testdir.makepyfile(
@@ -1019,7 +1019,7 @@ def test_3():
10191019
child.sendline("q")
10201020
child.expect_exact("Exit: Quitting debugger")
10211021
rest = child.read().decode("utf8")
1022-
assert "2 passed in" in rest
1022+
assert "= \x1b[32m\x1b[1m2 passed\x1b[0m\x1b[32m in" in rest
10231023
assert "reading from stdin while output" not in rest
10241024
# Only printed once - not on stderr.
10251025
assert "Exit: Quitting debugger" not in child.before.decode("utf8")
@@ -1130,7 +1130,7 @@ def test_inner({fixture}):
11301130

11311131
TestPDB.flush(child)
11321132
assert child.exitstatus == 0
1133-
assert "= 1 passed in " in rest
1133+
assert "= \x1b[32m\x1b[1m1 passed\x1b[0m\x1b[32m in" in rest
11341134
assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest
11351135

11361136

testing/test_terminal.py

Lines changed: 106 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def test_1():
164164
child.expect(r"collecting 2 items")
165165
child.expect(r"collected 2 items")
166166
rest = child.read().decode("utf8")
167-
assert "2 passed in" in rest
167+
assert "= \x1b[32m\x1b[1m2 passed\x1b[0m\x1b[32m in" in rest
168168

169169
def test_itemreport_subclasses_show_subclassed_file(self, testdir):
170170
testdir.makepyfile(
@@ -1252,42 +1252,123 @@ def test_failure():
12521252
# dict value, not the actual contents, so tuples of anything
12531253
# suffice
12541254
# Important statuses -- the highest priority of these always wins
1255-
("red", "1 failed", {"failed": (1,)}),
1256-
("red", "1 failed, 1 passed", {"failed": (1,), "passed": (1,)}),
1257-
("red", "1 error", {"error": (1,)}),
1258-
("red", "1 passed, 1 error", {"error": (1,), "passed": (1,)}),
1255+
("red", [("1 failed", {"bold": True, "red": True})], {"failed": (1,)}),
1256+
(
1257+
"red",
1258+
[
1259+
("1 failed", {"bold": True, "red": True}),
1260+
("1 passed", {"bold": False, "green": True}),
1261+
],
1262+
{"failed": (1,), "passed": (1,)},
1263+
),
1264+
("red", [("1 error", {"bold": True, "red": True})], {"error": (1,)}),
1265+
(
1266+
"red",
1267+
[
1268+
("1 passed", {"bold": False, "green": True}),
1269+
("1 error", {"bold": True, "red": True}),
1270+
],
1271+
{"error": (1,), "passed": (1,)},
1272+
),
12591273
# (a status that's not known to the code)
1260-
("yellow", "1 weird", {"weird": (1,)}),
1261-
("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}),
1262-
("yellow", "1 warnings", {"warnings": (1,)}),
1263-
("yellow", "1 passed, 1 warnings", {"warnings": (1,), "passed": (1,)}),
1264-
("green", "5 passed", {"passed": (1, 2, 3, 4, 5)}),
1274+
("yellow", [("1 weird", {"bold": True, "yellow": True})], {"weird": (1,)}),
1275+
(
1276+
"yellow",
1277+
[
1278+
("1 passed", {"bold": False, "green": True}),
1279+
("1 weird", {"bold": True, "yellow": True}),
1280+
],
1281+
{"weird": (1,), "passed": (1,)},
1282+
),
1283+
(
1284+
"yellow",
1285+
[("1 warnings", {"bold": True, "yellow": True})],
1286+
{"warnings": (1,)},
1287+
),
1288+
(
1289+
"yellow",
1290+
[
1291+
("1 passed", {"bold": False, "green": True}),
1292+
("1 warnings", {"bold": True, "yellow": True}),
1293+
],
1294+
{"warnings": (1,), "passed": (1,)},
1295+
),
1296+
(
1297+
"green",
1298+
[("5 passed", {"bold": True, "green": True})],
1299+
{"passed": (1, 2, 3, 4, 5)},
1300+
),
12651301
# "Boring" statuses. These have no effect on the color of the summary
12661302
# line. Thus, if *every* test has a boring status, the summary line stays
12671303
# at its default color, i.e. yellow, to warn the user that the test run
12681304
# produced no useful information
1269-
("yellow", "1 skipped", {"skipped": (1,)}),
1270-
("green", "1 passed, 1 skipped", {"skipped": (1,), "passed": (1,)}),
1271-
("yellow", "1 deselected", {"deselected": (1,)}),
1272-
("green", "1 passed, 1 deselected", {"deselected": (1,), "passed": (1,)}),
1273-
("yellow", "1 xfailed", {"xfailed": (1,)}),
1274-
("green", "1 passed, 1 xfailed", {"xfailed": (1,), "passed": (1,)}),
1275-
("yellow", "1 xpassed", {"xpassed": (1,)}),
1276-
("green", "1 passed, 1 xpassed", {"xpassed": (1,), "passed": (1,)}),
1305+
("yellow", [("1 skipped", {"bold": True, "yellow": True})], {"skipped": (1,)}),
1306+
(
1307+
"green",
1308+
[
1309+
("1 passed", {"bold": True, "green": True}),
1310+
("1 skipped", {"bold": False, "yellow": True}),
1311+
],
1312+
{"skipped": (1,), "passed": (1,)},
1313+
),
1314+
(
1315+
"yellow",
1316+
[("1 deselected", {"bold": True, "yellow": True})],
1317+
{"deselected": (1,)},
1318+
),
1319+
(
1320+
"green",
1321+
[
1322+
("1 passed", {"bold": True, "green": True}),
1323+
("1 deselected", {"bold": False, "yellow": True}),
1324+
],
1325+
{"deselected": (1,), "passed": (1,)},
1326+
),
1327+
("yellow", [("1 xfailed", {"bold": True, "yellow": True})], {"xfailed": (1,)}),
1328+
(
1329+
"green",
1330+
[
1331+
("1 passed", {"bold": True, "green": True}),
1332+
("1 xfailed", {"bold": False, "yellow": True}),
1333+
],
1334+
{"xfailed": (1,), "passed": (1,)},
1335+
),
1336+
("yellow", [("1 xpassed", {"bold": True, "yellow": True})], {"xpassed": (1,)}),
1337+
(
1338+
"green",
1339+
[
1340+
("1 passed", {"bold": True, "green": True}),
1341+
("1 xpassed", {"bold": False, "yellow": True}),
1342+
],
1343+
{"xpassed": (1,), "passed": (1,)},
1344+
),
12771345
# Likewise if no tests were found at all
1278-
("yellow", "no tests ran", {}),
1346+
("yellow", [("no tests ran", {"yellow": True})], {}),
12791347
# Test the empty-key special case
1280-
("yellow", "no tests ran", {"": (1,)}),
1281-
("green", "1 passed", {"": (1,), "passed": (1,)}),
1348+
("yellow", [("no tests ran", {"yellow": True})], {"": (1,)}),
1349+
(
1350+
"green",
1351+
[("1 passed", {"bold": True, "green": True})],
1352+
{"": (1,), "passed": (1,)},
1353+
),
12821354
# A couple more complex combinations
12831355
(
12841356
"red",
1285-
"1 failed, 2 passed, 3 xfailed",
1357+
[
1358+
("1 failed", {"bold": True, "red": True}),
1359+
("2 passed", {"bold": False, "green": True}),
1360+
("3 xfailed", {"bold": False, "yellow": True}),
1361+
],
12861362
{"passed": (1, 2), "failed": (1,), "xfailed": (1, 2, 3)},
12871363
),
12881364
(
12891365
"green",
1290-
"1 passed, 2 skipped, 3 deselected, 2 xfailed",
1366+
[
1367+
("1 passed", {"bold": True, "green": True}),
1368+
("2 skipped", {"bold": False, "yellow": True}),
1369+
("3 deselected", {"bold": False, "yellow": True}),
1370+
("2 xfailed", {"bold": False, "yellow": True}),
1371+
],
12911372
{
12921373
"passed": (1,),
12931374
"skipped": (1, 2),
@@ -1313,11 +1394,11 @@ class DummyReport(BaseReport):
13131394
r1 = DummyReport()
13141395
r2 = DummyReport()
13151396
res = build_summary_stats_line({"failed": (r1, r2)})
1316-
assert res == ("2 failed", "red")
1397+
assert res == ([("2 failed", {"bold": True, "red": True})], "red")
13171398

13181399
r1.count_towards_summary = False
13191400
res = build_summary_stats_line({"failed": (r1, r2)})
1320-
assert res == ("1 failed", "red")
1401+
assert res == ([("1 failed", {"bold": True, "red": True})], "red")
13211402

13221403

13231404
class TestClassicOutputStyle:

0 commit comments

Comments
 (0)