Commit f3f57473 authored by Tim Peters's avatar Tim Peters

Get rid of the ignore_imports argument to DocTestFinder.find().

This got slammed in when find() was fixed to stop grabbing doctests
from modules imported *by* the module being tested.  Such tests cannot
be expected to succeed, since they'll be run with the current module's
globals.  Dozens of Zope3 doctests were failing because of that.

It wasn't clear why ignore_imports got added then.  Maybe it's because
some existing tests failed when the change was made.  Whatever, it's
a Bad Idea so it's gone now.

The only use of it was exceedingly obscure, in test_doctest's "Duplicate
Removal" test.  It was "needed" there because, as an artifact of running
a doctest inside a doctest, the func_globals of functions compiled in
the second-level doctest don't match the module globals, and so the
test-finder believed these functions were from a foreign module and
skipped them.  But that took a long time to figure out, and I actually
understand some of this stuff <0.9 wink>.

That problem was resolved by moving the source code for the second-level
doctest into an actual module (test/doctest_aliases.py).

The only remaining difficulty was that the test for the deprecated
Tester.rundict() then failed, because the test finder doesn't take
module=None at face value, trying to guess which module the user really
intended then.  Its guess wasn't appropriate for what Tester.rundict
needs when module=None is given to *it*, which is "no, there is no
module here, and I mean it".  So now passing module=False means exactly
that.  This is hokey, but ignore_imports=False was really a hack to worm
around that there was no way to tell the test-finder that module=None
*sometimes* means what it says.  There was no use case for the combination
of passing a real module with ignore_imports=False.
parent 9fce44bc
...@@ -808,25 +808,32 @@ class DocTestFinder: ...@@ -808,25 +808,32 @@ class DocTestFinder:
self._namefilter = _namefilter self._namefilter = _namefilter
def find(self, obj, name=None, module=None, globs=None, def find(self, obj, name=None, module=None, globs=None,
extraglobs=None, ignore_imports=True): extraglobs=None):
""" """
Return a list of the DocTests that are defined by the given Return a list of the DocTests that are defined by the given
object's docstring, or by any of its contained objects' object's docstring, or by any of its contained objects'
docstrings. docstrings.
The optional parameter `module` is the module that contains The optional parameter `module` is the module that contains
the given object. If the module is not specified, then the the given object. If the module is not specified or is None, then
test finder will attempt to automatically determine the the test finder will attempt to automatically determine the
correct module. The object's module is used: correct module. The object's module is used:
- As a default namespace, if `globs` is not specified. - As a default namespace, if `globs` is not specified.
- To prevent the DocTestFinder from extracting DocTests - To prevent the DocTestFinder from extracting DocTests
from objects that are imported from other modules 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 find the name of the file containing the object.
- To help find the line number of the object within its - To help find the line number of the object within its
file. file.
Contained objects whose module does not match `module` are ignored.
If `module` is False, no attempt to find the module will be made.
This is obscure, of use mostly in tests: if `module` is False, or
is None but cannot be found automatically, then all objects are
considered to belong to the (non-existent) module, so all contained
objects will (recursively) be searched for doctests.
The globals for each DocTest is formed by combining `globs` The globals for each DocTest is formed by combining `globs`
and `extraglobs` (bindings in `extraglobs` override bindings and `extraglobs` (bindings in `extraglobs` override bindings
in `globs`). A new copy of the globals dictionary is created in `globs`). A new copy of the globals dictionary is created
...@@ -835,10 +842,6 @@ class DocTestFinder: ...@@ -835,10 +842,6 @@ class DocTestFinder:
otherwise. If `extraglobs` is not specified, then it defaults otherwise. If `extraglobs` is not specified, then it defaults
to {}. 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 was not specified, then extract it from the object.
if name is None: if name is None:
...@@ -851,7 +854,9 @@ class DocTestFinder: ...@@ -851,7 +854,9 @@ class DocTestFinder:
# Find the module that contains the given object (if obj is # Find the module that contains the given object (if obj is
# a module, then module=obj.). Note: this may fail, in which # a module, then module=obj.). Note: this may fail, in which
# case module will be None. # case module will be None.
if module is None: if module is False:
module = None
elif module is None:
module = inspect.getmodule(obj) module = inspect.getmodule(obj)
# Read the module's source code. This is used by # Read the module's source code. This is used by
...@@ -878,8 +883,7 @@ class DocTestFinder: ...@@ -878,8 +883,7 @@ class DocTestFinder:
# Recursively expore `obj`, extracting DocTests. # Recursively expore `obj`, extracting DocTests.
tests = [] tests = []
self._find(tests, obj, name, module, source_lines, self._find(tests, obj, name, module, source_lines, globs, {})
globs, ignore_imports, {})
return tests return tests
def _filter(self, obj, prefix, base): def _filter(self, obj, prefix, base):
...@@ -909,8 +913,7 @@ class DocTestFinder: ...@@ -909,8 +913,7 @@ class DocTestFinder:
else: else:
raise ValueError("object must be a class or function") raise ValueError("object must be a class or function")
def _find(self, tests, obj, name, module, source_lines, def _find(self, tests, obj, name, module, source_lines, globs, seen):
globs, ignore_imports, seen):
""" """
Find tests for the given object and any contained objects, and Find tests for the given object and any contained objects, and
add them to `tests`. add them to `tests`.
...@@ -937,9 +940,9 @@ class DocTestFinder: ...@@ -937,9 +940,9 @@ class DocTestFinder:
valname = '%s.%s' % (name, valname) valname = '%s.%s' % (name, valname)
# Recurse to functions & classes. # Recurse to functions & classes.
if ((inspect.isfunction(val) or inspect.isclass(val)) and if ((inspect.isfunction(val) or inspect.isclass(val)) and
(self._from_module(module, val) or not ignore_imports)): self._from_module(module, val)):
self._find(tests, val, valname, module, source_lines, self._find(tests, val, valname, module, source_lines,
globs, ignore_imports, seen) globs, seen)
# Look for tests in a module's __test__ dictionary. # Look for tests in a module's __test__ dictionary.
if inspect.ismodule(obj) and self._recurse: if inspect.ismodule(obj) and self._recurse:
...@@ -957,7 +960,7 @@ class DocTestFinder: ...@@ -957,7 +960,7 @@ class DocTestFinder:
(type(val),)) (type(val),))
valname = '%s.%s' % (name, valname) valname = '%s.%s' % (name, valname)
self._find(tests, val, valname, module, source_lines, self._find(tests, val, valname, module, source_lines,
globs, ignore_imports, seen) globs, seen)
# Look for tests in a class's contained objects. # Look for tests in a class's contained objects.
if inspect.isclass(obj) and self._recurse: if inspect.isclass(obj) and self._recurse:
...@@ -974,10 +977,10 @@ class DocTestFinder: ...@@ -974,10 +977,10 @@ class DocTestFinder:
# Recurse to methods, properties, and nested classes. # Recurse to methods, properties, and nested classes.
if ((inspect.isfunction(val) or inspect.isclass(val) or if ((inspect.isfunction(val) or inspect.isclass(val) or
isinstance(val, property)) and isinstance(val, property)) and
(self._from_module(module, val) or not ignore_imports)): self._from_module(module, val)):
valname = '%s.%s' % (name, valname) valname = '%s.%s' % (name, valname)
self._find(tests, val, valname, module, source_lines, self._find(tests, val, valname, module, source_lines,
globs, ignore_imports, seen) globs, seen)
def _get_test(self, obj, name, module, globs, source_lines): def _get_test(self, obj, name, module, globs, source_lines):
""" """
...@@ -1894,11 +1897,10 @@ class Tester: ...@@ -1894,11 +1897,10 @@ class Tester:
print f, "of", t, "examples failed in string", name print f, "of", t, "examples failed in string", name
return (f,t) return (f,t)
def rundoc(self, object, name=None, module=None, ignore_imports=True): def rundoc(self, object, name=None, module=None):
f = t = 0 f = t = 0
tests = self.testfinder.find(object, name, module=module, tests = self.testfinder.find(object, name, module=module,
globs=self.globs, globs=self.globs)
ignore_imports=ignore_imports)
for test in tests: for test in tests:
(f2, t2) = self.testrunner.run(test) (f2, t2) = self.testrunner.run(test)
(f,t) = (f+f2, t+t2) (f,t) = (f+f2, t+t2)
...@@ -1908,8 +1910,9 @@ class Tester: ...@@ -1908,8 +1910,9 @@ class Tester:
import new import new
m = new.module(name) m = new.module(name)
m.__dict__.update(d) m.__dict__.update(d)
ignore_imports = (module is not None) if module is None:
return self.rundoc(m, name, module, ignore_imports) module = False
return self.rundoc(m, name, module)
def run__test__(self, d, name): def run__test__(self, d, name):
import new import new
......
# Used by test_doctest.py.
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
...@@ -363,26 +363,19 @@ Duplicate Removal ...@@ -363,26 +363,19 @@ Duplicate Removal
If a single object is listed twice (under different names), then tests If a single object is listed twice (under different names), then tests
will only be generated for it once: will only be generated for it once:
>>> class TwoNames: >>> from test import doctest_aliases
... '''f() and g() are two names for the same method''' >>> tests = finder.find(doctest_aliases)
...
... 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() >>> tests.sort()
>>> print len(tests) >>> print len(tests)
2 2
>>> print tests[0].name >>> print tests[0].name
TwoNames test.doctest_aliases.TwoNames
>>> print tests[1].name in ('TwoNames.f', 'TwoNames.g')
TwoNames.f and TwoNames.g are bound to the same object.
We can't guess which will be found in doctest's traversal of
TwoNames.__dict__ first, so we have to allow for either.
>>> tests[1].name.split('.')[-1] in ['f', 'g']
True True
Filter Functions Filter Functions
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment