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).
This commit is contained in:
parent
d90f509b8f
commit
7402f791a4
121
Lib/doctest.py
121
Lib/doctest.py
@ -48,10 +48,10 @@ WHICH DOCSTRINGS ARE EXAMINED?
|
|||||||
+ M.__doc__.
|
+ M.__doc__.
|
||||||
|
|
||||||
+ f.__doc__ for all functions f in M.__dict__.values(), except those
|
+ 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
|
+ 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
|
+ 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
|
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
|
own isprivate function to Tester's constructor, or call the rundoc method
|
||||||
of a Tester instance).
|
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?
|
WHAT'S THE EXECUTION CONTEXT?
|
||||||
|
|
||||||
By default, each time testmod finds a docstring to test, it uses a *copy*
|
By default, each time testmod finds a docstring to test, it uses a *copy*
|
||||||
@ -303,13 +281,6 @@ __all__ = [
|
|||||||
|
|
||||||
import __future__
|
import __future__
|
||||||
|
|
||||||
import types
|
|
||||||
_FunctionType = types.FunctionType
|
|
||||||
_ClassType = types.ClassType
|
|
||||||
_ModuleType = types.ModuleType
|
|
||||||
_StringType = types.StringType
|
|
||||||
del types
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
PS1 = ">>>"
|
PS1 = ">>>"
|
||||||
PS2 = "..."
|
PS2 = "..."
|
||||||
@ -319,6 +290,12 @@ _isEmpty = re.compile(r"\s*$").match
|
|||||||
_isComment = re.compile(r"\s*#").match
|
_isComment = re.compile(r"\s*#").match
|
||||||
del re
|
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,
|
# Extract interactive examples from a string. Return a list of triples,
|
||||||
# (source, outcome, lineno). "source" is the source code, and ends
|
# (source, outcome, lineno). "source" is the source code, and ends
|
||||||
# with a newline iff the source spans more than one line. "outcome" is
|
# with a newline iff the source spans more than one line. "outcome" is
|
||||||
@ -574,6 +551,15 @@ def is_private(prefix, base):
|
|||||||
|
|
||||||
return base[:1] == "_" and not base[:2] == "__" == base[-2:]
|
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:
|
||||||
"""Class Tester -- runs docstring examples and accumulates stats.
|
"""Class Tester -- runs docstring examples and accumulates stats.
|
||||||
|
|
||||||
@ -590,9 +576,10 @@ Methods:
|
|||||||
Search object.__doc__ for examples to run; use name (or
|
Search object.__doc__ for examples to run; use name (or
|
||||||
object.__name__) for logging. Return (#failures, #tries).
|
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
|
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)
|
run__test__(d, name)
|
||||||
Treat dict d like module.__test__. Return (#failures, #tries).
|
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:
|
if mod is None and globs is None:
|
||||||
raise TypeError("Tester.__init__: must specify mod or globs")
|
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; " +
|
raise TypeError("Tester.__init__: mod must be a module; " +
|
||||||
`mod`)
|
`mod`)
|
||||||
if globs is None:
|
if globs is None:
|
||||||
@ -759,35 +746,65 @@ see its docs for details.
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print f, "of", t, "examples failed in", name + ".__doc__"
|
print f, "of", t, "examples failed in", name + ".__doc__"
|
||||||
self.__record_outcome(name, f, t)
|
self.__record_outcome(name, f, t)
|
||||||
if type(object) is _ClassType:
|
if _isclass(object):
|
||||||
f2, t2 = self.rundict(object.__dict__, name)
|
f2, t2 = self.rundict(object.__dict__, name)
|
||||||
f = f + f2
|
f = f + f2
|
||||||
t = t + t2
|
t = t + t2
|
||||||
return f, t
|
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,
|
For k, v in d.items() such that v is a function or class,
|
||||||
do self.rundoc(v, name + "." + k). Whether this includes
|
do self.rundoc(v, name + "." + k). Whether this includes
|
||||||
objects with private names depends on the constructor's
|
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).
|
Return aggregate (#failures, #examples).
|
||||||
|
|
||||||
>>> def _f():
|
Build and populate two modules with sample functions to test that
|
||||||
... '''>>> assert 1 == 1
|
exclusion of external functions and classes works.
|
||||||
... '''
|
|
||||||
>>> def g():
|
>>> import new
|
||||||
|
>>> m1 = new.module('_m1')
|
||||||
|
>>> m2 = new.module('_m2')
|
||||||
|
>>> test_data = \"""
|
||||||
|
... def f():
|
||||||
|
... '''>>> assert 1 == 1
|
||||||
|
... '''
|
||||||
|
... def g():
|
||||||
... '''>>> assert 2 != 1
|
... '''>>> 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 = Tester(globs={}, verbose=0)
|
||||||
>>> t.rundict(d, "rundict_test") # _f is skipped
|
>>> t.rundict(d, "rundict_test", m1) # _f, f2 and g2 and h2 skipped
|
||||||
(0, 1)
|
(0, 3)
|
||||||
|
|
||||||
|
Again, but with a custom isprivate function allowing _f:
|
||||||
|
|
||||||
>>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0)
|
>>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0)
|
||||||
>>> t.rundict(d, "rundict_test_pvt") # both are searched
|
>>> t.rundict(d, "rundict_test_pvt", m1) # Only f2, g2 and h2 skipped
|
||||||
(0, 2)
|
(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"):
|
if not hasattr(d, "items"):
|
||||||
@ -800,7 +817,9 @@ see its docs for details.
|
|||||||
names.sort()
|
names.sort()
|
||||||
for thisname in names:
|
for thisname in names:
|
||||||
value = d[thisname]
|
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)
|
f2, t2 = self.__runone(value, name + "." + thisname)
|
||||||
f = f + f2
|
f = f + f2
|
||||||
t = t + t2
|
t = t + t2
|
||||||
@ -825,9 +844,9 @@ see its docs for details.
|
|||||||
for k in keys:
|
for k in keys:
|
||||||
v = d[k]
|
v = d[k]
|
||||||
thisname = prefix + k
|
thisname = prefix + k
|
||||||
if type(v) is _StringType:
|
if type(v) in _StringTypes:
|
||||||
f, t = self.runstring(v, thisname)
|
f, t = self.runstring(v, thisname)
|
||||||
elif type(v) in (_FunctionType, _ClassType):
|
elif _isfunction(v) or _isclass(v):
|
||||||
f, t = self.rundoc(v, thisname)
|
f, t = self.rundoc(v, thisname)
|
||||||
else:
|
else:
|
||||||
raise TypeError("Tester.run__test__: values in "
|
raise TypeError("Tester.run__test__: values in "
|
||||||
@ -1012,7 +1031,7 @@ def testmod(m, name=None, globs=None, verbose=None, isprivate=None,
|
|||||||
|
|
||||||
global master
|
global master
|
||||||
|
|
||||||
if type(m) is not _ModuleType:
|
if not _ismodule(m):
|
||||||
raise TypeError("testmod: module required; " + `m`)
|
raise TypeError("testmod: module required; " + `m`)
|
||||||
if name is None:
|
if name is None:
|
||||||
name = m.__name__
|
name = m.__name__
|
||||||
|
@ -32,7 +32,7 @@ class PyclbrTest(unittest.TestCase):
|
|||||||
def assertHasattr(self, obj, attr, ignore):
|
def assertHasattr(self, obj, attr, ignore):
|
||||||
''' succeed iff hasattr(obj,attr) or attr in ignore. '''
|
''' succeed iff hasattr(obj,attr) or attr in ignore. '''
|
||||||
if attr in ignore: return
|
if attr in ignore: return
|
||||||
if not hasattr(obj, attr): print "???",attr
|
if not hasattr(obj, attr): print "???", attr
|
||||||
self.failUnless(hasattr(obj, attr))
|
self.failUnless(hasattr(obj, attr))
|
||||||
|
|
||||||
|
|
||||||
@ -100,7 +100,8 @@ class PyclbrTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_easy(self):
|
def test_easy(self):
|
||||||
self.checkModule('pyclbr')
|
self.checkModule('pyclbr')
|
||||||
self.checkModule('doctest')
|
self.checkModule('doctest',
|
||||||
|
ignore=['_isclass', '_isfunction', '_ismodule'])
|
||||||
self.checkModule('rfc822')
|
self.checkModule('rfc822')
|
||||||
self.checkModule('xmllib')
|
self.checkModule('xmllib')
|
||||||
self.checkModule('difflib')
|
self.checkModule('difflib')
|
||||||
|
@ -12,6 +12,9 @@ Core
|
|||||||
|
|
||||||
Library
|
Library
|
||||||
|
|
||||||
|
- doctest now excludes functions and classes not defined by the module
|
||||||
|
being tested, thanks to Tim Hochberg.
|
||||||
|
|
||||||
- quopri's encode and decode methods take an optional header parameter,
|
- quopri's encode and decode methods take an optional header parameter,
|
||||||
which indicates whether output is intended for the header 'Q' encoding.
|
which indicates whether output is intended for the header 'Q' encoding.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user