Code

2cb85a6ddb69b18fb59cad2359c67d750182af6b
[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     from setup import check_manifest
626     check_manifest()
628     # Initialize the path and cwd
629     global pathinit
630     pathinit = PathInit(build, build_inplace, libdir)
632 # No logging module in py 2.1
633 #    # Initialize the logging module.
635 #    import logging.config
636 #    logging.basicConfig()
638 #    level = os.getenv("LOGGING")
639 #    if level:
640 #        level = int(level)
641 #    else:
642 #        level = logging.CRITICAL
643 #    logging.root.setLevel(level)
645 #    if os.path.exists(logini):
646 #        logging.config.fileConfig(logini)
648     files = find_tests(module_filter)
649     files.sort()
651     if GUI:
652         gui_runner(files, test_filter)
653     elif LOOP:
654         if REFCOUNT:
655             rc = sys.gettotalrefcount()
656             track = TrackRefs()
657         while 1:
658             runner(files, test_filter, debug)
659             gc.collect()
660             if gc.garbage:
661                 print "GARBAGE:", len(gc.garbage), gc.garbage
662                 return
663             if REFCOUNT:
664                 prev = rc
665                 rc = sys.gettotalrefcount()
666                 print "totalrefcount=%-8d change=%-6d" % (rc, rc - prev)
667                 track.update()
668     else:
669         runner(files, test_filter, debug)
672 def process_args(argv=None):
673     import getopt
674     global module_filter
675     global test_filter
676     global VERBOSE
677     global LOOP
678     global GUI
679     global TRACE
680     global REFCOUNT
681     global debug
682     global debugger
683     global build
684     global level
685     global libdir
686     global timesfn
687     global timetests
688     global progress
689     global build_inplace
690     global keepStaleBytecode
691     global functional
692     global test_dir
694     if argv is None:
695         argv = sys.argv
697     module_filter = None
698     test_filter = None
699     VERBOSE = 1
700     LOOP = 0
701     GUI = 0
702     TRACE = 0
703     REFCOUNT = 0
704     debug = 0 # Don't collect test results; simply let tests crash
705     debugger = 0
706     build = 0
707     build_inplace = 0
708     gcthresh = None
709     gcdebug = 0
710     gcflags = []
711     level = 1
712     libdir = '.'
713     progress = 0
714     timesfn = None
715     timetests = 0
716     keepStaleBytecode = 0
717     functional = 0
718     test_dir = None
720     try:
721         opts, args = getopt.getopt(argv[1:], "a:bBcdDfg:G:hLmprtTuv",
722                                    ["all", "help", "libdir=", "times=",
723                                     "keepbytecode", "dir=", "build"])
724     except getopt.error, msg:
725         print msg
726         print "Try `python %s -h' for more information." % argv[0]
727         sys.exit(2)
729     for k, v in opts:
730         if k == "-a":
731             level = int(v)
732         elif k == "--all":
733             level = 0
734             os.environ["COMPLAIN_IF_TESTS_MISSED"]='1'
735         elif k in ("-b", "--build"):
736             build = 1
737         elif k == "-B":
738              build = build_inplace = 1
739         elif k == "-c":
740             # make sure you have a recent version of pychecker
741             if not os.environ.get("PYCHECKER"):
742                 os.environ["PYCHECKER"] = "-q"
743             import pychecker.checker
744         elif k == "-d":
745             debug = 1
746         elif k == "-D":
747             debug = 1
748             debugger = 1
749         elif k == "-f":
750             functional = 1
751         elif k in ("-h", "--help"):
752             print __doc__
753             sys.exit(0)
754         elif k == "-g":
755             gcthresh = int(v)
756         elif k == "-G":
757             if not v.startswith("DEBUG_"):
758                 print "-G argument must be DEBUG_ flag, not", repr(v)
759                 sys.exit(1)
760             gcflags.append(v)
761         elif k == '--keepbytecode':
762             keepStaleBytecode = 1
763         elif k == '--libdir':
764             libdir = v
765         elif k == "-L":
766             LOOP = 1
767         elif k == "-m":
768             GUI = "minimal"
769         elif k == "-p":
770             progress = 1
771         elif k == "-r":
772             if hasattr(sys, "gettotalrefcount"):
773                 REFCOUNT = 1
774             else:
775                 print "-r ignored, because it needs a debug build of Python"
776         elif k == "-T":
777             TRACE = 1
778         elif k == "-t":
779             if not timetests:
780                 timetests = 50
781         elif k == "-u":
782             GUI = 1
783         elif k == "-v":
784             VERBOSE += 1
785         elif k == "--times":
786             try:
787                 timetests = int(v)
788             except ValueError:
789                 # must be a filename to write
790                 timesfn = v
791         elif k == '--dir':
792             test_dir = v
794     if gcthresh is not None:
795         if gcthresh == 0:
796             gc.disable()
797             print "gc disabled"
798         else:
799             gc.set_threshold(gcthresh)
800             print "gc threshold:", gc.get_threshold()
802     if gcflags:
803         val = 0
804         for flag in gcflags:
805             v = getattr(gc, flag, None)
806             if v is None:
807                 print "Unknown gc flag", repr(flag)
808                 print gc.set_debug.__doc__
809                 sys.exit(1)
810             val |= v
811         gcdebug |= v
813     if gcdebug:
814         gc.set_debug(gcdebug)
816     if build:
817         # Python 2.3 is more sane in its non -q output
818         if sys.hexversion >= 0x02030000:
819             qflag = ""
820         else:
821             qflag = "-q"
822         cmd = sys.executable + " setup.py " + qflag + " build"
823         if build_inplace:
824             cmd += "_ext -i"
825         if VERBOSE:
826             print cmd
827         sts = os.system(cmd)
828         if sts:
829             print "Build failed", hex(sts)
830             sys.exit(1)
832     if VERBOSE:
833         kind = functional and "functional" or "unit"
834         if level == 0:
835             print "Running %s tests at all levels" % kind
836         else:
837             print "Running %s tests at level %d" % (kind, level)
839     # XXX We want to change *visible* warnings into errors.  The next
840     # line changes all warnings into errors, including warnings we
841     # normally never see.  In particular, test_datetime does some
842     # short-integer arithmetic that overflows to long ints, and, by
843     # default, Python doesn't display the overflow warning that can
844     # be enabled when this happens.  The next line turns that into an
845     # error instead.  Guido suggests that a better to get what we're
846     # after is to replace warnings.showwarning() with our own thing
847     # that raises an error.
848 ##    warnings.filterwarnings("error")
849     warnings.filterwarnings("ignore", module="logging")
851     if args:
852         if len(args) > 1:
853             test_filter = args[1]
854         module_filter = args[0]
855     try:
856         if TRACE:
857             # if the trace module is used, then we don't exit with
858             # status if on a false return value from main.
859             coverdir = os.path.join(os.getcwd(), "coverage")
860             import trace
861             ignoremods = ["os", "posixpath", "stat"]
862             tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix],
863                                  ignoremods=ignoremods,
864                                  trace=0, count=1)
866             tracer.runctx("main(module_filter, test_filter, libdir)",
867                           globals=globals(), locals=vars())
868             r = tracer.results()
869             path = "/tmp/trace.%s" % os.getpid()
870             import cPickle
871             f = open(path, "wb")
872             cPickle.dump(r, f)
873             f.close()
874             print path
875             r.write_results(show_missing=1, summary=1, coverdir=coverdir)
876         else:
877             bad = main(module_filter, test_filter, libdir)
878             if bad:
879                 sys.exit(1)
880     except ImportError, err:
881         print err
882         print sys.path
883         raise
886 if __name__ == "__main__":
887     process_args()