########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Lib/TestSuite/TestSuite.py,v 1.13 2006/08/11 15:50:12 jkloth Exp $
"""
Provides the TestSuite class, which represents the package(s) to test.
Copyright 2006 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""
__revision__ = "$Id: TestSuite.py,v 1.13 2006/08/11 15:50:12 jkloth Exp $"
import getopt, sys, os, inspect
from types import *
import TestLoader, TestModule, Tester
from Errors import *
USAGE = """\
Usage:
%(script)s [options] [test] [...]
%(script)s --help-tests
%(script)s --help
%(script)s --help [test]
Examples:
%(script)s run default set of tests
%(script)s directory run all tests in 'directory'
%(script)s directory%(sep)sfile run just 'file' from 'directory'
"""
class TestSuite:
"""
A command-line program that runs a set of tests; this is primarily
for making test modules conveniently executable.
"""
options = [
('help', 'h', 'Show detailed help message'),
('help-tests', 't', 'List all available tests'),
('verbose', 'v', 'Increase display verbosity'),
('quiet', 'q', 'Decrease display verbosity'),
('mode=', 'm', 'Add mode to default modes to run'),
('skip=', 'k', 'Remove a mode from the modes to run'),
('full', 'f', 'Use all modes'),
('stop', 's', 'Stop on errors'),
('nocolor', 'n', 'Disable ANSI color sequences'),
('noreport', 'r', 'Disable report generation'),
('outfile=', 'o', 'Specify an output file for all results'),
('offline', 'l', 'Skip tests requiring internet connection')
]
negative_opts = {'quiet' : 'verbose',
'nocolor' : 'color',
'noreport' : 'report',
}
# options that can only be true or false
boolean_opts = ('full', 'stop', 'nocolor', 'noreport', 'offline',
'help', 'help-tests')
def __init__(self, attrs):
# Default values for command-line options
self.verbose = 2 # counter
self.mode = [] # string list
self.skip = [] # string list
self.full = 0 # boolean
self.stop = 0 # boolean
self.color = 1 # boolean
self.report = 1 # boolean
self.outfile = '' # string
self.offline = 0 # boolean
self.help = 0 # boolean
self.help_tests = 0 # boolean
# 'script_name' and 'script_args' are usually set to sys.argv[0]
# and sys.argv[1:], but they can be overridden when the caller is
# not necessarily a setup script run from the command-line.
self.script_name = None
self.script_args = None
self.name = None
self.packages = None
# Now we'll use the attrs dictionary (ultimately, keyword args from
# the setup script) to possibly override any or all of these
# distribution options.
for key, value in attrs.items():
if hasattr(self, key):
setattr(self, key, value)
else:
raise TestSuiteSetupError('invalid test option %r' % key)
return
def _grokOptions(self):
long_opts = []
short_opts = []
short2long = {}
for option in self.options:
try:
(long, short, help) = option
except ValueError:
raise TestSuiteInternalError, \
'invalid option tuple %r' % option
# Type and value check the option names
if not isinstance(long, StringType) or len(long) < 2:
raise TestSuiteInternalError, \
'invalid long option %r' % long
if short and not isinstance(short, StringType) and len(short) > 1:
raise TestSuiteInternalError, \
'invalid short option %r' % short
long_opts.append(long)
if short:
if long.endswith('='):
short = short + ':'
long = long[:-1]
short2long[short[0]] = long
short_opts.append(short)
return (''.join(short_opts), long_opts, short2long)
def _getopt(self, args):
short_opts, long_opts, short2long = self._grokOptions()
parsed_args = []
parsed_opts = []
while args:
try:
opts, args = getopt.getopt(args, short_opts, long_opts)
except getopt.error, error:
raise TestSuiteArgumentError(str(error))
# Convert all options to their long form
for opt, value in opts:
if len(opt) == 2 and opt[0] == '-':
# a short option
opt = short2long[opt[1]]
else:
# a long option
opt = opt[2:]
parsed_opts.append((opt, value))
# Scan the arguments for any remaining options
while args and args[0][:1] != '-':
arg = args.pop(0)
# If the arg is an existing file/directory, convert it to a
# Python package path, otherwise assume a valid package.
pathname = os.path.normpath(arg)
if os.path.isdir(pathname):
# check for package directory
source = pathname + os.sep + '__init__.py'
compiled = source + (__debug__ and 'c' or 'o')
if os.path.exists(source) or os.path.exists(compiled):
arg = pathname.replace(os.sep, '.')
elif os.path.exists(pathname):
# Convert path to pkg.mod notation
modulename = inspect.getmodulename(pathname)
if modulename is not None:
names = pathname.split(os.sep)
names[-1] = modulename
arg = '.'.join(names)
# Add Python package path to the parsed arguments
parsed_args.append(arg)
# Handle the options now
for (name, value) in parsed_opts:
if name in self.boolean_opts:
# If specified it must be true
value = 1
alias = self.negative_opts.get(name)
if alias:
attr = alias.replace('-', '_')
else:
attr = name.replace('-', '_')
if not hasattr(self, attr):
raise TestSuiteInternalError, \
"missing attribute for option %r" % name
current = getattr(self, attr)
if name in self.boolean_opts:
# boolean
if alias:
setattr(self, attr, 0)
else:
setattr(self, attr, 1)
elif isinstance(current, IntType):
# counter
if alias:
setattr(self, attr, current - 1)
else:
setattr(self, attr, current + 1)
elif isinstance(current, ListType):
# string list
if alias:
while value in current:
current.remove(value)
else:
current.append(value)
elif isinstance(current, StringType):
# string
setattr(self, attr, value)
else:
raise TestSuiteInternalError, \
"unknown type for option %r" % name
return parsed_args
def addTests(self, packages):
for package in packages:
testobj = self.test
for step in package.split('.'):
# See if this step has already been added
for test in testobj.tests:
if test.name == step:
# Found an existing test, reuse it
testobj = test
break
else:
# Create a new test
testobj = testobj.addTest(step)
return
def parseCommandLine(self):
"""
Parse the test script's command line, taken from the 'script_args'
instance attribute (which defaults to 'sys.argv[1:]'). This is
first processed for options that set attributes of the TestSuite
instance. Then, it is scanned for test arguments.
"""
packages = self._getopt(self.script_args)
self.test = TestLoader.TestLoader(self.name, '', self.mode,
self.skip, self.full)
if self.help_tests:
# ignore any supplied arguments
print self.generateUsage()
print 'Available tests:'
self.showTests()
return 0 # done
if self.help:
# display usage and if there are tests specified info about them
self.showHelp(packages)
return 0 # done
self.addTests(packages or self.packages)
# Signal caller that is OK to continue
return 1
def showTests(self):
indent = ' '*2
self.addTests(self.packages)
for test in self.test.getTests():
test.showTests(indent)
return
def generateUsage(self):
usage = USAGE % {'script' : os.path.basename(self.script_name),
'sep' : os.sep}
return usage
def showHelp(self, tests):
print self.generateUsage()
# Display the options
print 'Options:'
display_opts = []
max_opt = 0
for opt in self.options:
long = opt[0]
if long[-1] == '=':
long = '%s<%s>' % (long, long[:-1])
display = '-%s, --%s' % (opt[1], long)
display_opts.append((display, opt[2]))
if len(display) > max_opt:
max_opt = len(display)
for display, help in display_opts:
print ' %-*s %s' % (max_opt, display, help)
print
# Display detailed help for the given tests
for path in tests:
modes = []
testobj = self.test
for step in path.split('.'):
testobj = testobj.loadTest(step)
for mode in testobj.getModes([], [], 1):
if mode.name:
modes.append((mode.name, testobj.path))
if modes:
print 'Modes for %r:' % testobj.path
for (name, path) in modes:
if path != testobj.path:
print " %s (declared in %s)" % (name, path)
else:
print " %s" % name
print
subtests = [ test for test in testobj.getTests()
if isinstance(test, TestModule.TestModule) ]
if subtests:
print 'Sub-tests for %r:' % testobj.path
for test in subtests:
print " %s" % test.name
print
return
def runTests(self):
tester = Tester.Tester(self.stop, self.color, self.verbose)
tester.offline = self.offline
try:
for test in self.test.getTests():
test.run(tester)
except KeyboardInterrupt:
sys.stderr.write('\n%s\n' % ('='*72))
sys.stderr.write('\nTesting interrupted\n')
if self.report:
tester.report()
return
|