Code

svn repository setup
[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         names = os.listdir(top)
452     except os.error:
453         return
454     func(arg, top, names)
455     exceptions = ('.', '..')
456     for name in names:
457         if name not in exceptions:
458             name = os.path.join(top, name)
459             if os.path.isdir(name):
460                 walk_with_symlinks(name, func, arg)
463 def check_test_dir():
464     global test_dir
465     if test_dir and not os.path.exists(test_dir):
466         d = pathinit.libdir
467         d = os.path.join(d, test_dir)
468         if os.path.exists(d):
469             if not os.path.isdir(d):
470                 raise ValueError(
471                     "%s does not exist and %s is not a directory"
472                     % (test_dir, d)
473                     )
474             test_dir = d
475         else:
476             raise ValueError("%s does not exist!" % test_dir)
479 def find_tests(rx):
480     global finder
481     finder = TestFileFinder(pathinit.libdir)
483     check_test_dir()
484     walkdir = test_dir or pathinit.libdir
485     walk_with_symlinks(walkdir, finder.visit, rx)
486     return finder.files
488 def package_import(modname):
489     mod = __import__(modname)
490     for part in modname.split(".")[1:]:
491         mod = getattr(mod, part)
492     return mod
494 def get_suite(file):
495     modname = finder.module_from_path(file)
496     try:
497         mod = package_import(modname)
498     except ImportError, err:
499         # print traceback
500         print "Error importing %s\n%s" % (modname, err)
501         traceback.print_exc()
502         if debug:
503             raise
504         return None
505     try:
506         suite_func = mod.test_suite
507     except AttributeError:
508         print "No test_suite() in %s" % file
509         return None
510     return suite_func()
512 def filter_testcases(s, rx):
513     new = unittest.TestSuite()
514     for test in s._tests:
515         # See if the levels match
516         dolevel = (level == 0) or level >= getattr(test, "level", 0)
517         if not dolevel:
518             continue
519         if isinstance(test, unittest.TestCase):
520             name = test.id() # Full test name: package.module.class.method
521             name = name[1 + name.rfind("."):] # extract method name
522             if not rx or match(rx, name):
523                 new.addTest(test)
524         else:
525             filtered = filter_testcases(test, rx)
526             if filtered:
527                 new.addTest(filtered)
528     return new
530 def gui_runner(files, test_filter):
531     if build_inplace:
532         utildir = os.path.join(os.getcwd(), "utilities")
533     else:
534         utildir = os.path.join(os.getcwd(), "..", "utilities")
535     sys.path.append(utildir)
536     import unittestgui
537     suites = []
538     for file in files:
539         suites.append(finder.module_from_path(file) + ".test_suite")
541     suites = ", ".join(suites)
542     minimal = (GUI == "minimal")
543     # unittestgui apparently doesn't take the minimal flag anymore
544     unittestgui.main(suites)
546 class TrackRefs:
547     """Object to track reference counts across test runs."""
549     def __init__(self):
550         self.type2count = {}
551         self.type2all = {}
553     def update(self):
554         obs = sys.getobjects(0)
555         type2count = {}
556         type2all = {}
557         for o in obs:
558             all = sys.getrefcount(o)
559             t = type(o)
560             if t in type2count:
561                 type2count[t] += 1
562                 type2all[t] += all
563             else:
564                 type2count[t] = 1
565                 type2all[t] = all
567         ct = [(type2count[t] - self.type2count.get(t, 0),
568                type2all[t] - self.type2all.get(t, 0),
569                t)
570               for t in type2count.iterkeys()]
571         ct.sort()
572         ct.reverse()
573         for delta1, delta2, t in ct:
574             if delta1 or delta2:
575                 print "%-55s %8d %8d" % (t, delta1, delta2)
577         self.type2count = type2count
578         self.type2all = type2all
580 def runner(files, test_filter, debug):
581     runner = ImmediateTestRunner(verbosity=VERBOSE, debug=debug,
582         progress=progress)
583     suite = unittest.TestSuite()
584     for file in files:
585         s = get_suite(file)
586         # See if the levels match
587         dolevel = (level == 0) or level >= getattr(s, "level", 0)
588         if s is not None and dolevel:
589             s = filter_testcases(s, test_filter)
590             suite.addTest(s)
591     try:
592         r = runner.run(suite)
593         if timesfn:
594             r.print_times(open(timesfn, "w"))
595             if VERBOSE:
596                 print "Wrote timing data to", timesfn
597         if timetests:
598             r.print_times(sys.stdout, timetests)
599     except:
600         if debugger:
601             print "%s:" % (sys.exc_info()[0], )
602             print sys.exc_info()[1]
603             pdb.post_mortem(sys.exc_info()[2])
604         else:
605             raise
607 def remove_stale_bytecode(arg, dirname, names):
608     names = map(os.path.normcase, names)
609     for name in names:
610         if name.endswith(".pyc") or name.endswith(".pyo"):
611             srcname = name[:-1]
612             if srcname not in names:
613                 fullname = os.path.join(dirname, name)
614                 print "Removing stale bytecode file", fullname
615                 os.unlink(fullname)
617 def main(module_filter, test_filter, libdir):
618     if not keepStaleBytecode:
619         os.path.walk(os.curdir, remove_stale_bytecode, None)
621     # Get the log.ini file from the current directory instead of possibly
622     # buried in the build directory.  XXX This isn't perfect because if
623     # log.ini specifies a log file, it'll be relative to the build directory.
624     # Hmm...
625     logini = os.path.abspath("log.ini")
627     from setup import check_manifest
628     check_manifest()
630     # Initialize the path and cwd
631     global pathinit
632     pathinit = PathInit(build, build_inplace, libdir)
634 # No logging module in py 2.1
635 #    # Initialize the logging module.
637 #    import logging.config
638 #    logging.basicConfig()
640 #    level = os.getenv("LOGGING")
641 #    if level:
642 #        level = int(level)
643 #    else:
644 #        level = logging.CRITICAL
645 #    logging.root.setLevel(level)
647 #    if os.path.exists(logini):
648 #        logging.config.fileConfig(logini)
650     files = find_tests(module_filter)
651     files.sort()
653     if GUI:
654         gui_runner(files, test_filter)
655     elif LOOP:
656         if REFCOUNT:
657             rc = sys.gettotalrefcount()
658             track = TrackRefs()
659         while 1:
660             runner(files, test_filter, debug)
661             gc.collect()
662             if gc.garbage:
663                 print "GARBAGE:", len(gc.garbage), gc.garbage
664                 return
665             if REFCOUNT:
666                 prev = rc
667                 rc = sys.gettotalrefcount()
668                 print "totalrefcount=%-8d change=%-6d" % (rc, rc - prev)
669                 track.update()
670     else:
671         runner(files, test_filter, debug)
674 def process_args(argv=None):
675     import getopt
676     global module_filter
677     global test_filter
678     global VERBOSE
679     global LOOP
680     global GUI
681     global TRACE
682     global REFCOUNT
683     global debug
684     global debugger
685     global build
686     global level
687     global libdir
688     global timesfn
689     global timetests
690     global progress
691     global build_inplace
692     global keepStaleBytecode
693     global functional
694     global test_dir
696     if argv is None:
697         argv = sys.argv
699     module_filter = None
700     test_filter = None
701     VERBOSE = 2
702     LOOP = 0
703     GUI = 0
704     TRACE = 0
705     REFCOUNT = 0
706     debug = 0 # Don't collect test results; simply let tests crash
707     debugger = 0
708     build = 0
709     build_inplace = 0
710     gcthresh = None
711     gcdebug = 0
712     gcflags = []
713     level = 1
714     libdir = '.'
715     progress = 0
716     timesfn = None
717     timetests = 0
718     keepStaleBytecode = 0
719     functional = 0
720     test_dir = None
722     try:
723         opts, args = getopt.getopt(argv[1:], "a:bBcdDfg:G:hLmprtTuv",
724                                    ["all", "help", "libdir=", "times=",
725                                     "keepbytecode", "dir=", "build"])
726     except getopt.error, msg:
727         print msg
728         print "Try `python %s -h' for more information." % argv[0]
729         sys.exit(2)
731     for k, v in opts:
732         if k == "-a":
733             level = int(v)
734         elif k == "--all":
735             level = 0
736             os.environ["COMPLAIN_IF_TESTS_MISSED"]='1'
737         elif k in ("-b", "--build"):
738             build = 1
739         elif k == "-B":
740              build = build_inplace = 1
741         elif k == "-c":
742             # make sure you have a recent version of pychecker
743             if not os.environ.get("PYCHECKER"):
744                 os.environ["PYCHECKER"] = "-q"
745             import pychecker.checker
746         elif k == "-d":
747             debug = 1
748         elif k == "-D":
749             debug = 1
750             debugger = 1
751         elif k == "-f":
752             functional = 1
753         elif k in ("-h", "--help"):
754             print __doc__
755             sys.exit(0)
756         elif k == "-g":
757             gcthresh = int(v)
758         elif k == "-G":
759             if not v.startswith("DEBUG_"):
760                 print "-G argument must be DEBUG_ flag, not", repr(v)
761                 sys.exit(1)
762             gcflags.append(v)
763         elif k == '--keepbytecode':
764             keepStaleBytecode = 1
765         elif k == '--libdir':
766             libdir = v
767         elif k == "-L":
768             LOOP = 1
769         elif k == "-m":
770             GUI = "minimal"
771         elif k == "-p":
772             progress = 1
773         elif k == "-r":
774             if hasattr(sys, "gettotalrefcount"):
775                 REFCOUNT = 1
776             else:
777                 print "-r ignored, because it needs a debug build of Python"
778         elif k == "-T":
779             TRACE = 1
780         elif k == "-t":
781             if not timetests:
782                 timetests = 50
783         elif k == "-u":
784             GUI = 1
785         elif k == "-v":
786             VERBOSE += 1
787         elif k == "--times":
788             try:
789                 timetests = int(v)
790             except ValueError:
791                 # must be a filename to write
792                 timesfn = v
793         elif k == '--dir':
794             test_dir = v
796     if gcthresh is not None:
797         if gcthresh == 0:
798             gc.disable()
799             print "gc disabled"
800         else:
801             gc.set_threshold(gcthresh)
802             print "gc threshold:", gc.get_threshold()
804     if gcflags:
805         val = 0
806         for flag in gcflags:
807             v = getattr(gc, flag, None)
808             if v is None:
809                 print "Unknown gc flag", repr(flag)
810                 print gc.set_debug.__doc__
811                 sys.exit(1)
812             val |= v
813         gcdebug |= v
815     if gcdebug:
816         gc.set_debug(gcdebug)
818     if build:
819         # Python 2.3 is more sane in its non -q output
820         if sys.hexversion >= 0x02030000:
821             qflag = ""
822         else:
823             qflag = "-q"
824         cmd = sys.executable + " setup.py " + qflag + " build"
825         if build_inplace:
826             cmd += "_ext -i"
827         if VERBOSE:
828             print cmd
829         sts = os.system(cmd)
830         if sts:
831             print "Build failed", hex(sts)
832             sys.exit(1)
834     if VERBOSE:
835         kind = functional and "functional" or "unit"
836         if level == 0:
837             print "Running %s tests at all levels" % kind
838         else:
839             print "Running %s tests at level %d" % (kind, level)
841     # XXX We want to change *visible* warnings into errors.  The next
842     # line changes all warnings into errors, including warnings we
843     # normally never see.  In particular, test_datetime does some
844     # short-integer arithmetic that overflows to long ints, and, by
845     # default, Python doesn't display the overflow warning that can
846     # be enabled when this happens.  The next line turns that into an
847     # error instead.  Guido suggests that a better to get what we're
848     # after is to replace warnings.showwarning() with our own thing
849     # that raises an error.
850 ##    warnings.filterwarnings("error")
851     warnings.filterwarnings("ignore", module="logging")
853     if args:
854         if len(args) > 1:
855             test_filter = args[1]
856         module_filter = args[0]
857     try:
858         if TRACE:
859             # if the trace module is used, then we don't exit with
860             # status if on a false return value from main.
861             coverdir = os.path.join(os.getcwd(), "coverage")
862             import trace
863             ignoremods = ["os", "posixpath", "stat"]
864             tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix],
865                                  ignoremods=ignoremods,
866                                  trace=0, count=1)
868             tracer.runctx("main(module_filter, test_filter, libdir)",
869                           globals=globals(), locals=vars())
870             r = tracer.results()
871             path = "/tmp/trace.%s" % os.getpid()
872             import cPickle
873             f = open(path, "wb")
874             cPickle.dump(r, f)
875             f.close()
876             print path
877             r.write_results(show_missing=1, summary=1, coverdir=coverdir)
878         else:
879             bad = main(module_filter, test_filter, libdir)
880             if bad:
881                 sys.exit(1)
882     except ImportError, err:
883         print err
884         print sys.path
885         raise
888 if __name__ == "__main__":
889     process_args()