- Split DocTestRunner's check_output and output_difference methods off
  into their own class, OutputChecker.
- Added optional OutputChecker arguments to DocTestRunner,
  DocTestCase, DocTestSuite.
diff --git a/Lib/doctest.py b/Lib/doctest.py
index 4c651ed..328e7db 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -1062,9 +1062,6 @@
 ## 5. 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.
@@ -1105,12 +1102,11 @@
         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`.
+    by an `OutputChecker`.  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 passing a subclass of
+    `OutputChecker` to the constructor.
 
     The test runner's display output can be controlled in two ways.
     First, an output function (`out) can be passed to
@@ -1125,10 +1121,14 @@
     # separate sections of the summary.
     DIVIDER = "*" * 70
 
-    def __init__(self, verbose=None, optionflags=0):
+    def __init__(self, checker=None, verbose=None, optionflags=0):
         """
         Create a new test runner.
 
+        Optional keyword arg `checker` is the `OutputChecker` that
+        should be used to compare the expected outputs and actual
+        outputs of doctest 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.
@@ -1138,6 +1138,7 @@
         it displays failures.  See the documentation for `testmod` for
         more information.
         """
+        self._checker = checker or OutputChecker()
         if verbose is None:
             verbose = '-v' in sys.argv
         self._verbose = verbose
@@ -1152,114 +1153,6 @@
         self._fakeout = _SpoofOut()
 
     #/////////////////////////////////////////////////////////////////
-    # 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
     #/////////////////////////////////////////////////////////////////
 
@@ -1286,7 +1179,8 @@
         """
         # Print an error message.
         out(self.__failure_header(test, example) +
-            self.output_difference(example.want, got))
+            self._checker.output_difference(example.want, got,
+                                            self.optionflags))
 
     def report_unexpected_exception(self, out, test, example, exc_info):
         """
@@ -1412,7 +1306,8 @@
             # 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):
+                if self._checker.check_output(example.want, got,
+                                              self.optionflags):
                     self.report_success(out, test, example, got)
                 else:
                     self.report_failure(out, test, example, got)
@@ -1436,8 +1331,10 @@
                     # 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)):
+                    if (self._checker.check_output(m.group('out'), got,
+                                                   self.optionflags) and
+                        self._checker.check_output(m.group('exc'), exc_msg,
+                                                   self.optionflags)):
                         # Is +exc_msg the right thing here??
                         self.report_success(out, test, example,
                                             got+exc_hdr+exc_msg)
@@ -1554,6 +1451,115 @@
             print "Test passed."
         return totalf, totalt
 
+class OutputChecker:
+    """
+    A class used to check the whether the actual output from a doctest
+    example matches the expected output.  `OutputChecker` defines two
+    methods: `check_output`, which compares a given pair of outputs,
+    and returns true if they match; and `output_difference`, which
+    returns a string describing the differences between two outputs.
+    """
+    def check_output(self, want, got, optionflags):
+        """
+        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 (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 (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 (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 (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, optionflags):
+        """
+        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 (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 ((optionflags & (UNIFIED_DIFF | CONTEXT_DIFF)) and
+            want.count('\n') > 2 and got.count('\n') > 2 and
+            not (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 optionflags & UNIFIED_DIFF:
+                diff = difflib.unified_diff(want_lines, got_lines, n=2,
+                                            fromfile='Expected', tofile='Got')
+                kind = 'unified'
+            elif 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))
+
 class DocTestFailure(Exception):
     """A DocTest example has failed in debugging mode.
 
@@ -1937,9 +1943,11 @@
 
 class DocTestCase(unittest.TestCase):
 
-    def __init__(self, test, optionflags=0, setUp=None, tearDown=None):
+    def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
+                 checker=None):
         unittest.TestCase.__init__(self)
         self._dt_optionflags = optionflags
+        self._dt_checker = checker
         self._dt_test = test
         self._dt_setUp = setUp
         self._dt_tearDown = tearDown
@@ -1956,7 +1964,8 @@
         test = self._dt_test
         old = sys.stdout
         new = StringIO()
-        runner = DocTestRunner(optionflags=self._dt_optionflags, verbose=False)
+        runner = DocTestRunner(optionflags=self._dt_optionflags,
+                               checker=self._dt_checker, verbose=False)
 
         try:
             runner.DIVIDER = "-"*70
@@ -2045,7 +2054,8 @@
 
            """
 
-        runner = DebugRunner(verbose = False, optionflags=self._dt_optionflags)
+        runner = DebugRunner(optionflags=self._dt_optionflags,
+                             checker=self._dt_checker, verbose=False)
         runner.run(self._dt_test, out=nooutput)
 
     def id(self):
@@ -2065,7 +2075,8 @@
 
 def DocTestSuite(module=None, globs=None, extraglobs=None,
                  optionflags=0, test_finder=None,
-                 setUp=lambda: None, tearDown=lambda: None):
+                 setUp=lambda: None, tearDown=lambda: None,
+                 checker=None):
     """
     Convert doctest tests for a mudule to a unittest test suite.
 
@@ -2103,7 +2114,8 @@
             elif filename.endswith(".pyo"):
                 filename = filename[:-1]
             test.filename = filename
-        suite.addTest(DocTestCase(test, optionflags, setUp, tearDown))
+        suite.addTest(DocTestCase(test, optionflags, setUp, tearDown,
+                                  checker))
 
     return suite