Code

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