[Zope-Checkins] CVS: Zope3 - test.py:1.1.2.1

Jeremy Hylton jeremy@zope.com
Wed, 13 Feb 2002 18:32:09 -0500


Update of /cvs-repository/Zope3
In directory cvs.zope.org:/tmp/cvs-serv10245

Added Files:
      Tag: Zope-3x-branch
	test.py 
Log Message:
A test script based on the StandaloneZODB test script.


=== Added File Zope3/test.py ===
"""
test.py [-bdvvL] [modfilter [testfilter]]

Test harness for StandbyStorage.

-b  build
    Run "python setup.py -q build" before running tests, where "python"
    is the version of python used to run test.py.  Highly recommended.

-d  debug
    Instead of the normal test harness, run a debug version which
    doesn't catch any exceptions.  This is occasionally handy when the
    unittest code catching the exception doesn't work right.
    Unfortunately, the debug harness doesn't print the name of the
    test, so Use With Care.

-v  verbose
    With one -v, unittest prints a dot (".") for each test run.  With
    -vv, unittest prints the name of each test (for some definition of
    "name" ...).  Witn no -v, unittest is silent until the end of the
    run, except when errors occur.

-L  Loop
    Keep running the selected tests in a loop.  You may experience
    memory leakage.

modfilter
testfilter
    Case-sensitive regexps to limit which tests are run, used in search
    (not match) mode.
    In an extension of Python regexp notation, a leading "!" is stripped
    and causes the sense of the remaining regexp to be negated (so "!bc"
    matches any string that does not match "bc", and vice versa).
    By default these act like ".", i.e. nothing is excluded.

    modfilter is applied to a test file's path, starting at "build" and
    including (OS-dependent) path separators.

    testfilter is applied to the (method) name of the unittest methods
    contained in the test files whose paths modfilter matched.

Extreme (yet useful) examples:

    test.py -vvb . "^checkWriteClient$"

    Builds the project silently, then runs unittest in verbose mode on all
    tests whose names are precisely "checkWriteClient".  Useful when
    debugging a specific test.

    test.py -vvb . "!^checkWriteClient$"

    As before, but runs all tests whose names aren't precisely
    "checkWriteClient".  Useful to avoid a specific failing test you don't
    want to deal with just yet.
"""


import os
import re
import sys
import traceback
import unittest

from distutils.util import get_platform

class ImmediateTestResult(unittest._TextTestResult):

    def _print_traceback(self, msg, err, test, errlist):
        if self.showAll or self.dots:
            self.stream.writeln("\n")

        tb = ''.join(traceback.format_exception(*err))
        self.stream.writeln(msg)
        self.stream.writeln(tb)
        errlist.append((test, tb))

    def addError(self, test, err):
        self._print_traceback("Error in test %s" % test, err,
                              test, self.errors)

    def addFailure(self, test, err):
        self._print_traceback("Failure in test %s" % test, err,
                              test, self.failures)

    def printErrorList(self, flavor, errors):
        for test, err in errors:
            self.stream.writeln(self.separator1)
            self.stream.writeln("%s: %s" % (flavor, self.getDescription(test)))
            self.stream.writeln(self.separator2)
            self.stream.writeln(err)


class ImmediateTestRunner(unittest.TextTestRunner):

    def _makeResult(self):
        return ImmediateTestResult(self.stream, self.descriptions,
                                   self.verbosity)

def check_spread():
    try:
        import spread
    except ImportError, msg:
        print "ImportError:", msg
        print "Can't import spread.  Forget about running tests."
        sys.exit(1)
    try:
        mbox = spread.connect()
        mbox.disconnect()
    except spread.error, msg:
        print "spread.error:", msg
        print "Can't use spread.  Forget about running tests."
        sys.exit(1)

# setup list of directories to put on the path

def setup_path():
    DIRS = ["lib/python",
            "lib/python/ZopeLegacy",
            "lib/python/ZopeLegacy/ExtensionClass",
            ]
    cwd = os.getcwd()
    for d in DIRS:
        sys.path.insert(0, os.path.join(cwd, d))

def match(rx, s):
    if not rx:
        return 1
    if rx[0] == '!':
        return re.search(rx[1:], s) is None
    else:
        return re.search(rx, s) is not None

class TestFileFinder:
    def __init__(self):
        self.files = []

    def visit(self, rx, dir, files):
        if dir[-5:] != "tests":
            return
        # ignore tests in ZopeLegacy directory
        if dir.startswith('lib/python/ZopeLegacy'):
            return
        # ignore tests that aren't in packages
        if not "__init__.py" in files:
            print "not a package", dir
            return
        for file in files:
            if file[:4] == "test" and file[-3:] == ".py":
                path = os.path.join(dir, file)
                if match(rx, path):
                    self.files.append(path)

def find_tests(rx):
    finder = TestFileFinder()
    os.path.walk("lib/python", finder.visit, rx)
    return finder.files

def package_import(modname):
    mod = __import__(modname)
    for part in modname.split(".")[1:]:
        mod = getattr(mod, part)
    return mod

def module_from_path(path):
    """Return the Python package name indiciated by the filesystem path."""

    assert path.endswith('.py')
    path = path[:-3]
    dirs = []
    while path:
        path, end = os.path.split(path)
        dirs.insert(0, end)
    return ".".join(dirs[2:])

def get_suite(file):
    modname = module_from_path(file)
    try:
        mod = package_import(modname)
    except ImportError, err:
        # print traceback
        print "Error importing %s\n%s" % (modname, err)
        return None
    try:
        return mod.test_suite()
    except AttributeError:
        print "No test_suite() in %s" % file

def filter_testcases(s, rx):
    new = unittest.TestSuite()
    for test in s._tests:
        if isinstance(test, unittest.TestCase):
            name = test.id() # Full test name: package.module.class.method
            name = name[1 + name.rfind('.'):] # extract method name
            if match(rx, name):
                new.addTest(test)
        else:
            filtered = filter_testcases(test, rx)
            if filtered:
                new.addTest(filtered)
    return new

def runner(files, test_filter, debug):
    runner = ImmediateTestRunner(verbosity=VERBOSE)
    suite = unittest.TestSuite()
    for file in files:
        s = get_suite(file)
        if s is not None:
            if test_filter is not None:
                s = filter_testcases(s, test_filter)
            suite.addTest(s)
    if debug:
        suite.debug()
        return 0
    r = runner.run(suite)
    return len(r.errors) + len(r.failures)

def main(module_filter, test_filter):
    setup_path()
    files = find_tests(module_filter)
    files.sort()

    if LOOP:
        while 1:
            runner(files, test_filter, debug)
    else:
        runner(files, test_filter, debug)

if __name__ == "__main__":
    import getopt

    module_filter = None
    test_filter = None
    VERBOSE = 0
    LOOP = 0
    debug = 0 # Don't collect test results; simply let tests crash
    build = 0

    try:
        opts, args = getopt.getopt(sys.argv[1:], 'vdLbh')
    except getopt.error, msg:
        print msg
        print "Try `python %s -h' for more information." % sys.argv[0]
        sys.exit(2)

    for k, v in opts:
        if k == '-v':
            VERBOSE = VERBOSE + 1
        elif k == '-d':
            debug = 1
        elif k == '-L':
            LOOP = 1
        elif k == '-b':
            build = 1
        elif k == '-h':
            print __doc__
            sys.exit(0)

    if build:
        cmd = sys.executable + " setup.py -q build"
        if VERBOSE:
            print cmd
        sts = os.system(cmd)
        if sts:
            print "Build failed", hex(sts)
            sys.exit(1)

    if args:
        if len(args) > 1:
            test_filter = args[1]
        module_filter = args[0]
    try:
        bad = main(module_filter, test_filter)
        if bad:
            sys.exit(1)
    except ImportError, err:
        print err
        print sys.path
        raise