[Python-checkins] python/dist/src/Lib doctest.py,1.114,1.115

edloper at users.sourceforge.net edloper at users.sourceforge.net
Tue Sep 28 06:30:00 CEST 2004


Update of /cvsroot/python/python/dist/src/Lib
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32322/dist/src/Lib

Modified Files:
	doctest.py 
Log Message:
Added a new NORMALIZE_NUMBERS option, which causes number literals in
the expected output to match corresponding number literals in the
actual output if their values are equal (to ten digits of precision).


Index: doctest.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/doctest.py,v
retrieving revision 1.114
retrieving revision 1.115
diff -u -d -r1.114 -r1.115
--- doctest.py	27 Sep 2004 03:42:58 -0000	1.114
+++ doctest.py	28 Sep 2004 04:29:57 -0000	1.115
@@ -55,6 +55,7 @@
     'NORMALIZE_WHITESPACE',
     'ELLIPSIS',
     'IGNORE_EXCEPTION_DETAIL',
+    'NORMALIZE_NUMBERS',
     'COMPARISON_FLAGS',
     'REPORT_UDIFF',
     'REPORT_CDIFF',
@@ -139,12 +140,14 @@
 NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
 ELLIPSIS = register_optionflag('ELLIPSIS')
 IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
+NORMALIZE_NUMBERS = register_optionflag('NORMALIZE_NUMBERS')
 
 COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
                     DONT_ACCEPT_BLANKLINE |
                     NORMALIZE_WHITESPACE |
                     ELLIPSIS |
-                    IGNORE_EXCEPTION_DETAIL)
+                    IGNORE_EXCEPTION_DETAIL |
+                    NORMALIZE_NUMBERS)
 
 REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
 REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
@@ -277,6 +280,72 @@
         if hasattr(self, "softspace"):
             del self.softspace
 
+# The number of digits of precision that must be equal for
+# NORMALIZE_NUMBERS to consider two numbers equal.
+_NORMALIZE_NUMBERS_PRECISION_THRESHOLD = 10
+
+# A regular expression that matches Python number literals.  This is
+# used by _normalize_numbers to look for numbers that should be
+# normalized.
+_NUMBER_LITERAL = re.compile(r'''
+    (\d+[.]\d*(?:[eE][-+]?\d+)?[jJ]? | # float (w/ digits left of ".")
+        [.]\d+(?:[eE][-+]?\d+)?[jJ]? | # float (no digits left of ".")
+     \d+      (?:[eE][-+]?\d+) [jJ]? | # float (no ".", exponent only)
+     \d                        [jJ]  | # float (no ".", imaginary only)
+     0[xX]\d+[lL]?                   | # hexint
+     0[0-7]*[lL]?                    | # octint or zero
+     \d+[lL]?                        ) # decint
+     ''', re.VERBOSE)
+
+def _normalize_numbers(want, got):
+    """
+    If all the numbers in `want` and `got` match (one-for-one), then
+    return a new version of `got` with the exact number strings from
+    `want` spliced in.  Two numbers match if `str` of their float
+    values are equal.  (I.e., `x` matches `y` if
+    `str(float(x))==str(float(y))`).
+    """
+    want_pieces = _NUMBER_LITERAL.split(want)
+    got_pieces = _NUMBER_LITERAL.split(got)
+
+    # If they don't have the same number of numbers, fail immediately.
+    if len(want_pieces) != len(got_pieces):
+        return got
+
+    # If any individual numbers don't match, then fail.
+    for i in range(1, len(got_pieces), 2):
+        w, g = eval(want_pieces[i]), eval(got_pieces[i])
+        if not _numbers_match(w, g):
+            return got
+
+    # Success; replace numbers in got w/ numbers from want.
+    for i in range(1, len(got_pieces), 2):
+        got_pieces[i] = want_pieces[i]
+    return ''.join(got_pieces)
+
+def _numbers_match(x, y):
+    """
+    A helper function for _normalize_numbers, that returns true if the
+    numbers `x` and `y` are close enough to match for NORMALIZE_NUMBERS.
+    """
+    # Equal numbers match.
+    if x == y:
+        return True
+    # Split up complex numbers into real & imag.
+    if isinstance(x, complex):
+        return (isinstance(y, complex) and
+                _numbers_match(x.real, y.real) and
+                _numbers_match(x.imag, y.imag))
+    # If the signs are different, they don't match.
+    if x*y < 0:
+        return False
+    # If one is zero and the other isn't, they don't match.
+    if x==0 or y==0:
+        return False
+    # They're not exactly equal, but are they close enough?
+    threshold = 10**-_NORMALIZE_NUMBERS_PRECISION_THRESHOLD
+    return (abs(x-y) / min(abs(x), abs(y))) < threshold
+
 # Worst-case linear-time ellipsis matching.
 def _ellipsis_match(want, got):
     """
@@ -1503,6 +1572,13 @@
             if got == want:
                 return True
 
+        # This flag causes doctest to treat numbers that are within a
+        # small threshold as if they are equal.
+        if optionflags & NORMALIZE_NUMBERS:
+            got = _normalize_numbers(want, got)
+            if got == want:
+                return True
+
         # The ELLIPSIS flag says to let the sequence "..." in `want`
         # match any substring in `got`.
         if optionflags & ELLIPSIS:
@@ -1783,6 +1859,7 @@
         NORMALIZE_WHITESPACE
         ELLIPSIS
         IGNORE_EXCEPTION_DETAIL
+        NORMALIZE_NUMBERS
         REPORT_UDIFF
         REPORT_CDIFF
         REPORT_NDIFF
@@ -1905,6 +1982,7 @@
         NORMALIZE_WHITESPACE
         ELLIPSIS
         IGNORE_EXCEPTION_DETAIL
+        NORMALIZE_NUMBERS
         REPORT_UDIFF
         REPORT_CDIFF
         REPORT_NDIFF



More information about the Python-checkins mailing list