diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index 1bfcd69f72df2e..0e737bcaa7f4a8 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -136,6 +136,9 @@ examples of doctests in the standard Python test suite and libraries. Especially useful examples can be found in the standard test file :file:`Lib/test/test_doctest/test_doctest.py`. +.. versionadded:: 3.13 + Doctest will parse markdown files and accept codeblock delimiters ``````` in + docstrings. .. _doctest-simple-testmod: @@ -340,9 +343,13 @@ but doctest isn't trying to do an exact emulation of any specific Python shell. single: >>>; interpreter prompt single: ...; interpreter prompt +.. versionadded:: 3.13 + Examples can also be enclosed in markdown codeblocks, although this is + *not required* + Any expected output must immediately follow the final ``'>>> '`` or ``'... '`` line containing the code, and the expected output (if any) extends to the next -``'>>> '`` or all-whitespace line. +``'>>> '``, ``````` or all-whitespace line. The fine print: @@ -351,6 +358,9 @@ The fine print: blank line, put ```` in your doctest example each place a blank line is expected. +* Expected output cannot contain triplebackticks ```````, since such a line is + taken to signal the end of expected output. + * All hard tab characters are expanded to spaces, using 8-column tab stops. Tabs in output generated by the tested code are not modified. Because any hard tabs in the sample output *are* expanded, this means that if the code diff --git a/Lib/doctest.py b/Lib/doctest.py index 6049423b5147a5..0b9d7f54a3c4e5 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -618,6 +618,7 @@ class DocTestParser: \n? # Want consists of any non-blank lines that do not start with PS1. (?P (?:(?![ ]*$) # Not a blank line + (?![ ]*```) # Not end of a code block (?![ ]*>>>) # Not a line starting with PS1 .+$\n? # But any other line )*) diff --git a/Lib/test/test_doctest/test_doctestmd.py b/Lib/test/test_doctest/test_doctestmd.py new file mode 100644 index 00000000000000..c6a8a0f2ea1108 --- /dev/null +++ b/Lib/test/test_doctest/test_doctestmd.py @@ -0,0 +1,99 @@ +"""Test that doctest processes strings where examples are enclosed in +markdown-style codeblocks. +""" + +import unittest +import doctest + +def dummyfunction_codeblocks(): + """ + A dummy function that uses codeblocks in the examples. + + It is used like this: + ``` + >>> 1 == 1 + True + ``` + """ + pass + +def dummyfunction_multiplecodeblocks(): + """ + A dummy function that uses codeblocks in the examples. + + It is used like this: + ``` + >>> 1 == 1 + True + ``` + + Or like this: + ``` + >>> 1 + 2 + 3 + ``` + """ + pass + +mdfile = r''' +# A Markdown file + +Some documentation in an md file which contains codeblocks like this: + +```pycon +>>> 1 == 1 +True +``` + +and another one like this: + +```pycon +>>> 1 + 2 +3 +``` + +and sometimes with multiple cases in one codeblock +```pycon +>>> 2 == 2 +True + +>>> 2 + 2 +4 +``` + +It would be nice if doctest could test them too ... +''' + +class TestMarkdownDocstring(unittest.TestCase): + """Test DocTestFinder processes docstrings including markup codeblocks""" + + def test_DocTestFinder_codeblocks(self): + """A single codeblock in the docstring""" + + # DocTestFinder returns a list of tests, we only need the first + test = doctest.DocTestFinder().find(dummyfunction_codeblocks)[0] + results = doctest.DocTestRunner().run(test) + self.assertEqual(results, (0,1)) + + def test_DocTestFinder_multiplecodeblocks(self): + """Multiple codeblocks in the docstring""" + + # DocTestFinder returns a list of tests, we only need the first + test = doctest.DocTestFinder().find(dummyfunction_multiplecodeblocks)[0] + results = doctest.DocTestRunner().run(test) + self.assertEqual(results, (0,2)) + +class TestMarkdownFile(unittest.TestCase): + """Test DocTestParser processes markdown files""" + + def test_DocTestParser_getdoctest(self): + parser = doctest.DocTestParser() + tests = parser.get_doctest( + mdfile, + globs=dict(), + name="mdfile", + filename=None, + lineno=None, + ) + results = doctest.DocTestRunner().run(tests) + self.assertEqual(results, (0,4)) diff --git a/Misc/NEWS.d/next/Library/2024-03-09-15-21-13.gh-issue-116546.7v8S44.rst b/Misc/NEWS.d/next/Library/2024-03-09-15-21-13.gh-issue-116546.7v8S44.rst new file mode 100644 index 00000000000000..f0895bbeadba30 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-09-15-21-13.gh-issue-116546.7v8S44.rst @@ -0,0 +1,3 @@ +Adjusted the regex used in :class:`doctest.DocTestParser` to also consider +triple backticks ``````` to signify the end of an example block +(in addition to a blank line and ``>>>``).