[Zope-Checkins] CVS: Zope - test.py:1.1.2.1
Jim Fulton
jim@zope.com
Wed, 17 Jul 2002 13:27:42 -0400
Update of /cvs-repository/Zope
In directory cvs.zope.org:/tmp/cvs-serv13357
Added Files:
Tag: Zope-2_7-development-branch
test.py
Log Message:
Refactored the way Zope 2 tests are done to work with Python 2.2, in
preparation for Zope 2.7:
- Turned all test directories into packages by adding __init__.py
files.
- Fixed up some tests that either counted on being run as scripts or
depended on deep magic performed by the old testrunner script.
- Removed the old testrunner script because:
o It caused tests to fail in strange and mysterious ways under
Python 2.2
o It output tracebacks that showed only file names, not paths. This
made it really painful to find the tests that failed.
- Copied the test.py from Zope 3 into the root directory. This script
works great. It makes it easy to run all the tests, or just the
tests in a package. It has a number of options, not all of which
have been tested with Zope 2. I suspect that the build option
doesn't work.
A remaining issue is that setup.py leaves behind a build directory
that confuses the test script. I need to look into this. For now, just
delete the build directory before running the tests.
=== Added File Zope/test.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
test.py [-bdvvL] [modfilter [testfilter]]
Test harness.
-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.
-g threshold
Set the garbage collector generation0 threshold. This can be used to
stress memory and gc correctness. Some crashes are only reproducible when
the threshold is set to 1 (agressive garbage collection). Do "-g 0" to
disable garbage collection altogether.
-G flag
Set garbage collector debug flag. The flag argument should be the name
of a DEBUG_ attribute of the gc module. This argument can be repeated.
-u Use unittestgui
-m Use unittestgui; start minimized
Use the PyUnit GUI instead of output to the command line. The GUI
imports tests on its own, taking care to reload all dependencies on each
run. The debug (-d), verbose (-v), and Loop (-L) options will be
ignored. The testfilter filter is also not applied.
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.
test.py -m . "!^checkWriteClient$"
As before, but now opens up a minimized PyUnit GUI window (only showing
the progressbar). Double-clicking the progressbar will start the import
and run all tests. Useful for refactoring runs where you continually
want to make sure all tests still pass.
"""
import os
import re
import sys
import traceback
import unittest
import linecache
from os.path import join
from distutils.util import get_platform
# We know we're going to need this so import it now. Python 2.2 does not come
# with the pyexpat library by default, although Python 2.3 will.
try:
import pyexpat
except ImportError:
print >> sys.stderr, "WARNING: the pyexpat module is required"
raise
class ImmediateTestResult(unittest._TextTestResult):
__super_init = unittest._TextTestResult.__init__
def __init__(self, *args, **kwarg):
debug = kwarg.get('debug')
if debug is not None:
del kwarg['debug']
self.__super_init(*args, **kwarg)
self._debug = debug
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):
if self._debug:
raise err[0], err[1], err[2]
self._print_traceback("Error in test %s" % test, err,
test, self.errors)
def addFailure(self, test, err):
if self._debug:
raise err[0], err[1], err[2]
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):
__super_init = unittest.TextTestRunner.__init__
def __init__(self, **kwarg):
debug = kwarg.get('debug')
if debug is not None:
del kwarg['debug']
self.__super_init(**kwarg)
self._debug = debug
def _makeResult(self):
return ImmediateTestResult(self.stream, self.descriptions,
self.verbosity, debug=self._debug)
# setup list of directories to put on the path
def setup_path():
DIRS = [join('lib','python'),
]
cwd = os.getcwd()
for d in DIRS:
sys.path.insert(0, 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 that aren't in packages
if not "__init__.py" in files:
if not files or files == ['CVS']:
return
print "not a package", dir
return
for file in files:
if file[:4] == "test" and file[-3:] == ".py":
path = join(dir, file)
if match(rx, path):
self.files.append(path)
def find_tests(rx):
finder = TestFileFinder()
os.path.walk(join('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)
print_tb_last()
print
if debug:
raise
return None
try:
suite_func = mod.test_suite
except AttributeError:
print "No test_suite() in %s" % file
return None
return suite_func()
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 gui_runner(files, test_filter):
sys.path.insert(0, join(os.getcwd(), 'utilities'))
import unittestgui
suites = []
for file in files:
suites.append(module_from_path(file) + '.test_suite')
suites = ", ".join(suites)
minimal = (GUI == 'minimal')
unittestgui.main(suites, minimal)
def runner(files, test_filter, debug):
runner = ImmediateTestRunner(verbosity=VERBOSE, debug=debug)
suite = unittest.TestSuite()
for file in files:
s = get_suite(file)
if test_filter is not None:
s = filter_testcases(s, test_filter)
suite.addTest(s)
r = runner.run(suite)
def remove_stale_bytecode(arg, dirname, names):
names = map(os.path.normcase, names)
for name in names:
if name.endswith(".pyc") or name.endswith(".pyo"):
srcname = name[:-1]
if srcname not in names:
fullname = os.path.join(dirname, name)
print "Removing stale bytecode file", fullname
os.unlink(fullname)
def main(module_filter, test_filter):
os.path.walk(os.curdir, remove_stale_bytecode, None)
setup_path()
files = find_tests(module_filter)
files.sort()
if GUI:
gui_runner(files, test_filter)
elif LOOP:
while 1:
runner(files, test_filter, debug)
else:
runner(files, test_filter, debug)
def process_args():
import getopt
global module_filter
global test_filter
global VERBOSE
global LOOP
global GUI
global debug
global build
global gcthresh
module_filter = None
test_filter = None
VERBOSE = 0
LOOP = 0
GUI = 0
debug = 0 # Don't collect test results; simply let tests crash
build = 0
gcthresh = None
gcflags = []
try:
opts, args = getopt.getopt(sys.argv[1:], 'vdLbhCumg:G:',
['help'])
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 += 1
elif k == '-d':
debug = 1
elif k == '-L':
LOOP = 1
elif k == '-b':
build = 1
elif k in ('-h', '--help'):
print __doc__
sys.exit(0)
elif k == '-C':
import pychecker.checker
elif k == '-g':
gcthresh = int(v)
elif k == '-u':
GUI = 1
elif k == '-m':
GUI = 'minimal'
elif k == '-G':
if not v.startswith("DEBUG_"):
print "-G argument must be DEBUG_ flag, not", repr(v)
sys.exit(1)
gcflags.append(v)
if gcthresh is not None:
import gc
gc.set_threshold(gcthresh)
print 'gc threshold:', gc.get_threshold()
if gcflags:
import gc
val = 0
for flag in gcflags:
v = getattr(gc, flag, None)
if v is None:
print "Unknown gc flag", repr(flag)
print gc.set_debug.__doc__
sys.exit(1)
val |= v
gc.set_debug(v)
if build:
cmd = sys.executable + " stupid_build.py"
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
def print_tb_last():
"""Print up to 'limit' stack trace entries from the traceback 'tb'.
If 'limit' is omitted or None, all entries are printed. If 'file'
is omitted or None, the output goes to sys.stderr; otherwise
'file' should be an open file or file-like object with a write()
method.
"""
tb = sys.exc_info()[2]
file = sys.stderr
while 1:
f = tb.tb_frame
lineno = traceback.tb_lineno(tb)
tb = tb.tb_next
if tb is not None:
continue
co = f.f_code
filename = co.co_filename
name = co.co_name
file.write(' File "%s", line %d, in %s\n' % (filename,lineno,name))
line = linecache.getline(filename, lineno)
if line: file.write(' %s\n' % line.strip())
break
if __name__ == "__main__":
process_args()