From 7402f791a4633689fea904b2bd9caea2b25de620 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Tue, 2 Oct 2001 03:53:41 +0000 Subject: [PATCH] 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). --- Lib/doctest.py | 121 +++++++++++++++++++++++----------------- Lib/test/test_pyclbr.py | 5 +- Misc/NEWS | 3 + 3 files changed, 76 insertions(+), 53 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index e23a1eb0af0..4689efed1b6 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__ diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py index 179b3b5968f..fdb3ddf8d4d 100644 --- a/Lib/test/test_pyclbr.py +++ b/Lib/test/test_pyclbr.py @@ -32,7 +32,7 @@ class PyclbrTest(unittest.TestCase): def assertHasattr(self, obj, attr, ignore): ''' succeed iff hasattr(obj,attr) or attr in ignore. ''' if attr in ignore: return - if not hasattr(obj, attr): print "???",attr + if not hasattr(obj, attr): print "???", attr self.failUnless(hasattr(obj, attr)) @@ -100,7 +100,8 @@ class PyclbrTest(unittest.TestCase): def test_easy(self): self.checkModule('pyclbr') - self.checkModule('doctest') + self.checkModule('doctest', + ignore=['_isclass', '_isfunction', '_ismodule']) self.checkModule('rfc822') self.checkModule('xmllib') self.checkModule('difflib') diff --git a/Misc/NEWS b/Misc/NEWS index 662bd69cdd0..427b9b6b55a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ Core 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, which indicates whether output is intended for the header 'Q' encoding.