]> granicus.if.org Git - python/commitdiff
A hack to ease compatibility with pre-2.3 Pythons: by default, doctest
authorTim Peters <tim.peters@gmail.com>
Fri, 27 Jun 2003 20:48:05 +0000 (20:48 +0000)
committerTim Peters <tim.peters@gmail.com>
Fri, 27 Jun 2003 20:48:05 +0000 (20:48 +0000)
now accepts "True" when a test expects "1", and similarly for "False"
versus "0".  This is un-doctest-like, but on balance makes it much
more pleasant to write doctests that pass under 2.2 and 2.3.  I expect
it to go away again, when 2.2 is forgotten.  In the meantime, there's
a new doctest module constant that can be passed to a new optional
argument, if you want to turn this behavior off.

Note that this substitution is very simple-minded:  the expected and
actual outputs have to consist of single tokens.  No attempt is made,
e.g., to accept [True, False] when a test expects [1, 0].  This is a
simple hack for simple tests, and I intend to keep it that way.

Doc/lib/libdoctest.tex
Lib/doctest.py
Misc/NEWS

index 9a795e949b36b58340eb7639fe4361c1103adbcb..253d6b48656870148b0bfe815f597075a371e058 100644 (file)
@@ -398,6 +398,23 @@ def _test():
     import doctest, sys
     doctest.testmod()
 \end{verbatim}
+
+\item WYSIWYG isn't always the case, starting in Python 2.3.  The
+  string form of boolean results changed from \code{"0"} and
+  \code{"1"} to \code{"False"} and \code{"True"} in Python 2.3.
+  This makes it clumsy to write a doctest showing boolean results that
+  passes under multiple versions of Python.  In Python 2.3, by default,
+  and as a special case, if an expected output block consists solely
+  of \code{"0"} and the actual output block consists solely of
+  \code{"False"}, that's accepted as an exact match, and similarly for
+  \code{"1"} versus \code{"True"}.  This behavior can be turned off by
+  passing the new (in 2.3) module constant
+  \constant{DONT_ACCEPT_TRUE_FOR_1} as the value of \function{testmod()}'s
+  new (in 2.3) optional \var{optionflags} argument.  Some years after
+  the integer spellings of booleans are history, this hack will
+  probably be removed again.
+
+
 \end{enumerate}
 
 
index c01606dca6ae739533fe4472ac3c21f885d8ff47..8b379cdf222f1595bb05fa3486dbb52180149214 100644 (file)
@@ -297,6 +297,9 @@ from inspect import isfunction as _isfunction
 from inspect import ismodule   as _ismodule
 from inspect import classify_class_attrs as _classify_class_attrs
 
+# Option constants.
+DONT_ACCEPT_TRUE_FOR_1 = 1 << 0
+
 # Extract interactive examples from a string.  Return a list of triples,
 # (source, outcome, lineno).  "source" is the source code, and ends
 # with a newline iff the source spans more than one line.  "outcome" is
@@ -414,7 +417,7 @@ def _tag_out(printer, *tag_msg_pairs):
 # that captures the examples' std output.  Return (#failures, #tries).
 
 def _run_examples_inner(out, fakeout, examples, globs, verbose, name,
-                        compileflags):
+                        compileflags, optionflags):
     import sys, traceback
     OK, BOOM, FAIL = range(3)
     NADA = "nothing"
@@ -449,7 +452,11 @@ def _run_examples_inner(out, fakeout, examples, globs, verbose, name,
                 state = BOOM
 
         if state == OK:
-            if got == want:
+            if (got == want or
+                (not (optionflags & DONT_ACCEPT_TRUE_FOR_1) and
+                 (got, want) in (("True\n", "1\n"), ("False\n", "0\n"))
+                )
+               ):
                 if verbose:
                     out("ok\n")
                 continue
@@ -482,14 +489,16 @@ def _extract_future_flags(globs):
 # Run list of examples, in a shallow copy of context (dict) globs.
 # Return (#failures, #tries).
 
-def _run_examples(examples, globs, verbose, name, compileflags):
+def _run_examples(examples, globs, verbose, name, compileflags,
+                  optionflags):
     import sys
     saveout = sys.stdout
     globs = globs.copy()
     try:
         sys.stdout = fakeout = _SpoofOut()
         x = _run_examples_inner(saveout.write, fakeout, examples,
-                                globs, verbose, name, compileflags)
+                                globs, verbose, name, compileflags,
+                                optionflags)
     finally:
         sys.stdout = saveout
         # While Python gc can clean up most cycles on its own, it doesn't
@@ -504,7 +513,7 @@ def _run_examples(examples, globs, verbose, name, compileflags):
     return x
 
 def run_docstring_examples(f, globs, verbose=0, name="NoName",
-                           compileflags=None):
+                           compileflags=None, optionflags=0):
     """f, globs, verbose=0, name="NoName" -> run examples from f.__doc__.
 
     Use (a shallow copy of) dict globs as the globals for execution.
@@ -533,7 +542,7 @@ def run_docstring_examples(f, globs, verbose=0, name="NoName",
         return 0, 0
     if compileflags is None:
         compileflags = _extract_future_flags(globs)
-    return _run_examples(e, globs, verbose, name, compileflags)
+    return _run_examples(e, globs, verbose, name, compileflags, optionflags)
 
 def is_private(prefix, base):
     """prefix, base -> true iff name prefix + "." + base is "private".
@@ -637,8 +646,9 @@ Got: 84
 """
 
     def __init__(self, mod=None, globs=None, verbose=None,
-                 isprivate=None):
-        """mod=None, globs=None, verbose=None, isprivate=None
+                 isprivate=None, optionflags=0):
+        """mod=None, globs=None, verbose=None, isprivate=None,
+optionflags=0
 
 See doctest.__doc__ for an overview.
 
@@ -658,6 +668,8 @@ failures if false; by default, it's true iff "-v" is in sys.argv.
 Optional keyword arg "isprivate" specifies a function used to determine
 whether a name is private.  The default function is doctest.is_private;
 see its docs for details.
+
+See doctest.testmod docs for the meaning of optionflags.
 """
 
         if mod is None and globs is None:
@@ -678,6 +690,8 @@ see its docs for details.
             isprivate = is_private
         self.isprivate = isprivate
 
+        self.optionflags = optionflags
+
         self.name2ft = {}   # map name to (#failures, #trials) pair
 
         self.compileflags = _extract_future_flags(globs)
@@ -714,7 +728,7 @@ see its docs for details.
         e = _extract_examples(s)
         if e:
             f, t = _run_examples(e, self.globs, self.verbose, name,
-                                 self.compileflags)
+                                 self.compileflags, self.optionflags)
         if self.verbose:
             print f, "of", t, "examples failed in string", name
         self.__record_outcome(name, f, t)
@@ -1045,8 +1059,9 @@ see its docs for details.
 master = None
 
 def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
-               report=1):
-    """m=None, name=None, globs=None, verbose=None, isprivate=None, report=1
+               report=True, optionflags=0):
+    """m=None, name=None, globs=None, verbose=None, isprivate=None,
+       report=True, optionflags=0
 
     Test examples in docstrings in functions and classes reachable
     from module m (or the current module if m is not supplied), starting
@@ -1080,6 +1095,16 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
     else prints nothing at the end.  In verbose mode, the summary is
     detailed, else very brief (in fact, empty if all tests passed).
 
+    Optional keyword arg "optionflags" or's together module constants,
+    and defaults to 0.  This is new in 2.3.  Possible values:
+
+        DONT_ACCEPT_TRUE_FOR_1
+            By default, if an expected output block contains just "1",
+            an actual output block containing just "True" is considered
+            to be a match, and similarly for "0" versus "False".  When
+            DONT_ACCEPT_TRUE_FOR_1 is specified, neither substitution
+            is allowed.
+
     Advanced tomfoolery:  testmod runs methods of a local instance of
     class doctest.Tester, then merges the results into (or creates)
     global Tester instance doctest.master.  Methods of doctest.master
@@ -1102,11 +1127,12 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
         raise TypeError("testmod: module required; " + `m`)
     if name is None:
         name = m.__name__
-    tester = Tester(m, globs=globs, verbose=verbose, isprivate=isprivate)
+    tester = Tester(m, globs=globs, verbose=verbose, isprivate=isprivate,
+                    optionflags=optionflags)
     failures, tries = tester.rundoc(m, name)
     f, t = tester.rundict(m.__dict__, name, m)
-    failures = failures + f
-    tries = tries + t
+    failures += f
+    tries += t
     if hasattr(m, "__test__"):
         testdict = m.__test__
         if testdict:
@@ -1114,8 +1140,8 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
                 raise TypeError("testmod: module.__test__ must support "
                                 ".items(); " + `testdict`)
             f, t = tester.run__test__(testdict, name + ".__test__")
-            failures = failures + f
-            tries = tries + t
+            failures += f
+            tries += t
     if report:
         tester.summarize()
     if master is None:
@@ -1174,7 +1200,22 @@ __test__ = {"_TestClass": _TestClass,
                       >>> x = 1; y = 2
                       >>> x + y, x * y
                       (3, 2)
-                      """
+                      """,
+            "bool-int equivalence": r"""
+                                    In 2.2, boolean expressions displayed
+                                    0 or 1.  By default, we still accept
+                                    them.  This can be disabled by passing
+                                    DONT_ACCEPT_TRUE_FOR_1 to the new
+                                    optionflags argument.
+                                    >>> 4 == 4
+                                    1
+                                    >>> 4 == 4
+                                    True
+                                    >>> 4 > 4
+                                    0
+                                    >>> 4 > 4
+                                    False
+                                    """,
            }
 
 def _test():
index e3dc76304ac0bbc730623b70ec1193e359f1a080..c562583b4a8ca84d7bacfcb97172ad424ed3aa3c 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -83,6 +83,14 @@ Extension modules
 Library
 -------
 
+- For compatibility with doctests created before 2.3, if an expected
+  output block consists solely of "1" and the actual output block
+  consists solely of "True", it's accepted as a match; similarly
+  for "0" and "False".  This is quite un-doctest-like, but is practical.
+  The behavior can be disabled by passing the new doctest module
+  constant DONT_ACCEPT_TRUE_FOR_1 to the new optionflags optional
+  argument.
+
 - The cgitb module has been extended to support plain text display (SF patch
   569574).