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