From 61147f63d9eb20bdd34d5f7549f8379155aefd60 Mon Sep 17 00:00:00 2001 From: "Michael W. Hudson" Date: Tue, 3 Aug 2004 11:33:28 +0000 Subject: 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. --- Lib/test/regrtest.py | 110 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 19 deletions(-) diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py index 1d1d825..5d5d78d 100755 --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -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() -- cgit v0.12