Skip to content

Commit

Permalink
pythongh-117225: doctest: only print "and X failed" when non-zero, do…
Browse files Browse the repository at this point in the history
…n't pluralise "1 items" (python#117228)
  • Loading branch information
hugovk authored Mar 27, 2024
1 parent 92397d5 commit ce00de4
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 48 deletions.
10 changes: 5 additions & 5 deletions Doc/library/doctest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ And so on, eventually ending with:
OverflowError: n too large
ok
2 items passed all tests:
1 tests in __main__
8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
1 test in __main__
6 tests in __main__.factorial
7 tests in 2 items.
7 passed.
Test passed.
$
Expand Down Expand Up @@ -1933,7 +1933,7 @@ such a test runner::
optionflags=flags)
else:
fail, total = doctest.testmod(optionflags=flags)
print("{} failures out of {} tests".format(fail, total))
print(f"{fail} failures out of {total} tests")


.. rubric:: Footnotes
Expand Down
59 changes: 39 additions & 20 deletions Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1191,9 +1191,9 @@ class DocTestRunner:
2 tests in _TestClass
2 tests in _TestClass.__init__
2 tests in _TestClass.get
1 tests in _TestClass.square
1 test in _TestClass.square
7 tests in 4 items.
7 passed and 0 failed.
7 passed.
Test passed.
TestResults(failed=0, attempted=7)
Expand Down Expand Up @@ -1568,49 +1568,59 @@ def summarize(self, verbose=None):
"""
if verbose is None:
verbose = self._verbose
notests = []
passed = []
failed = []

notests, passed, failed = [], [], []
total_tries = total_failures = total_skips = 0
for item in self._stats.items():
name, (failures, tries, skips) = item

for name, (failures, tries, skips) in self._stats.items():
assert failures <= tries
total_tries += tries
total_failures += failures
total_skips += skips

if tries == 0:
notests.append(name)
elif failures == 0:
passed.append((name, tries))
else:
failed.append(item)
failed.append((name, (failures, tries, skips)))

if verbose:
if notests:
print(f"{len(notests)} items had no tests:")
print(f"{_n_items(notests)} had no tests:")
notests.sort()
for name in notests:
print(f" {name}")

if passed:
print(f"{len(passed)} items passed all tests:")
passed.sort()
for name, count in passed:
print(f" {count:3d} tests in {name}")
print(f"{_n_items(passed)} passed all tests:")
for name, count in sorted(passed):
s = "" if count == 1 else "s"
print(f" {count:3d} test{s} in {name}")

if failed:
print(self.DIVIDER)
print(f"{len(failed)} items had failures:")
failed.sort()
for name, (failures, tries, skips) in failed:
print(f"{_n_items(failed)} had failures:")
for name, (failures, tries, skips) in sorted(failed):
print(f" {failures:3d} of {tries:3d} in {name}")

if verbose:
print(f"{total_tries} tests in {len(self._stats)} items.")
print(f"{total_tries - total_failures} passed and {total_failures} failed.")
s = "" if total_tries == 1 else "s"
print(f"{total_tries} test{s} in {_n_items(self._stats)}.")

and_f = f" and {total_failures} failed" if total_failures else ""
print(f"{total_tries - total_failures} passed{and_f}.")

if total_failures:
msg = f"***Test Failed*** {total_failures} failures"
s = "" if total_failures == 1 else "s"
msg = f"***Test Failed*** {total_failures} failure{s}"
if total_skips:
msg = f"{msg} and {total_skips} skipped tests"
s = "" if total_skips == 1 else "s"
msg = f"{msg} and {total_skips} skipped test{s}"
print(f"{msg}.")
elif verbose:
print("Test passed.")

return TestResults(total_failures, total_tries, skipped=total_skips)

#/////////////////////////////////////////////////////////////////
Expand All @@ -1627,6 +1637,15 @@ def merge(self, other):
d[name] = (failures, tries, skips)


def _n_items(items: list) -> str:
"""
Helper to pluralise the number of items in a list.
"""
n = len(items)
s = "" if n == 1 else "s"
return f"{n} item{s}"


class OutputChecker:
"""
A class used to check the whether the actual output from a doctest
Expand Down
46 changes: 23 additions & 23 deletions Lib/test/test_doctest/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2628,9 +2628,9 @@ def test_testfile(): r"""
...
NameError: name 'favorite_color' is not defined
**********************************************************************
1 items had failures:
1 item had failures:
1 of 2 in test_doctest.txt
***Test Failed*** 1 failures.
***Test Failed*** 1 failure.
TestResults(failed=1, attempted=2)
>>> doctest.master = None # Reset master.
Expand All @@ -2657,9 +2657,9 @@ def test_testfile(): r"""
Got:
'red'
**********************************************************************
1 items had failures:
1 item had failures:
1 of 2 in test_doctest.txt
***Test Failed*** 1 failures.
***Test Failed*** 1 failure.
TestResults(failed=1, attempted=2)
>>> doctest.master = None # Reset master.
Expand Down Expand Up @@ -2689,10 +2689,10 @@ def test_testfile(): r"""
<BLANKLINE>
b
ok
1 items passed all tests:
1 item passed all tests:
2 tests in test_doctest.txt
2 tests in 1 items.
2 passed and 0 failed.
2 tests in 1 item.
2 passed.
Test passed.
TestResults(failed=0, attempted=2)
>>> doctest.master = None # Reset master.
Expand Down Expand Up @@ -2749,7 +2749,7 @@ def test_testfile(): r"""
**********************************************************************
...
**********************************************************************
1 items had failures:
1 item had failures:
2 of 2 in test_doctest4.txt
***Test Failed*** 2 failures.
TestResults(failed=2, attempted=2)
Expand All @@ -2772,10 +2772,10 @@ def test_testfile(): r"""
Expecting:
'b\u0105r'
ok
1 items passed all tests:
1 item passed all tests:
2 tests in test_doctest4.txt
2 tests in 1 items.
2 passed and 0 failed.
2 tests in 1 item.
2 passed.
Test passed.
TestResults(failed=0, attempted=2)
>>> doctest.master = None # Reset master.
Expand Down Expand Up @@ -2997,10 +2997,10 @@ def test_CLI(): r"""
Expecting:
'a'
ok
1 items passed all tests:
1 item passed all tests:
2 tests in myfile.doc
2 tests in 1 items.
2 passed and 0 failed.
2 tests in 1 item.
2 passed.
Test passed.
Now we'll write a couple files, one with three tests, the other a python module
Expand Down Expand Up @@ -3074,7 +3074,7 @@ def test_CLI(): r"""
Got:
'ajkml'
**********************************************************************
1 items had failures:
1 item had failures:
2 of 3 in myfile.doc
***Test Failed*** 2 failures.
Expand All @@ -3101,9 +3101,9 @@ def test_CLI(): r"""
Got:
'abcdef'
**********************************************************************
1 items had failures:
1 item had failures:
1 of 2 in myfile.doc
***Test Failed*** 1 failures.
***Test Failed*** 1 failure.
The fifth test uses verbose with the two options, so we should get verbose
success output for the tests in both files:
Expand All @@ -3126,10 +3126,10 @@ def test_CLI(): r"""
Expecting:
'a...l'
ok
1 items passed all tests:
1 item passed all tests:
3 tests in myfile.doc
3 tests in 1 items.
3 passed and 0 failed.
3 tests in 1 item.
3 passed.
Test passed.
Trying:
1 + 1
Expand All @@ -3141,12 +3141,12 @@ def test_CLI(): r"""
Expecting:
'abc def'
ok
1 items had no tests:
1 item had no tests:
myfile2
1 items passed all tests:
1 item passed all tests:
2 tests in myfile2.test_func
2 tests in 2 items.
2 passed and 0 failed.
2 passed.
Test passed.
We should also check some typical error cases.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
doctest: only print "and X failed" when non-zero, don't pluralise "1 items".
Patch by Hugo van Kemenade.

0 comments on commit ce00de4

Please sign in to comment.