diff options
-rw-r--r-- | Doc/lib/libdoctest.tex | 12 | ||||
-rw-r--r-- | Lib/doctest.py | 34 | ||||
-rw-r--r-- | Lib/test/test_doctest.py | 32 |
3 files changed, 68 insertions, 10 deletions
diff --git a/Doc/lib/libdoctest.tex b/Doc/lib/libdoctest.tex index c0bdf6d..0741aa0 100644 --- a/Doc/lib/libdoctest.tex +++ b/Doc/lib/libdoctest.tex @@ -356,6 +356,15 @@ can also be used in doctest directives (see below). actual outputs will be displayed using a context diff. \end{datadesc} +\begin{datadesc}{NDIFF_DIFF} + When specified, differences are computed by \code{difflib.Differ}, + using the same algorithm as the popular \file{ndiff.py} utility. + This is the only method that marks differences within lines as + well as across lines. For example, if a line of expected output + contains digit \code{1} where actual output contains letter \code{l}, + a line is inserted with a caret marking the mismatching column + positions. +\end{datadesc} A "doctest directive" is a trailing Python comment on a line of a doctest example: @@ -414,7 +423,8 @@ can be useful. \versionchanged[Constants \constant{DONT_ACCEPT_BLANKLINE}, \constant{NORMALIZE_WHITESPACE}, \constant{ELLIPSIS}, - \constant{UNIFIED_DIFF}, and \constant{CONTEXT_DIFF} + \constant{UNIFIED_DIFF}, \constant{CONTEXT_DIFF}, and + \constant{NDIFF_DIFF} were added; by default \code{<BLANKLINE>} in expected output matches an empty line in actual output; and doctest directives were added]{2.4} diff --git a/Lib/doctest.py b/Lib/doctest.py index 74714c5..0cafac6 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -178,6 +178,7 @@ __all__ = [ 'ELLIPSIS', 'UNIFIED_DIFF', 'CONTEXT_DIFF', + 'NDIFF_DIFF', # 1. Utility Functions 'is_private', # 2. Example & DocTest @@ -253,6 +254,7 @@ NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') ELLIPSIS = register_optionflag('ELLIPSIS') UNIFIED_DIFF = register_optionflag('UNIFIED_DIFF') CONTEXT_DIFF = register_optionflag('CONTEXT_DIFF') +NDIFF_DIFF = register_optionflag('NDIFF_DIFF') # Special string markers for use in `want` strings: BLANKLINE_MARKER = '<BLANKLINE>' @@ -1569,6 +1571,24 @@ class OutputChecker: # We didn't find any match; return false. return False + # Should we do a fancy diff? + def _do_a_fancy_diff(self, want, got, optionflags): + # Not unless they asked for a fancy diff. + if not optionflags & (UNIFIED_DIFF | + CONTEXT_DIFF | + NDIFF_DIFF): + return False + # If expected output uses ellipsis, a meaningful fancy diff is + # too hard. + if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want: + return False + # ndiff does intraline difference marking, so can be useful even + # for 1-line inputs. + if optionflags & NDIFF_DIFF: + return True + # The other diff types need at least a few lines to be helpful. + return want.count('\n') > 2 and got.count('\n') > 2 + def output_difference(self, want, got, optionflags): """ Return a string describing the differences between the @@ -1586,9 +1606,7 @@ class OutputChecker: # Check if we should use diff. Don't use diff if the actual # or expected outputs are too short, or if the expected output # contains an ellipsis marker. - if ((optionflags & (UNIFIED_DIFF | CONTEXT_DIFF)) and - want.count('\n') > 2 and got.count('\n') > 2 and - not (optionflags & ELLIPSIS and '...' in want)): + if self._do_a_fancy_diff(want, got, optionflags): # Split want & got into lines. want_lines = [l+'\n' for l in want.split('\n')] got_lines = [l+'\n' for l in got.split('\n')] @@ -1596,16 +1614,20 @@ class OutputChecker: if optionflags & UNIFIED_DIFF: diff = difflib.unified_diff(want_lines, got_lines, n=2, fromfile='Expected', tofile='Got') - kind = 'unified' + kind = 'unified diff' elif optionflags & CONTEXT_DIFF: diff = difflib.context_diff(want_lines, got_lines, n=2, fromfile='Expected', tofile='Got') - kind = 'context' + kind = 'context diff' + elif optionflags & NDIFF_DIFF: + engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) + diff = list(engine.compare(want_lines, got_lines)) + kind = 'ndiff with -expected +actual' else: assert 0, 'Bad diff option' # Remove trailing whitespace on diff output. diff = [line.rstrip() + '\n' for line in diff] - return _tag_msg("Differences (" + kind + " diff)", + return _tag_msg("Differences (" + kind + ")", ''.join(diff)) # If we're not using diff, then simply list the expected diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 6d9d745..969ee17 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -283,7 +283,7 @@ We'll simulate a __file__ attr that ends in pyc: 'test_doctest.py' >>> test.test_doctest.__file__ = old - + >>> e = tests[0].examples[0] >>> (e.source, e.want, e.lineno) @@ -931,7 +931,33 @@ and actual outputs to be displayed using a context diff: g <BLANKLINE> (1, 1) -""" + + +The NDIFF_DIFF flag causes failures to use the difflib.Differ algorithm +used by the popular ndiff.py utility. This does intraline difference +marking, as well as interline differences. + + >>> def f(x): + ... r''' + ... >>> print "a b c d e f g h i j k l m" + ... a b c d e f g h i j k 1 m + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.NDIFF_DIFF + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ********************************************************************** + Line 2, in f + Failed example: + print "a b c d e f g h i j k l m" + Differences (ndiff with -expected +actual): + - a b c d e f g h i j k 1 m + ? ^ + + a b c d e f g h i j k l m + ? + ++ ^ + <BLANKLINE> + (1, 1) + """ + def option_directives(): r""" Tests of `DocTestRunner`'s option directive mechanism. @@ -1468,7 +1494,7 @@ def test_DocFileSuite(): def test_trailing_space_in_test(): """ Trailing spaces in expcted output are significant: - + >>> x, y = 'foo', '' >>> print x, y foo \n |