summaryrefslogtreecommitdiffstats
path: root/Lib/doctest.py
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2001-10-02 03:53:41 (GMT)
committerTim Peters <tim.peters@gmail.com>2001-10-02 03:53:41 (GMT)
commit7402f791a4633689fea904b2bd9caea2b25de620 (patch)
tree36d27033dcf9d5f36b607a94bb3c8d52ecfe557e /Lib/doctest.py
parentd90f509b8f3482787eeeae560d9bc1c909a8e857 (diff)
downloadcpython-7402f791a4633689fea904b2bd9caea2b25de620.zip
cpython-7402f791a4633689fea904b2bd9caea2b25de620.tar.gz
cpython-7402f791a4633689fea904b2bd9caea2b25de620.tar.bz2
SF patch [#466616] Exclude imported items from doctest,
from Tim Hochberg. Also mucho fiddling to change the way doctest determines whether a thing is a function, module or class. Under 2.2, this really requires the functions in inspect.py (e.g., types.ClassType is close to meaningless now, if not outright misleading).
Diffstat (limited to 'Lib/doctest.py')
-rw-r--r--Lib/doctest.py121
1 files changed, 70 insertions, 51 deletions
diff --git a/Lib/doctest.py b/Lib/doctest.py
index e23a1eb..4689efe 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -48,10 +48,10 @@ WHICH DOCSTRINGS ARE EXAMINED?
+ M.__doc__.
+ f.__doc__ for all functions f in M.__dict__.values(), except those
- with private names.
+ with private names and those defined in other modules.
+ C.__doc__ for all classes C in M.__dict__.values(), except those with
- private names.
+ private names and those defined in other modules.
+ If M.__test__ exists and "is true", it must be a dict, and
each entry maps a (string) name to a function object, class object, or
@@ -75,28 +75,6 @@ them into an M.__test__ dict, or see ADVANCED USAGE below (e.g., pass your
own isprivate function to Tester's constructor, or call the rundoc method
of a Tester instance).
-Warning: imports can cause trouble; e.g., if you do
-
-from XYZ import XYZclass
-
-then XYZclass is a name in M.__dict__ too, and doctest has no way to know
-that XYZclass wasn't *defined* in M. So it may try to execute the examples
-in XYZclass's docstring, and those in turn may require a different set of
-globals to work correctly. I prefer to do "import *"- friendly imports,
-a la
-
-import XYY
-_XYZclass = XYZ.XYZclass
-del XYZ
-
-or (Python 2.0)
-
-from XYZ import XYZclass as _XYZclass
-
-and then the leading underscore stops testmod from going nuts. You may
-prefer the method in the next section.
-
-
WHAT'S THE EXECUTION CONTEXT?
By default, each time testmod finds a docstring to test, it uses a *copy*
@@ -303,13 +281,6 @@ __all__ = [
import __future__
-import types
-_FunctionType = types.FunctionType
-_ClassType = types.ClassType
-_ModuleType = types.ModuleType
-_StringType = types.StringType
-del types
-
import re
PS1 = ">>>"
PS2 = "..."
@@ -319,6 +290,12 @@ _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 ismodule as _ismodule
+
# 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
@@ -574,6 +551,15 @@ def is_private(prefix, base):
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.
@@ -590,9 +576,10 @@ Methods:
Search object.__doc__ for examples to run; use name (or
object.__name__) for logging. Return (#failures, #tries).
- rundict(d, name)
+ rundict(d, name, module=None)
Search for examples in docstrings in all of d.values(); use name
- for logging. Return (#failures, #tries).
+ 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).
@@ -664,7 +651,7 @@ see its docs for details.
if mod is None and globs is None:
raise TypeError("Tester.__init__: must specify mod or globs")
- if mod is not None and type(mod) is not _ModuleType:
+ if mod is not None and not _ismodule(mod):
raise TypeError("Tester.__init__: mod must be a module; " +
`mod`)
if globs is None:
@@ -759,35 +746,65 @@ see its docs for details.
if self.verbose:
print f, "of", t, "examples failed in", name + ".__doc__"
self.__record_outcome(name, f, t)
- if type(object) is _ClassType:
+ if _isclass(object):
f2, t2 = self.rundict(object.__dict__, name)
f = f + f2
t = t + t2
return f, t
- def rundict(self, d, name):
+ def rundict(self, d, name, module=None):
"""
- d. name -> search for docstring examples in all of d.values().
+ d, name, module=None -> search for docstring examples in d.values().
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.
+ "isprivate" argument. If module is specified, functions and
+ classes that are not defined in module are excluded.
Return aggregate (#failures, #examples).
- >>> def _f():
- ... '''>>> assert 1 == 1
- ... '''
- >>> def g():
+ Build and populate two modules with sample functions to test that
+ exclusion of external functions and classes works.
+
+ >>> import new
+ >>> m1 = new.module('_m1')
+ >>> m2 = new.module('_m2')
+ >>> test_data = \"""
+ ... def f():
+ ... '''>>> assert 1 == 1
+ ... '''
+ ... def g():
... '''>>> assert 2 != 1
... '''
- >>> d = {"_f": _f, "g": g}
+ ... class H:
+ ... '''>>> assert 2 > 1
+ ... '''
+ ... def bar(self):
+ ... '''>>> assert 1 < 2
+ ... '''
+ ... \"""
+ >>> exec test_data in m1.__dict__
+ >>> exec test_data in m2.__dict__
+
+ Tests that objects outside m1 are excluded:
+
+ >>> d = {"_f": m1.f, "g": m1.g, "h": m1.H,
+ ... "f2": m2.f, "g2": m2.g, "h2": m2.H}
>>> t = Tester(globs={}, verbose=0)
- >>> t.rundict(d, "rundict_test") # _f is skipped
- (0, 1)
+ >>> t.rundict(d, "rundict_test", m1) # _f, f2 and g2 and h2 skipped
+ (0, 3)
+
+ Again, but with a custom isprivate function allowing _f:
+
>>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0)
- >>> t.rundict(d, "rundict_test_pvt") # both are searched
- (0, 2)
+ >>> t.rundict(d, "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, isprivate=lambda x,y: 0)
+ >>> t.rundict(d, "rundict_test_pvt") # None are skipped.
+ (0, 8)
"""
if not hasattr(d, "items"):
@@ -800,7 +817,9 @@ see its docs for details.
names.sort()
for thisname in names:
value = d[thisname]
- if type(value) in (_FunctionType, _ClassType):
+ 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
@@ -825,9 +844,9 @@ see its docs for details.
for k in keys:
v = d[k]
thisname = prefix + k
- if type(v) is _StringType:
+ if type(v) in _StringTypes:
f, t = self.runstring(v, thisname)
- elif type(v) in (_FunctionType, _ClassType):
+ elif _isfunction(v) or _isclass(v):
f, t = self.rundoc(v, thisname)
else:
raise TypeError("Tester.run__test__: values in "
@@ -1012,7 +1031,7 @@ def testmod(m, name=None, globs=None, verbose=None, isprivate=None,
global master
- if type(m) is not _ModuleType:
+ if not _ismodule(m):
raise TypeError("testmod: module required; " + `m`)
if name is None:
name = m.__name__