From afe28d0f32c13539c882110fece0af543786e078 Mon Sep 17 00:00:00 2001 From: symonk Date: Tue, 17 Nov 2020 15:12:02 +0000 Subject: [PATCH] refactor runner terminal summary and add more durations coverage --- src/_pytest/runner.py | 57 +++++++++++++++++++--------------- testing/acceptance_test.py | 63 +++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 25 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 794690ddb..adda20077 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -2,6 +2,7 @@ import bdb import os import sys +from operator import attrgetter from typing import Callable from typing import cast from typing import Dict @@ -64,35 +65,43 @@ def pytest_addoption(parser: Parser) -> None: def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: - durations = terminalreporter.config.option.durations - durations_min = terminalreporter.config.option.durations_min - verbose = terminalreporter.config.getvalue("verbose") + durations = terminalreporter.config.getoption("durations") if durations is None: return - tr = terminalreporter - dlist = [] - for replist in tr.stats.values(): - for rep in replist: - if hasattr(rep, "duration"): - dlist.append(rep) - if not dlist: - return - dlist.sort(key=lambda x: x.duration, reverse=True) # type: ignore[no-any-return] - if not durations: - tr.write_sep("=", "slowest durations") - else: - tr.write_sep("=", "slowest %s durations" % durations) - dlist = dlist[:durations] - for i, rep in enumerate(dlist): - if verbose < 2 and rep.duration < durations_min: - tr.write_line("") - tr.write_line( - "(%s durations < %gs hidden. Use -vv to show these durations.)" - % (len(dlist) - i, durations_min) + durations_min = terminalreporter.config.getoption("durations_min") + verbose = terminalreporter.config.getvalue("verbose") + reporter = terminalreporter + durations_list = [] + for report_list in reporter.stats.values(): + for report in report_list: + if hasattr(report, "duration"): + durations_list.append(report) + if not durations_list: + return + + durations_list.sort(key=attrgetter("duration"), reverse=True) + + if not durations: + reporter.write_sep("=", "slowest durations") + else: + reporter.write_sep("=", "slowest %s durations" % durations) + durations_list = durations_list[:durations] + + for index, test_report in enumerate(durations_list): + if verbose < 2 and test_report.duration < durations_min: + total = len(durations_list) - index + pronoun, dur = ( + ("this", "duration") if total == 1 else ("these", "durations") + ) + reporter.write_line("") + reporter.write_line( + f"({total} {dur} slower than {durations_min:02.3f}s hidden. Use -vv to show {pronoun} {dur}.)" ) break - tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") + reporter.write_line( + f"{test_report.duration:02.2f}s {test_report.when:<8} {test_report.nodeid}" + ) def pytest_sessionstart(session: "Session") -> None: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index c937ce9dc..bec4d033b 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -867,7 +867,9 @@ class TestDurations: ) result.stdout.fnmatch_lines( - ["(8 durations < 0.005s hidden. Use -vv to show these durations.)"] + [ + "(8 durations slower than 0.005s hidden. Use -vv to show these durations.)" + ] ) def test_calls_show_2(self, testdir, mock_timing): @@ -1288,3 +1290,62 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None: ret = popen.wait() assert popen.stderr.read() == b"" assert ret == 1 + + +def test_multiple_durations_pluralized_tr_summary(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + def test_delays(): + assert True + """ + ) + result = pytester.runpytest("--durations=5", "--durations-min=10") + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + ["(3 durations slower than 10.000s hidden. Use -vv to show these durations.)"] + ) + + +def test_single_duration_pluralized_tr_summary(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + import time + + def _sleep(delay): + time.sleep(delay) + + def test_one(): + _sleep(0.05) + def test_two(): + _sleep(0.25) + """ + ) + result = pytester.runpytest("--durations=2", "--durations-min=0.1") + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + ["(1 duration slower than 0.100s hidden. Use -vv to show this duration.)"] + ) + + +def test_durations_zero_lists_all(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + import time + @pytest.mark.parametrize('_', range(6)) + def test_this(_): + time.sleep(0.1) + """ + ) + result = pytester.runpytest("--durations=0") + assert result.ret == ExitCode.OK + # Check 6x setup & 6x teardown are hidden, but 6x call are shown + insertable_line = "*test_durations_zero_lists_all.py::test_this*{}*" + lines = [insertable_line.format(x) for x in range(6)] + lines.append( + "(12 durations slower than 0.005s hidden. Use -vv to show these durations.)" + ) + result.stdout.fnmatch_lines_random(lines)