]> granicus.if.org Git - python/commitdiff
Edward Loper's cool and massive refactoring of doctest.py, merged from
authorTim Peters <tim.peters@gmail.com>
Wed, 4 Aug 2004 18:46:34 +0000 (18:46 +0000)
committerTim Peters <tim.peters@gmail.com>
Wed, 4 Aug 2004 18:46:34 +0000 (18:46 +0000)
the tim-doctest-merge-24a2 tag on the the tim-doctest-branch branch.
We did development on the branch in case it wouldn't land in time for
2.4a2, but the branch looked good:  Edward's tests passed there, ditto
Python's tests, and ditto the Zope3 tests.  Together, those hit doctest
heavily.

Lib/doctest.py
Lib/test/test_doctest.py
Misc/NEWS

index 69047db516de6f3588636913ff1360f8db47f115..ecf0e1ac6deca124e444b1fba2fc25831bff26a7 100644 (file)
@@ -1,9 +1,12 @@
 # Module doctest.
-# Released to the public domain 16-Jan-2001,
-# by Tim Peters (tim.one@home.com).
+# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org).
+# Significant enhancements by:
+#     Jim Fulton
+#     Edward Loper
 
 # Provided as-is; use at your own risk; no warranty; no promises; enjoy!
 
+# [XX] This docstring is out-of-date:
 r"""Module doctest -- a framework for running examples in docstrings.
 
 NORMAL USAGE
@@ -285,281 +288,64 @@ Test passed.
 """
 
 __all__ = [
+    'is_private',
+    'Example',
+    'DocTest',
+    'DocTestFinder',
+    'DocTestRunner',
     'testmod',
     'run_docstring_examples',
-    'is_private',
     'Tester',
+    'DocTestTestCase',
     'DocTestSuite',
     'testsource',
     'debug',
-    'master',
+#    'master',
 ]
 
 import __future__
 
-import re
-PS1 = ">>>"
-PS2 = "..."
-_isPS1 = re.compile(r"(\s*)" + re.escape(PS1)).match
-_isPS2 = re.compile(r"(\s*)" + re.escape(PS2)).match
-_isEmpty = re.compile(r"\s*$").match
-_isComment = re.compile(r"\s*#").match
-del re
-
-from types import StringTypes as _StringTypes
-
-from inspect import isclass    as _isclass
-from inspect import isfunction as _isfunction
-from inspect import ismethod as _ismethod
-from inspect import ismodule   as _ismodule
-from inspect import classify_class_attrs as _classify_class_attrs
+import sys, traceback, inspect, linecache, re, types
+import unittest, difflib, tempfile
+from StringIO import StringIO
 
 # 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
-# the expected output if any, else an empty string.  When not empty,
-# outcome always ends with a newline.  "lineno" is the line number,
-# 0-based wrt the start of the string, of the first source line.
-
-def _extract_examples(s):
-    isPS1, isPS2 = _isPS1, _isPS2
-    isEmpty, isComment = _isEmpty, _isComment
-    examples = []
-    lines = s.split("\n")
-    i, n = 0, len(lines)
-    while i < n:
-        line = lines[i]
-        i = i + 1
-        m = isPS1(line)
-        if m is None:
-            continue
-        j = m.end(0)  # beyond the prompt
-        if isEmpty(line, j) or isComment(line, j):
-            # a bare prompt or comment -- not interesting
-            continue
-        lineno = i - 1
-        if line[j] != " ":
-            raise ValueError("line %r of docstring lacks blank after %s: %s" %
-                             (lineno, PS1, line))
-        j = j + 1
-        blanks = m.group(1)
-        nblanks = len(blanks)
-        # suck up this and following PS2 lines
-        source = []
-        while 1:
-            source.append(line[j:])
-            line = lines[i]
-            m = isPS2(line)
-            if m:
-                if m.group(1) != blanks:
-                    raise ValueError("inconsistent leading whitespace "
-                        "in line %r of docstring: %s" % (i, line))
-                i = i + 1
-            else:
-                break
-        if len(source) == 1:
-            source = source[0]
-        else:
-            # get rid of useless null line from trailing empty "..."
-            if source[-1] == "":
-                del source[-1]
-            source = "\n".join(source) + "\n"
-        # suck up response
-        if isPS1(line) or isEmpty(line):
-            expect = ""
-        else:
-            expect = []
-            while 1:
-                if line[:nblanks] != blanks:
-                    raise ValueError("inconsistent leading whitespace "
-                        "in line %r of docstring: %s" % (i, line))
-                expect.append(line[nblanks:])
-                i = i + 1
-                line = lines[i]
-                if isPS1(line) or isEmpty(line):
-                    break
-            expect = "\n".join(expect) + "\n"
-        examples.append( (source, expect, lineno) )
-    return examples
-
-# Capture stdout when running examples.
-
-class _SpoofOut:
-    def __init__(self):
-        self.clear()
-    def write(self, s):
-        self.buf.append(s)
-    def get(self):
-        guts = "".join(self.buf)
-        # If anything at all was written, make sure there's a trailing
-        # newline.  There's no way for the expected output to indicate
-        # that a trailing newline is missing.
-        if guts and not guts.endswith("\n"):
-            guts = guts + "\n"
-        # Prevent softspace from screwing up the next test case, in
-        # case they used print with a trailing comma in an example.
-        if hasattr(self, "softspace"):
-            del self.softspace
-        return guts
-    def clear(self):
-        self.buf = []
-        if hasattr(self, "softspace"):
-            del self.softspace
-    def flush(self):
-        # JPython calls flush
-        pass
-
-# Display some tag-and-msg pairs nicely, keeping the tag and its msg
-# on the same line when that makes sense.
-
-def _tag_out(printer, *tag_msg_pairs):
-    for tag, msg in tag_msg_pairs:
-        printer(tag + ":")
-        msg_has_nl = msg[-1:] == "\n"
-        msg_has_two_nl = msg_has_nl and \
-                        msg.find("\n") < len(msg) - 1
-        if len(tag) + len(msg) < 76 and not msg_has_two_nl:
-            printer(" ")
-        else:
-            printer("\n")
-        printer(msg)
-        if not msg_has_nl:
-            printer("\n")
-
-# Run list of examples, in context globs.  "out" can be used to display
-# stuff to "the real" stdout, and fakeout is an instance of _SpoofOut
-# that captures the examples' std output.  Return (#failures, #tries).
-
-def _run_examples_inner(out, fakeout, examples, globs, verbose, name,
-                        compileflags, optionflags):
-    import sys, traceback
-    OK, BOOM, FAIL = range(3)
-    NADA = "nothing"
-    stderr = _SpoofOut()
-    failures = 0
-    for source, want, lineno in examples:
-        if verbose:
-            _tag_out(out, ("Trying", source),
-                          ("Expecting", want or NADA))
-        fakeout.clear()
-        try:
-            exec compile(source, "<string>", "single",
-                         compileflags, 1) in globs
-            got = fakeout.get()
-            state = OK
-        except KeyboardInterrupt:
-            raise
-        except:
-            # See whether the exception was expected.
-            if want.find("Traceback (innermost last):\n") == 0 or \
-               want.find("Traceback (most recent call last):\n") == 0:
-                # Only compare exception type and value - the rest of
-                # the traceback isn't necessary.
-                want = want.split('\n')[-2] + '\n'
-                exc_type, exc_val = sys.exc_info()[:2]
-                got = traceback.format_exception_only(exc_type, exc_val)[-1]
-                state = OK
-            else:
-                # unexpected exception
-                stderr.clear()
-                traceback.print_exc(file=stderr)
-                state = BOOM
-
-        if state == OK:
-            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
-            state = FAIL
-
-        assert state in (FAIL, BOOM)
-        failures = failures + 1
-        out("*" * 65 + "\n")
-        _tag_out(out, ("Failure in example", source))
-        out("from line #%r of %s\n" % (lineno, name))
-        if state == FAIL:
-            _tag_out(out, ("Expected", want or NADA), ("Got", got))
-        else:
-            assert state == BOOM
-            _tag_out(out, ("Exception raised", stderr.get()))
-
-    return failures, len(examples)
-
-# Get the future-flags associated with the future features that have been
-# imported into globs.
-
-def _extract_future_flags(globs):
-    flags = 0
-    for fname in __future__.all_feature_names:
-        feature = globs.get(fname, None)
-        if feature is getattr(__future__, fname):
-            flags |= feature.compiler_flag
-    return flags
-
-# Run list of examples, in a shallow copy of context (dict) globs.
-# Return (#failures, #tries).
-
-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,
-                                optionflags)
-    finally:
-        sys.stdout = saveout
-        # While Python gc can clean up most cycles on its own, it doesn't
-        # chase frame objects.  This is especially irksome when running
-        # generator tests that raise exceptions, because a named generator-
-        # iterator gets an entry in globs, and the generator-iterator
-        # object's frame's traceback info points back to globs.  This is
-        # easy to break just by clearing the namespace.  This can also
-        # help to break other kinds of cycles, and even for cycles that
-        # gc can break itself it's better to break them ASAP.
-        globs.clear()
-    return x
-
-def run_docstring_examples(f, globs, verbose=0, name="NoName",
-                           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.
-    Return (#failures, #tries).
-
-    If optional arg verbose is true, print stuff even if there are no
-    failures.
-    Use string name in failure msgs.
-    """
-
-    try:
-        doc = f.__doc__
-        if not doc:
-            # docstring empty or None
-            return 0, 0
-        # just in case CT invents a doc object that has to be forced
-        # to look like a string <0.9 wink>
-        doc = str(doc)
-    except KeyboardInterrupt:
-        raise
-    except:
-        return 0, 0
-
-    e = _extract_examples(doc)
-    if not e:
-        return 0, 0
-    if compileflags is None:
-        compileflags = _extract_future_flags(globs)
-    return _run_examples(e, globs, verbose, name, compileflags, optionflags)
+DONT_ACCEPT_BLANKLINE = 1 << 1
+NORMALIZE_WHITESPACE = 1 << 2
+ELLIPSIS = 1 << 3
+UNIFIED_DIFF = 1 << 4
+CONTEXT_DIFF = 1 << 5
+
+OPTIONFLAGS_BY_NAME = {
+    'DONT_ACCEPT_TRUE_FOR_1': DONT_ACCEPT_TRUE_FOR_1,
+    'DONT_ACCEPT_BLANKLINE': DONT_ACCEPT_BLANKLINE,
+    'NORMALIZE_WHITESPACE': NORMALIZE_WHITESPACE,
+    'ELLIPSIS': ELLIPSIS,
+    'UNIFIED_DIFF': UNIFIED_DIFF,
+    'CONTEXT_DIFF': CONTEXT_DIFF,
+    }
+
+# Special string markers for use in `want` strings:
+BLANKLINE_MARKER = '<BLANKLINE>'
+ELLIPSIS_MARKER = '...'
+
+######################################################################
+## Table of Contents
+######################################################################
+# 1. Utility Functions
+# 2. Example & DocTest -- store test cases
+# 3. DocTest Finder -- extracts test cases from objects
+# 4. DocTest Runner -- runs test cases
+# 5. Test Functions -- convenient wrappers for testing
+# 6. Tester Class -- for backwards compatibility
+# 7. Unittest Support
+# 8. Debugging Support
+# 9. Example Usage
+
+######################################################################
+## 1. Utility Functions
+######################################################################
 
 def is_private(prefix, base):
     """prefix, base -> true iff name prefix + "." + base is "private".
@@ -585,391 +371,1016 @@ def is_private(prefix, base):
     >>> is_private("", "")  # senseless but consistent
     False
     """
-
     return base[:1] == "_" and not base[:2] == "__" == base[-2:]
 
-# Determine if a class of function was defined in the given module.
-
-def _from_module(module, object):
-    if _isfunction(object):
-        return module.__dict__ is object.func_globals
-    if _isclass(object):
-        return module.__name__ == object.__module__
-    raise ValueError("object must be a class or function")
-
-class Tester:
-    """Class Tester -- runs docstring examples and accumulates stats.
-
-In normal use, function doctest.testmod() hides all this from you,
-so use that if you can.  Create your own instances of Tester to do
-fancier things.
-
-Methods:
-    runstring(s, name)
-        Search string s for examples to run; use name for logging.
-        Return (#failures, #tries).
-
-    rundoc(object, name=None)
-        Search object.__doc__ for examples to run; use name (or
-        object.__name__) for logging.  Return (#failures, #tries).
-
-    rundict(d, name, module=None)
-        Search for examples in docstrings in all of d.values(); use name
-        for logging.  Exclude functions and classes not defined in module
-        if specified.  Return (#failures, #tries).
-
-    run__test__(d, name)
-        Treat dict d like module.__test__.  Return (#failures, #tries).
+def _extract_future_flags(globs):
+    """
+    Return the compiler-flags associated with the future features that
+    have been imported into the given namespace (globs).
+    """
+    flags = 0
+    for fname in __future__.all_feature_names:
+        feature = globs.get(fname, None)
+        if feature is getattr(__future__, fname):
+            flags |= feature.compiler_flag
+    return flags
 
-    summarize(verbose=None)
-        Display summary of testing results, to stdout.  Return
-        (#failures, #tries).
+def _normalize_module(module, depth=2):
+    """
+    Return the module specified by `module`.  In particular:
+      - If `module` is a module, then return module.
+      - If `module` is a string, then import and return the
+        module with that name.
+      - If `module` is None, then return the calling module.
+        The calling module is assumed to be the module of
+        the stack frame at the given depth in the call stack.
+    """
+    if inspect.ismodule(module):
+        return module
+    elif isinstance(module, (str, unicode)):
+        return __import__(module, globals(), locals(), ["*"])
+    elif module is None:
+        return sys.modules[sys._getframe(depth).f_globals['__name__']]
+    else:
+        raise TypeError("Expected a module, string, or None")
 
-    merge(other)
-        Merge in the test results from Tester instance "other".
+def _tag_msg(tag, msg, indent_msg=True):
+    """
+    Return a string that displays a tag-and-message pair nicely,
+    keeping the tag and its message on the same line when that
+    makes sense.  If `indent_msg` is true, then messages that are
+    put on separate lines will be indented.
+    """
+    # What string should we use to indent contents?
+    INDENT = '    '
+
+    # If the message doesn't end in a newline, then add one.
+    if msg[-1:] != '\n':
+        msg += '\n'
+    # If the message is short enough, and contains no internal
+    # newlines, then display it on the same line as the tag.
+    # Otherwise, display the tag on its own line.
+    if (len(tag) + len(msg) < 75 and
+        msg.find('\n', 0, len(msg)-1) == -1):
+        return '%s: %s' % (tag, msg)
+    else:
+        if indent_msg:
+            msg = '\n'.join([INDENT+l for l in msg.split('\n')])
+            msg = msg[:-len(INDENT)]
+        return '%s:\n%s' % (tag, msg)
+
+# Override some StringIO methods.
+class _SpoofOut(StringIO):
+    def getvalue(self):
+        result = StringIO.getvalue(self)
+        # If anything at all was written, make sure there's a trailing
+        # newline.  There's no way for the expected output to indicate
+        # that a trailing newline is missing.
+        if result and not result.endswith("\n"):
+            result += "\n"
+        # Prevent softspace from screwing up the next test case, in
+        # case they used print with a trailing comma in an example.
+        if hasattr(self, "softspace"):
+            del self.softspace
+        return result
 
->>> from doctest import Tester
->>> t = Tester(globs={'x': 42}, verbose=0)
->>> t.runstring(r'''
-...      >>> x = x * 2
-...      >>> print x
-...      42
-... ''', 'XYZ')
-*****************************************************************
-Failure in example: print x
-from line #2 of XYZ
-Expected: 42
-Got: 84
-(1, 2)
->>> t.runstring(">>> x = x * 2\\n>>> print x\\n84\\n", 'example2')
-(0, 2)
->>> t.summarize()
-*****************************************************************
-1 items had failures:
-   1 of   2 in XYZ
-***Test Failed*** 1 failures.
-(1, 4)
->>> t.summarize(verbose=1)
-1 items passed all tests:
-   2 tests in example2
-*****************************************************************
-1 items had failures:
-   1 of   2 in XYZ
-4 tests in 2 items.
-3 passed and 1 failed.
-***Test Failed*** 1 failures.
-(1, 4)
->>>
-"""
+    def truncate(self,   size=None):
+        StringIO.truncate(self, size)
+        if hasattr(self, "softspace"):
+            del self.softspace
 
-    def __init__(self, mod=None, globs=None, verbose=None,
-                 isprivate=None, optionflags=0):
-        """mod=None, globs=None, verbose=None, isprivate=None,
-optionflags=0
+######################################################################
+## 2. Example & DocTest
+######################################################################
+## - An "example" is a <source, want> pair, where "source" is a
+##   fragment of source code, and "want" is the expected output for
+##   "source."  The Example class also includes information about
+##   where the example was extracted from.
+##
+## - A "doctest" is a collection of examples extracted from a string
+##   (such as an object's docstring).  The DocTest class also includes
+##   information about where the string was extracted from.
+
+class Example:
+    """
+    A single doctest example, consisting of source code and expected
+    output.  Example defines the following attributes:
 
-See doctest.__doc__ for an overview.
+      - source: The source code that should be run.  It ends with a
+        newline iff the source spans more than one line.
 
-Optional keyword arg "mod" is a module, whose globals are used for
-executing examples.  If not specified, globs must be specified.
+      - want: The expected output from running the source code.  If
+        not empty, then this string ends with a newline.
 
-Optional keyword arg "globs" gives a dict to be used as the globals
-when executing examples; if not specified, use the globals from
-module mod.
+      - lineno: The line number within the DocTest string containing
+        this Example where the Example begins.  This line number is
+        zero-based, with respect to the beginning of the DocTest.
+    """
+    def __init__(self, source, want, lineno):
+        # Check invariants.
+        assert (source[-1:] == '\n') == ('\n' in source[:-1])
+        assert want == '' or want[-1] == '\n'
+        # Store properties.
+        self.source = source
+        self.want = want
+        self.lineno = lineno
+
+class DocTest:
+    """
+    A collection of doctest examples that should be run in a single
+    namespace.  Each DocTest defines the following attributes:
 
-In either case, a copy of the dict is used for each docstring
-examined.
+      - examples: the list of examples.
 
-Optional keyword arg "verbose" prints lots of stuff if true, only
-failures if false; by default, it's true iff "-v" is in sys.argv.
+      - globs: The namespace (aka globals) that the examples should
+        be run in.
 
-Optional keyword arg "isprivate" specifies a function used to determine
-whether a name is private.  The default function is to assume that
-no functions are private.  The "isprivate" arg may be set to
-doctest.is_private in order to skip over functions marked as private
-using an underscore naming convention; see its docs for details.
+      - name: A name identifying the DocTest (typically, the name of
+        the object whose docstring this DocTest was extracted from).
 
-See doctest.testmod docs for the meaning of optionflags.
-"""
+      - filename: The name of the file that this DocTest was extracted
+        from.
 
-        if mod is None and globs is None:
-            raise TypeError("Tester.__init__: must specify mod or globs")
-        if mod is not None and not _ismodule(mod):
-            raise TypeError("Tester.__init__: mod must be a module; %r" % (mod,))
-        if globs is None:
-            globs = mod.__dict__
-        self.globs = globs
+      - lineno: The line number within filename where this DocTest
+        begins.  This line number is zero-based, with respect to the
+        beginning of the file.
+    """
+    def __init__(self, docstring, globs, name, filename, lineno):
+        """
+        Create a new DocTest, by extracting examples from `docstring`.
+        The DocTest's globals are initialized with a copy of `globs`.
+        """
+        # Store a copy of the globals
+        self.globs = globs.copy()
+        # Store identifying information
+        self.name = name
+        self.filename = filename
+        self.lineno = lineno
+        # Parse the docstring.
+        self.examples = self._parse(docstring)
+
+    _PS1 = ">>>"
+    _PS2 = "..."
+    _isPS1 = re.compile(r"(\s*)" + re.escape(_PS1)).match
+    _isPS2 = re.compile(r"(\s*)" + re.escape(_PS2)).match
+    _isEmpty = re.compile(r"\s*$").match
+    _isComment = re.compile(r"\s*#").match
+
+    def _parse(self, string):
+        if not string.endswith('\n'):
+            string += '\n'
+        examples = []
+        isPS1, isPS2 = self._isPS1, self._isPS2
+        isEmpty, isComment = self._isEmpty, self._isComment
+        lines = string.split("\n")
+        i, n = 0, len(lines)
+        while i < n:
+            # Search for an example (a PS1 line).
+            line = lines[i]
+            i += 1
+            m = isPS1(line)
+            if m is None:
+                continue
+            # line is a PS1 line.
+            j = m.end(0)  # beyond the prompt
+            if isEmpty(line, j) or isComment(line, j):
+                # a bare prompt or comment -- not interesting
+                continue
+            # line is a non-trivial PS1 line.
+            lineno = i - 1
+            if line[j] != " ":
+                raise ValueError('line %r of the docstring for %s lacks '
+                                 'blank after %s: %r' %
+                                 (lineno, self.name, self._PS1, line))
+
+            j += 1
+            blanks = m.group(1)
+            nblanks = len(blanks)
+            # suck up this and following PS2 lines
+            source = []
+            while 1:
+                source.append(line[j:])
+                line = lines[i]
+                m = isPS2(line)
+                if m:
+                    if m.group(1) != blanks:
+                        raise ValueError('line %r of the docstring for %s '
+                            'has inconsistent leading whitespace: %r' %
+                            (i, self.name, line))
+                    i += 1
+                else:
+                    break
+            # get rid of useless null line from trailing empty "..."
+            if source[-1] == "":
+                assert len(source) > 1
+                del source[-1]
+            if len(source) == 1:
+                source = source[0]
+            else:
+                source = "\n".join(source) + "\n"
+            # suck up response
+            if isPS1(line) or isEmpty(line):
+                want = ""
+            else:
+                want = []
+                while 1:
+                    if line[:nblanks] != blanks:
+                        raise ValueError('line %r of the docstring for %s '
+                            'has inconsistent leading whitespace: %r' %
+                            (i, self.name, line))
+                    want.append(line[nblanks:])
+                    i += 1
+                    line = lines[i]
+                    if isPS1(line) or isEmpty(line):
+                        break
+                want = "\n".join(want) + "\n"
+            examples.append(Example(source, want, lineno))
+        return examples
 
-        if verbose is None:
-            import sys
-            verbose = "-v" in sys.argv
-        self.verbose = verbose
+    def __repr__(self):
+        if len(self.examples) == 0:
+            examples = 'no examples'
+        elif len(self.examples) == 1:
+            examples = '1 example'
+        else:
+            examples = '%d examples' % len(self.examples)
+        return ('<DocTest %s from %s:%s (%s)>' %
+                (self.name, self.filename, self.lineno, examples))
 
-        # By default, assume that nothing is private
-        if isprivate is None:
-            isprivate = lambda prefix, base:  0
-        self.isprivate = isprivate
 
-        self.optionflags = optionflags
+    # This lets us sort tests by name:
+    def __cmp__(self, other):
+        if not isinstance(other, DocTest):
+            return -1
+        return cmp((self.name, self.filename, self.lineno, id(self)),
+                   (other.name, other.filename, other.lineno, id(other)))
 
-        self.name2ft = {}   # map name to (#failures, #trials) pair
+######################################################################
+## 3. DocTest Finder
+######################################################################
 
-        self.compileflags = _extract_future_flags(globs)
+class DocTestFinder:
+    """
+    A class used to extract the DocTests that are relevant to a given
+    object, from its docstring and the docstrings of its contained
+    objects.  Doctests can currently be extracted from the following
+    object types: modules, functions, classes, methods, staticmethods,
+    classmethods, and properties.
+
+    An optional name filter and an optional object filter may be
+    passed to the constructor, to restrict which contained objects are
+    examined by the doctest finder:
+
+      - The name filter is a function `f(prefix, base)`, that returns
+        true if an object named `prefix.base` should be ignored.
+      - The object filter is a function `f(obj)` that returns true
+        if the given object should be ignored.
+
+    Each object is ignored if either filter function returns true for
+    that object.  These filter functions are applied when examining
+    the contents of a module or of a class, but not when examining a
+    module's `__test__` dictionary.  By default, no objects are
+    ignored.
+    """
 
-    def runstring(self, s, name):
+    def __init__(self, verbose=False, namefilter=None, objfilter=None,
+                 recurse=True):
         """
-        s, name -> search string s for examples to run, logging as name.
+        Create a new doctest finder.
 
-        Use string name as the key for logging the outcome.
-        Return (#failures, #examples).
+        If the optional argument `recurse` is false, then `find` will
+        only examine the given object, and not any contained objects.
+        """
+        self._verbose = verbose
+        self._namefilter = namefilter
+        self._objfilter = objfilter
+        self._recurse = recurse
 
-        >>> t = Tester(globs={}, verbose=1)
-        >>> test = r'''
-        ...    # just an example
-        ...    >>> x = 1 + 2
-        ...    >>> x
-        ...    3
-        ... '''
-        >>> t.runstring(test, "Example")
-        Running string Example
-        Trying: x = 1 + 2
-        Expecting: nothing
-        ok
-        Trying: x
-        Expecting: 3
-        ok
-        0 of 2 examples failed in string Example
-        (0, 2)
+    def find(self, obj, name=None, module=None, globs=None,
+             extraglobs=None, ignore_imports=True):
         """
+        Return a list of the DocTests that are defined by the given
+        object's docstring, or by any of its contained objects'
+        docstrings.
+
+        The optional parameter `module` is the module that contains
+        the given object.  If the module is not specified, then the
+        test finder will attempt to automatically determine the
+        correct module.  The object's module is used:
+
+            - As a default namespace, if `globs` is not specified.
+            - To prevent the DocTestFinder from extracting DocTests
+              from objects that are imported from other modules
+              (as long as `ignore_imports` is true).
+            - To find the name of the file containing the object.
+            - To help find the line number of the object within its
+              file.
+
+        The globals for each DocTest is formed by combining `globs`
+        and `extraglobs` (bindings in `extraglobs` override bindings
+        in `globs`).  A new copy of the globals dictionary is created
+        for each DocTest.  If `globs` is not specified, then it
+        defaults to the module's `__dict__`, if specified, or {}
+        otherwise.  If `extraglobs` is not specified, then it defaults
+        to {}.
+
+        If the optional flag `ignore_imports` is true, then the
+        doctest finder will ignore any contained objects whose module
+        does not match `module`.  Otherwise, it will extract tests
+        from all contained objects, including imported objects.
+        """
+        # If name was not specified, then extract it from the object.
+        if name is None:
+            name = getattr(obj, '__name__', None)
+            if name is None:
+                raise ValueError("DocTestFinder.find: name must be given "
+                        "when obj.__name__ doesn't exist: %r" %
+                                 (type(obj),))
+
+        # Find the module that contains the given object (if obj is
+        # a module, then module=obj.).  Note: this may fail, in which
+        # case module will be None.
+        if module is None:
+            module = inspect.getmodule(obj)
+
+        # Read the module's source code.  This is used by
+        # DocTestFinder._find_lineno to find the line number for a
+        # given object's docstring.
+        try:
+            file = inspect.getsourcefile(obj) or inspect.getfile(obj)
+            source_lines = linecache.getlines(file)
+            if not source_lines:
+                source_lines = None
+        except TypeError:
+            source_lines = None
+
+        # Initialize globals, and merge in extraglobs.
+        if globs is None:
+            if module is None:
+                globs = {}
+            else:
+                globs = module.__dict__.copy()
+        else:
+            globs = globs.copy()
+        if extraglobs is not None:
+            globs.update(extraglobs)
 
-        if self.verbose:
-            print "Running string", name
-        f = t = 0
-        e = _extract_examples(s)
-        if e:
-            f, t = _run_examples(e, self.globs, self.verbose, name,
-                                 self.compileflags, self.optionflags)
-        if self.verbose:
-            print f, "of", t, "examples failed in string", name
-        self.__record_outcome(name, f, t)
-        return f, t
+        # Recursively expore `obj`, extracting DocTests.
+        tests = []
+        self._find(tests, obj, name, module, source_lines,
+                   globs, ignore_imports, {})
+        return tests
 
-    def rundoc(self, object, name=None):
+    def _filter(self, obj, prefix, base):
         """
-        object, name=None -> search object.__doc__ for examples to run.
-
-        Use optional string name as the key for logging the outcome;
-        by default use object.__name__.
-        Return (#failures, #examples).
-        If object is a class object, search recursively for method
-        docstrings too.
-        object.__doc__ is examined regardless of name, but if object is
-        a class, whether private names reached from object are searched
-        depends on the constructor's "isprivate" argument.
+        Return true if the given object should not be examined.
+        """
+        return ((self._namefilter is not None and
+                 self._namefilter(prefix, base)) or
+                (self._objfilter is not None and
+                 self._objfilter(obj)))
 
-        >>> t = Tester(globs={}, verbose=0)
-        >>> def _f():
-        ...     '''Trivial docstring example.
-        ...     >>> assert 2 == 2
-        ...     '''
-        ...     return 32
-        ...
-        >>> t.rundoc(_f)  # expect 0 failures in 1 example
-        (0, 1)
+    def _from_module(self, module, object):
+        """
+        Return true if the given object is defined in the given
+        module.
         """
+        if module is None:
+            return True
+        elif inspect.isfunction(object):
+            return module.__dict__ is object.func_globals
+        elif inspect.isclass(object):
+            return module.__name__ == object.__module__
+        elif inspect.getmodule(object) is not None:
+            return module is inspect.getmodule(object)
+        elif hasattr(object, '__module__'):
+            return module.__name__ == object.__module__
+        elif isinstance(object, property):
+            return True # [XX] no way not be sure.
+        else:
+            raise ValueError("object must be a class or function")
 
-        if name is None:
-            try:
-                name = object.__name__
-            except AttributeError:
-                raise ValueError("Tester.rundoc: name must be given "
-                    "when object.__name__ doesn't exist; %r" % (object,))
-        if self.verbose:
-            print "Running", name + ".__doc__"
-        f, t = run_docstring_examples(object, self.globs, self.verbose, name,
-                                      self.compileflags, self.optionflags)
-        if self.verbose:
-            print f, "of", t, "examples failed in", name + ".__doc__"
-        self.__record_outcome(name, f, t)
-        if _isclass(object):
-            # In 2.2, class and static methods complicate life.  Build
-            # a dict "that works", by hook or by crook.
-            d = {}
-            for tag, kind, homecls, value in _classify_class_attrs(object):
-
-                if homecls is not object:
-                    # Only look at names defined immediately by the class.
+    def _find(self, tests, obj, name, module, source_lines,
+              globs, ignore_imports, seen):
+        """
+        Find tests for the given object and any contained objects, and
+        add them to `tests`.
+        """
+        if self._verbose:
+            print 'Finding tests in %s' % name
+
+        # If we've already processed this object, then ignore it.
+        if id(obj) in seen:
+            return
+        seen[id(obj)] = 1
+
+        # Find a test for this object, and add it to the list of tests.
+        test = self._get_test(obj, name, module, globs, source_lines)
+        if test is not None:
+            tests.append(test)
+
+        # Look for tests in a module's contained objects.
+        if inspect.ismodule(obj) and self._recurse:
+            for valname, val in obj.__dict__.items():
+                # Check if this contained object should be ignored.
+                if self._filter(val, name, valname):
                     continue
-
-                elif self.isprivate(name, tag):
+                valname = '%s.%s' % (name, valname)
+                # Recurse to functions & classes.
+                if ((inspect.isfunction(val) or inspect.isclass(val)) and
+                    (self._from_module(module, val) or not ignore_imports)):
+                    self._find(tests, val, valname, module, source_lines,
+                               globs, ignore_imports, seen)
+
+        # Look for tests in a module's __test__ dictionary.
+        if inspect.ismodule(obj) and self._recurse:
+            for valname, val in getattr(obj, '__test__', {}).items():
+                if not isinstance(valname, basestring):
+                    raise ValueError("DocTestFinder.find: __test__ keys "
+                                     "must be strings: %r" %
+                                     (type(valname),))
+                if not (inspect.isfunction(val) or inspect.isclass(val) or
+                        inspect.ismethod(val) or inspect.ismodule(val) or
+                        isinstance(val, basestring)):
+                    raise ValueError("DocTestFinder.find: __test__ values "
+                                     "must be strings, functions, methods, "
+                                     "classes, or modules: %r" %
+                                     (type(val),))
+                valname = '%s.%s' % (name, valname)
+                self._find(tests, val, valname, module, source_lines,
+                           globs, ignore_imports, seen)
+
+        # Look for tests in a class's contained objects.
+        if inspect.isclass(obj) and self._recurse:
+            for valname, val in obj.__dict__.items():
+                # Check if this contained object should be ignored.
+                if self._filter(val, name, valname):
                     continue
+                # Special handling for staticmethod/classmethod.
+                if isinstance(val, staticmethod):
+                    val = getattr(obj, valname)
+                if isinstance(val, classmethod):
+                    val = getattr(obj, valname).im_func
+
+                # Recurse to methods, properties, and nested classes.
+                if ((inspect.isfunction(val) or inspect.isclass(val) or
+                    isinstance(val, property)) and
+                    (self._from_module(module, val) or not ignore_imports)):
+                    valname = '%s.%s' % (name, valname)
+                    self._find(tests, val, valname, module, source_lines,
+                               globs, ignore_imports, seen)
+
+    def _get_test(self, obj, name, module, globs, source_lines):
+        """
+        Return a DocTest for the given object, if it defines a docstring;
+        otherwise, return None.
+        """
+        # Extract the object's docstring.  If it doesn't have one,
+        # then return None (no test for this object).
+        if isinstance(obj, basestring):
+            docstring = obj
+        else:
+            try:
+                if obj.__doc__ is None:
+                    return None
+                docstring = str(obj.__doc__)
+            except (TypeError, AttributeError):
+                return None
+
+        # Don't bother if the docstring is empty.
+        if not docstring:
+            return None
+
+        # Find the docstring's location in the file.
+        lineno = self._find_lineno(obj, source_lines)
+
+        # Return a DocTest for this object.
+        if module is None:
+            filename = None
+        else:
+            filename = getattr(module, '__file__', module.__name__)
+        return DocTest(docstring, globs, name, filename, lineno)
 
-                elif kind == "method":
-                    # value is already a function
-                    d[tag] = value
+    def _find_lineno(self, obj, source_lines):
+        """
+        Return a line number of the given object's docstring.  Note:
+        this method assumes that the object has a docstring.
+        """
+        lineno = None
+
+        # Find the line number for modules.
+        if inspect.ismodule(obj):
+            lineno = 0
+
+        # Find the line number for classes.
+        # Note: this could be fooled if a class is defined multiple
+        # times in a single file.
+        if inspect.isclass(obj):
+            if source_lines is None:
+                return None
+            pat = re.compile(r'^\s*class\s*%s\b' %
+                             getattr(obj, '__name__', '-'))
+            for i, line in enumerate(source_lines):
+                if pat.match(line):
+                    lineno = i
+                    break
 
-                elif kind == "static method":
-                    # value isn't a function, but getattr reveals one
-                    d[tag] = getattr(object, tag)
+        # Find the line number for functions & methods.
+        if inspect.ismethod(obj): obj = obj.im_func
+        if inspect.isfunction(obj): obj = obj.func_code
+        if inspect.istraceback(obj): obj = obj.tb_frame
+        if inspect.isframe(obj): obj = obj.f_code
+        if inspect.iscode(obj):
+            lineno = getattr(obj, 'co_firstlineno', None)-1
+
+        # Find the line number where the docstring starts.  Assume
+        # that it's the first line that begins with a quote mark.
+        # Note: this could be fooled by a multiline function
+        # signature, where a continuation line begins with a quote
+        # mark.
+        if lineno is not None:
+            if source_lines is None:
+                return lineno+1
+            pat = re.compile('(^|.*:)\s*\w*("|\')')
+            for lineno in range(lineno, len(source_lines)):
+                if pat.match(source_lines[lineno]):
+                    return lineno
+
+        # We couldn't find the line number.
+        return None
+
+######################################################################
+## 4. DocTest Runner
+######################################################################
+
+# [XX] Should overridable methods (eg DocTestRunner.check_output) be
+# named with a leading underscore?
+
+class DocTestRunner:
+    """
+    A class used to run DocTest test cases, and accumulate statistics.
+    The `run` method is used to process a single DocTest case.  It
+    returns a tuple `(f, t)`, where `t` is the number of test cases
+    tried, and `f` is the number of test cases that failed.
+
+        >>> tests = DocTestFinder().find(_TestClass)
+        >>> runner = DocTestRunner(verbose=False)
+        >>> for test in tests:
+        ...     print runner.run(test)
+        (0, 2)
+        (0, 1)
+        (0, 2)
+        (0, 2)
 
-                elif kind == "class method":
-                    # Hmm.  A classmethod object doesn't seem to reveal
-                    # enough.  But getattr turns it into a bound method,
-                    # and from there .im_func retrieves the underlying
-                    # function.
-                    d[tag] = getattr(object, tag).im_func
+    The `summarize` method prints a summary of all the test cases that
+    have been run by the runner, and returns an aggregated `(f, t)`
+    tuple:
+
+        >>> runner.summarize(verbose=1)
+        4 items passed all tests:
+           2 tests in _TestClass
+           2 tests in _TestClass.__init__
+           2 tests in _TestClass.get
+           1 tests in _TestClass.square
+        7 tests in 4 items.
+        7 passed and 0 failed.
+        Test passed.
+        (0, 7)
+
+    The aggregated number of tried examples and failed examples is
+    also available via the `tries` and `failures` attributes:
+
+        >>> runner.tries
+        7
+        >>> runner.failures
+        0
+
+    The comparison between expected outputs and actual outputs is done
+    by the `check_output` method.  This comparison may be customized
+    with a number of option flags; see the documentation for `testmod`
+    for more information.  If the option flags are insufficient, then
+    the comparison may also be customized by subclassing
+    DocTestRunner, and overriding the methods `check_output` and
+    `output_difference`.
+
+    The test runner's display output can be controlled in two ways.
+    First, an output function (`out) can be passed to
+    `TestRunner.run`; this function will be called with strings that
+    should be displayed.  It defaults to `sys.stdout.write`.  If
+    capturing the output is not sufficient, then the display output
+    can be also customized by subclassing DocTestRunner, and
+    overriding the methods `report_start`, `report_success`,
+    `report_unexpected_exception`, and `report_failure`.
+    """
+    # This divider string is used to separate failure messages, and to
+    # separate sections of the summary.
+    DIVIDER = "*" * 70
 
-                elif kind == "property":
-                    # The methods implementing the property have their
-                    # own docstrings -- but the property may have one too.
-                    if value.__doc__ is not None:
-                        d[tag] = str(value.__doc__)
+    def __init__(self, verbose=None, optionflags=0):
+        """
+        Create a new test runner.
 
-                elif kind == "data":
-                    # Grab nested classes.
-                    if _isclass(value):
-                        d[tag] = value
+        Optional keyword arg 'verbose' prints lots of stuff if true,
+        only failures if false; by default, it's true iff '-v' is in
+        sys.argv.
 
-                else:
-                    raise ValueError("teach doctest about %r" % kind)
+        Optional argument `optionflags` can be used to control how the
+        test runner compares expected output to actual output, and how
+        it displays failures.  See the documentation for `testmod` for
+        more information.
+        """
+        if verbose is None:
+            verbose = '-v' in sys.argv
+        self._verbose = verbose
+        self.optionflags = optionflags
 
-            f2, t2 = self.run__test__(d, name)
-            f += f2
-            t += t2
+        # Keep track of the examples we've run.
+        self.tries = 0
+        self.failures = 0
+        self._name2ft = {}
 
-        return f, t
+        # Create a fake output target for capturing doctest output.
+        self._fakeout = _SpoofOut()
 
-    def rundict(self, d, name, module=None):
+    #/////////////////////////////////////////////////////////////////
+    # Output verification methods
+    #/////////////////////////////////////////////////////////////////
+    # These two methods should be updated together, since the
+    # output_difference method needs to know what should be considered
+    # to match by check_output.
+
+    def check_output(self, want, got):
+        """
+        Return True iff the actual output (`got`) matches the expected
+        output (`want`).  These strings are always considered to match
+        if they are identical; but depending on what option flags the
+        test runner is using, several non-exact match types are also
+        possible.  See the documentation for `TestRunner` for more
+        information about option flags.
+        """
+        # Handle the common case first, for efficiency:
+        # if they're string-identical, always return true.
+        if got == want:
+            return True
+
+        # The values True and False replaced 1 and 0 as the return
+        # value for boolean comparisons in Python 2.3.
+        if not (self.optionflags & DONT_ACCEPT_TRUE_FOR_1):
+            if (got,want) == ("True\n", "1\n"):
+                return True
+            if (got,want) == ("False\n", "0\n"):
+                return True
+
+        # <BLANKLINE> can be used as a special sequence to signify a
+        # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used.
+        if not (self.optionflags & DONT_ACCEPT_BLANKLINE):
+            # Replace <BLANKLINE> in want with a blank line.
+            want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER),
+                          '', want)
+            # If a line in got contains only spaces, then remove the
+            # spaces.
+            got = re.sub('(?m)^\s*?$', '', got)
+            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 ELLISPIS flag.
+        if (self.optionflags & NORMALIZE_WHITESPACE):
+            got = ' '.join(got.split())
+            want = ' '.join(want.split())
+            if got == want:
+                return True
+
+        # The ELLIPSIS flag says to let the sequence "..." in `want`
+        # match any substring in `got`.  We implement this by
+        # transforming `want` into a regular expression.
+        if (self.optionflags & ELLIPSIS):
+            # Escape any special regexp characters
+            want_re = re.escape(want)
+            # Replace ellipsis markers ('...') with .*
+            want_re = want_re.replace(re.escape(ELLIPSIS_MARKER), '.*')
+            # Require that it matches the entire string; and set the
+            # re.DOTALL flag (with '(?s)').
+            want_re = '(?s)^%s$' % want_re
+            # Check if the `want_re` regexp matches got.
+            if re.match(want_re, got):
+                return True
+
+        # We didn't find any match; return false.
+        return False
+
+    def output_difference(self, want, got):
+        """
+        Return a string describing the differences between the
+        expected output (`want`) and the actual output (`got`).
+        """
+        # If <BLANKLINE>s are being used, then replace <BLANKLINE>
+        # with blank lines in the expected output string.
+        if not (self.optionflags & DONT_ACCEPT_BLANKLINE):
+            want = re.sub('(?m)^%s$' % re.escape(BLANKLINE_MARKER), '', want)
+
+        # 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 ((self.optionflags & (UNIFIED_DIFF | CONTEXT_DIFF)) and
+            want.count('\n') > 2 and got.count('\n') > 2 and
+            not (self.optionflags & ELLIPSIS and '...' in want)):
+            # 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')]
+            # Use difflib to find their differences.
+            if self.optionflags & UNIFIED_DIFF:
+                diff = difflib.unified_diff(want_lines, got_lines, n=2,
+                                            fromfile='Expected', tofile='Got')
+                kind = 'unified'
+            elif self.optionflags & CONTEXT_DIFF:
+                diff = difflib.context_diff(want_lines, got_lines, n=2,
+                                            fromfile='Expected', tofile='Got')
+                kind = 'context'
+            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)",
+                            ''.join(diff))
+
+        # If we're not using diff, then simply list the expected
+        # output followed by the actual output.
+        return (_tag_msg("Expected", want or "Nothing") +
+                _tag_msg("Got", got))
+
+    #/////////////////////////////////////////////////////////////////
+    # Reporting methods
+    #/////////////////////////////////////////////////////////////////
+
+    def report_start(self, out, test, example):
+        """
+        Report that the test runner is about to process the given
+        example.  (Only displays a message if verbose=True)
         """
-        d, name, module=None -> search for docstring examples in d.values().
+        if self._verbose:
+            out(_tag_msg("Trying", example.source) +
+                _tag_msg("Expecting", example.want or "nothing"))
 
-        For k, v in d.items() such that v is a function or class,
-        do self.rundoc(v, name + "." + k).  Whether this includes
-        objects with private names depends on the constructor's
-        "isprivate" argument.  If module is specified, functions and
-        classes that are not defined in module are excluded.
-        Return aggregate (#failures, #examples).
+    def report_success(self, out, test, example, got):
+        """
+        Report that the given example ran successfully.  (Only
+        displays a message if verbose=True)
+        """
+        if self._verbose:
+            out("ok\n")
 
-        Build and populate two modules with sample functions to test that
-        exclusion of external functions and classes works.
+    def report_failure(self, out, test, example, got):
+        """
+        Report that the given example failed.
+        """
+        # Print an error message.
+        out(self.__failure_header(test, example) +
+            self.output_difference(example.want, got))
 
-        >>> import new
-        >>> m1 = new.module('_m1')
-        >>> m2 = new.module('_m2')
-        >>> test_data = \"""
-        ... def _f():
-        ...     '''>>> assert 1 == 1
-        ...     '''
-        ... def g():
-        ...    '''>>> assert 2 != 1
-        ...    '''
-        ... class H:
-        ...    '''>>> assert 2 > 1
-        ...    '''
-        ...    def bar(self):
-        ...        '''>>> assert 1 < 2
-        ...        '''
-        ... \"""
-        >>> exec test_data in m1.__dict__
-        >>> exec test_data in m2.__dict__
-        >>> m1.__dict__.update({"f2": m2._f, "g2": m2.g, "h2": m2.H})
+    def report_unexpected_exception(self, out, test, example, exc_info):
+        """
+        Report that the given example raised an unexpected exception.
+        """
+        # Get a traceback message.
+        excout = StringIO()
+        exc_type, exc_val, exc_tb = exc_info
+        traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
+        exception_tb = excout.getvalue()
+        # Print an error message.
+        out(self.__failure_header(test, example) +
+            _tag_msg("Exception raised", exception_tb))
+
+    def __failure_header(self, test, example):
+        s = (self.DIVIDER + "\n" +
+             _tag_msg("Failure in example", example.source))
+        if test.filename is None:
+            # [XX] I'm not putting +1 here, to give the same output
+            # as the old version.  But I think it *should* go here.
+            return s + ("from line #%s of %s\n" %
+                        (example.lineno, test.name))
+        elif test.lineno is None:
+            return s + ("from line #%s of %s in %s\n" %
+                        (example.lineno+1, test.name, test.filename))
+        else:
+            lineno = test.lineno+example.lineno+1
+            return s + ("from line #%s of %s (%s)\n" %
+                        (lineno, test.filename, test.name))
+
+    #/////////////////////////////////////////////////////////////////
+    # DocTest Running
+    #/////////////////////////////////////////////////////////////////
+
+    # A regular expression for handling `want` strings that contain
+    # expected exceptions.  It divides `want` into two pieces: the
+    # pre-exception output (`out`) and the exception message (`exc`),
+    # as generated by traceback.format_exception_only().  (I assume
+    # that the exception_only message is the first non-indented line
+    # starting with word characters after the "Traceback ...".)
+    _EXCEPTION_RE = re.compile(('^(?P<out>.*)'
+                                '^(?P<hdr>Traceback \((?:%s|%s)\):)\s*$.*?'
+                                '^(?P<exc>\w+.*)') %
+                               ('most recent call last', 'innermost last'),
+                               re.MULTILINE | re.DOTALL)
+
+    _OPTION_DIRECTIVE_RE = re.compile('\s*doctest:\s*(?P<flags>[^#\n]*)')
+
+    def __handle_directive(self, example):
+        """
+        Check if the given example is actually a directive to doctest
+        (to turn an optionflag on or off); and if it is, then handle
+        the directive.
 
-        Tests that objects outside m1 are excluded:
+        Return true iff the example is actually a directive (and so
+        should not be executed).
 
-        >>> t = Tester(globs={}, verbose=0, isprivate=is_private)
-        >>> t.rundict(m1.__dict__, "rundict_test", m1)  # _f, f2 and g2 and h2 skipped
-        (0, 3)
+        """
+        m = self._OPTION_DIRECTIVE_RE.match(example.source)
+        if m is None:
+            return False
+
+        for flag in m.group('flags').upper().split():
+            if (flag[:1] not in '+-' or
+                flag[1:] not in OPTIONFLAGS_BY_NAME):
+                raise ValueError('Bad doctest option directive: '+flag)
+            if flag[0] == '+':
+                self.optionflags |= OPTIONFLAGS_BY_NAME[flag[1:]]
+            else:
+                self.optionflags &= ~OPTIONFLAGS_BY_NAME[flag[1:]]
+        return True
 
-        Again, but with the default isprivate function allowing _f:
+    def __run(self, test, compileflags, out):
+        """
+        Run the examples in `test`.  Write the outcome of each example
+        with one of the `DocTestRunner.report_*` methods, using the
+        writer function `out`.  `compileflags` is the set of compiler
+        flags that should be used to execute examples.  Return a tuple
+        `(f, t)`, where `t` is the number of examples tried, and `f`
+        is the number of examples that failed.  The examples are run
+        in the namespace `test.globs`.
+        """
+        # Keep track of the number of failures and tries.
+        failures = tries = 0
 
-        >>> t = Tester(globs={}, verbose=0)
-        >>> t.rundict(m1.__dict__, "rundict_test_pvt", m1)  # Only f2, g2 and h2 skipped
-        (0, 4)
+        # Save the option flags (since option directives can be used
+        # to modify them).
+        original_optionflags = self.optionflags
 
-        And once more, not excluding stuff outside m1:
+        # Process each example.
+        for example in test.examples:
+            # Check if it's an option directive.  If it is, then handle
+            # it, and go on to the next example.
+            if self.__handle_directive(example):
+                continue
 
-        >>> t = Tester(globs={}, verbose=0)
-        >>> t.rundict(m1.__dict__, "rundict_test_pvt")  # None are skipped.
-        (0, 8)
+            # Record that we started this example.
+            tries += 1
+            self.report_start(out, test, example)
 
-        The exclusion of objects from outside the designated module is
-        meant to be invoked automagically by testmod.
+            # Run the example in the given context (globs), and record
+            # any exception that gets raised.  (But don't intercept
+            # keyboard interrupts.)
+            try:
+                # If the example is a compound statement on one line,
+                # like "if 1: print 2", then compile() requires a
+                # trailing newline.  Rather than analyze that, always
+                # append one (it never hurts).
+                exec compile(example.source + '\n', "<string>", "single",
+                             compileflags, 1) in test.globs
+                exception = None
+            except KeyboardInterrupt:
+                raise
+            except:
+                exception = sys.exc_info()
+
+            # Extract the example's actual output from fakeout, and
+            # write it to `got`.  Add a terminating newline if it
+            # doesn't have already one.
+            got = self._fakeout.getvalue()
+            self._fakeout.truncate(0)
+
+            # If the example executed without raising any exceptions,
+            # then verify its output and report its outcome.
+            if exception is None:
+                if self.check_output(example.want, got):
+                    self.report_success(out, test, example, got)
+                else:
+                    self.report_failure(out, test, example, got)
+                    failures += 1
 
-        >>> testmod(m1, isprivate=is_private)
-        (0, 3)
+            # If the example raised an exception, then check if it was
+            # expected.
+            else:
+                exc_info = sys.exc_info()
+                exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
+
+                # Search the `want` string for an exception.  If we don't
+                # find one, then report an unexpected exception.
+                m = self._EXCEPTION_RE.match(example.want)
+                if m is None:
+                    self.report_unexpected_exception(out, test, example,
+                                                     exc_info)
+                    failures += 1
+                else:
+                    exc_hdr = m.group('hdr')+'\n' # Exception header
+                    # The test passes iff the pre-exception output and
+                    # the exception description match the values given
+                    # in `want`.
+                    if (self.check_output(m.group('out'), got) and
+                        self.check_output(m.group('exc'), exc_msg)):
+                        # Is +exc_msg the right thing here??
+                        self.report_success(out, test, example,
+                                            got+exc_hdr+exc_msg)
+                    else:
+                        self.report_failure(out, test, example,
+                                            got+exc_hdr+exc_msg)
+                        failures += 1
+
+        # Restore the option flags (in case they were modified)
+        self.optionflags = original_optionflags
+
+        # Record and return the number of failures and tries.
+        self.__record_outcome(test, failures, tries)
+        return failures, tries
 
+    def __record_outcome(self, test, f, t):
         """
+        Record the fact that the given DocTest (`test`) generated `f`
+        failures out of `t` tried examples.
+        """
+        f2, t2 = self._name2ft.get(test.name, (0,0))
+        self._name2ft[test.name] = (f+f2, t+t2)
+        self.failures += f
+        self.tries += t
 
-        if not hasattr(d, "items"):
-            raise TypeError("Tester.rundict: d must support .items(); %r" % (d,))
-        f = t = 0
-        # Run the tests by alpha order of names, for consistency in
-        # verbose-mode output.
-        names = d.keys()
-        names.sort()
-        for thisname in names:
-            value = d[thisname]
-            if _isfunction(value) or _isclass(value):
-                if module and not _from_module(module, value):
-                    continue
-                f2, t2 = self.__runone(value, name + "." + thisname)
-                f = f + f2
-                t = t + t2
-        return f, t
-
-    def run__test__(self, d, name):
-        """d, name -> Treat dict d like module.__test__.
-
-        Return (#failures, #tries).
-        See testmod.__doc__ for details.
+    def run(self, test, compileflags=None, out=None, clear_globs=True):
+        """
+        Run the examples in `test`, and display the results using the
+        writer function `out`.
+
+        The examples are run in the namespace `test.globs`.  If
+        `clear_globs` is true (the default), then this namespace will
+        be cleared after the test runs, to help with garbage
+        collection.  If you would like to examine the namespace after
+        the test completes, then use `clear_globs=False`.
+
+        `compileflags` gives the set of flags that should be used by
+        the Python compiler when running the examples.  If not
+        specified, then it will default to the set of future-import
+        flags that apply to `globs`.
+
+        The output of each example is checked using
+        `DocTestRunner.check_output`, and the results are formatted by
+        the `DocTestRunner.report_*` methods.
         """
+        if compileflags is None:
+            compileflags = _extract_future_flags(test.globs)
+        if out is None:
+            out = sys.stdout.write
+        saveout = sys.stdout
 
-        failures = tries = 0
-        prefix = name + "."
-        savepvt = self.isprivate
         try:
-            self.isprivate = lambda *args: 0
-            # Run the tests by alpha order of names, for consistency in
-            # verbose-mode output.
-            keys = d.keys()
-            keys.sort()
-            for k in keys:
-                v = d[k]
-                thisname = prefix + k
-                if type(v) in _StringTypes:
-                    f, t = self.runstring(v, thisname)
-                elif _isfunction(v) or _isclass(v) or _ismethod(v):
-                    f, t = self.rundoc(v, thisname)
-                else:
-                    raise TypeError("Tester.run__test__: values in "
-                            "dict must be strings, functions, methods, "
-                            "or classes; %r" % (v,))
-                failures = failures + f
-                tries = tries + t
+            sys.stdout = self._fakeout
+            return self.__run(test, compileflags, out)
         finally:
-            self.isprivate = savepvt
-        return failures, tries
-
+            sys.stdout = saveout
+            # While Python gc can clean up most cycles on its own, it doesn't
+            # chase frame objects.  This is especially irksome when running
+            # generator tests that raise exceptions, because a named generator-
+            # iterator gets an entry in globs, and the generator-iterator
+            # object's frame's traceback info points back to globs.  This is
+            # easy to break just by clearing the namespace.  This can also
+            # help to break other kinds of cycles, and even for cycles that
+            # gc can break itself it's better to break them ASAP.
+            if clear_globs:
+                test.globs.clear()
+
+    #/////////////////////////////////////////////////////////////////
+    # Summarization
+    #/////////////////////////////////////////////////////////////////
     def summarize(self, verbose=None):
         """
-        verbose=None -> summarize results, return (#failures, #tests).
-
-        Print summary of test results to stdout.
-        Optional arg 'verbose' controls how wordy this is.  By
-        default, use the verbose setting established by the
-        constructor.
+        Print a summary of all the test cases that have been run by
+        this DocTestRunner, and return a tuple `(f, t)`, where `f` is
+        the total number of failed examples, and `t` is the total
+        number of tried examples.
+
+        The optional `verbose` argument controls how detailed the
+        summary is.  If the verbosity is not specified, then the
+        DocTestRunner's verbosity is used.
         """
-
         if verbose is None:
-            verbose = self.verbose
+            verbose = self._verbose
         notests = []
         passed = []
         failed = []
         totalt = totalf = 0
-        for x in self.name2ft.items():
+        for x in self._name2ft.items():
             name, (f, t) = x
             assert f <= t
-            totalt = totalt + t
-            totalf = totalf + f
+            totalt += t
+            totalf += f
             if t == 0:
                 notests.append(name)
             elif f == 0:
@@ -988,13 +1399,13 @@ See doctest.testmod docs for the meaning of optionflags.
                 for thing, count in passed:
                     print " %3d tests in %s" % (count, thing)
         if failed:
-            print "*" * 65
+            print self.DIVIDER
             print len(failed), "items had failures:"
             failed.sort()
             for thing, (f, t) in failed:
                 print " %3d of %3d in %s" % (f, t, thing)
         if verbose:
-            print totalt, "tests in", len(self.name2ft), "items."
+            print totalt, "tests in", len(self._name2ft), "items."
             print totalt - totalf, "passed and", totalf, "failed."
         if totalf:
             print "***Test Failed***", totalf, "failures."
@@ -1002,84 +1413,15 @@ See doctest.testmod docs for the meaning of optionflags.
             print "Test passed."
         return totalf, totalt
 
-    def merge(self, other):
-        """
-        other -> merge in test results from the other Tester instance.
-
-        If self and other both have a test result for something
-        with the same name, the (#failures, #tests) results are
-        summed, and a warning is printed to stdout.
-
-        >>> from doctest import Tester
-        >>> t1 = Tester(globs={}, verbose=0)
-        >>> t1.runstring('''
-        ... >>> x = 12
-        ... >>> print x
-        ... 12
-        ... ''', "t1example")
-        (0, 2)
-        >>>
-        >>> t2 = Tester(globs={}, verbose=0)
-        >>> t2.runstring('''
-        ... >>> x = 13
-        ... >>> print x
-        ... 13
-        ... ''', "t2example")
-        (0, 2)
-        >>> common = ">>> assert 1 + 2 == 3\\n"
-        >>> t1.runstring(common, "common")
-        (0, 1)
-        >>> t2.runstring(common, "common")
-        (0, 1)
-        >>> t1.merge(t2)
-        *** Tester.merge: 'common' in both testers; summing outcomes.
-        >>> t1.summarize(1)
-        3 items passed all tests:
-           2 tests in common
-           2 tests in t1example
-           2 tests in t2example
-        6 tests in 3 items.
-        6 passed and 0 failed.
-        Test passed.
-        (0, 6)
-        >>>
-        """
-
-        d = self.name2ft
-        for name, (f, t) in other.name2ft.items():
-            if name in d:
-                print "*** Tester.merge: '" + name + "' in both" \
-                    " testers; summing outcomes."
-                f2, t2 = d[name]
-                f = f + f2
-                t = t + t2
-            d[name] = f, t
-
-    def __record_outcome(self, name, f, t):
-        if name in self.name2ft:
-            print "*** Warning: '" + name + "' was tested before;", \
-                "summing outcomes."
-            f2, t2 = self.name2ft[name]
-            f = f + f2
-            t = t + t2
-        self.name2ft[name] = f, t
-
-    def __runone(self, target, name):
-        if "." in name:
-            i = name.rindex(".")
-            prefix, base = name[:i], name[i+1:]
-        else:
-            prefix, base = "", base
-        if self.isprivate(prefix, base):
-            return 0, 0
-        return self.rundoc(target, name)
-
-master = None
+######################################################################
+## 5. Test Functions
+######################################################################
+# These should be backwards compatible.
 
 def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
-               report=True, optionflags=0):
+            report=True, optionflags=0, extraglobs=None):
     """m=None, name=None, globs=None, verbose=None, isprivate=None,
-       report=True, optionflags=0
+       report=True, optionflags=0, extraglobs=None
 
     Test examples in docstrings in functions and classes reachable
     from module m (or the current module if m is not supplied), starting
@@ -1103,6 +1445,10 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
     dict is actually used for each docstring, so that each docstring's
     examples start with a clean slate.
 
+    Optional keyword arg "extraglobs" gives a dictionary that should be
+    merged into the globals that are used to execute examples.  By
+    default, no extra globals are used.  This is new in 2.4.
+
     Optional keyword arg "verbose" prints lots of stuff if true, prints
     only failures if false; by default, it's true iff "-v" is in sys.argv.
 
@@ -1126,6 +1472,36 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
             DONT_ACCEPT_TRUE_FOR_1 is specified, neither substitution
             is allowed.
 
+        DONT_ACCEPT_BLANKLINE
+            By default, if an expected output block contains a line
+            containing only the string "<BLANKLINE>", then that line
+            will match a blank line in the actual output.  When
+            DONT_ACCEPT_BLANKLINE is specified, this substitution is
+            not allowed.
+
+        NORMALIZE_WHITESPACE
+            When NORMALIZE_WHITESPACE is specified, all sequences of
+            whitespace are treated as equal.  I.e., any sequence of
+            whitespace within the expected output will match any
+            sequence of whitespace within the actual output.
+
+        ELLIPSIS
+            When ELLIPSIS is specified, then an ellipsis marker
+            ("...") in the expected output can match any substring in
+            the actual output.
+
+        UNIFIED_DIFF
+            When UNIFIED_DIFF is specified, failures that involve
+            multi-line expected and actual outputs will be displayed
+            using a unified diff.
+
+        CONTEXT_DIFF
+            When CONTEXT_DIFF is specified, failures that involve
+            multi-line expected and actual outputs will be displayed
+            using a context diff.
+    """
+
+    """ [XX] This is no longer true:
     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
@@ -1134,168 +1510,129 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
     displaying a summary.  Invoke doctest.master.summarize(verbose)
     when you're done fiddling.
     """
-
-    global master
-
+    # If no module was given, then use __main__.
     if m is None:
-        import sys
         # DWA - m will still be None if this wasn't invoked from the command
         # line, in which case the following TypeError is about as good an error
         # as we should expect
         m = sys.modules.get('__main__')
 
-    if not _ismodule(m):
+    # Check that we were actually given a module.
+    if not inspect.ismodule(m):
         raise TypeError("testmod: module required; %r" % (m,))
+
+    # If no name was given, then use the module's name.
     if name is None:
         name = m.__name__
-    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 += f
-    tries += t
-    if hasattr(m, "__test__"):
-        testdict = m.__test__
-        if testdict:
-            if not hasattr(testdict, "items"):
-                raise TypeError("testmod: module.__test__ must support "
-                                ".items(); %r" % (testdict,))
-            f, t = tester.run__test__(testdict, name + ".__test__")
-            failures += f
-            tries += t
+
+    # Find, parse, and run all tests in the given module.
+    finder = DocTestFinder(namefilter=isprivate)
+    runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
+    for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
+        runner.run(test)
+
     if report:
-        tester.summarize()
-    if master is None:
-        master = tester
-    else:
-        master.merge(tester)
-    return failures, tries
+        runner.summarize()
 
-###########################################################################
-# Various doctest extensions, to make using doctest with unittest
-# easier, and to help debugging when a doctest goes wrong.  Original
-# code by Jim Fulton.
+    return runner.failures, runner.tries
 
-# Utilities.
+def run_docstring_examples(f, globs, verbose=False, name="NoName",
+                           compileflags=None, optionflags=0):
+    """
+    Test examples in the given object's docstring (`f`), using `globs`
+    as globals.  Optional argument `name` is used in failure messages.
+    If the optional argument `verbose` is true, then generate output
+    even if there are no failures.
+
+    `compileflags` gives the set of flags that should be used by the
+    Python compiler when running the examples.  If not specified, then
+    it will default to the set of future-import flags that apply to
+    `globs`.
+
+    Optional keyword arg `optionflags` specifies options for the
+    testing and output.  See the documentation for `testmod` for more
+    information.
+    """
+    # Find, parse, and run all tests in the given module.
+    finder = DocTestFinder(verbose=verbose, recurse=False)
+    runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
+    for test in finder.find(f, name, globs=globs):
+        runner.run(test, compileflags=compileflags)
+
+######################################################################
+## 6. Tester
+######################################################################
+# This is provided only for backwards compatibility.  It's not
+# actually used in any way.
 
-# If module is None, return the calling module (the module that called
-# the routine that called _normalize_module -- this normally won't be
-# doctest!).  If module is a string, it should be the (possibly dotted)
-# name of a module, and the (rightmost) module object is returned.  Else
-# module is returned untouched; the intent appears to be that module is
-# already a module object in this case (although this isn't checked).
+class Tester:
+    def __init__(self, mod=None, globs=None, verbose=None,
+                 isprivate=None, optionflags=0):
+        if mod is None and globs is None:
+            raise TypeError("Tester.__init__: must specify mod or globs")
+        if mod is not None and not _ismodule(mod):
+            raise TypeError("Tester.__init__: mod must be a module; %r" %
+                            (mod,))
+        if globs is None:
+            globs = mod.__dict__
+        self.globs = globs
 
-def _normalize_module(module):
-    import sys
+        self.verbose = verbose
+        self.isprivate = isprivate
+        self.optionflags = optionflags
+        self.testfinder = DocTestFinder(namefilter=isprivate)
+        self.testrunner = DocTestRunner(verbose=verbose,
+                                        optionflags=optionflags)
 
-    if module is None:
-        # Get our caller's caller's module.
-        module = sys._getframe(2).f_globals['__name__']
-        module = sys.modules[module]
-
-    elif isinstance(module, basestring):
-        # The ["*"] at the end is a mostly meaningless incantation with
-        # a crucial property:  if, e.g., module is 'a.b.c', it convinces
-        # __import__ to return c instead of a.
-        module = __import__(module, globals(), locals(), ["*"])
-
-    return module
-
-# tests is a list of (testname, docstring, filename, lineno) tuples.
-# If object has a __doc__ attr, and the __doc__ attr looks like it
-# contains a doctest (specifically, if it contains an instance of '>>>'),
-# then tuple
-#     prefix + name, object.__doc__, filename, lineno
-# is appended to tests.  Else tests is left alone.
-# There is no return value.
-
-def _get_doctest(name, object, tests, prefix, filename='', lineno=''):
-    doc = getattr(object, '__doc__', '')
-    if isinstance(doc, basestring) and '>>>' in doc:
-        tests.append((prefix + name, doc, filename, lineno))
-
-# tests is a list of (testname, docstring, filename, lineno) tuples.
-# docstrings containing doctests are appended to tests (if any are found).
-# items is a dict, like a module or class dict, mapping strings to objects.
-# mdict is the global dict of a "home" module -- only objects belonging
-# to this module are searched for docstrings.  module is the module to
-# which mdict belongs.
-# prefix is a string to be prepended to an object's name when adding a
-# tuple to tests.
-# The objects (values) in items are examined (recursively), and doctests
-# belonging to functions and classes in the home module are appended to
-# tests.
-# minlineno is a gimmick to try to guess the file-relative line number
-# at which a doctest probably begins.
-
-def _extract_doctests(items, module, mdict, tests, prefix, minlineno=0):
-
-    for name, object in items:
-        # Only interested in named objects.
-        if not hasattr(object, '__name__'):
-            continue
-
-        elif hasattr(object, 'func_globals'):
-            # Looks like a function.
-            if object.func_globals is not mdict:
-                # Non-local function.
-                continue
-            code = getattr(object, 'func_code', None)
-            filename = getattr(code, 'co_filename', '')
-            lineno = getattr(code, 'co_firstlineno', -1) + 1
-            if minlineno:
-                minlineno = min(lineno, minlineno)
-            else:
-                minlineno = lineno
-            _get_doctest(name, object, tests, prefix, filename, lineno)
+    def runstring(self, s, name):
+        test = DocTest(s, self.globs, name, None, None)
+        if self.verbose:
+            print "Running string", name
+        (f,t) = self.testrunner.run(test)
+        if self.verbose:
+            print f, "of", t, "examples failed in string", name
+        return (f,t)
 
-        elif hasattr(object, "__module__"):
-            # Maybe a class-like thing, in which case we care.
-            if object.__module__ != module.__name__:
-                # Not the same module.
-                continue
-            if not (hasattr(object, '__dict__')
-                    and hasattr(object, '__bases__')):
-                # Not a class.
-                continue
+    def rundoc(self, object, name=None, module=None, ignore_imports=True):
+        f = t = 0
+        tests = self.testfinder.find(object, name, module=module,
+                                     globs=self.globs,
+                                     ignore_imports=ignore_imports)
+        for test in tests:
+            (f2, t2) = self.testrunner.run(test)
+            (f,t) = (f+f2, t+t2)
+        return (f,t)
+
+    def rundict(self, d, name, module=None):
+        import new
+        m = new.module(name)
+        m.__dict__.update(d)
+        ignore_imports = (module is not None)
+        return self.rundoc(m, name, module, ignore_imports)
 
-            lineno = _extract_doctests(object.__dict__.items(),
-                                       module,
-                                       mdict,
-                                       tests,
-                                       prefix + name + ".")
-            # XXX "-3" is unclear.
-            _get_doctest(name, object, tests, prefix,
-                         lineno="%s (or above)" % (lineno - 3))
-
-    return minlineno
-
-# Find all the doctests belonging to the module object.
-# Return a list of
-#     (testname, docstring, filename, lineno)
-# tuples.
-
-def _find_tests(module, prefix=None):
-    if prefix is None:
-        prefix = module.__name__
-    mdict = module.__dict__
-    tests = []
-    # Get the module-level doctest (if any).
-    _get_doctest(prefix, module, tests, '', lineno="1 (or above)")
-    # Recursively search the module __dict__ for doctests.
-    if prefix:
-        prefix += "."
-    _extract_doctests(mdict.items(), module, mdict, tests, prefix)
-    return tests
-
-###############################################################################
-# unitest support
+    def run__test__(self, d, name):
+        import new
+        m = new.module(name)
+        m.__test__ = d
+        return self.rundoc(m, name, module)
 
-from StringIO import StringIO
-import os
-import sys
-import tempfile
-import unittest
+    def summarize(self, verbose=None):
+        return self.testrunner.summarize(verbose)
+
+    def merge(self, other):
+        d = self.testrunner._name2ft
+        for name, (f, t) in other.testrunner._name2ft.items():
+            if name in d:
+                print "*** Tester.merge: '" + name + "' in both" \
+                    " testers; summing outcomes."
+                f2, t2 = d[name]
+                f = f + f2
+                t = t + t2
+            d[name] = f, t
+
+######################################################################
+## 7. Unittest Support
+######################################################################
 
 class DocTestTestCase(unittest.TestCase):
     """A test case that wraps a test function.
@@ -1306,13 +1643,13 @@ class DocTestTestCase(unittest.TestCase):
     always be called if the set-up ('setUp') function ran successfully.
     """
 
-    def __init__(self, tester, name, doc, filename, lineno,
+    def __init__(self, test_runner, test,
                  setUp=None, tearDown=None):
         unittest.TestCase.__init__(self)
-        (self.__tester, self.__name, self.__doc,
-         self.__filename, self.__lineno,
-         self.__setUp, self.__tearDown
-         ) = tester, name, doc, filename, lineno, setUp, tearDown
+        self.__test_runner = test_runner
+        self.__test = test
+        self.__setUp = setUp
+        self.__tearDown = tearDown
 
     def setUp(self):
         if self.__setUp is not None:
@@ -1323,41 +1660,47 @@ class DocTestTestCase(unittest.TestCase):
             self.__tearDown()
 
     def runTest(self):
+        test = self.__test
         old = sys.stdout
         new = StringIO()
         try:
-            sys.stdout = new
-            failures, tries = self.__tester.runstring(self.__doc, self.__name)
+            self.__test_runner.DIVIDER = "-"*70
+            failures, tries = self.__test_runner.run(test, out=new.write)
         finally:
             sys.stdout = old
 
         if failures:
-            lname = '.'.join(self.__name.split('.')[-1:])
-            lineno = self.__lineno or "0 (don't know line no)"
+            lname = '.'.join(test.name.split('.')[-1:])
+            if test.lineno is None:
+                lineno = 'unknown line number'
+            else:
+                lineno = 'line %s' % test.lineno
+            err = new.getvalue()
+
             raise self.failureException(
                 'Failed doctest test for %s\n'
-                '  File "%s", line %s, in %s\n\n%s'
-                % (self.__name, self.__filename, lineno, lname, new.getvalue())
-                )
+                '  File "%s", %s, in %s\n\n%s'
+                % (test.name, test.filename, lineno, lname, err))
 
     def id(self):
-        return self.__name
+        return self.__test.name
 
     def __repr__(self):
-        name = self.__name.split('.')
+        name = self.__test.name.split('.')
         return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
 
     __str__ = __repr__
 
     def shortDescription(self):
-        return "Doctest: " + self.__name
+        return "Doctest: " + self.__test.name
 
 
-def DocTestSuite(module=None,
-                 setUp=lambda: None,
-                 tearDown=lambda: None,
-                 ):
-    """Convert doctest tests for a mudule to a unittest test suite
+def DocTestSuite(module=None, filename=None, globs=None, extraglobs=None,
+                 optionflags=0,
+                 test_finder=None, test_runner=None,
+                 setUp=lambda: None, tearDown=lambda: None):
+    """
+    Convert doctest tests for a mudule to a unittest test suite
 
     This tests convers each documentation string in a module that
     contains doctest tests to a unittest test case. If any of the
@@ -1369,109 +1712,60 @@ def DocTestSuite(module=None,
     can be either a module or a module name.
 
     If no argument is given, the calling module is used.
-
     """
-    module = _normalizeModule(module)
-    tests = _findTests(module)
+    if module is not None and filename is not None:
+        raise ValueError('Specify module or filename, not both.')
 
-    if not tests:
-        raise ValueError(module, "has no tests")
+    if test_finder is None:
+        test_finder = DocTestFinder()
+    if test_runner is None:
+        test_runner = DocTestRunner(optionflags=optionflags)
+
+    if filename is not None:
+        name = os.path.basename(filename)
+        test = Test(open(filename).read(),name,filename,0)
+        if globs is None:
+            globs = {}
+    else:
+        module = _normalize_module(module)
+        tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
+        if globs is None:
+            globs = module.__dict__
+        if not tests: # [XX] why do we want to do this?
+            raise ValueError(module, "has no tests")
 
     tests.sort()
     suite = unittest.TestSuite()
-    tester = Tester(module)
-    for name, doc, filename, lineno in tests:
-        if not filename:
+    for test in tests:
+        if len(test.examples) == 0: continue
+        if not test.filename:
             filename = module.__file__
             if filename.endswith(".pyc"):
                 filename = filename[:-1]
             elif filename.endswith(".pyo"):
                 filename = filename[:-1]
-
-        suite.addTest(DocTestTestCase(
-            tester, name, doc, filename, lineno,
-            setUp, tearDown))
+            test.filename = filename
+        suite.addTest(DocTestTestCase(test_runner, test,
+                                      setUp, tearDown))
 
     return suite
 
-def _normalizeModule(module):
-    # Normalize a module
-    if module is None:
-        # Test the calling module
-        module = sys._getframe(2).f_globals['__name__']
-        module = sys.modules[module]
+######################################################################
+## 8. Debugging Support
+######################################################################
 
-    elif isinstance(module, (str, unicode)):
-        module = __import__(module, globals(), locals(), ["*"])
-
-    return module
-
-def _doc(name, object, tests, prefix, filename='', lineno=''):
-    doc = getattr(object, '__doc__', '')
-    if doc and doc.find('>>>') >= 0:
-        tests.append((prefix+name, doc, filename, lineno))
-
-
-def _findTests(module, prefix=None):
-    if prefix is None:
-        prefix = module.__name__
-    dict = module.__dict__
-    tests = []
-    _doc(prefix, module, tests, '',
-         lineno="1 (or below)")
-    prefix = prefix and (prefix + ".")
-    _find(dict.items(), module, dict, tests, prefix)
-    return tests
-
-def _find(items, module, dict, tests, prefix, minlineno=0):
-    for name, object in items:
-
-        # Only interested in named objects
-        if not hasattr(object, '__name__'):
-            continue
-
-        if hasattr(object, 'func_globals'):
-            # Looks like a func
-            if object.func_globals is not dict:
-                # Non-local func
-                continue
-            code = getattr(object, 'func_code', None)
-            filename = getattr(code, 'co_filename', '')
-            lineno = getattr(code, 'co_firstlineno', -1) + 1
-            if minlineno:
-                minlineno = min(lineno, minlineno)
-            else:
-                minlineno = lineno
-            _doc(name, object, tests, prefix, filename, lineno)
-
-        elif hasattr(object, "__module__"):
-            # Maybe a class-like things. In which case, we care
-            if object.__module__ != module.__name__:
-                continue # not the same module
-            if not (hasattr(object, '__dict__')
-                    and hasattr(object, '__bases__')):
-                continue # not a class
-
-            lineno = _find(object.__dict__.items(), module, dict, tests,
-                           prefix+name+".")
-
-            _doc(name, object, tests, prefix,
-                 lineno="%s (or above)" % (lineno-3))
-
-    return minlineno
-
-# end unitest support
-###############################################################################
-
-###############################################################################
-# debugger
-
-def _expect(expect):
+def _want_comment(example):
+    """
+    Return a comment containing the expected output for the given
+    example.
+    """
     # Return the expected output, if any
-    if expect:
-        expect = "\n# ".join(expect.split("\n"))
-        expect = "\n# Expect:\n# %s" % expect
-    return expect
+    want = example.want
+    if want:
+        if want[-1] == '\n': want = want[:-1]
+        want = "\n#     ".join(want.split("\n"))
+        want = "\n# Expected:\n#     %s" % want
+    return want
 
 def testsource(module, name):
     """Extract the test sources from a doctest test docstring as a script
@@ -1481,17 +1775,15 @@ def testsource(module, name):
     with the doc string with tests to be debugged.
 
     """
-    module = _normalizeModule(module)
-    tests = _findTests(module, "")
-    test = [doc for (tname, doc, f, l) in tests if tname == name]
+    module = _normalize_module(module)
+    tests = DocTestFinder().find(module)
+    test = [t for t in tests if t.name == name]
     if not test:
         raise ValueError(name, "not found in tests")
     test = test[0]
-    # XXX we rely on an internal doctest function:
-    examples = _extract_examples(test)
     testsrc = '\n'.join([
-        "%s%s" % (source, _expect(expect))
-        for (source, expect, lineno) in examples
+        "%s%s" % (example.source, _want_comment(example))
+        for example in test.examples
         ])
     return testsrc
 
@@ -1500,38 +1792,38 @@ def debug_src(src, pm=False, globs=None):
 
     The string is provided directly
     """
-    # XXX we rely on an internal doctest function:
-    examples = _extract_examples(src)
-    src = '\n'.join([
-        "%s%s" % (source, _expect(expect))
-        for (source, expect, lineno) in examples
+    test = DocTest(src, globs or {}, 'debug', None, None)
+
+    testsrc = '\n'.join([
+        "%s%s" % (example.source, _want_comment(example))
+        for example in test.examples
         ])
-    debug_script(src, pm, globs)
+    debug_script(testsrc, pm, globs)
 
 def debug_script(src, pm=False, globs=None):
     "Debug a test script"
     import pdb
 
     srcfilename = tempfile.mktemp("doctestdebug.py")
-    open(srcfilename, 'w').write(src)
+    f = open(srcfilename, 'w')
+    f.write(src)
+    f.close()
+
     if globs:
         globs = globs.copy()
     else:
         globs = {}
 
-    try:
-        if pm:
-            try:
-                execfile(srcfilename, globs, globs)
-            except:
-                print sys.exc_info()[1]
-                pdb.post_mortem(sys.exc_info()[2])
-        else:
-            # Note that %r is vital here.  '%s' instead can, e.g., cause
-            # backslashes to get treated as metacharacters on Windows.
-            pdb.run("execfile(%r)" % srcfilename, globs, globs)
-    finally:
-        os.remove(srcfilename)
+    if pm:
+        try:
+            execfile(srcfilename, globs, globs)
+        except:
+            print sys.exc_info()[1]
+            pdb.post_mortem(sys.exc_info()[2])
+    else:
+        # Note that %r is vital here.  '%s' instead can, e.g., cause
+        # backslashes to get treated as metacharacters on Windows.
+        pdb.run("execfile(%r)" % srcfilename, globs, globs)
 
 def debug(module, name, pm=False):
     """Debug a single doctest test doc string
@@ -1541,14 +1833,13 @@ def debug(module, name, pm=False):
     with the doc string with tests to be debugged.
 
     """
-    module = _normalizeModule(module)
+    module = _normalize_module(module)
     testsrc = testsource(module, name)
     debug_script(testsrc, pm, module.__dict__)
 
-# end debugger
-###############################################################################
-
-
+######################################################################
+## 9. Example Usage
+######################################################################
 class _TestClass:
     """
     A pointless class, for sanity-checking of docstring testing.
@@ -1615,11 +1906,150 @@ __test__ = {"_TestClass": _TestClass,
                                     >>> 4 > 4
                                     False
                                     """,
-           }
+            "blank lines": r"""
+            Blank lines can be marked with <BLANKLINE>:
+                >>> print 'foo\n\nbar\n'
+                foo
+                <BLANKLINE>
+                bar
+                <BLANKLINE>
+            """,
+            }
+#             "ellipsis": r"""
+#             If the ellipsis flag is used, then '...' can be used to
+#             elide substrings in the desired output:
+#                 >>> print range(1000)
+#                 [0, 1, 2, ..., 999]
+#             """,
+#             "whitespace normalization": r"""
+#             If the whitespace normalization flag is used, then
+#             differences in whitespace are ignored.
+#                 >>> print range(30)
+#                 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+#                  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
+#                  27, 28, 29]
+#             """,
+#            }
+
+def test1(): r"""
+>>> from doctest import Tester
+>>> t = Tester(globs={'x': 42}, verbose=0)
+>>> t.runstring(r'''
+...      >>> x = x * 2
+...      >>> print x
+...      42
+... ''', 'XYZ')
+**********************************************************************
+Failure in example: print x
+from line #2 of XYZ
+Expected: 42
+Got: 84
+(1, 2)
+>>> t.runstring(">>> x = x * 2\n>>> print x\n84\n", 'example2')
+(0, 2)
+>>> t.summarize()
+**********************************************************************
+1 items had failures:
+   1 of   2 in XYZ
+***Test Failed*** 1 failures.
+(1, 4)
+>>> t.summarize(verbose=1)
+1 items passed all tests:
+   2 tests in example2
+**********************************************************************
+1 items had failures:
+   1 of   2 in XYZ
+4 tests in 2 items.
+3 passed and 1 failed.
+***Test Failed*** 1 failures.
+(1, 4)
+"""
+
+def test2(): r"""
+        >>> t = Tester(globs={}, verbose=1)
+        >>> test = r'''
+        ...    # just an example
+        ...    >>> x = 1 + 2
+        ...    >>> x
+        ...    3
+        ... '''
+        >>> t.runstring(test, "Example")
+        Running string Example
+        Trying: x = 1 + 2
+        Expecting: nothing
+        ok
+        Trying: x
+        Expecting: 3
+        ok
+        0 of 2 examples failed in string Example
+        (0, 2)
+"""
+def test3(): r"""
+        >>> t = Tester(globs={}, verbose=0)
+        >>> def _f():
+        ...     '''Trivial docstring example.
+        ...     >>> assert 2 == 2
+        ...     '''
+        ...     return 32
+        ...
+        >>> t.rundoc(_f)  # expect 0 failures in 1 example
+        (0, 1)
+"""
+def test4(): """
+        >>> import new
+        >>> m1 = new.module('_m1')
+        >>> m2 = new.module('_m2')
+        >>> test_data = \"""
+        ... def _f():
+        ...     '''>>> assert 1 == 1
+        ...     '''
+        ... def g():
+        ...    '''>>> assert 2 != 1
+        ...    '''
+        ... class H:
+        ...    '''>>> assert 2 > 1
+        ...    '''
+        ...    def bar(self):
+        ...        '''>>> assert 1 < 2
+        ...        '''
+        ... \"""
+        >>> exec test_data in m1.__dict__
+        >>> exec test_data in m2.__dict__
+        >>> m1.__dict__.update({"f2": m2._f, "g2": m2.g, "h2": m2.H})
+
+        Tests that objects outside m1 are excluded:
+
+        >>> t = Tester(globs={}, verbose=0, isprivate=is_private)
+        >>> t.rundict(m1.__dict__, "rundict_test", m1)  # _f, f2 and g2 and h2 skipped
+        (0, 3)
+
+        Again, but with the default isprivate function allowing _f:
+
+        >>> t = Tester(globs={}, verbose=0)
+        >>> t.rundict(m1.__dict__, "rundict_test_pvt", m1)  # Only f2, g2 and h2 skipped
+        (0, 4)
+
+        And once more, not excluding stuff outside m1:
+
+        >>> t = Tester(globs={}, verbose=0)
+        >>> t.rundict(m1.__dict__, "rundict_test_pvt")  # None are skipped.
+        (0, 8)
+
+        The exclusion of objects from outside the designated module is
+        meant to be invoked automagically by testmod.
+
+        >>> testmod(m1, isprivate=is_private, verbose=False)
+        (0, 3)
+"""
 
 def _test():
-    import doctest
-    return doctest.testmod(doctest)
+    #import doctest
+    #doctest.testmod(doctest, verbose=False,
+    #                optionflags=ELLIPSIS | NORMALIZE_WHITESPACE |
+    #                UNIFIED_DIFF)
+    #print '~'*70
+    r = unittest.TextTestRunner()
+    r.run(DocTestSuite())
 
 if __name__ == "__main__":
     _test()
index fd3426c91e89953b3be3e95b9f094a975a5d2671..68ac44c6e6f793a8185405ea5ff0b5136b4fd8ae 100644 (file)
-import doctest
+"""
+Test script for doctest.
+"""
+
 from test import test_support
-test_support.run_doctest(doctest)
+import doctest
+
+######################################################################
+## Sample Objects (used by test cases)
+######################################################################
+
+def sample_func(v):
+    """
+    >>> print sample_func(22)
+    44
+    """
+    return v+v
+
+class SampleClass:
+    """
+    >>> print 1
+    1
+    """
+    def __init__(self, val):
+        """
+        >>> print SampleClass(12).get()
+        12
+        """
+        self.val = val
+
+    def double(self):
+        """
+        >>> print SampleClass(12).double().get()
+        24
+        """
+        return SampleClass(self.val + self.val)
+
+    def get(self):
+        """
+        >>> print SampleClass(-5).get()
+        -5
+        """
+        return self.val
+
+    def a_staticmethod(v):
+        """
+        >>> print SampleClass.a_staticmethod(10)
+        11
+        """
+        return v+1
+    a_staticmethod = staticmethod(a_staticmethod)
+
+    def a_classmethod(cls, v):
+        """
+        >>> print SampleClass.a_classmethod(10)
+        12
+        >>> print SampleClass(0).a_classmethod(10)
+        12
+        """
+        return v+2
+    a_classmethod = classmethod(a_classmethod)
+
+    a_property = property(get, doc="""
+        >>> print SampleClass(22).a_property
+        22
+        """)
+
+    class NestedClass:
+        """
+        >>> x = SampleClass.NestedClass(5)
+        >>> y = x.square()
+        >>> print y.get()
+        25
+        """
+        def __init__(self, val=0):
+            """
+            >>> print SampleClass.NestedClass().get()
+            0
+            """
+            self.val = val
+        def square(self):
+            return SampleClass.NestedClass(self.val*self.val)
+        def get(self):
+            return self.val
+
+class SampleNewStyleClass(object):
+    r"""
+    >>> print '1\n2\n3'
+    1
+    2
+    3
+    """
+    def __init__(self, val):
+        """
+        >>> print SampleNewStyleClass(12).get()
+        12
+        """
+        self.val = val
+
+    def double(self):
+        """
+        >>> print SampleNewStyleClass(12).double().get()
+        24
+        """
+        return SampleNewStyleClass(self.val + self.val)
+
+    def get(self):
+        """
+        >>> print SampleNewStyleClass(-5).get()
+        -5
+        """
+        return self.val
+
+######################################################################
+## Test Cases
+######################################################################
+
+def test_Example(): r"""
+Unit tests for the `Example` class.
+
+Example is a simple container class that holds a source code string,
+an expected output string, and a line number (within the docstring):
+
+    >>> example = doctest.Example('print 1', '1\n', 0)
+    >>> (example.source, example.want, example.lineno)
+    ('print 1', '1\n', 0)
+
+The `source` string should end in a newline iff the source spans more
+than one line:
+
+    >>> # Source spans a single line: no terminating newline.
+    >>> e = doctest.Example('print 1', '1\n', 0)
+    >>> e = doctest.Example('print 1\n', '1\n', 0)
+    Traceback (most recent call last):
+    AssertionError
+
+    >>> # Source spans multiple lines: require terminating newline.
+    >>> e = doctest.Example('print 1;\nprint 2\n', '1\n2\n', 0)
+    >>> e = doctest.Example('print 1;\nprint 2', '1\n2\n', 0)
+    Traceback (most recent call last):
+    AssertionError
+
+The `want` string should be terminated by a newline, unless it's the
+empty string:
+
+    >>> e = doctest.Example('print 1', '1\n', 0)
+    >>> e = doctest.Example('print 1', '1', 0)
+    Traceback (most recent call last):
+    AssertionError
+    >>> e = doctest.Example('print', '', 0)
+"""
+
+def test_DocTest(): r"""
+Unit tests for the `DocTest` class.
+
+DocTest is a collection of examples, extracted from a docstring, along
+with information about where the docstring comes from (a name,
+filename, and line number).  The docstring is parsed by the `DocTest`
+constructor:
+
+    >>> docstring = '''
+    ...     >>> print 12
+    ...     12
+    ...
+    ... Non-example text.
+    ...
+    ...     >>> print 'another\example'
+    ...     another
+    ...     example
+    ... '''
+    >>> globs = {} # globals to run the test in.
+    >>> test = doctest.DocTest(docstring, globs, 'some_test', 'some_file', 20)
+    >>> print test
+    <DocTest some_test from some_file:20 (2 examples)>
+    >>> len(test.examples)
+    2
+    >>> e1, e2 = test.examples
+    >>> (e1.source, e1.want, e1.lineno)
+    ('print 12', '12\n', 1)
+    >>> (e2.source, e2.want, e2.lineno)
+    ("print 'another\\example'", 'another\nexample\n', 6)
+
+Source information (name, filename, and line number) is available as
+attributes on the doctest object:
+
+    >>> (test.name, test.filename, test.lineno)
+    ('some_test', 'some_file', 20)
+
+The line number of an example within its containing file is found by
+adding the line number of the example and the line number of its
+containing test:
+
+    >>> test.lineno + e1.lineno
+    21
+    >>> test.lineno + e2.lineno
+    26
+
+If the docstring contains inconsistant leading whitespace in the
+expected output of an example, then `DocTest` will raise a ValueError:
+
+    >>> docstring = r'''
+    ...       >>> print 'bad\nindentation'
+    ...       bad
+    ...     indentation
+    ...     '''
+    >>> doctest.DocTest(docstring, globs, 'some_test', 'filename', 0)
+    Traceback (most recent call last):
+    ValueError: line 3 of the docstring for some_test has inconsistent leading whitespace: '    indentation'
+
+If the docstring contains inconsistent leading whitespace on
+continuation lines, then `DocTest` will raise a ValueError:
+
+    >>> docstring = r'''
+    ...       >>> print ('bad indentation',
+    ...     ...          2)
+    ...       ('bad', 'indentation')
+    ...     '''
+    >>> doctest.DocTest(docstring, globs, 'some_test', 'filename', 0)
+    Traceback (most recent call last):
+    ValueError: line 2 of the docstring for some_test has inconsistent leading whitespace: '    ...          2)'
+
+If there's no blank space after a PS1 prompt ('>>>'), then `DocTest`
+will raise a ValueError:
+
+    >>> docstring = '>>>print 1\n1'
+    >>> doctest.DocTest(docstring, globs, 'some_test', 'filename', 0)
+    Traceback (most recent call last):
+    ValueError: line 0 of the docstring for some_test lacks blank after >>>: '>>>print 1'
+"""
+
+# [XX] test that it's getting line numbers right.
+def test_DocTestFinder(): r"""
+Unit tests for the `DocTestFinder` class.
+
+DocTestFinder is used to extract DocTests from an object's docstring
+and the docstrings of its contained objects.  It can be used with
+modules, functions, classes, methods, staticmethods, classmethods, and
+properties.
+
+Finding Tests in Functions
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+For a function whose docstring contains examples, DocTestFinder.find()
+will return a single test (for that function's docstring):
+
+    >>> # Allow ellipsis in the following examples (since the filename
+    >>> # and line number in the traceback can vary):
+    >>> doctest: +ELLIPSIS
+
+    >>> finder = doctest.DocTestFinder()
+    >>> tests = finder.find(sample_func)
+    >>> print tests
+    [<DocTest sample_func from ...:12 (1 example)>]
+    >>> e = tests[0].examples[0]
+    >>> print (e.source, e.want, e.lineno)
+    ('print sample_func(22)', '44\n', 1)
+
+    >>> doctest: -ELLIPSIS # Turn ellipsis back off
+
+If an object has no docstring, then a test is not created for it:
+
+    >>> def no_docstring(v):
+    ...     pass
+    >>> finder.find(no_docstring)
+    []
+
+If the function has a docstring with no examples, then a test with no
+examples is returned.  (This lets `DocTestRunner` collect statistics
+about which functions have no tests -- but is that useful?  And should
+an empty test also be created when there's no docstring?)
+
+    >>> def no_examples(v):
+    ...     ''' no doctest examples '''
+    >>> finder.find(no_examples)
+    [<DocTest no_examples from None:1 (no examples)>]
+
+Finding Tests in Classes
+~~~~~~~~~~~~~~~~~~~~~~~~
+For a class, DocTestFinder will create a test for the class's
+docstring, and will recursively explore its contents, including
+methods, classmethods, staticmethods, properties, and nested classes.
+
+    >>> finder = doctest.DocTestFinder()
+    >>> tests = finder.find(SampleClass)
+    >>> tests.sort()
+    >>> for t in tests:
+    ...     print '%2s  %s' % (len(t.examples), t.name)
+     1  SampleClass
+     3  SampleClass.NestedClass
+     1  SampleClass.NestedClass.__init__
+     1  SampleClass.__init__
+     2  SampleClass.a_classmethod
+     1  SampleClass.a_property
+     1  SampleClass.a_staticmethod
+     1  SampleClass.double
+     1  SampleClass.get
+
+New-style classes are also supported:
+
+    >>> tests = finder.find(SampleNewStyleClass)
+    >>> tests.sort()
+    >>> for t in tests:
+    ...     print '%2s  %s' % (len(t.examples), t.name)
+     1  SampleNewStyleClass
+     1  SampleNewStyleClass.__init__
+     1  SampleNewStyleClass.double
+     1  SampleNewStyleClass.get
+
+Finding Tests in Modules
+~~~~~~~~~~~~~~~~~~~~~~~~
+For a module, DocTestFinder will create a test for the class's
+docstring, and will recursively explore its contents, including
+functions, classes, and the `__test__` dictionary, if it exists:
+
+    >>> # A module
+    >>> import new
+    >>> m = new.module('some_module')
+    >>> def triple(val):
+    ...     '''
+    ...     >>> print tripple(11)
+    ...     33
+    ...     '''
+    ...     return val*3
+    >>> m.__dict__.update({
+    ...     'sample_func': sample_func,
+    ...     'SampleClass': SampleClass,
+    ...     '__doc__': '''
+    ...         Module docstring.
+    ...             >>> print 'module'
+    ...             module
+    ...         ''',
+    ...     '__test__': {
+    ...         'd': '>>> print 6\n6\n>>> print 7\n7\n',
+    ...         'c': triple}})
+
+    >>> finder = doctest.DocTestFinder()
+    >>> # Use module=test.test_doctest, to prevent doctest from
+    >>> # ignoring the objects since they weren't defined in m.
+    >>> import test.test_doctest
+    >>> tests = finder.find(m, module=test.test_doctest)
+    >>> tests.sort()
+    >>> for t in tests:
+    ...     print '%2s  %s' % (len(t.examples), t.name)
+     1  some_module
+     1  some_module.SampleClass
+     3  some_module.SampleClass.NestedClass
+     1  some_module.SampleClass.NestedClass.__init__
+     1  some_module.SampleClass.__init__
+     2  some_module.SampleClass.a_classmethod
+     1  some_module.SampleClass.a_property
+     1  some_module.SampleClass.a_staticmethod
+     1  some_module.SampleClass.double
+     1  some_module.SampleClass.get
+     1  some_module.c
+     2  some_module.d
+     1  some_module.sample_func
+
+Duplicate Removal
+~~~~~~~~~~~~~~~~~
+If a single object is listed twice (under different names), then tests
+will only be generated for it once:
+
+    >>> class TwoNames:
+    ...     '''f() and g() are two names for the same method'''
+    ...
+    ...     def f(self):
+    ...         '''
+    ...         >>> print TwoNames().f()
+    ...         f
+    ...         '''
+    ...         return 'f'
+    ...
+    ...     g = f # define an alias for f.
+
+    >>> finder = doctest.DocTestFinder()
+    >>> tests = finder.find(TwoNames, ignore_imports=False)
+    >>> tests.sort()
+    >>> print len(tests)
+    2
+    >>> print tests[0].name
+    TwoNames
+    >>> print tests[1].name in ('TwoNames.f', 'TwoNames.g')
+    True
+
+Filter Functions
+~~~~~~~~~~~~~~~~
+Two filter functions can be used to restrict which objects get
+examined: a name-based filter and an object-based filter.
+
+    >>> def namefilter(prefix, base):
+    ...     return base.startswith('a_')
+    >>> tests = doctest.DocTestFinder(namefilter=namefilter).find(SampleClass)
+    >>> tests.sort()
+    >>> for t in tests:
+    ...     print '%2s  %s' % (len(t.examples), t.name)
+     1  SampleClass
+     3  SampleClass.NestedClass
+     1  SampleClass.NestedClass.__init__
+     1  SampleClass.__init__
+     1  SampleClass.double
+     1  SampleClass.get
+
+    >>> def objfilter(obj):
+    ...     return isinstance(obj, (staticmethod, classmethod))
+    >>> tests = doctest.DocTestFinder(objfilter=objfilter).find(SampleClass)
+    >>> tests.sort()
+    >>> for t in tests:
+    ...     print '%2s  %s' % (len(t.examples), t.name)
+     1  SampleClass
+     3  SampleClass.NestedClass
+     1  SampleClass.NestedClass.__init__
+     1  SampleClass.__init__
+     1  SampleClass.a_property
+     1  SampleClass.double
+     1  SampleClass.get
+
+If a given object is filtered out, then none of the objects that it
+contains will be added either:
+
+    >>> def namefilter(prefix, base):
+    ...     return base == 'NestedClass'
+    >>> tests = doctest.DocTestFinder(namefilter=namefilter).find(SampleClass)
+    >>> tests.sort()
+    >>> for t in tests:
+    ...     print '%2s  %s' % (len(t.examples), t.name)
+     1  SampleClass
+     1  SampleClass.__init__
+     2  SampleClass.a_classmethod
+     1  SampleClass.a_property
+     1  SampleClass.a_staticmethod
+     1  SampleClass.double
+     1  SampleClass.get
+
+The filter functions apply to contained objects, and *not* to the
+object explicitly passed to DocTestFinder:
+
+    >>> def namefilter(prefix, base):
+    ...     return base == 'SampleClass'
+    >>> tests = doctest.DocTestFinder(namefilter=namefilter).find(SampleClass)
+    >>> len(tests)
+    9
+
+Turning off Recursion
+~~~~~~~~~~~~~~~~~~~~~
+DocTestFinder can be told not to look for tests in contained objects
+using the `recurse` flag:
+
+    >>> tests = doctest.DocTestFinder(recurse=False).find(SampleClass)
+    >>> tests.sort()
+    >>> for t in tests:
+    ...     print '%2s  %s' % (len(t.examples), t.name)
+     1  SampleClass
+"""
+
+class test_DocTestRunner:
+    def basics(): r"""
+Unit tests for the `DocTestRunner` class.
+
+DocTestRunner is used to run DocTest test cases, and to accumulate
+statistics.  Here's a simple DocTest case we can use:
+
+    >>> def f(x):
+    ...     '''
+    ...     >>> x = 12
+    ...     >>> print x
+    ...     12
+    ...     >>> x/2
+    ...     6
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+
+The main DocTestRunner interface is the `run` method, which runs a
+given DocTest case in a given namespace (globs).  It returns a tuple
+`(f,t)`, where `f` is the number of failed tests and `t` is the number
+of tried tests.
+
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    (0, 3)
+
+If any example produces incorrect output, then the test runner reports
+the failure and proceeds to the next example:
+
+    >>> def f(x):
+    ...     '''
+    ...     >>> x = 12
+    ...     >>> print x
+    ...     14
+    ...     >>> x/2
+    ...     6
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=True).run(test)
+    Trying: x = 12
+    Expecting: nothing
+    ok
+    Trying: print x
+    Expecting: 14
+    **********************************************************************
+    Failure in example: print x
+    from line #2 of f
+    Expected: 14
+    Got: 12
+    Trying: x/2
+    Expecting: 6
+    ok
+    (1, 3)
+"""
+    def verbose_flag(): r"""
+The `verbose` flag makes the test runner generate more detailed
+output:
+
+    >>> def f(x):
+    ...     '''
+    ...     >>> x = 12
+    ...     >>> print x
+    ...     12
+    ...     >>> x/2
+    ...     6
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+
+    >>> doctest.DocTestRunner(verbose=True).run(test)
+    Trying: x = 12
+    Expecting: nothing
+    ok
+    Trying: print x
+    Expecting: 12
+    ok
+    Trying: x/2
+    Expecting: 6
+    ok
+    (0, 3)
+
+If the `verbose` flag is unspecified, then the output will be verbose
+iff `-v` appears in sys.argv:
+
+    >>> # Save the real sys.argv list.
+    >>> old_argv = sys.argv
+
+    >>> # If -v does not appear in sys.argv, then output isn't verbose.
+    >>> sys.argv = ['test']
+    >>> doctest.DocTestRunner().run(test)
+    (0, 3)
+
+    >>> # If -v does appear in sys.argv, then output is verbose.
+    >>> sys.argv = ['test', '-v']
+    >>> doctest.DocTestRunner().run(test)
+    Trying: x = 12
+    Expecting: nothing
+    ok
+    Trying: print x
+    Expecting: 12
+    ok
+    Trying: x/2
+    Expecting: 6
+    ok
+    (0, 3)
+
+    >>> # Restore sys.argv
+    >>> sys.argv = old_argv
+
+In the remaining examples, the test runner's verbosity will be
+explicitly set, to ensure that the test behavior is consistent.
+    """
+    def exceptions(): r"""
+Tests of `DocTestRunner`'s exception handling.
+
+An expected exception is specified with a traceback message.  The
+lines between the first line and the type/value may be omitted or
+replaced with any other string:
+
+    >>> def f(x):
+    ...     '''
+    ...     >>> x = 12
+    ...     >>> print x/0
+    ...     Traceback (most recent call last):
+    ...     ZeroDivisionError: integer division or modulo by zero
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    (0, 2)
+
+An example may generate output before it raises an exception; if it
+does, then the output must match the expected output:
+
+    >>> def f(x):
+    ...     '''
+    ...     >>> x = 12
+    ...     >>> print 'pre-exception output', x/0
+    ...     pre-exception output
+    ...     Traceback (most recent call last):
+    ...     ZeroDivisionError: integer division or modulo by zero
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    (0, 2)
+
+Exception messages may contain newlines:
+
+    >>> def f(x):
+    ...     r'''
+    ...     >>> raise ValueError, 'multi\nline\nmessage'
+    ...     Traceback (most recent call last):
+    ...     ValueError: multi
+    ...     line
+    ...     message
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    (0, 1)
+
+If an exception is expected, but an exception with the wrong type or
+message is raised, then it is reported as a failure:
+
+    >>> def f(x):
+    ...     r'''
+    ...     >>> raise ValueError, 'message'
+    ...     Traceback (most recent call last):
+    ...     ValueError: wrong message
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    **********************************************************************
+    Failure in example: raise ValueError, 'message'
+    from line #1 of f
+    Expected:
+        Traceback (most recent call last):
+        ValueError: wrong message
+    Got:
+        Traceback (most recent call last):
+        ValueError: message
+    (1, 1)
+
+If an exception is raised but not expected, then it is reported as an
+unexpected exception:
+
+    >>> # Allow ellipsis in the following examples (since the filename
+    >>> # and line number in the traceback can vary):
+    >>> doctest: +ELLIPSIS
+
+    >>> def f(x):
+    ...     r'''
+    ...     >>> 1/0
+    ...     0
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    **********************************************************************
+    Failure in example: 1/0
+    from line #1 of f
+    Exception raised:
+        Traceback (most recent call last):
+          ...
+        ZeroDivisionError: integer division or modulo by zero
+    (1, 1)
+
+    >>> doctest: -ELLIPSIS # Turn ellipsis back off:
+"""
+    def optionflags(): r"""
+Tests of `DocTestRunner`'s option flag handling.
+
+Several option flags can be used to customize the behavior of the test
+runner.  These are defined as module constants in doctest, and passed
+to the DocTestRunner constructor (multiple constants should be or-ed
+together).
+
+The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False
+and 1/0:
+
+    >>> def f(x):
+    ...     '>>> True\n1\n'
+
+    >>> # Without the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    (0, 1)
+
+    >>> # With the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> flags = doctest.DONT_ACCEPT_TRUE_FOR_1
+    >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+    **********************************************************************
+    Failure in example: True
+    from line #0 of f
+    Expected: 1
+    Got: True
+    (1, 1)
+
+The DONT_ACCEPT_BLANKLINE flag disables the match between blank lines
+and the '<BLANKLINE>' marker:
+
+    >>> def f(x):
+    ...     '>>> print "a\\n\\nb"\na\n<BLANKLINE>\nb\n'
+
+    >>> # Without the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    (0, 1)
+
+    >>> # With the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> flags = doctest.DONT_ACCEPT_BLANKLINE
+    >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+    **********************************************************************
+    Failure in example: print "a\n\nb"
+    from line #0 of f
+    Expected:
+        a
+        <BLANKLINE>
+        b
+    Got:
+        a
+    <BLANKLINE>
+        b
+    (1, 1)
+
+The NORMALIZE_WHITESPACE flag causes all sequences of whitespace to be
+treated as equal:
+
+    >>> def f(x):
+    ...     '>>> print 1, 2, 3\n  1   2\n 3'
+
+    >>> # Without the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    **********************************************************************
+    Failure in example: print 1, 2, 3
+    from line #0 of f
+    Expected:
+          1   2
+         3
+    Got: 1 2 3
+    (1, 1)
+
+    >>> # With the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> flags = doctest.NORMALIZE_WHITESPACE
+    >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+    (0, 1)
+
+The ELLIPSIS flag causes ellipsis marker ("...") in the expected
+output to match any substring in the actual output:
+
+    >>> def f(x):
+    ...     '>>> print range(15)\n[0, 1, 2, ..., 14]\n'
+
+    >>> # Without the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    **********************************************************************
+    Failure in example: print range(15)
+    from line #0 of f
+    Expected: [0, 1, 2, ..., 14]
+    Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
+    (1, 1)
+
+    >>> # With the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> flags = doctest.ELLIPSIS
+    >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+    (0, 1)
+
+The UNIFIED_DIFF flag causes failures that involve multi-line expected
+and actual outputs to be displayed using a unified diff:
+
+    >>> def f(x):
+    ...     r'''
+    ...     >>> print '\n'.join('abcdefg')
+    ...     a
+    ...     B
+    ...     c
+    ...     d
+    ...     f
+    ...     g
+    ...     h
+    ...     '''
+
+    >>> # Without the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    **********************************************************************
+    Failure in example: print '\n'.join('abcdefg')
+    from line #1 of f
+    Expected:
+        a
+        B
+        c
+        d
+        f
+        g
+        h
+    Got:
+        a
+        b
+        c
+        d
+        e
+        f
+        g
+    (1, 1)
+
+    >>> # With the flag:
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> flags = doctest.UNIFIED_DIFF
+    >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+    **********************************************************************
+    Failure in example: print '\n'.join('abcdefg')
+    from line #1 of f
+    Differences (unified diff):
+        --- Expected
+        +++ Got
+        @@ -1,8 +1,8 @@
+         a
+        -B
+        +b
+         c
+         d
+        +e
+         f
+         g
+        -h
+    <BLANKLINE>
+    (1, 1)
+
+The CONTEXT_DIFF flag causes failures that involve multi-line expected
+and actual outputs to be displayed using a context diff:
+
+    >>> # Reuse f() from the UNIFIED_DIFF example, above.
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> flags = doctest.CONTEXT_DIFF
+    >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+    **********************************************************************
+    Failure in example: print '\n'.join('abcdefg')
+    from line #1 of f
+    Differences (context diff):
+        *** Expected
+        --- Got
+        ***************
+        *** 1,8 ****
+          a
+        ! B
+          c
+          d
+          f
+          g
+        - h
+    <BLANKLINE>
+        --- 1,8 ----
+          a
+        ! b
+          c
+          d
+        + e
+          f
+          g
+    <BLANKLINE>
+    (1, 1)
+"""
+    def option_directives(): r"""
+Tests of `DocTestRunner`'s option directive mechanism.
+
+Option directives can be used to turn option flags on or off from
+within a DocTest case.  The following example shows how a flag can be
+turned on and off.  Note that comments on the same line as the option
+directive are ignored.
+
+    >>> def f(x): r'''
+    ...     >>> print range(10)       # Should fail: no ellipsis
+    ...     [0, 1, ..., 9]
+    ...
+    ...     >>> doctest: +ELLIPSIS    # turn ellipsis on.
+    ...     >>> print range(10)       # Should succeed
+    ...     [0, 1, ..., 9]
+    ...
+    ...     >>> doctest: -ELLIPSIS    # turn ellipsis back off.
+    ...     >>> print range(10)       # Should fail: no ellipsis
+    ...     [0, 1, ..., 9]
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    **********************************************************************
+    Failure in example: print range(10)       # Should fail: no ellipsis
+    from line #1 of f
+    Expected: [0, 1, ..., 9]
+    Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    **********************************************************************
+    Failure in example: print range(10)       # Should fail: no ellipsis
+    from line #9 of f
+    Expected: [0, 1, ..., 9]
+    Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    (2, 3)
+
+Multiple flags can be toggled by a single option directive:
+
+    >>> def f(x): r'''
+    ...     >>> print range(10)       # Should fail
+    ...     [0, 1,  ...,   9]
+    ...     >>> doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+    ...     >>> print range(10)       # Should succeed
+    ...     [0, 1,  ...,   9]
+    ...     '''
+    >>> test = doctest.DocTestFinder().find(f)[0]
+    >>> doctest.DocTestRunner(verbose=False).run(test)
+    **********************************************************************
+    Failure in example: print range(10)       # Should fail
+    from line #1 of f
+    Expected: [0, 1,  ...,   9]
+    Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    (1, 2)
+"""
+
+def test_testsource(): r"""
+Unit tests for `testsource()`.
+
+The testsource() function takes a module and a name, finds the (first)
+test with that name in that module, and converts it to an
+
+    >>> import test.test_doctest
+    >>> name = 'test.test_doctest.sample_func'
+    >>> print doctest.testsource(test.test_doctest, name)
+    print sample_func(22)
+    # Expected:
+    #     44
+
+    >>> name = 'test.test_doctest.SampleNewStyleClass'
+    >>> print doctest.testsource(test.test_doctest, name)
+    print '1\n2\n3'
+    # Expected:
+    #     1
+    #     2
+    #     3
+
+    >>> name = 'test.test_doctest.SampleClass.a_classmethod'
+    >>> print doctest.testsource(test.test_doctest, name)
+    print SampleClass.a_classmethod(10)
+    # Expected:
+    #     12
+    print SampleClass(0).a_classmethod(10)
+    # Expected:
+    #     12
+"""
+
+def test_debug(): r"""
+
+Create a docstring that we want to debug:
+
+    >>> s = '''
+    ...     >>> x = 12
+    ...     >>> print x
+    ...     12
+    ...     '''
+
+Create some fake stdin input, to feed to the debugger:
+
+    >>> import tempfile
+    >>> fake_stdin = tempfile.TemporaryFile(mode='w+')
+    >>> fake_stdin.write('\n'.join(['next', 'print x', 'continue', '']))
+    >>> fake_stdin.seek(0)
+    >>> real_stdin = sys.stdin
+    >>> sys.stdin = fake_stdin
+
+Run the debugger on the docstring, and then restore sys.stdin.
+
+    >>> doctest: +NORMALIZE_WHITESPACE
+    >>> try:
+    ...     doctest.debug_src(s)
+    ... finally:
+    ...      sys.stdin = real_stdin
+    ...      fake_stdin.close()
+    > <string>(1)?()
+    (Pdb) 12
+    --Return--
+    > <string>(1)?()->None
+    (Pdb) 12
+    (Pdb)
+
+"""
+
+######################################################################
+## Main
+######################################################################
+
+def test_main():
+    # Check the doctest cases in doctest itself:
+    test_support.run_doctest(doctest, verbosity=True)
+    # Check the doctest cases defined here:
+    from test import test_doctest
+    test_support.run_doctest(test_doctest, verbosity=True)
+
+import trace, sys, re, StringIO
+def test_coverage(coverdir):
+    tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,],
+                         trace=0, count=1)
+    tracer.run('reload(doctest); test_main()')
+    r = tracer.results()
+    print 'Writing coverage results...'
+    r.write_results(show_missing=True, summary=True,
+                    coverdir=coverdir)
+
+if __name__ == '__main__':
+    if '-c' in sys.argv:
+        test_coverage('/tmp/doctest.cover')
+    else:
+        test_main()
index be1d1cee4f66f67dc0ef2ecaa435bea9f335a0ab..74be15f8c5a0a392b2b7362c2696f205a5afbe87 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -83,6 +83,17 @@ Extension modules
 Library
 -------
 
+- Thanks to Edward Loper, doctest has been massively refactored, and
+  many new features were added.  Full docs will appear later.  For now
+  the doctest module comments and new test cases give good coverage.
+  The refactoring provides many hook points for customizing behavior
+  (such as how to report errors, and how to compare expected to actual
+  output).  New features include a <BLANKLINE> marker for expected
+  output containing blank lines, options to produce unified or context
+  diffs when actual output doesn't match expectations, an option to
+  normalize whitespace before comparing, and an option to use an
+  ellipsis to signify "don't care" regions of output.
+
 - Tkinter now supports the wish -sync and -use options.
 
 - The following methods in time support passing of None: ctime(), gmtime(),