source: trunk/packages/xen-3.1/xen-3.1/tools/python/test.py @ 34

Last change on this file since 34 was 34, checked in by hartmans, 18 years ago

Add xen and xen-common

  • Property svn:mime-type set to text/script
File size: 36.1 KB
Line 
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()
Note: See TracBrowser for help on using the repository browser.