Commit 14e11016 authored by Fred Drake's avatar Fred Drake

Lots of really small changes.

parent e2b6f25b
#! /usr/bin/env python2.2
"""testrunner - a Zope test suite utility. """testrunner - a Zope test suite utility.
The testrunner utility is used to execute PyUnit test suites. You can find The testrunner utility is used to execute PyUnit test suites. This utility
more information on PyUnit at http://pyunit.sourceforge.net. This utility should be run from the root of your Zope source directory. It will set up the
should be run from the root of your Zope installation. It will set up the correct python path environment based on your source directory so that
correct python path environment based on your installation directory so that
test suites can import Zope modules in a way that is fairly independent of test suites can import Zope modules in a way that is fairly independent of
the location of the test suite. It does *not* import the Zope package, so the location of the test suite. It does *not* import the Zope package, so
a test thats depend on dynamic aspects of the Zope environment (such as a test thats depend on dynamic aspects of the Zope environment (such as
...@@ -13,14 +13,18 @@ test suite. ...@@ -13,14 +13,18 @@ test suite.
Testrunner will look for and execute test suites that follow some simple Testrunner will look for and execute test suites that follow some simple
conventions. Test modules should have a name prefixed with 'test', such as conventions. Test modules should have a name prefixed with 'test', such as
'testMyModule.py', and test modules are expected to define a module function 'testMyModule.py', and test modules are expected to define a module function
named 'test_suite' that returns a PyUnit TestSuite object. By convention, named 'test_suite' that returns a TestSuite object. By convention,
we put test suites in 'tests' subdirectories of the packages they test. we put test modules in a 'tests' sub-package of the package they test.
Testrunner is used to run all checked in test suites before (final) releases Testrunner is used to run all checked in test suites before (final) releases
are made, and can be used to quickly run a particular suite or all suites in are made, and can be used to quickly run a particular suite or all suites in
a particular directory.""" a particular directory."""
import sys, os, imp, string, getopt, traceback import getopt
import imp
import os
import sys
import traceback
import unittest import unittest
VERBOSE = 2 VERBOSE = 2
...@@ -28,30 +32,31 @@ VERBOSE = 2 ...@@ -28,30 +32,31 @@ VERBOSE = 2
class TestRunner: class TestRunner:
"""Test suite runner""" """Test suite runner"""
def __init__(self, basepath, verbosity=VERBOSE, results=[], mega_suite=0): def __init__(self, path, verbosity, mega_suite):
# initialize python path self.basepath = path
self.basepath=path=basepath
self.verbosity = verbosity self.verbosity = verbosity
self.results = results self.results = []
self.mega_suite = mega_suite self.mega_suite = mega_suite
pjoin=os.path.join # initialize python path
pjoin = os.path.join
if sys.platform == 'win32': if sys.platform == 'win32':
sys.path.insert(0, pjoin(path, 'lib/python')) newpaths = [pjoin(path, 'lib', 'python'),
sys.path.insert(1, pjoin(path, 'bin/lib')) pjoin(path, 'bin', 'lib'),
sys.path.insert(2, pjoin(path, 'bin/lib/plat-win')) pjoin(path, 'bin', 'lib', 'plat-win'),
sys.path.insert(3, pjoin(path, 'bin/lib/win32')) pjoin(path, 'bin', 'lib', 'win32'),
sys.path.insert(4, pjoin(path, 'bin/lib/win32/lib')) pjoin(path, 'bin', 'lib', 'win32', 'lib'),
sys.path.insert(5, path) path]
else: else:
sys.path.insert(0, pjoin(path, 'lib/python')) newpaths = [pjoin(path, 'lib', 'python'),
sys.path.insert(1, path) path]
sys.path[:0] = newpaths
def getSuiteFromFile(self, filepath): def getSuiteFromFile(self, filepath):
if not os.path.isfile(filepath): if not os.path.isfile(filepath):
raise ValueError, '%s is not a file' % filepath raise ValueError, '%s is not a file' % filepath
path, filename=os.path.split(filepath) path, filename = os.path.split(filepath)
name, ext=os.path.splitext(filename) name, ext = os.path.splitext(filename)
file, pathname, desc=imp.find_module(name, [path]) file, pathname, desc = imp.find_module(name, [path])
saved_syspath = sys.path[:] saved_syspath = sys.path[:]
module = None module = None
try: try:
...@@ -64,7 +69,7 @@ class TestRunner: ...@@ -64,7 +69,7 @@ class TestRunner:
(tb_t, tb_v, tb_tb) = sys.exc_info() (tb_t, tb_v, tb_tb) = sys.exc_info()
self.report("Module %s failed to load\n%s: %s" % (pathname, self.report("Module %s failed to load\n%s: %s" % (pathname,
tb_t, tb_v)) tb_t, tb_v))
self.report(string.join(traceback.format_tb(tb_tb)) + '\n') self.report(''.join(traceback.format_tb(tb_tb)) + '\n')
del tb_tb del tb_tb
finally: finally:
file.close() file.close()
...@@ -75,76 +80,77 @@ class TestRunner: ...@@ -75,76 +80,77 @@ class TestRunner:
return None return None
return function() return function()
def smellsLikeATest(self, filepath, find=string.find): def smellsLikeATest(self, filepath):
path, name = os.path.split(filepath) path, name = os.path.split(filepath)
fname, ext = os.path.splitext(name) fname, ext = os.path.splitext(name)
if name[:4]=='test' and name[-3:]=='.py' and \ if ( name[:4] == 'test'
name != 'testrunner.py': and name[-3:] == '.py'
and name != 'testrunner.py'):
file=open(filepath, 'r') file = open(filepath, 'r')
lines=file.readlines() lines = file.readlines()
file.close() file.close()
for line in lines: for line in lines:
if (find(line, 'def test_suite(') > -1) or \ if (line.find('def test_suite(') > -1) or \
(find(line, 'framework(') > -1): (line.find('framework(') > -1):
return 1 return True
return 0 return False
def runSuite(self, suite): def runSuite(self, suite):
if suite: if suite:
runner=unittest.TextTestRunner(stream=sys.stderr, runner = self.getTestRunner()
verbosity=self.verbosity)
self.results.append(runner.run(suite)) self.results.append(runner.run(suite))
else: else:
self.report('No suitable tests found') self.report('No suitable tests found')
_runner = None
def getTestRunner(self):
if self._runner is None:
self._runner = self.createTestRunner()
return self._runner
def createTestRunner(self):
return unittest.TextTestRunner(stream=sys.stderr,
verbosity=self.verbosity)
def report(self, message): def report(self, message):
sys.stderr.write( '%s\n' % message ) print >>sys.stderr, message
def runAllTests(self): def runAllTests(self):
"""Run all tests found in the current working directory and """Run all tests found in the current working directory and
all subdirectories.""" all subdirectories."""
self.runPath(self.basepath) self.runPath(self.basepath)
def listTestableNames( self, pathname ): def listTestableNames(self, pathname):
""" """Return a list of the names to be traversed to build tests."""
Return a list of the names to be traversed to build tests.
"""
names = os.listdir(pathname) names = os.listdir(pathname)
if "build" in names: if "build" in names:
# Don't recurse into build directories created by setup.py # Don't recurse into build directories created by setup.py
names.remove("build") names.remove("build")
if '.testinfo' in names: # allow local control if '.testinfo' in names: # allow local control
f = open( os.path.join( pathname, '.testinfo' ) ) f = open(os.path.join(pathname, '.testinfo'))
lines = filter( None, f.readlines() ) lines = filter(None, f.readlines())
lines = map( lambda x: x[-1]=='\n' and x[:-1] or x, lines ) lines = map(lambda x: x[-1]=='\n' and x[:-1] or x, lines)
names = filter( lambda x: x and x[0] != '#', lines ) names = filter(lambda x: x and x[0] != '#', lines)
f.close() f.close()
return names return names
def extractSuite( self, pathname ): def extractSuite(self, pathname):
""" """Extract and return the appropriate test suite."""
Extract and return the appropriate test suite. if os.path.isdir(pathname):
"""
if os.path.isdir( pathname ):
suite = unittest.TestSuite() suite = unittest.TestSuite()
for name in self.listTestableNames(pathname):
for name in self.listTestableNames( pathname ): fullpath = os.path.join(pathname, name)
sub_suite = self.extractSuite(fullpath)
fullpath = os.path.join( pathname, name )
sub_suite = self.extractSuite( fullpath )
if sub_suite: if sub_suite:
suite.addTest( sub_suite ) suite.addTest(sub_suite)
return suite.countTestCases() and suite or None return suite.countTestCases() and suite or None
elif self.smellsLikeATest( pathname ): elif self.smellsLikeATest(pathname):
dirname, name = os.path.split(pathname)
working_dir = os.getcwd() working_dir = os.getcwd()
try: try:
dirname, name = os.path.split(pathname)
if dirname: if dirname:
os.chdir(dirname) os.chdir(dirname)
try: try:
...@@ -159,11 +165,10 @@ class TestRunner: ...@@ -159,11 +165,10 @@ class TestRunner:
suite = None suite = None
finally: finally:
os.chdir(working_dir) os.chdir(working_dir)
return suite return suite
else: # no test there! else:
# no test there!
return None return None
def runPath(self, pathname): def runPath(self, pathname):
...@@ -173,11 +178,11 @@ class TestRunner: ...@@ -173,11 +178,11 @@ class TestRunner:
pathname = os.path.join(self.basepath, pathname) pathname = os.path.join(self.basepath, pathname)
if self.mega_suite: if self.mega_suite:
suite = self.extractSuite( pathname ) suite = self.extractSuite(pathname)
self.runSuite( suite ) self.runSuite(suite)
else: else:
for name in self.listTestableNames(pathname): for name in self.listTestableNames(pathname):
fullpath=os.path.join(pathname, name) fullpath = os.path.join(pathname, name)
if os.path.isdir(fullpath): if os.path.isdir(fullpath):
self.runPath(fullpath) self.runPath(fullpath)
elif self.smellsLikeATest(fullpath): elif self.smellsLikeATest(fullpath):
...@@ -189,22 +194,22 @@ class TestRunner: ...@@ -189,22 +194,22 @@ class TestRunner:
dirname, name = os.path.split(filename) dirname, name = os.path.split(filename)
if dirname: if dirname:
if self.verbosity > 2: if self.verbosity > 2:
sys.stderr.write('*** Changing directory to: %s\n' % dirname) print >>sys.stderr, '*** Changing directory to:', dirname
os.chdir(dirname) os.chdir(dirname)
self.report('Running: %s' % filename) self.report('Running: %s' % filename)
try: try:
suite=self.getSuiteFromFile(name) suite = self.getSuiteFromFile(name)
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except: except:
traceback.print_exc() traceback.print_exc()
suite=None suite = None
if suite is not None: if suite is not None:
self.runSuite(suite) self.runSuite(suite)
else: else:
self.report('No test suite found in file:\n%s\n' % filename) self.report('No test suite found in file:\n%s\n' % filename)
if self.verbosity > 2: if self.verbosity > 2:
sys.stderr.write('*** Restoring directory to: %s\n' % working_dir) print >>sys.stderr, '*** Restoring directory to:', working_dir
os.chdir(working_dir) os.chdir(working_dir)
def remove_stale_bytecode(arg, dirname, names): def remove_stale_bytecode(arg, dirname, names):
...@@ -218,8 +223,7 @@ def remove_stale_bytecode(arg, dirname, names): ...@@ -218,8 +223,7 @@ def remove_stale_bytecode(arg, dirname, names):
os.unlink(fullname) os.unlink(fullname)
def main(args): def main(args):
usage_msg = """Usage: python testrunner.py options
usage_msg="""Usage: python testrunner.py options
If run without options, testrunner will display this usage If run without options, testrunner will display this usage
message. If you want to run all test suites found in all message. If you want to run all test suites found in all
...@@ -229,41 +233,33 @@ def main(args): ...@@ -229,41 +233,33 @@ def main(args):
options: options:
-a -a
Run all tests found in all subdirectories of the current Run all tests found in all subdirectories of the current
working directory. working directory.
-m -m
Run all tests in a single, giant suite (consolidates error Run all tests in a single, giant suite (consolidates error
reporting). [default] reporting). [default]
-M -M
Run each test file's suite separately (noisier output, may Run each test file's suite separately (noisier output, may
help in isolating global effects later). help in isolating global effects later).
-p -p
Add 'lib/python' to the Python search path. [default] Add 'lib/python' to the Python search path. [default]
-P -P
*Don't* add 'lib/python' to the Python search path. *Don't* add 'lib/python' to the Python search path.
-d dirpath -d dirpath
Run all tests found in the directory specified by dirpath, Run all tests found in the directory specified by dirpath,
and recursively in all its subdirectories. The dirpath and recursively in all its subdirectories. The dirpath
should be a full system path. should be a full system path.
-f filepath -f filepath
Run the test suite found in the file specified. The filepath Run the test suite found in the file specified. The filepath
should be a fully qualified path to the file to be run. should be a fully qualified path to the file to be run.
-v level -v level
Set the Verbosity level to level. Newer versions of Set the Verbosity level to level. Newer versions of
unittest.py allow more options than older ones. Allowed unittest.py allow more options than older ones. Allowed
values are: values are:
...@@ -273,7 +269,6 @@ def main(args): ...@@ -273,7 +269,6 @@ def main(args):
2 - Verbose (default - produces a line of output for each test) 2 - Verbose (default - produces a line of output for each test)
-q -q
Run tests without producing verbose output. The tests are Run tests without producing verbose output. The tests are
normally run in verbose mode, which produces a line of normally run in verbose mode, which produces a line of
output for each test that includes the name of the test and output for each test that includes the name of the test and
...@@ -281,67 +276,62 @@ def main(args): ...@@ -281,67 +276,62 @@ def main(args):
running with -v1. running with -v1.
-o filename -o filename
Output test results to the specified file rather than Output test results to the specified file rather than
to stderr. to stderr.
-h -h
Display usage information. Display usage information.
""" """
pathname=None pathname = None
filename=None filename = None
test_all=None test_all = False
verbosity = VERBOSE verbosity = VERBOSE
mega_suite = 1 mega_suite = True
set_python_path = 1 set_python_path = True
options, arg=getopt.getopt(args, 'amPhd:f:v:qMo:') options, arg = getopt.getopt(args, 'amPhd:f:v:qMo:')
if not options: if not options:
err_exit(usage_msg) err_exit(usage_msg)
for name, value in options: for name, value in options:
name=name[1:] if name == '-a':
if name == 'a': test_all = True
test_all=1 elif name == '-m':
elif name == 'm': mega_suite = True
mega_suite = 1 elif name == '-M':
elif name == 'M': mega_suite = False
mega_suite = 0 elif name == '-p':
elif name == 'p': set_python_path = True
set_python_path = 1 elif name == '-P':
elif name == 'P': set_python_path = False
set_python_path = 0 elif name == '-d':
elif name == 'd': pathname = value.strip()
pathname=string.strip(value) elif name == '-f':
elif name == 'f': filename = value.strip()
filename=string.strip(value) elif name == '-h':
elif name == 'h':
err_exit(usage_msg, 0) err_exit(usage_msg, 0)
elif name == 'v': elif name == '-v':
verbosity = int(value) verbosity = int(value)
elif name == 'q': elif name == '-q':
verbosity = 1 verbosity = 1
elif name == 'o': elif name == '-o':
f = open(value,'w') f = open(value, 'w')
sys.stderr = f sys.stderr = f
else: else:
err_exit(usage_msg) err_exit(usage_msg)
os.path.walk(os.curdir, remove_stale_bytecode, None) os.path.walk(os.curdir, remove_stale_bytecode, None)
testrunner = TestRunner( os.getcwd() testrunner = TestRunner(os.getcwd(), verbosity, mega_suite)
, verbosity=verbosity
, mega_suite=mega_suite)
if set_python_path: if set_python_path:
script = sys.argv[0] script = sys.argv[0]
script_dir = os.path.split( os.path.abspath( script ) )[0] script_dir = os.path.dirname(os.path.abspath(script))
zope_dir = os.path.abspath( os.path.join( script_dir, '..' ) ) zope_dir = os.path.dirname(script_dir)
sw_home = os.path.join( zope_dir, 'lib', 'python' ) sw_home = os.path.join(zope_dir, 'lib', 'python')
if verbosity > 1: if verbosity > 1:
testrunner.report( "Adding %s to sys.path." % sw_home ) testrunner.report("Adding %s to sys.path." % sw_home)
sys.path.insert( 0, sw_home ) sys.path.insert(0, sw_home)
os.environ['SOFTWARE_HOME'] = sw_home os.environ['SOFTWARE_HOME'] = sw_home
try: try:
...@@ -358,7 +348,6 @@ def main(args): ...@@ -358,7 +348,6 @@ def main(args):
elif filename: elif filename:
testrunner.runFile(filename) testrunner.runFile(filename)
## Report overall errors / failures if there were any ## Report overall errors / failures if there were any
fails = reduce(lambda x, y: x + len(y.failures), testrunner.results, 0) fails = reduce(lambda x, y: x + len(y.failures), testrunner.results, 0)
errs = reduce(lambda x, y: x + len(y.errors), testrunner.results, 0) errs = reduce(lambda x, y: x + len(y.errors), testrunner.results, 0)
...@@ -368,7 +357,8 @@ def main(args): ...@@ -368,7 +357,8 @@ def main(args):
if fails: if fails:
msg += "total failures=%d" % fails msg += "total failures=%d" % fails
if errs: if errs:
if fails: msg += ", " if fails:
msg += ", "
msg += "total errors=%d" % errs msg += "total errors=%d" % errs
msg += ")" msg += ")"
err_exit(msg, 1) err_exit(msg, 1)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment