Code

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