Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion Doc/library/doctest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,60 @@ doctest decides whether actual output matches an example's expected output:
sequence of whitespace within the actual output. By default, whitespace must
match exactly. :const:`NORMALIZE_WHITESPACE` is especially useful when a line of
expected output is very long, and you want to wrap it across multiple lines in
your source.
your source. If the expected output does not contain any whitespace, consider
using :data:`IGNORE_LINEBREAK` or :data:`ELLIPSIS`.


.. data:: IGNORE_LINEBREAK

When specified, single line breaks in the expected output are eliminated,
thereby allowing strings without whitespaces to span multiple lines.

.. doctest::
:no-trim-doctest-flags:

>>> "foobar123456" # doctest: +IGNORE_LINEBREAK
'foobar
123456'

Consider using :data:`NORMALIZE_WHITESPACE` when strings with whitespaces
need to be split across multiple lines:

.. doctest::
:no-trim-doctest-flags:

>>> "the string to split" # doctest: +NORMALIZE_WHITESPACE
'the string
to split'

Note that any leading whitespaces on each expected output line are retained.
In other words, the following expected outputs are equivalent under
:data:`!IGNORE_LINEBREAK`:

.. code-block::

[
'a', 'b', 'c',
'1', '2', '3'
]

[ 'a', 'b', 'c', '1', '2', '3']

To break a list-like output with :data:`!IGNORE_LINEBREAK`,
leading whitespaces for visual indentation purposes should
be avoided, for instance:

.. doctest::
:no-trim-doctest-flags:

>>> list("abc123") # doctest: +IGNORE_LINEBREAK
['a', 'b', 'c',
'1', '2', '3']

For more complex outputs, consider using :func:`pprint.pp` and matching
its output directly.

.. versionadded:: next


.. index:: single: ...; in doctests
Expand Down
17 changes: 17 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,23 @@ difflib
(Contributed by Jiahao Li in :gh:`134580`.)


doctest
-------

* Add :data:`~doctest.IGNORE_LINEBREAK` option to allow breaking expected
output strings without whitespaces into multiple lines:

.. doctest::
:no-trim-doctest-flags:

>>> import string
>>> print(string.ascii_letters) # doctest: +IGNORE_LINEBREAK
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ

(Contributed by Bénédikt Tran in :gh:`138135`.)


hashlib
-------

Expand Down
22 changes: 20 additions & 2 deletions Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def _test():
'DONT_ACCEPT_TRUE_FOR_1',
'DONT_ACCEPT_BLANKLINE',
'NORMALIZE_WHITESPACE',
'IGNORE_LINEBREAK',
'ELLIPSIS',
'SKIP',
'IGNORE_EXCEPTION_DETAIL',
Expand Down Expand Up @@ -156,6 +157,7 @@ def register_optionflag(name):
DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1')
DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
IGNORE_LINEBREAK = register_optionflag('IGNORE_LINEBREAK')
ELLIPSIS = register_optionflag('ELLIPSIS')
SKIP = register_optionflag('SKIP')
IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
Expand Down Expand Up @@ -1751,9 +1753,18 @@ def check_output(self, want, got, optionflags):
if got == want:
return True

# This flag causes doctest to ignore '\n' in `want`.
# Note that this can be used in conjunction with
# the NORMALIZE_WHITESPACE and ELLIPSIS flags.
if optionflags & IGNORE_LINEBREAK:
# `want` originally ends with '\n' so we add it back
want = ''.join(want.splitlines()) + '\n'
if got == want:
return True

# This flag causes doctest to ignore any differences in the
# contents of whitespace strings. Note that this can be used
# in conjunction with the ELLIPSIS flag.
# in conjunction with the IGNORE_LINEBREAK and ELLIPSIS flags.
if optionflags & NORMALIZE_WHITESPACE:
got = ' '.join(got.split())
want = ' '.join(want.split())
Expand Down Expand Up @@ -2268,7 +2279,7 @@ def set_unittest_reportflags(flags):
>>> doctest.set_unittest_reportflags(ELLIPSIS)
Traceback (most recent call last):
...
ValueError: ('Only reporting flags allowed', 8)
ValueError: ('Only reporting flags allowed', 16)

>>> doctest.set_unittest_reportflags(old) == (REPORT_NDIFF |
... REPORT_ONLY_FIRST_FAILURE)
Expand Down Expand Up @@ -2924,6 +2935,13 @@ def get(self):
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29]
""",

"line break elimination": r"""
>>> "foobar" # doctest: +IGNORE_LINEBREAK
'foo
bar
'
""",
}


Expand Down
111 changes: 110 additions & 1 deletion Lib/test/test_doctest/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def test_Example(): r"""
... options={doctest.ELLIPSIS: True})
>>> (example.source, example.want, example.exc_msg,
... example.lineno, example.indent, example.options)
('[].pop()\n', '', 'IndexError: pop from an empty list\n', 5, 4, {8: True})
('[].pop()\n', '', 'IndexError: pop from an empty list\n', 5, 4, {16: True})

The constructor normalizes the `source` string to end in a newline:

Expand Down Expand Up @@ -1396,6 +1396,115 @@ def optionflags(): r"""
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

The IGNORE_LINEBREAK flag causes all sequences of newlines to be removed,
but retains the leading whitespaces as they cannot be distinguished from
real textual whitespaces:

>>> def f(x): pass
>>> f.__doc__ = '''
... >>> "foobar"
... 'foo
... bar'
... '''.strip()

>>> # Without the flag:
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
... # doctest: +ELLIPSIS
**********************************************************************
File ..., line ?, in f
Failed example:
"foobar"
Expected:
'foo
bar'
Got:
'foobar'
TestResults(failed=1, attempted=1)

>>> # With the flag:
>>> test = doctest.DocTestFinder().find(f)[0]
>>> flags = doctest.IGNORE_LINEBREAK
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
TestResults(failed=0, attempted=1)

... ignore surrounding new lines

>>> "foobar" # doctest: +IGNORE_LINEBREAK
'
foo
bar'
>>> "foobar" # doctest: +IGNORE_LINEBREAK
'foo
bar
'
>>> "foobar" # doctest: +IGNORE_LINEBREAK
'
foo
bar
'

... non-quoted output:

>>> import string
>>> print(string.ascii_letters) # doctest: +IGNORE_LINEBREAK
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ

... mixing flags:

>>> import string
>>> print(string.ascii_letters) # doctest: +ELLIPSIS, +IGNORE_LINEBREAK
abc...xyz
ABC...

... mixing flags:

>>> print(list("abc123")) # doctest: +IGNORE_LINEBREAK
... # doctest: +ELLIPSIS
... # doctest: +NORMALIZE_WHITESPACE
['a', ..., 'c',
'1', ..., '3']

>>> prelude = r'''
... >>> print(list("abc123")) # doctest: +IGNORE_LINEBREAK
... ... # doctest: +ELLIPSIS
... ... # doctest: +NORMALIZE_WHITESPACE
... '''.strip()

>>> def good(x): pass
>>> good.__doc__ = '\n'.join([prelude, r'''
... ['a', ..., 'c',
... '1', ..., '3']
... '''.lstrip()]).lstrip()
>>> test = doctest.DocTestFinder().find(good)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
TestResults(failed=0, attempted=1)

>>> def fail(x): pass
>>> fail.__doc__ = '\n'.join([prelude, '''
... [
... 'a', ..., 'c',
... '1', ..., '3'
... ]\n'''.lstrip()])
>>> test = doctest.DocTestFinder().find(fail)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
... # doctest: +ELLIPSIS
**********************************************************************
File ..., line ?, in fail
Failed example:
print(list("abc123")) # doctest: +IGNORE_LINEBREAK
# doctest: +ELLIPSIS
# doctest: +NORMALIZE_WHITESPACE
Expected:
[
'a', ..., 'c',
'1', ..., '3'
]
Got:
['a', 'b', 'c', '1', '2', '3']
TestResults(failed=1, attempted=1)

The ELLIPSIS flag causes ellipsis marker ("...") in the expected
output to match any substring in the actual output:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:mod:`doctest`: Add :data:`~doctest.IGNORE_LINEBREAK` option to allow
breaking expected output strings without whitespaces into multiple lines.
Patch by Bénédikt Tran.
Loading