Commit 61147f63 authored by Michael W. Hudson's avatar Michael W. Hudson

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.
parent 0bb8454e
...@@ -21,6 +21,7 @@ Command line options: ...@@ -21,6 +21,7 @@ Command line options:
-t: threshold -- call gc.set_threshold(N) -t: threshold -- call gc.set_threshold(N)
-T: coverage -- turn on code coverage using the trace module -T: coverage -- turn on code coverage using the trace module
-L: runleaks -- run the leaks(1) command just before exit -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, 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. 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. ...@@ -47,6 +48,14 @@ whittling down failures involving interactions among tests.
leaks(1) is available on Mac OS X and presumably on some other leaks(1) is available on Mac OS X and presumably on some other
FreeBSD-derived systems. 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, -u is used to specify which special resource intensive tests to run,
such as those requiring large file support or network connectivity. such as those requiring large file support or network connectivity.
The argument is a comma-separated list of words indicating the The argument is a comma-separated list of words indicating the
...@@ -84,6 +93,7 @@ import sys ...@@ -84,6 +93,7 @@ import sys
import getopt import getopt
import random import random
import warnings import warnings
import sre
import cStringIO import cStringIO
import traceback import traceback
...@@ -127,7 +137,8 @@ def usage(code, msg=''): ...@@ -127,7 +137,8 @@ def usage(code, msg=''):
def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False, def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
exclude=False, single=False, randomize=False, fromfile=None, 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. """Execute a test suite.
This also parses command-line options and modifies its behavior 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, ...@@ -152,11 +163,11 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
test_support.record_original_stdout(sys.stdout) test_support.record_original_stdout(sys.stdout)
try: 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', ['help', 'verbose', 'quiet', 'generate',
'exclude', 'single', 'random', 'fromfile', 'exclude', 'single', 'random', 'fromfile',
'findleaks', 'use=', 'threshold=', 'trace', 'findleaks', 'use=', 'threshold=', 'trace',
'runleaks' 'runleaks', 'huntrleaks='
]) ])
except getopt.error, msg: except getopt.error, msg:
usage(2, msg) usage(2, msg)
...@@ -191,6 +202,21 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False, ...@@ -191,6 +202,21 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
gc.set_threshold(int(a)) gc.set_threshold(int(a))
elif o in ('-T', '--coverage'): elif o in ('-T', '--coverage'):
trace = True 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'): elif o in ('-u', '--use'):
u = [x.lower() for x in a.split(',')] u = [x.lower() for x in a.split(',')]
for r in u: for r in u:
...@@ -288,7 +314,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False, ...@@ -288,7 +314,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, generate=False,
tracer.runctx('runtest(test, generate, verbose, quiet, testdir)', tracer.runctx('runtest(test, generate, verbose, quiet, testdir)',
globals=globals(), locals=vars()) globals=globals(), locals=vars())
else: else:
ok = runtest(test, generate, verbose, quiet, testdir) ok = runtest(test, generate, verbose, quiet, testdir, huntrleaks)
if ok > 0: if ok > 0:
good.append(test) good.append(test)
elif ok == 0: elif ok == 0:
...@@ -397,7 +423,7 @@ def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS): ...@@ -397,7 +423,7 @@ def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS):
tests.sort() tests.sort()
return stdtests + tests 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. """Run a single test.
test -- the name of the test test -- the name of the test
generate -- if true, generate output, instead of running the test generate -- if true, generate output, instead of running the test
...@@ -415,6 +441,8 @@ def runtest(test, generate, verbose, quiet, testdir=None): ...@@ -415,6 +441,8 @@ def runtest(test, generate, verbose, quiet, testdir=None):
cfp = None cfp = None
else: else:
cfp = cStringIO.StringIO() cfp = cStringIO.StringIO()
if huntrleaks:
refrep = open(huntrleaks[2], "a")
try: try:
save_stdout = sys.stdout save_stdout = sys.stdout
try: try:
...@@ -435,6 +463,50 @@ def runtest(test, generate, verbose, quiet, testdir=None): ...@@ -435,6 +463,50 @@ def runtest(test, generate, verbose, quiet, testdir=None):
indirect_test = getattr(the_module, "test_main", None) indirect_test = getattr(the_module, "test_main", None)
if indirect_test is not None: if indirect_test is not None:
indirect_test() 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: finally:
sys.stdout = save_stdout sys.stdout = save_stdout
except test_support.ResourceDenied, msg: except test_support.ResourceDenied, msg:
...@@ -486,7 +558,7 @@ def runtest(test, generate, verbose, quiet, testdir=None): ...@@ -486,7 +558,7 @@ def runtest(test, generate, verbose, quiet, testdir=None):
fp.close() fp.close()
else: else:
expected = test + "\n" expected = test + "\n"
if output == expected: if output == expected or huntrleaks:
return 1 return 1
print "test", test, "produced unexpected output:" print "test", test, "produced unexpected output:"
sys.stdout.flush() sys.stdout.flush()
......
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