gh-134857: Improve error report for doctests run with unittest (GH-134858)

Remove doctest module frames from tracebacks and redundant newline
character from a failure message.
This commit is contained in:
Serhiy Storchaka 2025-05-30 00:32:44 +03:00 committed by GitHub
parent dafd14146f
commit cb8a72b301
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 22 additions and 135 deletions

View file

@ -108,6 +108,8 @@ import _colorize # Used in doctests
from _colorize import ANSIColors, can_colorize from _colorize import ANSIColors, can_colorize
__unittest = True
class TestResults(namedtuple('TestResults', 'failed attempted')): class TestResults(namedtuple('TestResults', 'failed attempted')):
def __new__(cls, failed, attempted, *, skipped=0): def __new__(cls, failed, attempted, *, skipped=0):
results = super().__new__(cls, failed, attempted) results = super().__new__(cls, failed, attempted)
@ -1395,11 +1397,11 @@ class DocTestRunner:
exec(compile(example.source, filename, "single", exec(compile(example.source, filename, "single",
compileflags, True), test.globs) compileflags, True), test.globs)
self.debugger.set_continue() # ==== Example Finished ==== self.debugger.set_continue() # ==== Example Finished ====
exception = None exc_info = None
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except: except BaseException as exc:
exception = sys.exc_info() exc_info = type(exc), exc, exc.__traceback__.tb_next
self.debugger.set_continue() # ==== Example Finished ==== self.debugger.set_continue() # ==== Example Finished ====
got = self._fakeout.getvalue() # the actual output got = self._fakeout.getvalue() # the actual output
@ -1408,21 +1410,21 @@ class DocTestRunner:
# If the example executed without raising any exceptions, # If the example executed without raising any exceptions,
# verify its output. # verify its output.
if exception is None: if exc_info is None:
if check(example.want, got, self.optionflags): if check(example.want, got, self.optionflags):
outcome = SUCCESS outcome = SUCCESS
# The example raised an exception: check if it was expected. # The example raised an exception: check if it was expected.
else: else:
formatted_ex = traceback.format_exception_only(*exception[:2]) formatted_ex = traceback.format_exception_only(*exc_info[:2])
if issubclass(exception[0], SyntaxError): if issubclass(exc_info[0], SyntaxError):
# SyntaxError / IndentationError is special: # SyntaxError / IndentationError is special:
# we don't care about the carets / suggestions / etc # we don't care about the carets / suggestions / etc
# We only care about the error message and notes. # We only care about the error message and notes.
# They start with `SyntaxError:` (or any other class name) # They start with `SyntaxError:` (or any other class name)
exception_line_prefixes = ( exception_line_prefixes = (
f"{exception[0].__qualname__}:", f"{exc_info[0].__qualname__}:",
f"{exception[0].__module__}.{exception[0].__qualname__}:", f"{exc_info[0].__module__}.{exc_info[0].__qualname__}:",
) )
exc_msg_index = next( exc_msg_index = next(
index index
@ -1433,7 +1435,7 @@ class DocTestRunner:
exc_msg = "".join(formatted_ex) exc_msg = "".join(formatted_ex)
if not quiet: if not quiet:
got += _exception_traceback(exception) got += _exception_traceback(exc_info)
# If `example.exc_msg` is None, then we weren't expecting # If `example.exc_msg` is None, then we weren't expecting
# an exception. # an exception.
@ -1462,7 +1464,7 @@ class DocTestRunner:
elif outcome is BOOM: elif outcome is BOOM:
if not quiet: if not quiet:
self.report_unexpected_exception(out, test, example, self.report_unexpected_exception(out, test, example,
exception) exc_info)
failures += 1 failures += 1
else: else:
assert False, ("unknown outcome", outcome) assert False, ("unknown outcome", outcome)
@ -2324,7 +2326,7 @@ class DocTestCase(unittest.TestCase):
sys.stdout = old sys.stdout = old
if results.failed: if results.failed:
raise self.failureException(self.format_failure(new.getvalue())) raise self.failureException(self.format_failure(new.getvalue().rstrip('\n')))
def format_failure(self, err): def format_failure(self, err):
test = self._dt_test test = self._dt_test

View file

@ -2411,9 +2411,6 @@ def test_DocTestSuite_errors():
>>> result >>> result
<unittest.result.TestResult run=4 errors=0 failures=4> <unittest.result.TestResult run=4 errors=0 failures=4>
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
Traceback (most recent call last):
File ...
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors
File "...sample_doctest_errors.py", line 0, in sample_doctest_errors File "...sample_doctest_errors.py", line 0, in sample_doctest_errors
<BLANKLINE> <BLANKLINE>
@ -2431,21 +2428,12 @@ def test_DocTestSuite_errors():
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
ZeroDivisionError: division by zero ZeroDivisionError: division by zero
<BLANKLINE> <BLANKLINE>
<BLANKLINE>
>>> print(result.failures[1][1]) # doctest: +ELLIPSIS >>> print(result.failures[1][1]) # doctest: +ELLIPSIS
Traceback (most recent call last):
File ...
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.__test__.bad AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.__test__.bad
File "...sample_doctest_errors.py", line unknown line number, in bad File "...sample_doctest_errors.py", line unknown line number, in bad
<BLANKLINE> <BLANKLINE>
@ -2463,21 +2451,12 @@ def test_DocTestSuite_errors():
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
ZeroDivisionError: division by zero ZeroDivisionError: division by zero
<BLANKLINE> <BLANKLINE>
<BLANKLINE>
>>> print(result.failures[2][1]) # doctest: +ELLIPSIS >>> print(result.failures[2][1]) # doctest: +ELLIPSIS
Traceback (most recent call last):
File ...
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.errors AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.errors
File "...sample_doctest_errors.py", line 14, in errors File "...sample_doctest_errors.py", line 14, in errors
<BLANKLINE> <BLANKLINE>
@ -2495,11 +2474,6 @@ def test_DocTestSuite_errors():
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
@ -2510,11 +2484,6 @@ def test_DocTestSuite_errors():
f() f()
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module>
f() f()
~^^ ~^^
@ -2528,11 +2497,6 @@ def test_DocTestSuite_errors():
g() g()
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module>
g() g()
~^^ ~^^
@ -2541,11 +2505,7 @@ def test_DocTestSuite_errors():
~~^^^ ~~^^^
IndexError: list index out of range IndexError: list index out of range
<BLANKLINE> <BLANKLINE>
<BLANKLINE>
>>> print(result.failures[3][1]) # doctest: +ELLIPSIS >>> print(result.failures[3][1]) # doctest: +ELLIPSIS
Traceback (most recent call last):
File ...
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.syntax_error AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.syntax_error
File "...sample_doctest_errors.py", line 29, in syntax_error File "...sample_doctest_errors.py", line 29, in syntax_error
<BLANKLINE> <BLANKLINE>
@ -2554,18 +2514,11 @@ def test_DocTestSuite_errors():
Failed example: Failed example:
2+*3 2+*3
Exception raised: Exception raised:
Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1 File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1
2+*3 2+*3
^ ^
SyntaxError: invalid syntax SyntaxError: invalid syntax
<BLANKLINE> <BLANKLINE>
<BLANKLINE>
""" """
def test_DocFileSuite(): def test_DocFileSuite():
@ -2740,9 +2693,6 @@ def test_DocFileSuite_errors():
>>> result >>> result
<unittest.result.TestResult run=1 errors=0 failures=1> <unittest.result.TestResult run=1 errors=0 failures=1>
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
Traceback (most recent call last):
File ...
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for test_doctest_errors.txt AssertionError: Failed doctest test for test_doctest_errors.txt
File "...test_doctest_errors.txt", line 0 File "...test_doctest_errors.txt", line 0
<BLANKLINE> <BLANKLINE>
@ -2760,11 +2710,6 @@ def test_DocFileSuite_errors():
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test_doctest_errors.txt[1]>", line 1, in <module> File "<doctest test_doctest_errors.txt[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
@ -2775,11 +2720,6 @@ def test_DocFileSuite_errors():
f() f()
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test_doctest_errors.txt[3]>", line 1, in <module> File "<doctest test_doctest_errors.txt[3]>", line 1, in <module>
f() f()
~^^ ~^^
@ -2792,18 +2732,11 @@ def test_DocFileSuite_errors():
Failed example: Failed example:
2+*3 2+*3
Exception raised: Exception raised:
Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^
File "<doctest test_doctest_errors.txt[4]>", line 1 File "<doctest test_doctest_errors.txt[4]>", line 1
2+*3 2+*3
^ ^
SyntaxError: invalid syntax SyntaxError: invalid syntax
<BLANKLINE> <BLANKLINE>
<BLANKLINE>
""" """
def test_trailing_space_in_test(): def test_trailing_space_in_test():
@ -2876,7 +2809,8 @@ def test_unittest_reportflags():
>>> result >>> result
<unittest.result.TestResult run=1 errors=0 failures=1> <unittest.result.TestResult run=1 errors=0 failures=1>
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
Traceback ... AssertionError: Failed doctest test for test_doctest.txt
...
Failed example: Failed example:
favorite_color favorite_color
... ...
@ -2895,14 +2829,14 @@ def test_unittest_reportflags():
>>> result >>> result
<unittest.result.TestResult run=1 errors=0 failures=1> <unittest.result.TestResult run=1 errors=0 failures=1>
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
Traceback ... AssertionError: Failed doctest test for test_doctest.txt
...
Failed example: Failed example:
favorite_color favorite_color
Exception raised: Exception raised:
... ...
NameError: name 'favorite_color' is not defined NameError: name 'favorite_color' is not defined
<BLANKLINE> <BLANKLINE>
<BLANKLINE>
We get only the first failure. We get only the first failure.
@ -2922,7 +2856,8 @@ def test_unittest_reportflags():
the trailing whitespace using `\x20` in the diff below. the trailing whitespace using `\x20` in the diff below.
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
Traceback ... AssertionError: Failed doctest test for test_doctest.txt
...
Failed example: Failed example:
favorite_color favorite_color
... ...
@ -2937,7 +2872,6 @@ def test_unittest_reportflags():
+\x20 +\x20
b b
<BLANKLINE> <BLANKLINE>
<BLANKLINE>
Test runners can restore the formatting flags after they run: Test runners can restore the formatting flags after they run:
@ -3145,11 +3079,6 @@ Tests for error reporting in the testfile() function.
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test_doctest_errors.txt[1]>", line 1, in <module> File "<doctest test_doctest_errors.txt[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
@ -3160,11 +3089,6 @@ Tests for error reporting in the testfile() function.
f() f()
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test_doctest_errors.txt[3]>", line 1, in <module> File "<doctest test_doctest_errors.txt[3]>", line 1, in <module>
f() f()
~^^ ~^^
@ -3177,12 +3101,6 @@ Tests for error reporting in the testfile() function.
Failed example: Failed example:
2+*3 2+*3
Exception raised: Exception raised:
Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^
File "<doctest test_doctest_errors.txt[4]>", line 1 File "<doctest test_doctest_errors.txt[4]>", line 1
2+*3 2+*3
^ ^
@ -3343,11 +3261,6 @@ Tests for error reporting in the testmod() function.
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
@ -3366,11 +3279,6 @@ Tests for error reporting in the testmod() function.
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
@ -3389,11 +3297,6 @@ Tests for error reporting in the testmod() function.
1/0 1/0
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module>
1/0 1/0
~^~ ~^~
@ -3404,11 +3307,6 @@ Tests for error reporting in the testmod() function.
f() f()
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module>
f() f()
~^^ ~^^
@ -3422,11 +3320,6 @@ Tests for error reporting in the testmod() function.
g() g()
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module> File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module>
g() g()
~^^ ~^^
@ -3439,12 +3332,6 @@ Tests for error reporting in the testmod() function.
Failed example: Failed example:
2+*3 2+*3
Exception raised: Exception raised:
Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^
File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1 File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1
2+*3 2+*3
^ ^
@ -3490,11 +3377,6 @@ Check doctest with a non-ascii filename:
raise Exception('clé') raise Exception('clé')
Exception raised: Exception raised:
Traceback (most recent call last): Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
compileflags, True), test.globs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest foo-bär@baz[0]>", line 1, in <module> File "<doctest foo-bär@baz[0]>", line 1, in <module>
raise Exception('clé') raise Exception('clé')
Exception: clé Exception: clé

View file

@ -0,0 +1,3 @@
Improve error report for :mod:`doctest`\ s run with :mod:`unittest`. Remove
:mod:`!doctest` module frames from tracebacks and redundant newline
character from a failure message.