mirror of https://github.com/python/cpython
Clear the copy of the globs dict after running examples. This helps to
break cycles, which are a special problem when running generator tests that provoke exceptions by invoking the .next() method of a named generator-iterator: then the iterator is named in globs, and the iterator's frame gets a tracekback object pointing back to globs, and gc doesn't chase these types so the cycle leaks. Also changed _run_examples() to make a copy of globs itself, so its callers (direct and indirect) don't have to (and changed the callers to stop making their own copies); *that* much is a change I've been meaning to make for a long time (it's more robust the new way). Here's a way to provoke the symptom without doctest; it leaks at a prodigious rate; if the last two "source" lines are replaced with g().next() the iterator isn't named and then there's no leak: source = """\ def g(): yield 1/0 k = g() k.next() """ code = compile(source, "<source>", "exec") def f(globs): try: exec code in globs except ZeroDivisionError: pass while 1: f(globals().copy()) After this change, running test_generators in an infinite loop still leaks, but reduced from a flood to a trickle.
This commit is contained in:
parent
77f2d504c3
commit
d4ad59e1eb
|
@ -529,23 +529,34 @@ def _run_examples_inner(out, fakeout, examples, globs, verbose, name):
|
|||
|
||||
return failures, len(examples)
|
||||
|
||||
# Run list of examples, in context globs. Return (#failures, #tries).
|
||||
# Run list of examples, in a shallow copy of context (dict) globs.
|
||||
# Return (#failures, #tries).
|
||||
# CAUTION: globs is cleared before returning. This is to help break
|
||||
# cycles that may have been created by the examples.
|
||||
|
||||
def _run_examples(examples, globs, verbose, name):
|
||||
import sys
|
||||
saveout = sys.stdout
|
||||
globs = globs.copy()
|
||||
try:
|
||||
sys.stdout = fakeout = _SpoofOut()
|
||||
x = _run_examples_inner(saveout.write, fakeout, examples,
|
||||
globs, verbose, name)
|
||||
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.
|
||||
globs.clear()
|
||||
return x
|
||||
|
||||
def run_docstring_examples(f, globs, verbose=0, name="NoName"):
|
||||
"""f, globs, verbose=0, name="NoName" -> run examples from f.__doc__.
|
||||
|
||||
Use dict globs as the globals for execution.
|
||||
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
|
||||
|
@ -735,7 +746,7 @@ see its docs for details.
|
|||
f = t = 0
|
||||
e = _extract_examples(s)
|
||||
if e:
|
||||
f, t = _run_examples(e, self.globs.copy(), self.verbose, name)
|
||||
f, t = _run_examples(e, self.globs, self.verbose, name)
|
||||
if self.verbose:
|
||||
print f, "of", t, "examples failed in string", name
|
||||
self.__record_outcome(name, f, t)
|
||||
|
@ -773,8 +784,7 @@ see its docs for details.
|
|||
"when object.__name__ doesn't exist; " + `object`)
|
||||
if self.verbose:
|
||||
print "Running", name + ".__doc__"
|
||||
f, t = run_docstring_examples(object, self.globs.copy(),
|
||||
self.verbose, name)
|
||||
f, t = run_docstring_examples(object, self.globs, self.verbose, name)
|
||||
if self.verbose:
|
||||
print f, "of", t, "examples failed in", name + ".__doc__"
|
||||
self.__record_outcome(name, f, t)
|
||||
|
|
Loading…
Reference in New Issue