From 345df99db7365b34867e8429accb91287867341c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 10 Aug 2019 09:14:57 -0300 Subject: [PATCH] Show session duration in human-readable format Fix #5707 --- changelog/5707.improvement.rst | 4 ++++ src/_pytest/pytester.py | 16 ++++++++-------- src/_pytest/terminal.py | 12 +++++++++++- testing/logging/test_reporting.py | 2 +- testing/test_pytester.py | 2 +- testing/test_terminal.py | 19 ++++++++++++++++++- 6 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 changelog/5707.improvement.rst diff --git a/changelog/5707.improvement.rst b/changelog/5707.improvement.rst new file mode 100644 index 000000000..59176e1bb --- /dev/null +++ b/changelog/5707.improvement.rst @@ -0,0 +1,4 @@ +Time taken to run the test suite now includes a human-readable representation when it takes over +60 seconds, for example:: + + ===== 2 failed in 102.70s (0:01:42) ===== diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 2068761fa..80219b1a6 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -340,7 +340,10 @@ def _config_for_test(): config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles. -rex_outcome = re.compile(r"(\d+) ([\w-]+)") +# regex to match the session duration string in the summary: "74.34s" +rex_session_duration = re.compile(r"\d+\.\d\ds") +# regex to match all the counts and phrases in the summary line: "34 passed, 111 skipped" +rex_outcome = re.compile(r"(\d+) (\w+)") class RunResult: @@ -379,14 +382,11 @@ class RunResult: """ for line in reversed(self.outlines): - if "seconds" in line: + if rex_session_duration.search(line): outcomes = rex_outcome.findall(line) - if outcomes: - d = {} - for num, cat in outcomes: - d[cat] = int(num) - return d - raise ValueError("Pytest terminal report not found") + return {noun: int(count) for (count, noun) in outcomes} + + raise ValueError("Pytest terminal summary report not found") def assert_outcomes( self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=0 diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 05d5427c3..0d9794159 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -4,6 +4,7 @@ This is a good source for looking at the various reporting hooks. """ import argparse import collections +import datetime import platform import sys import time @@ -861,7 +862,7 @@ class TerminalReporter: def summary_stats(self): session_duration = time.time() - self._sessionstarttime (line, color) = build_summary_stats_line(self.stats) - msg = "{} in {:.2f} seconds".format(line, session_duration) + msg = "{} in {}".format(line, format_session_duration(session_duration)) markup = {color: True, "bold": True} if self.verbosity >= 0: @@ -1055,3 +1056,12 @@ def _plugin_nameversions(plugininfo): if name not in values: values.append(name) return values + + +def format_session_duration(seconds): + """Format the given seconds in a human readable manner to show in the final summary""" + if seconds < 60: + return "{:.2f}s".format(seconds) + else: + dt = datetime.timedelta(seconds=int(seconds)) + return "{:.2f}s ({})".format(seconds, dt) diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index bb1aebc09..1ae0bd783 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -946,7 +946,7 @@ def test_collection_collect_only_live_logging(testdir, verbose): expected_lines.extend( [ "*test_collection_collect_only_live_logging.py::test_simple*", - "no tests ran in * seconds", + "no tests ran in 0.[0-9][0-9]s", ] ) elif verbose == "-qq": diff --git a/testing/test_pytester.py b/testing/test_pytester.py index cf92741af..d330ff253 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -278,7 +278,7 @@ def test_assert_outcomes_after_pytest_error(testdir): testdir.makepyfile("def test_foo(): assert True") result = testdir.runpytest("--unexpected-argument") - with pytest.raises(ValueError, match="Pytest terminal report not found"): + with pytest.raises(ValueError, match="Pytest terminal summary report not found"): result.assert_outcomes(passed=0) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 381a5b2e1..88f96f894 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -617,7 +617,7 @@ class TestTerminalFunctional: pluggy.__version__, ), "*test_header_trailer_info.py .*", - "=* 1 passed*in *.[0-9][0-9] seconds *=", + "=* 1 passed*in *.[0-9][0-9]s *=", ] ) if request.config.pluginmanager.list_plugin_distinfo(): @@ -1678,3 +1678,20 @@ def test_line_with_reprcrash(monkeypatch): check("😄😄😄😄😄\n2nd line", 41, "FAILED nodeid::😄::withunicode - 😄😄...") check("😄😄😄😄😄\n2nd line", 42, "FAILED nodeid::😄::withunicode - 😄😄😄...") check("😄😄😄😄😄\n2nd line", 80, "FAILED nodeid::😄::withunicode - 😄😄😄😄😄") + + +@pytest.mark.parametrize( + "seconds, expected", + [ + (10.0, "10.00s"), + (10.34, "10.34s"), + (59.99, "59.99s"), + (60.55, "60.55s (0:01:00)"), + (123.55, "123.55s (0:02:03)"), + (60 * 60 + 0.5, "3600.50s (1:00:00)"), + ], +) +def test_format_session_duration(seconds, expected): + from _pytest.terminal import format_session_duration + + assert format_session_duration(seconds) == expected