From 30669a89386f45256c37da5c6a134e6223c816a7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 21 Oct 2023 13:57:33 +0300 Subject: [PATCH 1/4] gh-111159: Fix `doctest` output comparison for exceptions with notes --- Lib/doctest.py | 8 ++- Lib/test/test_doctest.py | 59 +++++++++++++++++++ ...-10-21-13-57-06.gh-issue-111159.GoHp7s.rst | 1 + 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-21-13-57-06.gh-issue-111159.GoHp7s.rst diff --git a/Lib/doctest.py b/Lib/doctest.py index 46b4dd6769b063..f2984094a828c2 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1393,7 +1393,13 @@ def __run(self, test, compileflags, out): # The example raised an exception: check if it was expected. else: - exc_msg = traceback.format_exception_only(*exception[:2])[-1] + formatted_ex = traceback.format_exception_only(*exception[:2]) + if issubclass(exception[0], SyntaxError): + # SyntaxError / IndentationError is special: + # we don't care about the carets / suggestions / etc + exc_msg = formatted_ex[-1] + else: + exc_msg = "".join(formatted_ex) if not quiet: got += _exception_traceback(exception) diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 6e12e82a7a0084..f4c36f0b867de1 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -3212,6 +3212,65 @@ def test_run_doctestsuite_multiple_times(): """ +def test_exception_with_note(note): + """ + It was failing until https://github.com/python/cpython/issues/111159 + + >>> test_exception_with_note('Note') + Traceback (most recent call last): + ... + ValueError: Text + Note + + >>> test_exception_with_note('Note') # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Text + Note + + >>> test_exception_with_note('''Note + ... multiline + ... example''') + Traceback (most recent call last): + ValueError: Text + Note + multiline + example + + Different note will fail the test: + + >>> def f(x): + ... r''' + ... >>> exc = ValueError('message') + ... >>> exc.add_note('note') + ... >>> raise exc + ... Traceback (most recent call last): + ... ValueError: message + ... wrong note + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 5, in f + Failed example: + raise exc + Expected: + Traceback (most recent call last): + ValueError: message + wrong note + Got: + Traceback (most recent call last): + ... + ValueError: message + note + TestResults(failed=1, attempted=...) + """ + exc = ValueError('Text') + exc.add_note(note) + raise exc + + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite(doctest)) tests.addTest(doctest.DocTestSuite()) diff --git a/Misc/NEWS.d/next/Library/2023-10-21-13-57-06.gh-issue-111159.GoHp7s.rst b/Misc/NEWS.d/next/Library/2023-10-21-13-57-06.gh-issue-111159.GoHp7s.rst new file mode 100644 index 00000000000000..bdec4f4443d80b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-21-13-57-06.gh-issue-111159.GoHp7s.rst @@ -0,0 +1 @@ +Fix :mod:`doctest` output comparison for exceptions with notes. From d9f14fa6df93e6739550c2c143df4644d3d3acc6 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 21 Oct 2023 15:10:20 +0300 Subject: [PATCH 2/4] Address review, add more tests --- Lib/doctest.py | 13 +++++++--- Lib/test/test_doctest.py | 56 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index f2984094a828c2..f00d9358ffe10b 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1397,9 +1397,16 @@ def __run(self, test, compileflags, out): if issubclass(exception[0], SyntaxError): # SyntaxError / IndentationError is special: # we don't care about the carets / suggestions / etc - exc_msg = formatted_ex[-1] - else: - exc_msg = "".join(formatted_ex) + # We only care about the error message and notes. + # They start with `SyntaxError:` (or any other class name) + exc_msg_index = next( + index + for index, line in enumerate(formatted_ex) + if line.startswith(f"{exception[0].__name__}:") + ) + formatted_ex = formatted_ex[exc_msg_index:] + + exc_msg = "".join(formatted_ex) if not quiet: got += _exception_traceback(exception) diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index f4c36f0b867de1..5f44084e000878 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -3214,8 +3214,6 @@ def test_run_doctestsuite_multiple_times(): def test_exception_with_note(note): """ - It was failing until https://github.com/python/cpython/issues/111159 - >>> test_exception_with_note('Note') Traceback (most recent call last): ... @@ -3271,6 +3269,60 @@ def test_exception_with_note(note): raise exc +def test_exception_with_multiple_notes(): + """ + >>> test_exception_with_multiple_notes() + Traceback (most recent call last): + ... + ValueError: Text + One + Two + """ + exc = ValueError('Text') + exc.add_note('One') + exc.add_note('Two') + raise exc + + +def test_syntax_error_with_note(cls, multiline=False): + """ + >>> test_syntax_error_with_note(SyntaxError) + Traceback (most recent call last): + ... + SyntaxError: error + Note + + >>> test_syntax_error_with_note(SyntaxError) + Traceback (most recent call last): + SyntaxError: error + Note + + >>> test_syntax_error_with_note(SyntaxError) + Traceback (most recent call last): + ... + File "x.py", line 23 + bad syntax + SyntaxError: error + Note + + >>> test_syntax_error_with_note(IndentationError) + Traceback (most recent call last): + ... + IndentationError: error + Note + + >>> test_syntax_error_with_note(TabError, multiline=True) + Traceback (most recent call last): + ... + TabError: error + Note + Line + """ + exc = cls("error", ("x.py", 23, None, "bad syntax")) + exc.add_note('Note\nLine' if multiline else 'Note') + raise exc + + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite(doctest)) tests.addTest(doctest.DocTestSuite()) From a7daef0797dc6dcc05144475f308d65391065114 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 21 Oct 2023 15:17:55 +0300 Subject: [PATCH 3/4] One more test --- Lib/test/test_doctest.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 5f44084e000878..5b44da33a987c1 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -3323,6 +3323,39 @@ def test_syntax_error_with_note(cls, multiline=False): raise exc +def test_syntax_error_with_incorrect_note(): + """ + >>> def f(x): + ... r''' + ... >>> exc = SyntaxError("error", ("x.py", 23, None, "bad syntax")) + ... >>> exc.add_note('note1') + ... >>> exc.add_note('note2') + ... >>> raise exc + ... Traceback (most recent call last): + ... SyntaxError: error + ... wrong note + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 6, in f + Failed example: + raise exc + Expected: + Traceback (most recent call last): + SyntaxError: error + wrong note + Got: + Traceback (most recent call last): + ... + SyntaxError: error + note1 + note2 + TestResults(failed=1, attempted=...) + """ + + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite(doctest)) tests.addTest(doctest.DocTestSuite()) From a5f73568ae04747e6251f0ed05ecb5f5048771e6 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 21 Oct 2023 17:44:54 +0300 Subject: [PATCH 4/4] Update Lib/test/test_doctest.py Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Lib/test/test_doctest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 5b44da33a987c1..6a903ed041ab8d 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -3323,7 +3323,7 @@ def test_syntax_error_with_note(cls, multiline=False): raise exc -def test_syntax_error_with_incorrect_note(): +def test_syntax_error_with_incorrect_expected_note(): """ >>> def f(x): ... r'''