From 8485b562164304d068dfac4cee0f5108251eda55 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Wed, 4 Aug 2004 18:46:34 +0000 Subject: Edward Loper's cool and massive refactoring of doctest.py, merged from 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 | 2316 +++++++++++++++++++++++++++------------------- Lib/test/test_doctest.py | 1004 +++++++++++++++++++- Misc/NEWS | 11 + 3 files changed, 2386 insertions(+), 945 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 69047db..ecf0e1a 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -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, "", "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 = '' +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 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 ('' % + (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 + + # 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 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 s are being used, then replace + # 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.*)' + '^(?PTraceback \((?:%s|%s)\):)\s*$.*?' + '^(?P\w+.*)') % + ('most recent call last', 'innermost last'), + re.MULTILINE | re.DOTALL) + + _OPTION_DIRECTIVE_RE = re.compile('\s*doctest:\s*(?P[^#\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', "", "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 "", 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 : + >>> print 'foo\n\nbar\n' + foo + + bar + + """, + } +# "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() diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index fd3426c..68ac44c 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -1,3 +1,1003 @@ -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 + + >>> 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 + [] + >>> 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) + [] + +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 '' marker: + + >>> def f(x): + ... '>>> print "a\\n\\nb"\na\n\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 + + b + Got: + a + + 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 + + (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 + + --- 1,8 ---- + a + ! b + c + d + + e + f + g + + (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() + > (1)?() + (Pdb) 12 + --Return-- + > (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() diff --git a/Misc/NEWS b/Misc/NEWS index be1d1ce..74be15f 100644 --- 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 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(), -- cgit v0.12