1#! /usr/bin/env python2.3 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""" 16test.py [-abBcdDfFgGhklLmMPprstTuUv] [modfilter [testfilter]] 17 18Find and run tests written using the unittest module. 19 20The test runner searches for Python modules that contain test suites. 21It collects those suites, and runs the tests. There are many options 22for controlling how the tests are run. There are options for using 23the debugger, reporting code coverage, and checking for refcount problems. 24 25The test runner uses the following rules for finding tests to run. It 26searches for packages and modules that contain "tests" as a component 27of the name, e.g. "frob.tests.nitz" matches this rule because tests is 28a sub-package of frob. Within each "tests" package, it looks for 29modules that begin with the name "test." For each test module, it 30imports the module and calls the module's test_suite() function, which must 31return a unittest TestSuite object. 32 33Options can be specified as command line arguments (see below). However, 34options may also be specified in a file named 'test.config', a Python 35script which, if found, will be executed before the command line 36arguments are processed. 37 38The test.config script should specify options by setting zero or more of the 39global variables: LEVEL, BUILD, and other capitalized variable names found in 40the test runner script (see the list of global variables in process_args().). 41 42 43-a level 44--at-level level 45--all 46 Run the tests at the given level. Any test at a level at or below 47 this is run, any test at a level above this is not run. Level 0 48 runs all tests. The default is to run tests at level 1. --all is 49 a shortcut for -a 0. 50 51-b 52--build 53 Run "python setup.py build" before running tests, where "python" 54 is the version of python used to run test.py. Highly recommended. 55 Tests will be run from the build directory. 56 57-B 58--build-inplace 59 Run "python setup.py build_ext -i" before running tests. Tests will be 60 run from the source directory. 61 62-c 63--pychecker 64 use pychecker 65 66-d 67--debug 68 Instead of the normal test harness, run a debug version which 69 doesn't catch any exceptions. This is occasionally handy when the 70 unittest code catching the exception doesn't work right. 71 Unfortunately, the debug harness doesn't print the name of the 72 test, so Use With Care. 73 74-D 75--debug-inplace 76 Works like -d, except that it loads pdb when an exception occurs. 77 78--dir directory 79-s directory 80 Option to limit where tests are searched for. This is important 81 when you *really* want to limit the code that gets run. This can 82 be specified more than once to run tests in two different parts of 83 the source tree. 84 For example, if refactoring interfaces, you don't want to see the way 85 you have broken setups for tests in other packages. You *just* want to 86 run the interface tests. 87 88-f 89--skip-unit 90 Run functional tests but not unit tests. 91 Note that functional tests will be skipped if the module 92 zope.app.tests.functional cannot be imported. 93 Functional tests also expect to find the file ftesting.zcml, 94 which is used to configure the functional-test run. 95 96-F 97 DEPRECATED. Run both unit and functional tests. 98 This option is deprecated, because this is the new default mode. 99 Note that functional tests will be skipped if the module 100 zope.app.tests.functional cannot be imported. 101 102-g threshold 103--gc-threshold threshold 104 Set the garbage collector generation0 threshold. This can be used 105 to stress memory and gc correctness. Some crashes are only 106 reproducible when the threshold is set to 1 (agressive garbage 107 collection). Do "-g 0" to disable garbage collection altogether. 108 109-G gc_option 110--gc-option gc_option 111 Set the garbage collection debugging flags. The argument must be one 112 of the DEBUG_ flags defined bythe Python gc module. Multiple options 113 can be specified by using "-G OPTION1 -G OPTION2." 114 115-k 116--keepbytecode 117 Do not delete all stale bytecode before running tests 118 119-l test_root 120--libdir test_root 121 Search for tests starting in the specified start directory 122 (useful for testing components being developed outside the main 123 "src" or "build" trees). 124 125-L 126--loop 127 Keep running the selected tests in a loop. You may experience 128 memory leakage. 129 130-m 131-M minimal GUI. See -U. 132 133-P 134--profile 135 Run the tests under hotshot and display the top 50 stats, sorted by 136 cumulative time and number of calls. 137 138-p 139--progress 140 Show running progress. It can be combined with -v or -vv. 141 142-r 143--refcount 144 Look for refcount problems. 145 This requires that Python was built --with-pydebug. 146 147-t 148--top-fifty 149 Time the individual tests and print a list of the top 50, sorted from 150 longest to shortest. 151 152--times n 153--times outfile 154 With an integer argument, time the tests and print a list of the top <n> 155 tests, sorted from longest to shortest. 156 With a non-integer argument, specifies a file to which timing information 157 is to be printed. 158 159-T 160--trace 161 Use the trace module from Python for code coverage. The current 162 utility writes coverage files to a directory named `coverage' that 163 is parallel to `build'. It also prints a summary to stdout. 164 165-u 166--skip-functional 167 CHANGED. Run unit tests but not functional tests. 168 Note that the meaning of -u is changed from its former meaning, 169 which is now specified by -U or --gui. 170 171-U 172--gui 173 Use the PyUnit GUI instead of output to the command line. The GUI 174 imports tests on its own, taking care to reload all dependencies 175 on each run. The debug (-d), verbose (-v), progress (-p), and 176 Loop (-L) options will be ignored. The testfilter filter is also 177 not applied. 178 179-m 180-M 181--minimal-gui 182 Note: -m is DEPRECATED in favour of -M or --minimal-gui. 183 -m starts the gui minimized. Double-clicking the progress bar 184 will start the import and run all tests. 185 186 187-v 188--verbose 189 Verbose output. With one -v, unittest prints a dot (".") for each 190 test run. With -vv, unittest prints the name of each test (for 191 some definition of "name" ...). With no -v, unittest is silent 192 until the end of the run, except when errors occur. 193 194 When -p is also specified, the meaning of -v is slightly 195 different. With -p and no -v only the percent indicator is 196 displayed. With -p and -v the test name of the current test is 197 shown to the right of the percent indicator. With -p and -vv the 198 test name is not truncated to fit into 80 columns and it is not 199 cleared after the test finishes. 200 201 202modfilter 203testfilter 204 Case-sensitive regexps to limit which tests are run, used in search 205 (not match) mode. 206 In an extension of Python regexp notation, a leading "!" is stripped 207 and causes the sense of the remaining regexp to be negated (so "!bc" 208 matches any string that does not match "bc", and vice versa). 209 By default these act like ".", i.e. nothing is excluded. 210 211 modfilter is applied to a test file's path, starting at "build" and 212 including (OS-dependent) path separators. 213 214 testfilter is applied to the (method) name of the unittest methods 215 contained in the test files whose paths modfilter matched. 216 217Extreme (yet useful) examples: 218 219 test.py -vvb . "^testWriteClient$" 220 221 Builds the project silently, then runs unittest in verbose mode on all 222 tests whose names are precisely "testWriteClient". Useful when 223 debugging a specific test. 224 225 test.py -vvb . "!^testWriteClient$" 226 227 As before, but runs all tests whose names aren't precisely 228 "testWriteClient". Useful to avoid a specific failing test you don't 229 want to deal with just yet. 230 231 test.py -M . "!^testWriteClient$" 232 233 As before, but now opens up a minimized PyUnit GUI window (only showing 234 the progress bar). Useful for refactoring runs where you continually want 235 to make sure all tests still pass. 236""" 237 238import gc 239import hotshot, hotshot.stats 240import os 241import re 242import pdb 243import sys 244import threading # just to get at Thread objects created by tests 245import time 246import traceback 247import unittest 248import warnings 249 250def set_trace_doctest(stdin=sys.stdin, stdout=sys.stdout, trace=pdb.set_trace): 251 sys.stdin = stdin 252 sys.stdout = stdout 253 trace() 254 255pdb.set_trace_doctest = set_trace_doctest 256 257from distutils.util import get_platform 258 259PLAT_SPEC = "%s-%s" % (get_platform(), sys.version[0:3]) 260 261class ImmediateTestResult(unittest._TextTestResult): 262 263 __super_init = unittest._TextTestResult.__init__ 264 __super_startTest = unittest._TextTestResult.startTest 265 __super_printErrors = unittest._TextTestResult.printErrors 266 267 def __init__(self, stream, descriptions, verbosity, debug=False, 268 count=None, progress=False): 269 self.__super_init(stream, descriptions, verbosity) 270 self._debug = debug 271 self._progress = progress 272 self._progressWithNames = False 273 self.count = count 274 self._testtimes = {} 275 if progress and verbosity == 1: 276 self.dots = False 277 self._progressWithNames = True 278 self._lastWidth = 0 279 self._maxWidth = 80 280 try: 281 import curses 282 except ImportError: 283 pass 284 else: 285 curses.setupterm() 286 self._maxWidth = curses.tigetnum('cols') 287 self._maxWidth -= len("xxxx/xxxx (xxx.x%): ") + 1 288 289 def stopTest(self, test): 290 self._testtimes[test] = time.time() - self._testtimes[test] 291 if gc.garbage: 292 print "The following test left garbage:" 293 print test 294 print gc.garbage 295 # XXX Perhaps eat the garbage here, so that the garbage isn't 296 # printed for every subsequent test. 297 298 # Did the test leave any new threads behind? 299 new_threads = [t for t in threading.enumerate() 300 if (t.isAlive() 301 and 302 t not in self._threads)] 303 if new_threads: 304 print "The following test left new threads behind:" 305 print test 306 print "New thread(s):", new_threads 307 308 def print_times(self, stream, count=None): 309 results = self._testtimes.items() 310 results.sort(lambda x, y: cmp(y[1], x[1])) 311 if count: 312 n = min(count, len(results)) 313 if n: 314 print >>stream, "Top %d longest tests:" % n 315 else: 316 n = len(results) 317 if not n: 318 return 319 for i in range(n): 320 print >>stream, "%6dms" % int(results[i][1] * 1000), results[i][0] 321 322 def _print_traceback(self, msg, err, test, errlist): 323 if self.showAll or self.dots or self._progress: 324 self.stream.writeln("\n") 325 self._lastWidth = 0 326 327 tb = "".join(traceback.format_exception(*err)) 328 self.stream.writeln(msg) 329 self.stream.writeln(tb) 330 errlist.append((test, tb)) 331 332 def startTest(self, test): 333 if self._progress: 334 self.stream.write("\r%4d" % (self.testsRun + 1)) 335 if self.count: 336 self.stream.write("/%d (%5.1f%%)" % (self.count, 337 (self.testsRun + 1) * 100.0 / self.count)) 338 if self.showAll: 339 self.stream.write(": ") 340 elif self._progressWithNames: 341 # XXX will break with multibyte strings 342 name = self.getShortDescription(test) 343 width = len(name) 344 if width < self._lastWidth: 345 name += " " * (self._lastWidth - width) 346 self.stream.write(": %s" % name) 347 self._lastWidth = width 348 self.stream.flush() 349 self._threads = threading.enumerate() 350 self.__super_startTest(test) 351 self._testtimes[test] = time.time() 352 353 def getShortDescription(self, test): 354 s = self.getDescription(test) 355 if len(s) > self._maxWidth: 356 pos = s.find(" (") 357 if pos >= 0: 358 w = self._maxWidth - (pos + 5) 359 if w < 1: 360 # first portion (test method name) is too long 361 s = s[:self._maxWidth-3] + "..." 362 else: 363 pre = s[:pos+2] 364 post = s[-w:] 365 s = "%s...%s" % (pre, post) 366 return s[:self._maxWidth] 367 368 def addError(self, test, err): 369 if self._progress: 370 self.stream.write("\r") 371 if self._debug: 372 raise err[0], err[1], err[2] 373 self._print_traceback("Error in test %s" % test, err, 374 test, self.errors) 375 376 def addFailure(self, test, err): 377 if self._progress: 378 self.stream.write("\r") 379 if self._debug: 380 raise err[0], err[1], err[2] 381 self._print_traceback("Failure in test %s" % test, err, 382 test, self.failures) 383 384 def printErrors(self): 385 if self._progress and not (self.dots or self.showAll): 386 self.stream.writeln() 387 self.__super_printErrors() 388 389 def printErrorList(self, flavor, errors): 390 for test, err in errors: 391 self.stream.writeln(self.separator1) 392 self.stream.writeln("%s: %s" % (flavor, self.getDescription(test))) 393 self.stream.writeln(self.separator2) 394 self.stream.writeln(err) 395 396 397class ImmediateTestRunner(unittest.TextTestRunner): 398 399 __super_init = unittest.TextTestRunner.__init__ 400 401 def __init__(self, **kwarg): 402 debug = kwarg.get("debug") 403 if debug is not None: 404 del kwarg["debug"] 405 progress = kwarg.get("progress") 406 if progress is not None: 407 del kwarg["progress"] 408 profile = kwarg.get("profile") 409 if profile is not None: 410 del kwarg["profile"] 411 self.__super_init(**kwarg) 412 self._debug = debug 413 self._progress = progress 414 self._profile = profile 415 # Create the test result here, so that we can add errors if 416 # the test suite search process has problems. The count 417 # attribute must be set in run(), because we won't know the 418 # count until all test suites have been found. 419 self.result = ImmediateTestResult( 420 self.stream, self.descriptions, self.verbosity, debug=self._debug, 421 progress=self._progress) 422 423 def _makeResult(self): 424 # Needed base class run method. 425 return self.result 426 427 def run(self, test): 428 self.result.count = test.countTestCases() 429 if self._debug: 430 club_debug(test) 431 if self._profile: 432 prof = hotshot.Profile("tests_profile.prof") 433 args = (self, test) 434 r = prof.runcall(unittest.TextTestRunner.run, *args) 435 prof.close() 436 stats = hotshot.stats.load("tests_profile.prof") 437 stats.sort_stats('cumulative', 'calls') 438 stats.print_stats(50) 439 return r 440 return unittest.TextTestRunner.run(self, test) 441 442def club_debug(test): 443 # Beat a debug flag into debug-aware test cases 444 setDebugModeOn = getattr(test, 'setDebugModeOn', None) 445 if setDebugModeOn is not None: 446 setDebugModeOn() 447 448 for subtest in getattr(test, '_tests', ()): 449 club_debug(subtest) 450 451# setup list of directories to put on the path 452class PathInit: 453 def __init__(self, build, build_inplace, libdir=None): 454 self.inplace = None 455 # Figure out if we should test in-place or test in-build. If the -b 456 # or -B option was given, test in the place we were told to build in. 457 # Otherwise, we'll look for a build directory and if we find one, 458 # we'll test there, otherwise we'll test in-place. 459 if build: 460 self.inplace = build_inplace 461 if self.inplace is None: 462 # Need to figure it out 463 if os.path.isdir(os.path.join("build", "lib.%s" % PLAT_SPEC)): 464 self.inplace = False 465 else: 466 self.inplace = True 467 # Calculate which directories we're going to add to sys.path, and cd 468 # to the appropriate working directory 469 self.org_cwd = os.getcwd() 470 if self.inplace: 471 self.libdir = "src" 472 else: 473 self.libdir = "lib.%s" % PLAT_SPEC 474 os.chdir("build") 475 # Hack sys.path 476 self.cwd = os.getcwd() 477 sys.path.insert(0, os.path.join(self.cwd, self.libdir)) 478 # Hack again for external products. 479 global functional 480 kind = functional and "FUNCTIONAL" or "UNIT" 481 if libdir: 482 extra = os.path.join(self.org_cwd, libdir) 483 print "Running %s tests from %s" % (kind, extra) 484 self.libdir = extra 485 sys.path.insert(0, extra) 486 else: 487 print "Running %s tests from %s" % (kind, self.cwd) 488 # Make sure functional tests find ftesting.zcml 489 if functional: 490 config_file = 'ftesting.zcml' 491 if not self.inplace: 492 # We chdired into build, so ftesting.zcml is in the 493 # parent directory 494 config_file = os.path.join('..', 'ftesting.zcml') 495 print "Parsing %s" % config_file 496 from zope.app.tests.functional import FunctionalTestSetup 497 FunctionalTestSetup(config_file) 498 499def match(rx, s): 500 if not rx: 501 return True 502 if rx[0] == "!": 503 return re.search(rx[1:], s) is None 504 else: 505 return re.search(rx, s) is not None 506 507class TestFileFinder: 508 def __init__(self, prefix): 509 self.files = [] 510 self._plen = len(prefix) 511 if not prefix.endswith(os.sep): 512 self._plen += 1 513 global functional 514 if functional: 515 self.dirname = "ftests" 516 else: 517 self.dirname = "tests" 518 519 def visit(self, rx, dir, files): 520 if os.path.split(dir)[1] != self.dirname: 521 # Allow tests/ftests module rather than package. 522 modfname = self.dirname + '.py' 523 if modfname in files: 524 path = os.path.join(dir, modfname) 525 if match(rx, path): 526 self.files.append(path) 527 return 528 return 529 # ignore tests that aren't in packages 530 if not "__init__.py" in files: 531 if not files or files == ["CVS"]: 532 return 533 print "not a package", dir 534 return 535 536 # Put matching files in matches. If matches is non-empty, 537 # then make sure that the package is importable. 538 matches = [] 539 for file in files: 540 if file.startswith('test') and os.path.splitext(file)[-1] == '.py': 541 path = os.path.join(dir, file) 542 if match(rx, path): 543 matches.append(path) 544 545 # ignore tests when the package can't be imported, possibly due to 546 # dependency failures. 547 pkg = dir[self._plen:].replace(os.sep, '.') 548 try: 549 __import__(pkg) 550 # We specifically do not want to catch ImportError since that's useful 551 # information to know when running the tests. 552 except RuntimeError, e: 553 if VERBOSE: 554 print "skipping %s because: %s" % (pkg, e) 555 return 556 else: 557 self.files.extend(matches) 558 559 def module_from_path(self, path): 560 """Return the Python package name indicated by the filesystem path.""" 561 assert path.endswith(".py") 562 path = path[self._plen:-3] 563 mod = path.replace(os.sep, ".") 564 return mod 565 566def walk_with_symlinks(top, func, arg): 567 """Like os.path.walk, but follows symlinks on POSIX systems. 568 569 This could theoreticaly result in an infinite loop, if you create symlink 570 cycles in your Zope sandbox, so don't do that. 571 """ 572 try: 573 names = os.listdir(top) 574 except os.error: 575 return 576 func(arg, top, names) 577 exceptions = ('.', '..') 578 for name in names: 579 if name not in exceptions: 580 name = os.path.join(top, name) 581 if os.path.isdir(name): 582 walk_with_symlinks(name, func, arg) 583 584def find_test_dir(dir): 585 if os.path.exists(dir): 586 return dir 587 d = os.path.join(pathinit.libdir, dir) 588 if os.path.exists(d): 589 if os.path.isdir(d): 590 return d 591 raise ValueError("%s does not exist and %s is not a directory" 592 % (dir, d)) 593 raise ValueError("%s does not exist!" % dir) 594 595def find_tests(rx): 596 global finder 597 finder = TestFileFinder(pathinit.libdir) 598 599 if TEST_DIRS: 600 for d in TEST_DIRS: 601 d = find_test_dir(d) 602 walk_with_symlinks(d, finder.visit, rx) 603 else: 604 walk_with_symlinks(pathinit.libdir, finder.visit, rx) 605 return finder.files 606 607def package_import(modname): 608 mod = __import__(modname) 609 for part in modname.split(".")[1:]: 610 mod = getattr(mod, part) 611 return mod 612 613class PseudoTestCase: 614 """Minimal test case objects to create error reports. 615 616 If test.py finds something that looks like it should be a test but 617 can't load it or find its test suite, it will report an error 618 using a PseudoTestCase. 619 """ 620 621 def __init__(self, name, descr=None): 622 self.name = name 623 self.descr = descr 624 625 def shortDescription(self): 626 return self.descr 627 628 def __str__(self): 629 return "Invalid Test (%s)" % self.name 630 631def get_suite(file, result): 632 modname = finder.module_from_path(file) 633 try: 634 mod = package_import(modname) 635 return mod.test_suite() 636 except: 637 result.addError(PseudoTestCase(modname), sys.exc_info()) 638 return None 639 640def filter_testcases(s, rx): 641 new = unittest.TestSuite() 642 for test in s._tests: 643 # See if the levels match 644 dolevel = (LEVEL == 0) or LEVEL >= getattr(test, "level", 0) 645 if not dolevel: 646 continue 647 if isinstance(test, unittest.TestCase): 648 name = test.id() # Full test name: package.module.class.method 649 name = name[1 + name.rfind("."):] # extract method name 650 if not rx or match(rx, name): 651 new.addTest(test) 652 else: 653 filtered = filter_testcases(test, rx) 654 if filtered: 655 new.addTest(filtered) 656 return new 657 658def gui_runner(files, test_filter): 659 if BUILD_INPLACE: 660 utildir = os.path.join(os.getcwd(), "utilities") 661 else: 662 utildir = os.path.join(os.getcwd(), "..", "utilities") 663 sys.path.append(utildir) 664 import unittestgui 665 suites = [] 666 for file in files: 667 suites.append(finder.module_from_path(file) + ".test_suite") 668 669 suites = ", ".join(suites) 670 minimal = (GUI == "minimal") 671 unittestgui.main(suites, minimal) 672 673class TrackRefs: 674 """Object to track reference counts across test runs.""" 675 676 def __init__(self): 677 self.type2count = {} 678 self.type2all = {} 679 680 def update(self): 681 obs = sys.getobjects(0) 682 type2count = {} 683 type2all = {} 684 for o in obs: 685 all = sys.getrefcount(o) 686 687 if type(o) is str and o == '<dummy key>': 688 # avoid dictionary madness 689 continue 690 t = type(o) 691 if t in type2count: 692 type2count[t] += 1 693 type2all[t] += all 694 else: 695 type2count[t] = 1 696 type2all[t] = all 697 698 ct = [(type2count[t] - self.type2count.get(t, 0), 699 type2all[t] - self.type2all.get(t, 0), 700 t) 701 for t in type2count.iterkeys()] 702 ct.sort() 703 ct.reverse() 704 printed = False 705 for delta1, delta2, t in ct: 706 if delta1 or delta2: 707 if not printed: 708 print "%-55s %8s %8s" % ('', 'insts', 'refs') 709 printed = True 710 print "%-55s %8d %8d" % (t, delta1, delta2) 711 712 self.type2count = type2count 713 self.type2all = type2all 714 715def runner(files, test_filter, debug): 716 runner = ImmediateTestRunner(verbosity=VERBOSE, debug=DEBUG, 717 progress=PROGRESS, profile=PROFILE, 718 descriptions=False) 719 suite = unittest.TestSuite() 720 for file in files: 721 s = get_suite(file, runner.result) 722 # See if the levels match 723 dolevel = (LEVEL == 0) or LEVEL >= getattr(s, "level", 0) 724 if s is not None and dolevel: 725 s = filter_testcases(s, test_filter) 726 suite.addTest(s) 727 try: 728 r = runner.run(suite) 729 if TIMESFN: 730 r.print_times(open(TIMESFN, "w")) 731 if VERBOSE: 732 print "Wrote timing data to", TIMESFN 733 if TIMETESTS: 734 r.print_times(sys.stdout, TIMETESTS) 735 except: 736 if DEBUGGER: 737 print "%s:" % (sys.exc_info()[0], ) 738 print sys.exc_info()[1] 739 pdb.post_mortem(sys.exc_info()[2]) 740 else: 741 raise 742 743def remove_stale_bytecode(arg, dirname, names): 744 names = map(os.path.normcase, names) 745 for name in names: 746 if name.endswith(".pyc") or name.endswith(".pyo"): 747 srcname = name[:-1] 748 if srcname not in names: 749 fullname = os.path.join(dirname, name) 750 print "Removing stale bytecode file", fullname 751 os.unlink(fullname) 752 753def main(module_filter, test_filter, libdir): 754 if not KEEP_STALE_BYTECODE: 755 os.path.walk(os.curdir, remove_stale_bytecode, None) 756 757 configure_logging() 758 759 # Initialize the path and cwd 760 global pathinit 761 pathinit = PathInit(BUILD, BUILD_INPLACE, libdir) 762 763 files = find_tests(module_filter) 764 files.sort() 765 766 if GUI: 767 gui_runner(files, test_filter) 768 elif LOOP: 769 if REFCOUNT: 770 rc = sys.gettotalrefcount() 771 track = TrackRefs() 772 while True: 773 runner(files, test_filter, DEBUG) 774 gc.collect() 775 if gc.garbage: 776 print "GARBAGE:", len(gc.garbage), gc.garbage 777 return 778 if REFCOUNT: 779 prev = rc 780 rc = sys.gettotalrefcount() 781 print "totalrefcount=%-8d change=%-6d" % (rc, rc - prev) 782 track.update() 783 else: 784 runner(files, test_filter, DEBUG) 785 786 os.chdir(pathinit.org_cwd) 787 788 789def configure_logging(): 790 """Initialize the logging module.""" 791 import logging.config 792 793 # Get the log.ini file from the current directory instead of possibly 794 # buried in the build directory. XXX This isn't perfect because if 795 # log.ini specifies a log file, it'll be relative to the build directory. 796 # Hmm... 797 logini = os.path.abspath("log.ini") 798 799 if os.path.exists(logini): 800 logging.config.fileConfig(logini) 801 else: 802 logging.basicConfig() 803 804 if os.environ.has_key("LOGGING"): 805 level = int(os.environ["LOGGING"]) 806 logging.getLogger().setLevel(level) 807 808 809def process_args(argv=None): 810 import getopt 811 global MODULE_FILTER 812 global TEST_FILTER 813 global VERBOSE 814 global LOOP 815 global GUI 816 global TRACE 817 global REFCOUNT 818 global DEBUG 819 global DEBUGGER 820 global BUILD 821 global LEVEL 822 global LIBDIR 823 global TIMESFN 824 global TIMETESTS 825 global PROGRESS 826 global BUILD_INPLACE 827 global KEEP_STALE_BYTECODE 828 global TEST_DIRS 829 global PROFILE 830 global GC_THRESHOLD 831 global GC_FLAGS 832 global RUN_UNIT 833 global RUN_FUNCTIONAL 834 global PYCHECKER 835 836 if argv is None: 837 argv = sys.argv 838 839 MODULE_FILTER = None 840 TEST_FILTER = None 841 VERBOSE = 0 842 LOOP = False 843 GUI = False 844 TRACE = False 845 REFCOUNT = False 846 DEBUG = False # Don't collect test results; simply let tests crash 847 DEBUGGER = False 848 BUILD = False 849 BUILD_INPLACE = False 850 GC_THRESHOLD = None 851 gcdebug = 0 852 GC_FLAGS = [] 853 LEVEL = 1 854 LIBDIR = None 855 PROGRESS = False 856 TIMESFN = None 857 TIMETESTS = 0 858 KEEP_STALE_BYTECODE = 0 859 RUN_UNIT = True 860 RUN_FUNCTIONAL = True 861 TEST_DIRS = [] 862 PROFILE = False 863 PYCHECKER = False 864 config_filename = 'test.config' 865 866 # import the config file 867 if os.path.isfile(config_filename): 868 print 'Configuration file found.' 869 execfile(config_filename, globals()) 870 871 872 try: 873 opts, args = getopt.getopt(argv[1:], "a:bBcdDfFg:G:hkl:LmMPprs:tTuUv", 874 ["all", "help", "libdir=", "times=", 875 "keepbytecode", "dir=", "build", 876 "build-inplace", 877 "at-level=", 878 "pychecker", "debug", "pdebug", 879 "gc-threshold=", "gc-option=", 880 "loop", "gui", "minimal-gui", 881 "profile", "progress", "refcount", "trace", 882 "top-fifty", "verbose", 883 ]) 884 # fixme: add the long names 885 # fixme: add the extra documentation 886 # fixme: test for functional first! 887 except getopt.error, msg: 888 print msg 889 print "Try `python %s -h' for more information." % argv[0] 890 sys.exit(2) 891 892 for k, v in opts: 893 if k in ("-a", "--at-level"): 894 LEVEL = int(v) 895 elif k == "--all": 896 LEVEL = 0 897 os.environ["COMPLAIN_IF_TESTS_MISSED"]='1' 898 elif k in ("-b", "--build"): 899 BUILD = True 900 elif k in ("-B", "--build-inplace"): 901 BUILD = BUILD_INPLACE = True 902 elif k in("-c", "--pychecker"): 903 PYCHECKER = True 904 elif k in ("-d", "--debug"): 905 DEBUG = True 906 elif k in ("-D", "--pdebug"): 907 DEBUG = True 908 DEBUGGER = True 909 elif k in ("-f", "--skip-unit"): 910 RUN_UNIT = False 911 elif k in ("-u", "--skip-functional"): 912 RUN_FUNCTIONAL = False 913 elif k == "-F": 914 message = 'Unit plus functional is the default behaviour.' 915 warnings.warn(message, DeprecationWarning) 916 RUN_UNIT = True 917 RUN_FUNCTIONAL = True 918 elif k in ("-h", "--help"): 919 print __doc__ 920 sys.exit(0) 921 elif k in ("-g", "--gc-threshold"): 922 GC_THRESHOLD = int(v) 923 elif k in ("-G", "--gc-option"): 924 if not v.startswith("DEBUG_"): 925 print "-G argument must be DEBUG_ flag, not", repr(v) 926 sys.exit(1) 927 GC_FLAGS.append(v) 928 elif k in ('-k', '--keepbytecode'): 929 KEEP_STALE_BYTECODE = 1 930 elif k in ('-l', '--libdir'): 931 LIBDIR = v 932 elif k in ("-L", "--loop"): 933 LOOP = 1 934 elif k == "-m": 935 GUI = "minimal" 936 msg = "Use -M or --minimal-gui instead of -m." 937 warnings.warn(msg, DeprecationWarning) 938 elif k in ("-M", "--minimal-gui"): 939 GUI = "minimal" 940 elif k in ("-P", "--profile"): 941 PROFILE = True 942 elif k in ("-p", "--progress"): 943 PROGRESS = True 944 elif k in ("-r", "--refcount"): 945 REFCOUNT = True 946 elif k in ("-T", "--trace"): 947 TRACE = True 948 elif k in ("-t", "--top-fifty"): 949 if not TIMETESTS: 950 TIMETESTS = 50 951 elif k in ("-u", "--gui"): 952 GUI = 1 953 elif k in ("-v", "--verbose"): 954 VERBOSE += 1 955 elif k == "--times": 956 try: 957 TIMETESTS = int(v) 958 except ValueError: 959 # must be a filename to write 960 TIMESFN = v 961 elif k in ('-s', '--dir'): 962 TEST_DIRS.append(v) 963 964 if PYCHECKER: 965 # make sure you have a recent version of pychecker 966 if not os.environ.get("PYCHECKER"): 967 os.environ["PYCHECKER"] = "-q" 968 import pychecker.checker 969 970 if REFCOUNT and not hasattr(sys, "gettotalrefcount"): 971 print "-r ignored, because it needs a debug build of Python" 972 REFCOUNT = False 973 974 if sys.version_info < ( 2,3,2 ): 975 print """\ 976 ERROR: Your python version is not supported by Zope3. 977 Zope3 needs Python 2.3.2 or greater. You are running:""" + sys.version 978 sys.exit(1) 979 980 if GC_THRESHOLD is not None: 981 if GC_THRESHOLD == 0: 982 gc.disable() 983 print "gc disabled" 984 else: 985 gc.set_threshold(GC_THRESHOLD) 986 print "gc threshold:", gc.get_threshold() 987 988 if GC_FLAGS: 989 val = 0 990 for flag in GC_FLAGS: 991 v = getattr(gc, flag, None) 992 if v is None: 993 print "Unknown gc flag", repr(flag) 994 print gc.set_debug.__doc__ 995 sys.exit(1) 996 val |= v 997 gcdebug |= v 998 999 if gcdebug: 1000 gc.set_debug(gcdebug) 1001 1002 if BUILD: 1003 # Python 2.3 is more sane in its non -q output 1004 if sys.hexversion >= 0x02030000: 1005 qflag = "" 1006 else: 1007 qflag = "-q" 1008 cmd = sys.executable + " setup.py " + qflag + " build" 1009 if BUILD_INPLACE: 1010 cmd += "_ext -i" 1011 if VERBOSE: 1012 print cmd 1013 sts = os.system(cmd) 1014 if sts: 1015 print "Build failed", hex(sts) 1016 sys.exit(1) 1017 1018 k = [] 1019 if RUN_UNIT: 1020 k.append(False) 1021 if RUN_FUNCTIONAL: 1022 k.append(True) 1023 1024 global functional 1025 for functional in k: 1026 1027 if VERBOSE: 1028 kind = functional and "FUNCTIONAL" or "UNIT" 1029 if LEVEL == 0: 1030 print "Running %s tests at all levels" % kind 1031 else: 1032 print "Running %s tests at level %d" % (kind, LEVEL) 1033 1034# This was to avoid functional tests outside of z3, but this doesn't really 1035# work right. 1036## if functional: 1037## try: 1038## from zope.app.tests.functional import FunctionalTestSetup 1039## except ImportError: 1040## raise 1041## print ('Skipping functional tests: could not import ' 1042## 'zope.app.tests.functional') 1043## continue 1044 1045 # XXX We want to change *visible* warnings into errors. The next 1046 # line changes all warnings into errors, including warnings we 1047 # normally never see. In particular, test_datetime does some 1048 # short-integer arithmetic that overflows to long ints, and, by 1049 # default, Python doesn't display the overflow warning that can 1050 # be enabled when this happens. The next line turns that into an 1051 # error instead. Guido suggests that a better to get what we're 1052 # after is to replace warnings.showwarning() with our own thing 1053 # that raises an error. 1054 ## warnings.filterwarnings("error") 1055 warnings.filterwarnings("ignore", module="logging") 1056 1057 if args: 1058 if len(args) > 1: 1059 TEST_FILTER = args[1] 1060 MODULE_FILTER = args[0] 1061 try: 1062 if TRACE: 1063 # if the trace module is used, then we don't exit with 1064 # status if on a false return value from main. 1065 coverdir = os.path.join(os.getcwd(), "coverage") 1066 import trace 1067 ignoremods = ["os", "posixpath", "stat"] 1068 tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix], 1069 ignoremods=ignoremods, 1070 trace=False, count=True) 1071 1072 tracer.runctx("main(MODULE_FILTER, TEST_FILTER, LIBDIR)", 1073 globals=globals(), locals=vars()) 1074 r = tracer.results() 1075 path = "/tmp/trace.%s" % os.getpid() 1076 import cPickle 1077 f = open(path, "wb") 1078 cPickle.dump(r, f) 1079 f.close() 1080 print path 1081 r.write_results(show_missing=True, 1082 summary=True, coverdir=coverdir) 1083 else: 1084 bad = main(MODULE_FILTER, TEST_FILTER, LIBDIR) 1085 if bad: 1086 sys.exit(1) 1087 except ImportError, err: 1088 print err 1089 print sys.path 1090 raise 1091 1092 1093if __name__ == "__main__": 1094 process_args() 1095