Skip to content

Commit ce00de4

Browse files
authored
gh-117225: doctest: only print "and X failed" when non-zero, don't pluralise "1 items" (#117228)
1 parent 92397d5 commit ce00de4

File tree

4 files changed

+69
-48
lines changed

4 files changed

+69
-48
lines changed

Doc/library/doctest.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ And so on, eventually ending with:
123123
OverflowError: n too large
124124
ok
125125
2 items passed all tests:
126-
1 tests in __main__
127-
8 tests in __main__.factorial
128-
9 tests in 2 items.
129-
9 passed and 0 failed.
126+
1 test in __main__
127+
6 tests in __main__.factorial
128+
7 tests in 2 items.
129+
7 passed.
130130
Test passed.
131131
$
132132
@@ -1933,7 +1933,7 @@ such a test runner::
19331933
optionflags=flags)
19341934
else:
19351935
fail, total = doctest.testmod(optionflags=flags)
1936-
print("{} failures out of {} tests".format(fail, total))
1936+
print(f"{fail} failures out of {total} tests")
19371937

19381938

19391939
.. rubric:: Footnotes

Lib/doctest.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,9 +1191,9 @@ class DocTestRunner:
11911191
2 tests in _TestClass
11921192
2 tests in _TestClass.__init__
11931193
2 tests in _TestClass.get
1194-
1 tests in _TestClass.square
1194+
1 test in _TestClass.square
11951195
7 tests in 4 items.
1196-
7 passed and 0 failed.
1196+
7 passed.
11971197
Test passed.
11981198
TestResults(failed=0, attempted=7)
11991199
@@ -1568,49 +1568,59 @@ def summarize(self, verbose=None):
15681568
"""
15691569
if verbose is None:
15701570
verbose = self._verbose
1571-
notests = []
1572-
passed = []
1573-
failed = []
1571+
1572+
notests, passed, failed = [], [], []
15741573
total_tries = total_failures = total_skips = 0
1575-
for item in self._stats.items():
1576-
name, (failures, tries, skips) = item
1574+
1575+
for name, (failures, tries, skips) in self._stats.items():
15771576
assert failures <= tries
15781577
total_tries += tries
15791578
total_failures += failures
15801579
total_skips += skips
1580+
15811581
if tries == 0:
15821582
notests.append(name)
15831583
elif failures == 0:
15841584
passed.append((name, tries))
15851585
else:
1586-
failed.append(item)
1586+
failed.append((name, (failures, tries, skips)))
1587+
15871588
if verbose:
15881589
if notests:
1589-
print(f"{len(notests)} items had no tests:")
1590+
print(f"{_n_items(notests)} had no tests:")
15901591
notests.sort()
15911592
for name in notests:
15921593
print(f" {name}")
1594+
15931595
if passed:
1594-
print(f"{len(passed)} items passed all tests:")
1595-
passed.sort()
1596-
for name, count in passed:
1597-
print(f" {count:3d} tests in {name}")
1596+
print(f"{_n_items(passed)} passed all tests:")
1597+
for name, count in sorted(passed):
1598+
s = "" if count == 1 else "s"
1599+
print(f" {count:3d} test{s} in {name}")
1600+
15981601
if failed:
15991602
print(self.DIVIDER)
1600-
print(f"{len(failed)} items had failures:")
1601-
failed.sort()
1602-
for name, (failures, tries, skips) in failed:
1603+
print(f"{_n_items(failed)} had failures:")
1604+
for name, (failures, tries, skips) in sorted(failed):
16031605
print(f" {failures:3d} of {tries:3d} in {name}")
1606+
16041607
if verbose:
1605-
print(f"{total_tries} tests in {len(self._stats)} items.")
1606-
print(f"{total_tries - total_failures} passed and {total_failures} failed.")
1608+
s = "" if total_tries == 1 else "s"
1609+
print(f"{total_tries} test{s} in {_n_items(self._stats)}.")
1610+
1611+
and_f = f" and {total_failures} failed" if total_failures else ""
1612+
print(f"{total_tries - total_failures} passed{and_f}.")
1613+
16071614
if total_failures:
1608-
msg = f"***Test Failed*** {total_failures} failures"
1615+
s = "" if total_failures == 1 else "s"
1616+
msg = f"***Test Failed*** {total_failures} failure{s}"
16091617
if total_skips:
1610-
msg = f"{msg} and {total_skips} skipped tests"
1618+
s = "" if total_skips == 1 else "s"
1619+
msg = f"{msg} and {total_skips} skipped test{s}"
16111620
print(f"{msg}.")
16121621
elif verbose:
16131622
print("Test passed.")
1623+
16141624
return TestResults(total_failures, total_tries, skipped=total_skips)
16151625

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

16291639

1640+
def _n_items(items: list) -> str:
1641+
"""
1642+
Helper to pluralise the number of items in a list.
1643+
"""
1644+
n = len(items)
1645+
s = "" if n == 1 else "s"
1646+
return f"{n} item{s}"
1647+
1648+
16301649
class OutputChecker:
16311650
"""
16321651
A class used to check the whether the actual output from a doctest

Lib/test/test_doctest/test_doctest.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2628,9 +2628,9 @@ def test_testfile(): r"""
26282628
...
26292629
NameError: name 'favorite_color' is not defined
26302630
**********************************************************************
2631-
1 items had failures:
2631+
1 item had failures:
26322632
1 of 2 in test_doctest.txt
2633-
***Test Failed*** 1 failures.
2633+
***Test Failed*** 1 failure.
26342634
TestResults(failed=1, attempted=2)
26352635
>>> doctest.master = None # Reset master.
26362636
@@ -2657,9 +2657,9 @@ def test_testfile(): r"""
26572657
Got:
26582658
'red'
26592659
**********************************************************************
2660-
1 items had failures:
2660+
1 item had failures:
26612661
1 of 2 in test_doctest.txt
2662-
***Test Failed*** 1 failures.
2662+
***Test Failed*** 1 failure.
26632663
TestResults(failed=1, attempted=2)
26642664
>>> doctest.master = None # Reset master.
26652665
@@ -2689,10 +2689,10 @@ def test_testfile(): r"""
26892689
<BLANKLINE>
26902690
b
26912691
ok
2692-
1 items passed all tests:
2692+
1 item passed all tests:
26932693
2 tests in test_doctest.txt
2694-
2 tests in 1 items.
2695-
2 passed and 0 failed.
2694+
2 tests in 1 item.
2695+
2 passed.
26962696
Test passed.
26972697
TestResults(failed=0, attempted=2)
26982698
>>> doctest.master = None # Reset master.
@@ -2749,7 +2749,7 @@ def test_testfile(): r"""
27492749
**********************************************************************
27502750
...
27512751
**********************************************************************
2752-
1 items had failures:
2752+
1 item had failures:
27532753
2 of 2 in test_doctest4.txt
27542754
***Test Failed*** 2 failures.
27552755
TestResults(failed=2, attempted=2)
@@ -2772,10 +2772,10 @@ def test_testfile(): r"""
27722772
Expecting:
27732773
'b\u0105r'
27742774
ok
2775-
1 items passed all tests:
2775+
1 item passed all tests:
27762776
2 tests in test_doctest4.txt
2777-
2 tests in 1 items.
2778-
2 passed and 0 failed.
2777+
2 tests in 1 item.
2778+
2 passed.
27792779
Test passed.
27802780
TestResults(failed=0, attempted=2)
27812781
>>> doctest.master = None # Reset master.
@@ -2997,10 +2997,10 @@ def test_CLI(): r"""
29972997
Expecting:
29982998
'a'
29992999
ok
3000-
1 items passed all tests:
3000+
1 item passed all tests:
30013001
2 tests in myfile.doc
3002-
2 tests in 1 items.
3003-
2 passed and 0 failed.
3002+
2 tests in 1 item.
3003+
2 passed.
30043004
Test passed.
30053005
30063006
Now we'll write a couple files, one with three tests, the other a python module
@@ -3074,7 +3074,7 @@ def test_CLI(): r"""
30743074
Got:
30753075
'ajkml'
30763076
**********************************************************************
3077-
1 items had failures:
3077+
1 item had failures:
30783078
2 of 3 in myfile.doc
30793079
***Test Failed*** 2 failures.
30803080
@@ -3101,9 +3101,9 @@ def test_CLI(): r"""
31013101
Got:
31023102
'abcdef'
31033103
**********************************************************************
3104-
1 items had failures:
3104+
1 item had failures:
31053105
1 of 2 in myfile.doc
3106-
***Test Failed*** 1 failures.
3106+
***Test Failed*** 1 failure.
31073107
31083108
The fifth test uses verbose with the two options, so we should get verbose
31093109
success output for the tests in both files:
@@ -3126,10 +3126,10 @@ def test_CLI(): r"""
31263126
Expecting:
31273127
'a...l'
31283128
ok
3129-
1 items passed all tests:
3129+
1 item passed all tests:
31303130
3 tests in myfile.doc
3131-
3 tests in 1 items.
3132-
3 passed and 0 failed.
3131+
3 tests in 1 item.
3132+
3 passed.
31333133
Test passed.
31343134
Trying:
31353135
1 + 1
@@ -3141,12 +3141,12 @@ def test_CLI(): r"""
31413141
Expecting:
31423142
'abc def'
31433143
ok
3144-
1 items had no tests:
3144+
1 item had no tests:
31453145
myfile2
3146-
1 items passed all tests:
3146+
1 item passed all tests:
31473147
2 tests in myfile2.test_func
31483148
2 tests in 2 items.
3149-
2 passed and 0 failed.
3149+
2 passed.
31503150
Test passed.
31513151
31523152
We should also check some typical error cases.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
doctest: only print "and X failed" when non-zero, don't pluralise "1 items".
2+
Patch by Hugo van Kemenade.

0 commit comments

Comments
 (0)