Code

When debugging mail (debug = <filename> setting in [mail] section of
[roundup.git] / run_tests.py
1 #! /usr/bin/env python
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         # docstrings for tests don't override test-descriptions:
188         self.descriptions = False
189         if progress and verbosity == 1:
190             self.dots = 0
191             self._progressWithNames = 1
192             self._lastWidth = 0
193             self._maxWidth = 80
194             try:
195                 import curses
196             except ImportError:
197                 pass
198             else:
199                 curses.setupterm()
200                 self._maxWidth = curses.tigetnum('cols')
201             self._maxWidth -= len("xxxx/xxxx (xxx.x%): ") + 1
203     def stopTest(self, test):
204         self._testtimes[test] = time.time() - self._testtimes[test]
205         if gc.garbage:
206             print "The following test left garbage:"
207             print test
208             print gc.garbage
209             # eat the garbage here, so that the garbage isn't
210             # printed for every subsequent test.
211             gc.garbage[:] = []
213         # Did the test leave any new threads behind?
214         new_threads = [t for t in threading.enumerate()
215                          if (t.isAlive()
216                              and
217                              t not in self._threads)]
218         if new_threads:
219             print "The following test left new threads behind:"
220             print test
221             print "New thread(s):", new_threads
223     def print_times(self, stream, count=None):
224         results = self._testtimes.items()
225         results.sort(lambda x, y: cmp(y[1], x[1]))
226         if count:
227             n = min(count, len(results))
228             if n:
229                 print >>stream, "Top %d longest tests:" % n
230         else:
231             n = len(results)
232         if not n:
233             return
234         for i in range(n):
235             print >>stream, "%6dms" % int(results[i][1] * 1000), results[i][0]
237     def _print_traceback(self, msg, err, test, errlist):
238         if self.showAll or self.dots or self._progress:
239             self.stream.writeln("\n")
240             self._lastWidth = 0
242         tb = "".join(traceback.format_exception(*err))
243         self.stream.writeln(msg)
244         self.stream.writeln(tb)
245         errlist.append((test, tb))
247     def startTest(self, test):
248         if self._progress:
249             self.stream.write("\r%4d" % (self.testsRun + 1))
250             if self._count:
251                 self.stream.write("/%d (%5.1f%%)" % (self._count,
252                                   (self.testsRun + 1) * 100.0 / self._count))
253             if self.showAll:
254                 self.stream.write(": ")
255             elif self._progressWithNames:
256                 # XXX will break with multibyte strings
257                 name = self.getShortDescription(test)
258                 width = len(name)
259                 if width < self._lastWidth:
260                     name += " " * (self._lastWidth - width)
261                 self.stream.write(": %s" % name)
262                 self._lastWidth = width
263             self.stream.flush()
264         self._threads = threading.enumerate()
265         self.__super_startTest(test)
266         self._testtimes[test] = time.time()
268     def getShortDescription(self, test):
269         s = self.getDescription(test)
270         if len(s) > self._maxWidth:
271             pos = s.find(" (")
272             if pos >= 0:
273                 w = self._maxWidth - (pos + 5)
274                 if w < 1:
275                     # first portion (test method name) is too long
276                     s = s[:self._maxWidth-3] + "..."
277                 else:
278                     pre = s[:pos+2]
279                     post = s[-w:]
280                     s = "%s...%s" % (pre, post)
281         return s[:self._maxWidth]
283     def addError(self, test, err):
284         if self._progress:
285             self.stream.write("\r")
286         if self._debug:
287             raise err[0], err[1], err[2]
288         self._print_traceback("Error in test %s" % test, err,
289                               test, self.errors)
291     def addFailure(self, test, err):
292         if self._progress:
293             self.stream.write("\r")
294         if self._debug:
295             raise err[0], err[1], err[2]
296         self._print_traceback("Failure in test %s" % test, err,
297                               test, self.failures)
299     def printErrors(self):
300         if self._progress and not (self.dots or self.showAll):
301             self.stream.writeln()
302         self.__super_printErrors()
304     def printErrorList(self, flavor, errors):
305         for test, err in errors:
306             self.stream.writeln(self.separator1)
307             self.stream.writeln("%s: %s" % (flavor, self.getDescription(test)))
308             self.stream.writeln(self.separator2)
309             self.stream.writeln(err)
312 class ImmediateTestRunner(unittest.TextTestRunner):
314     __super_init = unittest.TextTestRunner.__init__
316     def __init__(self, **kwarg):
317         debug = kwarg.get("debug")
318         if debug is not None:
319             del kwarg["debug"]
320         progress = kwarg.get("progress")
321         if progress is not None:
322             del kwarg["progress"]
323         self.__super_init(**kwarg)
324         self._debug = debug
325         self._progress = progress
327     def _makeResult(self):
328         return ImmediateTestResult(self.stream, self.descriptions,
329                                    self.verbosity, debug=self._debug,
330                                    count=self._count, progress=self._progress)
332     def run(self, test):
333         self._count = test.countTestCases()
334         return unittest.TextTestRunner.run(self, test)
336 # setup list of directories to put on the path
337 class PathInit:
338     def __init__(self, build, build_inplace, libdir=None):
339         self.inplace = None
340         # Figure out if we should test in-place or test in-build.  If the -b
341         # or -B option was given, test in the place we were told to build in.
342         # Otherwise, we'll look for a build directory and if we find one,
343         # we'll test there, otherwise we'll test in-place.
344         if build:
345             self.inplace = build_inplace
346         if self.inplace is None:
347             # Need to figure it out
348             if os.path.isdir(os.path.join("build", "lib.%s" % PLAT_SPEC)):
349                 self.inplace = 0
350             else:
351                 self.inplace = 1
352         # Calculate which directories we're going to add to sys.path, and cd
353         # to the appropriate working directory
354         org_cwd = os.getcwd()
355         if self.inplace:
356             self.libdir = "src"
357         else:
358             self.libdir = "lib.%s" % PLAT_SPEC
359             os.chdir("build")
360         # Hack sys.path
361         self.cwd = os.getcwd()
362         sys.path.insert(0, os.path.join(self.cwd, self.libdir))
363         # Hack again for external products.
364         global functional
365         kind = functional and "functional" or "unit"
366         if libdir:
367             extra = os.path.join(org_cwd, libdir)
368             print "Running %s tests from %s" % (kind, extra)
369             self.libdir = extra
370             sys.path.insert(0, extra)
371         else:
372             print "Running %s tests from %s" % (kind, self.cwd)
373         # Make sure functional tests find ftesting.zcml
374         if functional:
375             config_file = 'ftesting.zcml'
376             if not self.inplace:
377                 # We chdired into build, so ftesting.zcml is in the
378                 # parent directory
379                 config_file = os.path.join('..', 'ftesting.zcml')
380             print "Parsing %s" % config_file
381             from zope.testing.functional import FunctionalTestSetup
382             FunctionalTestSetup(config_file)
384 def match(rx, s):
385     if not rx:
386         return 1
387     if rx[0] == "!":
388         return re.search(rx[1:], s) is None
389     else:
390         return re.search(rx, s) is not None
392 class TestFileFinder:
393     def __init__(self, prefix):
394         self.files = []
395         self._plen = len(prefix)
396         if not prefix.endswith(os.sep):
397             self._plen += 1
398         global functional
399         if functional:
400             self.dirname = "ftest"
401         else:
402             self.dirname = "test"
404     def visit(self, rx, dir, files):
405         if os.path.split(dir)[1] != self.dirname:
406             return
407         # ignore tests that aren't in packages
408         if not "__init__.py" in files:
409             if not files or files == ["CVS"]:
410                 return
411             print "not a package", dir
412             return
414         # Put matching files in matches.  If matches is non-empty,
415         # then make sure that the package is importable.
416         matches = []
417         for file in files:
418             if file.startswith('test') and os.path.splitext(file)[-1] == '.py':
419                 path = os.path.join(dir, file)
420                 if match(rx, path):
421                     matches.append(path)
423         # ignore tests when the package can't be imported, possibly due to
424         # dependency failures.
425         pkg = dir[self._plen:].replace(os.sep, '.')
426         try:
427             __import__(pkg)
428         # We specifically do not want to catch ImportError since that's useful
429         # information to know when running the tests.
430         except RuntimeError, e:
431             if VERBOSE:
432                 print "skipping %s because: %s" % (pkg, e)
433             return
434         else:
435             self.files.extend(matches)
437     def module_from_path(self, path):
438         """Return the Python package name indicated by the filesystem path."""
439         assert path.endswith(".py")
440         path = path[self._plen:-3]
441         mod = path.replace(os.sep, ".")
442         return mod
444 def walk_with_symlinks(top, func, arg):
445     """Like os.path.walk, but follows symlinks on POSIX systems.
447     This could theoreticaly result in an infinite loop, if you create symlink
448     cycles in your Zope sandbox, so don't do that.
449     """
450     try:
451         # Prevent 'hidden' files (those starting with '.') from being considered.
452         names = [f for f in os.listdir(top) if not f.startswith('.')]
453     except os.error:
454         return
455     func(arg, top, names)
456     exceptions = ('.', '..')
457     for name in names:
458         if name not in exceptions:
459             name = os.path.join(top, name)
460             if os.path.isdir(name):
461                 walk_with_symlinks(name, func, arg)
464 def check_test_dir():
465     global test_dir
466     if test_dir and not os.path.exists(test_dir):
467         d = pathinit.libdir
468         d = os.path.join(d, test_dir)
469         if os.path.exists(d):
470             if not os.path.isdir(d):
471                 raise ValueError(
472                     "%s does not exist and %s is not a directory"
473                     % (test_dir, d)
474                     )
475             test_dir = d
476         else:
477             raise ValueError("%s does not exist!" % test_dir)
480 def find_tests(rx):
481     global finder
482     finder = TestFileFinder(pathinit.libdir)
484     check_test_dir()
485     walkdir = test_dir or pathinit.libdir
486     walk_with_symlinks(walkdir, finder.visit, rx)
487     return finder.files
489 def package_import(modname):
490     mod = __import__(modname)
491     for part in modname.split(".")[1:]:
492         mod = getattr(mod, part)
493     return mod
495 def get_suite(file):
496     modname = finder.module_from_path(file)
497     try:
498         mod = package_import(modname)
499     except ImportError, err:
500         # print traceback
501         print "Error importing %s\n%s" % (modname, err)
502         traceback.print_exc()
503         if debug:
504             raise
505         return None
506     try:
507         suite_func = mod.test_suite
508     except AttributeError:
509         print "No test_suite() in %s" % file
510         return None
511     return suite_func()
513 def filter_testcases(s, rx):
514     new = unittest.TestSuite()
515     for test in s._tests:
516         # See if the levels match
517         dolevel = (level == 0) or level >= getattr(test, "level", 0)
518         if not dolevel:
519             continue
520         if isinstance(test, unittest.TestCase):
521             name = test.id() # Full test name: package.module.class.method
522             name = name[1 + name.rfind("."):] # extract method name
523             if not rx or match(rx, name):
524                 new.addTest(test)
525         else:
526             filtered = filter_testcases(test, rx)
527             if filtered:
528                 new.addTest(filtered)
529     return new
531 def gui_runner(files, test_filter):
532     if build_inplace:
533         utildir = os.path.join(os.getcwd(), "utilities")
534     else:
535         utildir = os.path.join(os.getcwd(), "..", "utilities")
536     sys.path.append(utildir)
537     import unittestgui
538     suites = []
539     for file in files:
540         suites.append(finder.module_from_path(file) + ".test_suite")
542     suites = ", ".join(suites)
543     minimal = (GUI == "minimal")
544     # unittestgui apparently doesn't take the minimal flag anymore
545     unittestgui.main(suites)
547 class TrackRefs:
548     """Object to track reference counts across test runs."""
550     def __init__(self):
551         self.type2count = {}
552         self.type2all = {}
554     def update(self):
555         obs = sys.getobjects(0)
556         type2count = {}
557         type2all = {}
558         for o in obs:
559             all = sys.getrefcount(o)
560             t = type(o)
561             if t in type2count:
562                 type2count[t] += 1
563                 type2all[t] += all
564             else:
565                 type2count[t] = 1
566                 type2all[t] = all
568         ct = [(type2count[t] - self.type2count.get(t, 0),
569                type2all[t] - self.type2all.get(t, 0),
570                t)
571               for t in type2count.iterkeys()]
572         ct.sort()
573         ct.reverse()
574         for delta1, delta2, t in ct:
575             if delta1 or delta2:
576                 print "%-55s %8d %8d" % (t, delta1, delta2)
578         self.type2count = type2count
579         self.type2all = type2all
581 def runner(files, test_filter, debug):
582     runner = ImmediateTestRunner(verbosity=VERBOSE, debug=debug,
583         progress=progress)
584     suite = unittest.TestSuite()
585     for file in files:
586         s = get_suite(file)
587         # See if the levels match
588         dolevel = (level == 0) or level >= getattr(s, "level", 0)
589         if s is not None and dolevel:
590             s = filter_testcases(s, test_filter)
591             suite.addTest(s)
592     try:
593         r = runner.run(suite)
594         if timesfn:
595             r.print_times(open(timesfn, "w"))
596             if VERBOSE:
597                 print "Wrote timing data to", timesfn
598         if timetests:
599             r.print_times(sys.stdout, timetests)
600     except:
601         if debugger:
602             print "%s:" % (sys.exc_info()[0], )
603             print sys.exc_info()[1]
604             pdb.post_mortem(sys.exc_info()[2])
605         else:
606             raise
608 def remove_stale_bytecode(arg, dirname, names):
609     names = map(os.path.normcase, names)
610     for name in names:
611         if name.endswith(".pyc") or name.endswith(".pyo"):
612             srcname = name[:-1]
613             if srcname not in names:
614                 fullname = os.path.join(dirname, name)
615                 print "Removing stale bytecode file", fullname
616                 os.unlink(fullname)
618 def main(module_filter, test_filter, libdir):
619     if not keepStaleBytecode:
620         os.path.walk(os.curdir, remove_stale_bytecode, None)
622     # Get the log.ini file from the current directory instead of possibly
623     # buried in the build directory.  XXX This isn't perfect because if
624     # log.ini specifies a log file, it'll be relative to the build directory.
625     # Hmm...
626     logini = os.path.abspath("log.ini")
628     # Initialize the path and cwd
629     global pathinit
630     pathinit = PathInit(build, build_inplace, libdir)
632     # Initialize the logging module.
634     import logging.config
635     logging.basicConfig()
637     level = os.getenv("LOGGING")
638     if level:
639         level = int(level)
640     else:
641         level = logging.CRITICAL
642     logging.root.setLevel(level)
644     if os.path.exists(logini):
645         logging.config.fileConfig(logini)
647     files = find_tests(module_filter)
648     files.sort()
650     if GUI:
651         gui_runner(files, test_filter)
652     elif LOOP:
653         if REFCOUNT:
654             rc = sys.gettotalrefcount()
655             track = TrackRefs()
656         while 1:
657             runner(files, test_filter, debug)
658             gc.collect()
659             if gc.garbage:
660                 print "GARBAGE:", len(gc.garbage), gc.garbage
661                 return
662             if REFCOUNT:
663                 prev = rc
664                 rc = sys.gettotalrefcount()
665                 print "totalrefcount=%-8d change=%-6d" % (rc, rc - prev)
666                 track.update()
667     else:
668         runner(files, test_filter, debug)
671 def process_args(argv=None):
672     import getopt
673     global module_filter
674     global test_filter
675     global VERBOSE
676     global LOOP
677     global GUI
678     global TRACE
679     global REFCOUNT
680     global debug
681     global debugger
682     global build
683     global level
684     global libdir
685     global timesfn
686     global timetests
687     global progress
688     global build_inplace
689     global keepStaleBytecode
690     global functional
691     global test_dir
693     if argv is None:
694         argv = sys.argv
696     module_filter = None
697     test_filter = None
698     VERBOSE = 2
699     LOOP = 0
700     GUI = 0
701     TRACE = 0
702     REFCOUNT = 0
703     debug = 0 # Don't collect test results; simply let tests crash
704     debugger = 0
705     build = 0
706     build_inplace = 0
707     gcthresh = None
708     gcdebug = 0
709     gcflags = []
710     level = 1
711     libdir = '.'
712     progress = 0
713     timesfn = None
714     timetests = 0
715     keepStaleBytecode = 0
716     functional = 0
717     test_dir = None
719     try:
720         opts, args = getopt.getopt(argv[1:], "a:bBcdDfg:G:hLmprtTuv",
721                                    ["all", "help", "libdir=", "times=",
722                                     "keepbytecode", "dir=", "build"])
723     except getopt.error, msg:
724         print msg
725         print "Try `python %s -h' for more information." % argv[0]
726         sys.exit(2)
728     for k, v in opts:
729         if k == "-a":
730             level = int(v)
731         elif k == "--all":
732             level = 0
733             os.environ["COMPLAIN_IF_TESTS_MISSED"]='1'
734         elif k in ("-b", "--build"):
735             build = 1
736         elif k == "-B":
737              build = build_inplace = 1
738         elif k == "-c":
739             # make sure you have a recent version of pychecker
740             if not os.environ.get("PYCHECKER"):
741                 os.environ["PYCHECKER"] = "-q"
742             import pychecker.checker
743         elif k == "-d":
744             debug = 1
745         elif k == "-D":
746             debug = 1
747             debugger = 1
748         elif k == "-f":
749             functional = 1
750         elif k in ("-h", "--help"):
751             print __doc__
752             sys.exit(0)
753         elif k == "-g":
754             gcthresh = int(v)
755         elif k == "-G":
756             if not v.startswith("DEBUG_"):
757                 print "-G argument must be DEBUG_ flag, not", repr(v)
758                 sys.exit(1)
759             gcflags.append(v)
760         elif k == '--keepbytecode':
761             keepStaleBytecode = 1
762         elif k == '--libdir':
763             libdir = v
764         elif k == "-L":
765             LOOP = 1
766         elif k == "-m":
767             GUI = "minimal"
768         elif k == "-p":
769             progress = 1
770         elif k == "-r":
771             if hasattr(sys, "gettotalrefcount"):
772                 REFCOUNT = 1
773             else:
774                 print "-r ignored, because it needs a debug build of Python"
775         elif k == "-T":
776             TRACE = 1
777         elif k == "-t":
778             if not timetests:
779                 timetests = 50
780         elif k == "-u":
781             GUI = 1
782         elif k == "-v":
783             VERBOSE += 1
784         elif k == "--times":
785             try:
786                 timetests = int(v)
787             except ValueError:
788                 # must be a filename to write
789                 timesfn = v
790         elif k == '--dir':
791             test_dir = v
793     if gcthresh is not None:
794         if gcthresh == 0:
795             gc.disable()
796             print "gc disabled"
797         else:
798             gc.set_threshold(gcthresh)
799             print "gc threshold:", gc.get_threshold()
801     if gcflags:
802         val = 0
803         for flag in gcflags:
804             v = getattr(gc, flag, None)
805             if v is None:
806                 print "Unknown gc flag", repr(flag)
807                 print gc.set_debug.__doc__
808                 sys.exit(1)
809             val |= v
810         gcdebug |= v
812     if gcdebug:
813         gc.set_debug(gcdebug)
815     if build:
816         # Python 2.3 is more sane in its non -q output
817         if sys.hexversion >= 0x02030000:
818             qflag = ""
819         else:
820             qflag = "-q"
821         cmd = sys.executable + " setup.py " + qflag + " build"
822         if build_inplace:
823             cmd += "_ext -i"
824         if VERBOSE:
825             print cmd
826         sts = os.system(cmd)
827         if sts:
828             print "Build failed", hex(sts)
829             sys.exit(1)
831     if VERBOSE:
832         kind = functional and "functional" or "unit"
833         if level == 0:
834             print "Running %s tests at all levels" % kind
835         else:
836             print "Running %s tests at level %d" % (kind, level)
838     # XXX We want to change *visible* warnings into errors.  The next
839     # line changes all warnings into errors, including warnings we
840     # normally never see.  In particular, test_datetime does some
841     # short-integer arithmetic that overflows to long ints, and, by
842     # default, Python doesn't display the overflow warning that can
843     # be enabled when this happens.  The next line turns that into an
844     # error instead.  Guido suggests that a better to get what we're
845     # after is to replace warnings.showwarning() with our own thing
846     # that raises an error.
847 ##    warnings.filterwarnings("error")
848     warnings.filterwarnings("ignore", module="logging")
850     if args:
851         if len(args) > 1:
852             test_filter = args[1]
853         module_filter = args[0]
854     try:
855         if TRACE:
856             # if the trace module is used, then we don't exit with
857             # status if on a false return value from main.
858             coverdir = os.path.join(os.getcwd(), "coverage")
859             import trace
860             ignoremods = ["os", "posixpath", "stat"]
861             tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix],
862                                  ignoremods=ignoremods,
863                                  trace=0, count=1)
865             tracer.runctx("main(module_filter, test_filter, libdir)",
866                           globals=globals(), locals=vars())
867             r = tracer.results()
868             path = "/tmp/trace.%s" % os.getpid()
869             import cPickle
870             f = open(path, "wb")
871             cPickle.dump(r, f)
872             f.close()
873             print path
874             r.write_results(show_missing=1, summary=1, coverdir=coverdir)
875         else:
876             bad = main(module_filter, test_filter, libdir)
877             if bad:
878                 sys.exit(1)
879     except ImportError, err:
880         print err
881         print sys.path
882         raise
885 if __name__ == "__main__":
886     process_args()