Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/5707.improvement.rst
Original file line number Diff line number Diff line change
@@ -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) =====
16 changes: 8 additions & 8 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -379,14 +382,11 @@ def parseoutcomes(self):

"""
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
Expand Down
12 changes: 11 additions & 1 deletion src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
import argparse
import collections
import datetime
import platform
import sys
import time
Expand Down Expand Up @@ -861,7 +862,7 @@ def _outrep_summary(self, rep):
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:
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not really need the "else".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware, I usually like to be explicit thought (it is a matter of style after all).

2 changes: 1 addition & 1 deletion testing/logging/test_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ def test_simple():
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":
Expand Down
2 changes: 1 addition & 1 deletion testing/test_pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
19 changes: 18 additions & 1 deletion testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ def test_passes():
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():
Expand Down Expand Up @@ -1678,3 +1678,20 @@ def check(msg, width, expected):
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