Code

70311a2d23829e5d51293ec585894548f88dded7
[roundup.git] / run_tests.py
1 #! /usr/bin/env python2.2
2 ##############################################################################
3 #
4 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
5 # All Rights Reserved.
6 #
7 # This software is subject to the provisions of the Zope Public License,
8 # Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
9 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
10 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
12 # FOR A PARTICULAR PURPOSE.
13 #
14 ##############################################################################
15 """
16 test.py [-aBbcdDfgGhLmprtTuv] [modfilter [testfilter]]
18 Test harness.
20 -a level
21 --all
22     Run the tests at the given level.  Any test at a level at or below this is
23     run, any test at a level above this is not run.  Level 0 runs all tests.
24     The default is to run tests at level 1.  --all is a shortcut for -a 0.
26 -b
27 --build
28     Run "python setup.py build" before running tests, where "python"
29     is the version of python used to run test.py.  Highly recommended.
30     Tests will be run from the build directory.  (Note: In Python < 2.3
31     the -q flag is added to the setup.py command line.)
33 -B
34     Run "python setup.py build_ext -i" before running tests.  Tests will be
35     run from the source directory.
37 -c  use pychecker
39 -d
40     Instead of the normal test harness, run a debug version which
41     doesn't catch any exceptions.  This is occasionally handy when the
42     unittest code catching the exception doesn't work right.
43     Unfortunately, the debug harness doesn't print the name of the
44     test, so Use With Care.
46 --dir directory
47     Option to limit where tests are searched for. This is
48     important when you *really* want to limit the code that gets run.
49     For example, if refactoring interfaces, you don't want to see the way
50     you have broken setups for tests in other packages. You *just* want to
51     run the interface tests.
53 -D
54     Works like -d, except that it loads pdb when an exception occurs.
56 -f
57     Run functional tests instead of unit tests.
59 -g threshold
60     Set the garbage collector generation0 threshold.  This can be used to
61     stress memory and gc correctness.  Some crashes are only reproducible when
62     the threshold is set to 1 (agressive garbage collection).  Do "-g 0" to
63     disable garbage collection altogether.
65 -G gc_option
66     Set the garbage collection debugging flags.  The argument must be one
67     of the DEBUG_ flags defined bythe Python gc module.  Multiple options
68     can be specified by using "-G OPTION1 -G OPTION2."
70 --libdir test_root
71     Search for tests starting in the specified start directory
72     (useful for testing components being developed outside the main
73     "src" or "build" trees).
75 --keepbytecode
76     Do not delete all stale bytecode before running tests
78 -L
79     Keep running the selected tests in a loop.  You may experience
80     memory leakage.
82 -t
83     Time the individual tests and print a list of the top 50, sorted from
84     longest to shortest.
86 -p
87     Show running progress.  It can be combined with -v or -vv.
89 -r
90     Look for refcount problems.
91     This requires that Python was built --with-pydebug.
93 -T
94     Use the trace module from Python for code coverage.  XXX This only works
95     if trace.py is explicitly added to PYTHONPATH.  The current utility writes
96     coverage files to a directory named `coverage' that is parallel to
97     `build'.  It also prints a summary to stdout.
99 -v
100     Verbose output.  With one -v, unittest prints a dot (".") for each test
101     run.  With -vv, unittest prints the name of each test (for some definition
102     of "name" ...).  With no -v, unittest is silent until the end of the run,
103     except when errors occur.
105     When -p is also specified, the meaning of -v is sligtly changed.  With
106     -p and no -v only the percent indicator is displayed.  With -p and -v
107     the test name of the current test is shown to the right of the percent
108     indicator.  With -p and -vv the test name is not truncated to fit into
109     80 columns and it is not cleared after the test finishes.
111 -u
112 -m
113     Use the PyUnit GUI instead of output to the command line.  The GUI imports
114     tests on its own, taking care to reload all dependencies on each run.  The
115     debug (-d), verbose (-v), progress (-p), and Loop (-L) options will be
116     ignored.  The testfilter filter is also not applied.
118     -m starts the gui minimized.  Double-clicking the progress bar will start
119     the import and run all tests.
122 modfilter
123 testfilter
124     Case-sensitive regexps to limit which tests are run, used in search
125     (not match) mode.
126     In an extension of Python regexp notation, a leading "!" is stripped
127     and causes the sense of the remaining regexp to be negated (so "!bc"
128     matches any string that does not match "bc", and vice versa).
129     By default these act like ".", i.e. nothing is excluded.
131     modfilter is applied to a test file's path, starting at "build" and
132     including (OS-dependent) path separators.
134     testfilter is applied to the (method) name of the unittest methods
135     contained in the test files whose paths modfilter matched.
137 Extreme (yet useful) examples:
139     test.py -vvb . "^testWriteClient$"
141     Builds the project silently, then runs unittest in verbose mode on all
142     tests whose names are precisely "testWriteClient".  Useful when
143     debugging a specific test.
145     test.py -vvb . "!^testWriteClient$"
147     As before, but runs all tests whose names aren't precisely
148     "testWriteClient".  Useful to avoid a specific failing test you don't
149     want to deal with just yet.
151     test.py -m . "!^testWriteClient$"
153     As before, but now opens up a minimized PyUnit GUI window (only showing
154     the progress bar).  Useful for refactoring runs where you continually want
155     to make sure all tests still pass.
156 """
158 import gc
159 import os
160 import re
161 import pdb
162 import sys
163 import threading    # just to get at Thread objects created by tests
164 import time
165 import traceback
166 import unittest
167 import warnings
169 from distutils.util import get_platform
171 PLAT_SPEC = "%s-%s" % (get_platform(), sys.version[0:3])
173 class ImmediateTestResult(unittest._TextTestResult):
175     __super_init = unittest._TextTestResult.__init__
176     __super_startTest = unittest._TextTestResult.startTest
177     __super_printErrors = unittest._TextTestResult.printErrors
179     def __init__(self, stream, descriptions, verbosity, debug=0,
180                  count=None, progress=0):
181         self.__super_init(stream, descriptions, verbosity)
182         self._debug = debug
183         self._progress = progress
184         self._progressWithNames = 0
185         self._count = count
186         self._testtimes = {}
187         if progress and verbosity == 1:
188             self.dots = 0
189             self._progressWithNames = 1
190             self._lastWidth = 0
191             self._maxWidth = 80
192             try:
193                 import curses
194             except ImportError:
195                 pass
196             else:
197                 curses.setupterm()
198                 self._maxWidth = curses.tigetnum('cols')
199             self._maxWidth -= len("xxxx/xxxx (xxx.x%): ") + 1
201     def stopTest(self, test):
202         self._testtimes[test] = time.time() - self._testtimes[test]
203         if gc.garbage:
204             print "The following test left garbage:"
205             print test
206             print gc.garbage
207             # eat the garbage here, so that the garbage isn't
208             # printed for every subsequent test.
209             gc.garbage[:] = []
211         # Did the test leave any new threads behind?
212         new_threads = [t for t in threading.enumerate()
213                          if (t.isAlive()
214                              and
215                              t not in self._threads)]
216         if new_threads:
217             print "The following test left new threads behind:"
218             print test
219             print "New thread(s):", new_threads
221     def print_times(self, stream, count=None):
222         results = self._testtimes.items()
223         results.sort(lambda x, y: cmp(y[1], x[1]))
224         if count:
225             n = min(count, len(results))
226             if n:
227                 print >>stream, "Top %d longest tests:" % n
228         else:
229             n = len(results)
230         if not n:
231             return
232         for i in range(n):
233             print >>stream, "%6dms" % int(results[i][1] * 1000), results[i][0]
235     def _print_traceback(self, msg, err, test, errlist):
236         if self.showAll or self.dots or self._progress:
237             self.stream.writeln("\n")
238             self._lastWidth = 0
240         tb = "".join(traceback.format_exception(*err))
241         self.stream.writeln(msg)
242         self.stream.writeln(tb)
243         errlist.append((test, tb))
245     def startTest(self, test):
246         if self._progress:
247             self.stream.write("\r%4d" % (self.testsRun + 1))
248             if self._count:
249                 self.stream.write("/%d (%5.1f%%)" % (self._count,
250                                   (self.testsRun + 1) * 100.0 / self._count))
251             if self.showAll:
252                 self.stream.write(": ")
253             elif self._progressWithNames:
254                 # XXX will break with multibyte strings
255                 name = self.getShortDescription(test)
256                 width = len(name)
257                 if width < self._lastWidth:
258                     name += " " * (self._lastWidth - width)
259                 self.stream.write(": %s" % name)
260                 self._lastWidth = width
261             self.stream.flush()
262         self._threads = threading.enumerate()
263         self.__super_startTest(test)
264         self._testtimes[test] = time.time()
266     def getShortDescription(self, test):
267         s = self.getDescription(test)
268         if len(s) > self._maxWidth:
269             pos = s.find(" (")
270             if pos >= 0:
271                 w = self._maxWidth - (pos + 5)
272                 if w < 1:
273                     # first portion (test method name) is too long
274                     s = s[:self._maxWidth-3] + "..."
275                 else:
276                     pre = s[:pos+2]
277                     post = s[-w:]
278                     s = "%s...%s" % (pre, post)
279         return s[:self._maxWidth]
281     def addError(self, test, err):
282         if self._progress:
283             self.stream.write("\r")
284         if self._debug:
285             raise err[0], err[1], err[2]
286         self._print_traceback("Error in test %s" % test, err,
287                               test, self.errors)
289     def addFailure(self, test, err):
290         if self._progress:
291             self.stream.write("\r")
292         if self._debug:
293             raise err[0], err[1], err[2]
294         self._print_traceback("Failure in test %s" % test, err,
295                               test, self.failures)
297     def printErrors(self):
298         if self._progress and not (self.dots or self.showAll):
299             self.stream.writeln()
300         self.__super_printErrors()
302     def printErrorList(self, flavor, errors):
303         for test, err in errors:
304             self.stream.writeln(self.separator1)
305             self.stream.writeln("%s: %s" % (flavor, self.getDescription(test)))
306             self.stream.writeln(self.separator2)
307             self.stream.writeln(err)
310 class ImmediateTestRunner(unittest.TextTestRunner):
312     __super_init = unittest.TextTestRunner.__init__
314     def __init__(self, **kwarg):
315         debug = kwarg.get("debug")
316         if debug is not None:
317             del kwarg["debug"]
318         progress = kwarg.get("progress")
319         if progress is not None:
320             del kwarg["progress"]
321         self.__super_init(**kwarg)
322         self._debug = debug
323         self._progress = progress
325     def _makeResult(self):
326         return ImmediateTestResult(self.stream, self.descriptions,
327                                    self.verbosity, debug=self._debug,
328                                    count=self._count, progress=self._progress)
330     def run(self, test):
331         self._count = test.countTestCases()
332         return unittest.TextTestRunner.run(self, test)
334 # setup list of directories to put on the path
335 class PathInit:
336     def __init__(self, build, build_inplace, libdir=None):
337         self.inplace = None
338         # Figure out if we should test in-place or test in-build.  If the -b
339         # or -B option was given, test in the place we were told to build in.
340         # Otherwise, we'll look for a build directory and if we find one,
341         # we'll test there, otherwise we'll test in-place.
342         if build:
343             self.inplace = build_inplace
344         if self.inplace is None:
345             # Need to figure it out
346             if os.path.isdir(os.path.join("build", "lib.%s" % PLAT_SPEC)):
347                 self.inplace = 0
348             else:
349                 self.inplace = 1
350         # Calculate which directories we're going to add to sys.path, and cd
351         # to the appropriate working directory
352         org_cwd = os.getcwd()
353         if self.inplace:
354             self.libdir = "src"
355         else:
356             self.libdir = "lib.%s" % PLAT_SPEC
357             os.chdir("build")
358         # Hack sys.path
359         self.cwd = os.getcwd()
360         sys.path.insert(0, os.path.join(self.cwd, self.libdir))
361         # Hack again for external products.
362         global functional
363         kind = functional and "functional" or "unit"
364         if libdir:
365             extra = os.path.join(org_cwd, libdir)
366             print "Running %s tests from %s" % (kind, extra)
367             self.libdir = extra
368             sys.path.insert(0, extra)
369         else:
370             print "Running %s tests from %s" % (kind, self.cwd)
371         # Make sure functional tests find ftesting.zcml
372         if functional:
373             config_file = 'ftesting.zcml'
374             if not self.inplace:
375                 # We chdired into build, so ftesting.zcml is in the
376                 # parent directory
377                 config_file = os.path.join('..', 'ftesting.zcml')
378             print "Parsing %s" % config_file
379             from zope.testing.functional import FunctionalTestSetup
380             FunctionalTestSetup(config_file)
382 def match(rx, s):
383     if not rx:
384         return 1
385     if rx[0] == "!":
386         return re.search(rx[1:], s) is None
387     else:
388         return re.search(rx, s) is not None
390 class TestFileFinder:
391     def __init__(self, prefix):
392         self.files = []
393         self._plen = len(prefix)
394         if not prefix.endswith(os.sep):
395             self._plen += 1
396         global functional
397         if functional:
398             self.dirname = "ftest"
399         else:
400             self.dirname = "test"
402     def visit(self, rx, dir, files):
403         if os.path.split(dir)[1] != self.dirname:
404             return
405         # ignore tests that aren't in packages
406         if not "__init__.py" in files:
407             if not files or files == ["CVS"]:
408                 return
409             print "not a package", dir
410             return
412         # Put matching files in matches.  If matches is non-empty,
413         # then make sure that the package is importable.
414         matches = []
415         for file in files:
416             if file.startswith('test') and os.path.splitext(file)[-1] == '.py':
417                 path = os.path.join(dir, file)
418                 if match(rx, path):
419                     matches.append(path)
421         # ignore tests when the package can't be imported, possibly due to
422         # dependency failures.
423         pkg = dir[self._plen:].replace(os.sep, '.')
424         try:
425             __import__(pkg)
426         # We specifically do not want to catch ImportError since that's useful
427         # information to know when running the tests.
428         except RuntimeError, e:
429             if VERBOSE:
430                 print "skipping %s because: %s" % (pkg, e)
431             return
432         else:
433             self.files.extend(matches)
435     def module_from_path(self, path):
436         """Return the Python package name indicated by the filesystem path."""
437         assert path.endswith(".py")
438         path = path[self._plen:-3]
439         mod = path.replace(os.sep, ".")
440         return mod
442 def walk_with_symlinks(top, func, arg):
443     """Like os.path.walk, but follows symlinks on POSIX systems.
445     This could theoreticaly result in an infinite loop, if you create symlink
446     cycles in your Zope sandbox, so don't do that.
447     """
448     try:
449         names = os.listdir(top)
450     except os.error:
451         return
452     func(arg, top, names)
453     exceptions = ('.', '..')
454     for name in names:
455         if name not in exceptions:
456             name = os.path.join(top, name)
457             if os.path.isdir(name):
458                 walk_with_symlinks(name, func, arg)
461 def check_test_dir():
462     global test_dir
463     if test_dir and not os.path.exists(test_dir):
464         d = pathinit.libdir
465         d = os.path.join(d, test_dir)
466         if os.path.exists(d):
467             if not os.path.isdir(d):
468                 raise ValueError(
469                     "%s does not exist and %s is not a directory"
470                     % (test_dir, d)
471                     )
472             test_dir = d
473         else:
474             raise ValueError("%s does not exist!" % test_dir)
477 def find_tests(rx):
478     global finder
479     finder = TestFileFinder(pathinit.libdir)
481     check_test_dir()
482     walkdir = test_dir or pathinit.libdir
483     walk_with_symlinks(walkdir, finder.visit, rx)
484     return finder.files
486 def package_import(modname):
487     mod = __import__(modname)
488     for part in modname.split(".")[1:]:
489         mod = getattr(mod, part)
490     return mod
492 def get_suite(file):
493     modname = finder.module_from_path(file)
494     try:
495         mod = package_import(modname)
496     except ImportError, err:
497         # print traceback
498         print "Error importing %s\n%s" % (modname, err)
499         traceback.print_exc()
500         if debug:
501             raise
502         return None
503     try:
504         suite_func = mod.test_suite
505     except AttributeError:
506         print "No test_suite() in %s" % file
507         return None
508     return suite_func()
510 def filter_testcases(s, rx):
511     new = unittest.TestSuite()
512     for test in s._tests:
513         # See if the levels match
514         dolevel = (level == 0) or level >= getattr(test, "level", 0)
515         if not dolevel:
516             continue
517         if isinstance(test, unittest.TestCase):
518             name = test.id() # Full test name: package.module.class.method
519             name = name[1 + name.rfind("."):] # extract method name
520             if not rx or match(rx, name):
521                 new.addTest(test)
522         else:
523             filtered = filter_testcases(test, rx)
524             if filtered:
525                 new.addTest(filtered)
526     return new
528 def gui_runner(files, test_filter):
529     if build_inplace:
530         utildir = os.path.join(os.getcwd(), "utilities")
531     else:
532         utildir = os.path.join(os.getcwd(), "..", "utilities")
533     sys.path.append(utildir)
534     import unittestgui
535     suites = []
536     for file in files:
537         suites.append(finder.module_from_path(file) + ".test_suite")
539     suites = ", ".join(suites)
540     minimal = (GUI == "minimal")
541     # unittestgui apparently doesn't take the minimal flag anymore
542     unittestgui.main(suites)
544 class TrackRefs:
545     """Object to track reference counts across test runs."""
547     def __init__(self):
548         self.type2count = {}
549         self.type2all = {}
551     def update(self):
552         obs = sys.getobjects(0)
553         type2count = {}
554         type2all = {}
555         for o in obs:
556             all = sys.getrefcount(o)
557             t = type(o)
558             if t in type2count:
559                 type2count[t] += 1
560                 type2all[t] += all
561             else:
562                 type2count[t] = 1
563                 type2all[t] = all
565         ct = [(type2count[t] - self.type2count.get(t, 0),
566                type2all[t] - self.type2all.get(t, 0),
567                t)
568               for t in type2count.iterkeys()]
569         ct.sort()
570         ct.reverse()
571         for delta1, delta2, t in ct:
572             if delta1 or delta2:
573                 print "%-55s %8d %8d" % (t, delta1, delta2)
575         self.type2count = type2count
576         self.type2all = type2all
578 def runner(files, test_filter, debug):
579     runner = ImmediateTestRunner(verbosity=VERBOSE, debug=debug,
580         progress=progress)
581     suite = unittest.TestSuite()
582     for file in files:
583         s = get_suite(file)
584         # See if the levels match
585         dolevel = (level == 0) or level >= getattr(s, "level", 0)
586         if s is not None and dolevel:
587             s = filter_testcases(s, test_filter)
588             suite.addTest(s)
589     try:
590         r = runner.run(suite)
591         if timesfn:
592             r.print_times(open(timesfn, "w"))
593             if VERBOSE:
594                 print "Wrote timing data to", timesfn
595         if timetests:
596             r.print_times(sys.stdout, timetests)
597     except:
598         if debugger:
599             print "%s:" % (sys.exc_info()[0], )
600             print sys.exc_info()[1]
601             pdb.post_mortem(sys.exc_info()[2])
602         else:
603             raise
605 def remove_stale_bytecode(arg, dirname, names):
606     names = map(os.path.normcase, names)
607     for name in names:
608         if name.endswith(".pyc") or name.endswith(".pyo"):
609             srcname = name[:-1]
610             if srcname not in names:
611                 fullname = os.path.join(dirname, name)
612                 print "Removing stale bytecode file", fullname
613                 os.unlink(fullname)
615 def main(module_filter, test_filter, libdir):
616     if not keepStaleBytecode:
617         os.path.walk(os.curdir, remove_stale_bytecode, None)
619     # Get the log.ini file from the current directory instead of possibly
620     # buried in the build directory.  XXX This isn't perfect because if
621     # log.ini specifies a log file, it'll be relative to the build directory.
622     # Hmm...
623     logini = os.path.abspath("log.ini")
625     # Initialize the path and cwd
626     global pathinit
627     pathinit = PathInit(build, build_inplace, libdir)
629 # No logging module in py 2.1
630 #    # Initialize the logging module.
632 #    import logging.config
633 #    logging.basicConfig()
635 #    level = os.getenv("LOGGING")
636 #    if level:
637 #        level = int(level)
638 #    else:
639 #        level = logging.CRITICAL
640 #    logging.root.setLevel(level)
642 #    if os.path.exists(logini):
643 #        logging.config.fileConfig(logini)
645     files = find_tests(module_filter)
646     files.sort()
648     if GUI:
649         gui_runner(files, test_filter)
650     elif LOOP:
651         if REFCOUNT:
652             rc = sys.gettotalrefcount()
653             track = TrackRefs()
654         while 1:
655             runner(files, test_filter, debug)
656             gc.collect()
657             if gc.garbage:
658                 print "GARBAGE:", len(gc.garbage), gc.garbage
659                 return
660             if REFCOUNT:
661                 prev = rc
662                 rc = sys.gettotalrefcount()
663                 print "totalrefcount=%-8d change=%-6d" % (rc, rc - prev)
664                 track.update()
665     else:
666         runner(files, test_filter, debug)
669 def process_args(argv=None):
670     import getopt
671     global module_filter
672     global test_filter
673     global VERBOSE
674     global LOOP
675     global GUI
676     global TRACE
677     global REFCOUNT
678     global debug
679     global debugger
680     global build
681     global level
682     global libdir
683     global timesfn
684     global timetests
685     global progress
686     global build_inplace
687     global keepStaleBytecode
688     global functional
689     global test_dir
691     if argv is None:
692         argv = sys.argv
694     module_filter = None
695     test_filter = None
696     VERBOSE = 1
697     LOOP = 0
698     GUI = 0
699     TRACE = 0
700     REFCOUNT = 0
701     debug = 0 # Don't collect test results; simply let tests crash
702     debugger = 0
703     build = 0
704     build_inplace = 0
705     gcthresh = None
706     gcdebug = 0
707     gcflags = []
708     level = 1
709     libdir = '.'
710     progress = 0
711     timesfn = None
712     timetests = 0
713     keepStaleBytecode = 0
714     functional = 0
715     test_dir = None
717     try:
718         opts, args = getopt.getopt(argv[1:], "a:bBcdDfg:G:hLmprtTuv",
719                                    ["all", "help", "libdir=", "times=",
720                                     "keepbytecode", "dir=", "build"])
721     except getopt.error, msg:
722         print msg
723         print "Try `python %s -h' for more information." % argv[0]
724         sys.exit(2)
726     for k, v in opts:
727         if k == "-a":
728             level = int(v)
729         elif k == "--all":
730             level = 0
731             os.environ["COMPLAIN_IF_TESTS_MISSED"]='1'
732         elif k in ("-b", "--build"):
733             build = 1
734         elif k == "-B":
735              build = build_inplace = 1
736         elif k == "-c":
737             # make sure you have a recent version of pychecker
738             if not os.environ.get("PYCHECKER"):
739                 os.environ["PYCHECKER"] = "-q"
740             import pychecker.checker
741         elif k == "-d":
742             debug = 1
743         elif k == "-D":
744             debug = 1
745             debugger = 1
746         elif k == "-f":
747             functional = 1
748         elif k in ("-h", "--help"):
749             print __doc__
750             sys.exit(0)
751         elif k == "-g":
752             gcthresh = int(v)
753         elif k == "-G":
754             if not v.startswith("DEBUG_"):
755                 print "-G argument must be DEBUG_ flag, not", repr(v)
756                 sys.exit(1)
757             gcflags.append(v)
758         elif k == '--keepbytecode':
759             keepStaleBytecode = 1
760         elif k == '--libdir':
761             libdir = v
762         elif k == "-L":
763             LOOP = 1
764         elif k == "-m":
765             GUI = "minimal"
766         elif k == "-p":
767             progress = 1
768         elif k == "-r":
769             if hasattr(sys, "gettotalrefcount"):
770                 REFCOUNT = 1
771             else:
772                 print "-r ignored, because it needs a debug build of Python"
773         elif k == "-T":
774             TRACE = 1
775         elif k == "-t":
776             if not timetests:
777                 timetests = 50
778         elif k == "-u":
779             GUI = 1
780         elif k == "-v":
781             VERBOSE += 1
782         elif k == "--times":
783             try:
784                 timetests = int(v)
785             except ValueError:
786                 # must be a filename to write
787                 timesfn = v
788         elif k == '--dir':
789             test_dir = v
791     if gcthresh is not None:
792         if gcthresh == 0:
793             gc.disable()
794             print "gc disabled"
795         else:
796             gc.set_threshold(gcthresh)
797             print "gc threshold:", gc.get_threshold()
799     if gcflags:
800         val = 0
801         for flag in gcflags:
802             v = getattr(gc, flag, None)
803             if v is None:
804                 print "Unknown gc flag", repr(flag)
805                 print gc.set_debug.__doc__
806                 sys.exit(1)
807             val |= v
808         gcdebug |= v
810     if gcdebug:
811         gc.set_debug(gcdebug)
813     if build:
814         # Python 2.3 is more sane in its non -q output
815         if sys.hexversion >= 0x02030000:
816             qflag = ""
817         else:
818             qflag = "-q"
819         cmd = sys.executable + " setup.py " + qflag + " build"
820         if build_inplace:
821             cmd += "_ext -i"
822         if VERBOSE:
823             print cmd
824         sts = os.system(cmd)
825         if sts:
826             print "Build failed", hex(sts)
827             sys.exit(1)
829     if VERBOSE:
830         kind = functional and "functional" or "unit"
831         if level == 0:
832             print "Running %s tests at all levels" % kind
833         else:
834             print "Running %s tests at level %d" % (kind, level)
836     # XXX We want to change *visible* warnings into errors.  The next
837     # line changes all warnings into errors, including warnings we
838     # normally never see.  In particular, test_datetime does some
839     # short-integer arithmetic that overflows to long ints, and, by
840     # default, Python doesn't display the overflow warning that can
841     # be enabled when this happens.  The next line turns that into an
842     # error instead.  Guido suggests that a better to get what we're
843     # after is to replace warnings.showwarning() with our own thing
844     # that raises an error.
845 ##    warnings.filterwarnings("error")
846     warnings.filterwarnings("ignore", module="logging")
848     if args:
849         if len(args) > 1:
850             test_filter = args[1]
851         module_filter = args[0]
852     try:
853         if TRACE:
854             # if the trace module is used, then we don't exit with
855             # status if on a false return value from main.
856             coverdir = os.path.join(os.getcwd(), "coverage")
857             import trace
858             ignoremods = ["os", "posixpath", "stat"]
859             tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix],
860                                  ignoremods=ignoremods,
861                                  trace=0, count=1)
863             tracer.runctx("main(module_filter, test_filter, libdir)",
864                           globals=globals(), locals=vars())
865             r = tracer.results()
866             path = "/tmp/trace.%s" % os.getpid()
867             import cPickle
868             f = open(path, "wb")
869             cPickle.dump(r, f)
870             f.close()
871             print path
872             r.write_results(show_missing=1, summary=1, coverdir=coverdir)
873         else:
874             bad = main(module_filter, test_filter, libdir)
875             if bad:
876                 sys.exit(1)
877     except ImportError, err:
878         print err
879         print sys.path
880         raise
883 if __name__ == "__main__":
884     process_args()