Commit b0c59f3f authored by Tim Peters's avatar Tim Peters

A hack to ease compatibility with pre-2.3 Pythons: by default, doctest

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.
parent 0d03a56c
...@@ -398,6 +398,23 @@ def _test(): ...@@ -398,6 +398,23 @@ def _test():
import doctest, sys import doctest, sys
doctest.testmod() doctest.testmod()
\end{verbatim} \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} \end{enumerate}
......
...@@ -297,6 +297,9 @@ from inspect import isfunction as _isfunction ...@@ -297,6 +297,9 @@ from inspect import isfunction as _isfunction
from inspect import ismodule as _ismodule from inspect import ismodule as _ismodule
from inspect import classify_class_attrs as _classify_class_attrs 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, # Extract interactive examples from a string. Return a list of triples,
# (source, outcome, lineno). "source" is the source code, and ends # (source, outcome, lineno). "source" is the source code, and ends
# with a newline iff the source spans more than one line. "outcome" is # with a newline iff the source spans more than one line. "outcome" is
...@@ -414,7 +417,7 @@ def _tag_out(printer, *tag_msg_pairs): ...@@ -414,7 +417,7 @@ def _tag_out(printer, *tag_msg_pairs):
# that captures the examples' std output. Return (#failures, #tries). # that captures the examples' std output. Return (#failures, #tries).
def _run_examples_inner(out, fakeout, examples, globs, verbose, name, def _run_examples_inner(out, fakeout, examples, globs, verbose, name,
compileflags): compileflags, optionflags):
import sys, traceback import sys, traceback
OK, BOOM, FAIL = range(3) OK, BOOM, FAIL = range(3)
NADA = "nothing" NADA = "nothing"
...@@ -449,7 +452,11 @@ def _run_examples_inner(out, fakeout, examples, globs, verbose, name, ...@@ -449,7 +452,11 @@ def _run_examples_inner(out, fakeout, examples, globs, verbose, name,
state = BOOM state = BOOM
if state == OK: 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: if verbose:
out("ok\n") out("ok\n")
continue continue
...@@ -482,14 +489,16 @@ def _extract_future_flags(globs): ...@@ -482,14 +489,16 @@ def _extract_future_flags(globs):
# Run list of examples, in a shallow copy of context (dict) globs. # Run list of examples, in a shallow copy of context (dict) globs.
# Return (#failures, #tries). # Return (#failures, #tries).
def _run_examples(examples, globs, verbose, name, compileflags): def _run_examples(examples, globs, verbose, name, compileflags,
optionflags):
import sys import sys
saveout = sys.stdout saveout = sys.stdout
globs = globs.copy() globs = globs.copy()
try: try:
sys.stdout = fakeout = _SpoofOut() sys.stdout = fakeout = _SpoofOut()
x = _run_examples_inner(saveout.write, fakeout, examples, x = _run_examples_inner(saveout.write, fakeout, examples,
globs, verbose, name, compileflags) globs, verbose, name, compileflags,
optionflags)
finally: finally:
sys.stdout = saveout sys.stdout = saveout
# While Python gc can clean up most cycles on its own, it doesn't # 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): ...@@ -504,7 +513,7 @@ def _run_examples(examples, globs, verbose, name, compileflags):
return x return x
def run_docstring_examples(f, globs, verbose=0, name="NoName", 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__. """f, globs, verbose=0, name="NoName" -> run examples from f.__doc__.
Use (a shallow copy of) dict globs as the globals for execution. 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", ...@@ -533,7 +542,7 @@ def run_docstring_examples(f, globs, verbose=0, name="NoName",
return 0, 0 return 0, 0
if compileflags is None: if compileflags is None:
compileflags = _extract_future_flags(globs) 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): def is_private(prefix, base):
"""prefix, base -> true iff name prefix + "." + base is "private". """prefix, base -> true iff name prefix + "." + base is "private".
...@@ -637,8 +646,9 @@ Got: 84 ...@@ -637,8 +646,9 @@ Got: 84
""" """
def __init__(self, mod=None, globs=None, verbose=None, def __init__(self, mod=None, globs=None, verbose=None,
isprivate=None): isprivate=None, optionflags=0):
"""mod=None, globs=None, verbose=None, isprivate=None """mod=None, globs=None, verbose=None, isprivate=None,
optionflags=0
See doctest.__doc__ for an overview. See doctest.__doc__ for an overview.
...@@ -658,6 +668,8 @@ failures if false; by default, it's true iff "-v" is in sys.argv. ...@@ -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 Optional keyword arg "isprivate" specifies a function used to determine
whether a name is private. The default function is doctest.is_private; whether a name is private. The default function is doctest.is_private;
see its docs for details. see its docs for details.
See doctest.testmod docs for the meaning of optionflags.
""" """
if mod is None and globs is None: if mod is None and globs is None:
...@@ -678,6 +690,8 @@ see its docs for details. ...@@ -678,6 +690,8 @@ see its docs for details.
isprivate = is_private isprivate = is_private
self.isprivate = isprivate self.isprivate = isprivate
self.optionflags = optionflags
self.name2ft = {} # map name to (#failures, #trials) pair self.name2ft = {} # map name to (#failures, #trials) pair
self.compileflags = _extract_future_flags(globs) self.compileflags = _extract_future_flags(globs)
...@@ -714,7 +728,7 @@ see its docs for details. ...@@ -714,7 +728,7 @@ see its docs for details.
e = _extract_examples(s) e = _extract_examples(s)
if e: if e:
f, t = _run_examples(e, self.globs, self.verbose, name, f, t = _run_examples(e, self.globs, self.verbose, name,
self.compileflags) self.compileflags, self.optionflags)
if self.verbose: if self.verbose:
print f, "of", t, "examples failed in string", name print f, "of", t, "examples failed in string", name
self.__record_outcome(name, f, t) self.__record_outcome(name, f, t)
...@@ -1045,8 +1059,9 @@ see its docs for details. ...@@ -1045,8 +1059,9 @@ see its docs for details.
master = None master = None
def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, def testmod(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=1 """m=None, name=None, globs=None, verbose=None, isprivate=None,
report=True, optionflags=0
Test examples in docstrings in functions and classes reachable Test examples in docstrings in functions and classes reachable
from module m (or the current module if m is not supplied), starting 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, ...@@ -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 else prints nothing at the end. In verbose mode, the summary is
detailed, else very brief (in fact, empty if all tests passed). 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 Advanced tomfoolery: testmod runs methods of a local instance of
class doctest.Tester, then merges the results into (or creates) class doctest.Tester, then merges the results into (or creates)
global Tester instance doctest.master. Methods of doctest.master 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, ...@@ -1102,11 +1127,12 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
raise TypeError("testmod: module required; " + `m`) raise TypeError("testmod: module required; " + `m`)
if name is None: if name is None:
name = m.__name__ 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) failures, tries = tester.rundoc(m, name)
f, t = tester.rundict(m.__dict__, name, m) f, t = tester.rundict(m.__dict__, name, m)
failures = failures + f failures += f
tries = tries + t tries += t
if hasattr(m, "__test__"): if hasattr(m, "__test__"):
testdict = m.__test__ testdict = m.__test__
if testdict: if testdict:
...@@ -1114,8 +1140,8 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, ...@@ -1114,8 +1140,8 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
raise TypeError("testmod: module.__test__ must support " raise TypeError("testmod: module.__test__ must support "
".items(); " + `testdict`) ".items(); " + `testdict`)
f, t = tester.run__test__(testdict, name + ".__test__") f, t = tester.run__test__(testdict, name + ".__test__")
failures = failures + f failures += f
tries = tries + t tries += t
if report: if report:
tester.summarize() tester.summarize()
if master is None: if master is None:
...@@ -1174,7 +1200,22 @@ __test__ = {"_TestClass": _TestClass, ...@@ -1174,7 +1200,22 @@ __test__ = {"_TestClass": _TestClass,
>>> x = 1; y = 2 >>> x = 1; y = 2
>>> x + y, x * y >>> x + y, x * y
(3, 2) (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(): def _test():
......
...@@ -83,6 +83,14 @@ Extension modules ...@@ -83,6 +83,14 @@ Extension modules
Library 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 - The cgitb module has been extended to support plain text display (SF patch
569574). 569574).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment