mirror of
https://github.com/python/cpython.git
synced 2025-11-01 10:45:30 +00:00
Check in my refleak hunting code.
It's not the 100% solution -- it may not even be the 90% solution -- but it does seem to help.
This commit is contained in:
parent
0bb8454ed4
commit
61147f63d9
1 changed files with 91 additions and 19 deletions
|
|
@ -8,19 +8,20 @@ additional facilities.
|
|||
|
||||
Command line options:
|
||||
|
||||
-v: verbose -- run tests in verbose mode with output to stdout
|
||||
-q: quiet -- don't print anything except if a test fails
|
||||
-g: generate -- write the output file for a test instead of comparing it
|
||||
-x: exclude -- arguments are tests to *exclude*
|
||||
-s: single -- run only a single test (see below)
|
||||
-r: random -- randomize test execution order
|
||||
-f: fromfile -- read names of tests to run from a file (see below)
|
||||
-l: findleaks -- if GC is available detect tests that leak memory
|
||||
-u: use -- specify which special resource intensive tests to run
|
||||
-h: help -- print this text and exit
|
||||
-t: threshold -- call gc.set_threshold(N)
|
||||
-T: coverage -- turn on code coverage using the trace module
|
||||
-L: runleaks -- run the leaks(1) command just before exit
|
||||
-v: verbose -- run tests in verbose mode with output to stdout
|
||||
-q: quiet -- don't print anything except if a test fails
|
||||
-g: generate -- write the output file for a test instead of comparing it
|
||||
-x: exclude -- arguments are tests to *exclude*
|
||||
-s: single -- run only a single test (see below)
|
||||
-r: random -- randomize test execution order
|
||||
-f: fromfile -- read names of tests to run from a file (see below)
|
||||
-l: findleaks -- if GC is available detect tests that leak memory
|
||||
-u: use -- specify which special resource intensive tests to run
|
||||
-h: help -- print this text and exit
|
||||
-t: threshold -- call gc.set_threshold(N)
|
||||
-T: coverage -- turn on code coverage using the trace module
|
||||
-L: runleaks -- run the leaks(1) command just before exit
|
||||
-R: huntrleaks -- search for reference leaks (needs debug build, v. slow)
|
||||
|
||||
If non-option arguments are present, they are names for tests to run,
|
||||
unless -x is given, in which case they are names for tests not to run.
|
||||
|
|
@ -47,6 +48,14 @@ whittling down failures involving interactions among tests.
|
|||
leaks(1) is available on Mac OS X and presumably on some other
|
||||
FreeBSD-derived systems.
|
||||
|
||||
-R runs each test several times and examines sys.gettotalrefcount() to
|
||||
see if the test appears to be leaking references. The argument should
|
||||
be of the form stab:run:fname where 'stab' is the number of times the
|
||||
test is run to let gettotalrefcount settle down, 'run' is the number
|
||||
of times further it is run and 'fname' is the name of the file the
|
||||
reports are written to. These parameters all have defaults (5, 4 and
|
||||
"reflog.txt" respectively), so the minimal invocation is '-R ::'.
|
||||
|
||||
-u is used to specify which special resource intensive tests to run,
|
||||
such as those requiring large file support or network connectivity.
|
||||
The argument is a comma-separated list of words indicating the
|
||||
|
|
@ -84,6 +93,7 @@ import sys
|
|||
import getopt
|
||||
import random
|
||||
import warnings
|
||||
import sre
|
||||
import cStringIO
|
||||
import traceback
|
||||
|
||||
|
|
@ -127,7 +137,8 @@ def usage(code, msg=''):
|
|||
|
||||
def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
|
||||
exclude=False, single=False, randomize=False, fromfile=None,
|
||||
findleaks=False, use_resources=None, trace=False, runleaks=False):
|
||||
findleaks=False, use_resources=None, trace=False, runleaks=False,
|
||||
huntrleaks=False):
|
||||
"""Execute a test suite.
|
||||
|
||||
This also parses command-line options and modifies its behavior
|
||||
|
|
@ -152,11 +163,11 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
|
|||
|
||||
test_support.record_original_stdout(sys.stdout)
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'hvgqxsrf:lu:t:TL',
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'hvgqxsrf:lu:t:TLR:',
|
||||
['help', 'verbose', 'quiet', 'generate',
|
||||
'exclude', 'single', 'random', 'fromfile',
|
||||
'findleaks', 'use=', 'threshold=', 'trace',
|
||||
'runleaks'
|
||||
'runleaks', 'huntrleaks='
|
||||
])
|
||||
except getopt.error, msg:
|
||||
usage(2, msg)
|
||||
|
|
@ -191,6 +202,21 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
|
|||
gc.set_threshold(int(a))
|
||||
elif o in ('-T', '--coverage'):
|
||||
trace = True
|
||||
elif o in ('-R', '--huntrleaks'):
|
||||
huntrleaks = a.split(':')
|
||||
if len(huntrleaks) != 3:
|
||||
print a, huntrleaks
|
||||
usage(2, '-R takes three colon-separated arguments')
|
||||
if len(huntrleaks[0]) == 0:
|
||||
huntrleaks[0] = 5
|
||||
else:
|
||||
huntrleaks[0] = int(huntrleaks[0])
|
||||
if len(huntrleaks[1]) == 0:
|
||||
huntrleaks[1] = 4
|
||||
else:
|
||||
huntrleaks[1] = int(huntrleaks[1])
|
||||
if len(huntrleaks[2]) == 0:
|
||||
huntrleaks[2] = "reflog.txt"
|
||||
elif o in ('-u', '--use'):
|
||||
u = [x.lower() for x in a.split(',')]
|
||||
for r in u:
|
||||
|
|
@ -288,7 +314,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
|
|||
tracer.runctx('runtest(test, generate, verbose, quiet, testdir)',
|
||||
globals=globals(), locals=vars())
|
||||
else:
|
||||
ok = runtest(test, generate, verbose, quiet, testdir)
|
||||
ok = runtest(test, generate, verbose, quiet, testdir, huntrleaks)
|
||||
if ok > 0:
|
||||
good.append(test)
|
||||
elif ok == 0:
|
||||
|
|
@ -397,7 +423,7 @@ def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS):
|
|||
tests.sort()
|
||||
return stdtests + tests
|
||||
|
||||
def runtest(test, generate, verbose, quiet, testdir=None):
|
||||
def runtest(test, generate, verbose, quiet, testdir=None, huntrleaks=False):
|
||||
"""Run a single test.
|
||||
test -- the name of the test
|
||||
generate -- if true, generate output, instead of running the test
|
||||
|
|
@ -415,6 +441,8 @@ def runtest(test, generate, verbose, quiet, testdir=None):
|
|||
cfp = None
|
||||
else:
|
||||
cfp = cStringIO.StringIO()
|
||||
if huntrleaks:
|
||||
refrep = open(huntrleaks[2], "a")
|
||||
try:
|
||||
save_stdout = sys.stdout
|
||||
try:
|
||||
|
|
@ -435,6 +463,50 @@ def runtest(test, generate, verbose, quiet, testdir=None):
|
|||
indirect_test = getattr(the_module, "test_main", None)
|
||||
if indirect_test is not None:
|
||||
indirect_test()
|
||||
if huntrleaks:
|
||||
# This code *is* hackish and inelegant, yes.
|
||||
# But it seems to do the job.
|
||||
import copy_reg
|
||||
fs = warnings.filters[:]
|
||||
ps = copy_reg.dispatch_table.copy()
|
||||
pic = sys.path_importer_cache.copy()
|
||||
import gc
|
||||
def cleanup():
|
||||
import _strptime, urlparse, warnings, dircache
|
||||
from distutils.dir_util import _path_created
|
||||
_path_created.clear()
|
||||
warnings.filters[:] = fs
|
||||
gc.collect()
|
||||
sre.purge()
|
||||
_strptime._regex_cache.clear()
|
||||
urlparse.clear_cache()
|
||||
copy_reg.dispatch_table.clear()
|
||||
copy_reg.dispatch_table.update(ps)
|
||||
sys.path_importer_cache.clear()
|
||||
sys.path_importer_cache.update(pic)
|
||||
dircache.reset()
|
||||
if indirect_test:
|
||||
def run_the_test():
|
||||
indirect_test()
|
||||
else:
|
||||
def run_the_test():
|
||||
reload(the_module)
|
||||
deltas = []
|
||||
repcount = huntrleaks[0] + huntrleaks[1]
|
||||
print >> sys.stderr, "beginning", repcount, "repetitions"
|
||||
print >> sys.stderr, \
|
||||
("1234567890"*(repcount//10 + 1))[:repcount]
|
||||
for i in range(repcount):
|
||||
rc = sys.gettotalrefcount()
|
||||
run_the_test()
|
||||
sys.stderr.write('.')
|
||||
cleanup()
|
||||
deltas.append(sys.gettotalrefcount() - rc - 2)
|
||||
print >>sys.stderr
|
||||
if max(map(abs, deltas[-huntrleaks[1]:])) > 0:
|
||||
print >>refrep, test, 'leaked', \
|
||||
deltas[-huntrleaks[1]:], 'references'
|
||||
# The end of the huntrleaks hackishness.
|
||||
finally:
|
||||
sys.stdout = save_stdout
|
||||
except test_support.ResourceDenied, msg:
|
||||
|
|
@ -486,7 +558,7 @@ def runtest(test, generate, verbose, quiet, testdir=None):
|
|||
fp.close()
|
||||
else:
|
||||
expected = test + "\n"
|
||||
if output == expected:
|
||||
if output == expected or huntrleaks:
|
||||
return 1
|
||||
print "test", test, "produced unexpected output:"
|
||||
sys.stdout.flush()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue