You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
5.2 KiB
158 lines
5.2 KiB
#! /usr/bin/env python |
|
|
|
# $Id: package_unittest.py 7320 2012-01-19 22:33:02Z milde $ |
|
# Author: Garth Kidd <garth@deadlybloodyserious.com> |
|
# Copyright: This module has been placed in the public domain. |
|
|
|
""" |
|
This module extends unittest.py with `loadTestModules()`, by loading multiple |
|
test modules from a directory. Optionally, test packages are also loaded, |
|
recursively. |
|
""" |
|
|
|
import sys |
|
import os |
|
import getopt |
|
import types |
|
import unittest |
|
import re |
|
|
|
|
|
# So that individual test modules can share a bit of state, |
|
# `package_unittest` acts as an intermediary for the following |
|
# variables: |
|
debug = False |
|
verbosity = 1 |
|
|
|
USAGE = """\ |
|
Usage: test_whatever [options] |
|
|
|
Options: |
|
-h, --help Show this message |
|
-v, --verbose Verbose output |
|
-q, --quiet Minimal output |
|
-d, --debug Debug mode |
|
""" |
|
|
|
def usageExit(msg=None): |
|
"""Print usage and exit.""" |
|
if msg: |
|
print msg |
|
print USAGE |
|
sys.exit(2) |
|
|
|
def parseArgs(argv=sys.argv): |
|
"""Parse command line arguments and set TestFramework state. |
|
|
|
State is to be acquired by test_* modules by a grotty hack: |
|
``from TestFramework import *``. For this stylistic |
|
transgression, I expect to be first up against the wall |
|
when the revolution comes. --Garth""" |
|
global verbosity, debug |
|
try: |
|
options, args = getopt.getopt(argv[1:], 'hHvqd', |
|
['help', 'verbose', 'quiet', 'debug']) |
|
for opt, value in options: |
|
if opt in ('-h', '-H', '--help'): |
|
usageExit() |
|
if opt in ('-q', '--quiet'): |
|
verbosity = 0 |
|
if opt in ('-v', '--verbose'): |
|
verbosity = 2 |
|
if opt in ('-d', '--debug'): |
|
debug =1 |
|
if len(args) != 0: |
|
usageExit("No command-line arguments supported yet.") |
|
except getopt.error, msg: |
|
usageExit(msg) |
|
|
|
def loadTestModules(path, name='', packages=None): |
|
""" |
|
Return a test suite composed of all the tests from modules in a directory. |
|
|
|
Search for modules in directory `path`, beginning with `name`. If |
|
`packages` is true, search subdirectories (also beginning with `name`) |
|
recursively. Subdirectories must be Python packages; they must contain an |
|
'__init__.py' module. |
|
""" |
|
testLoader = unittest.defaultTestLoader |
|
testSuite = unittest.TestSuite() |
|
testModules = [] |
|
path = os.path.abspath(path) # current working dir if `path` empty |
|
paths = [path] |
|
while paths: |
|
p = paths.pop(0) |
|
files = os.listdir(p) |
|
for filename in files: |
|
if filename.startswith(name): |
|
fullpath = os.path.join(p, filename) |
|
if filename.endswith('.py'): |
|
fullpath = fullpath[len(path)+1:] |
|
testModules.append(path2mod(fullpath)) |
|
elif packages and os.path.isdir(fullpath) and \ |
|
os.path.isfile(os.path.join(fullpath, '__init__.py')): |
|
paths.append(fullpath) |
|
# Import modules and add their tests to the suite. |
|
sys.path.insert(0, path) |
|
for mod in testModules: |
|
if debug: |
|
print >>sys.stderr, "importing %s" % mod |
|
try: |
|
module = import_module(mod) |
|
except ImportError: |
|
print >>sys.stderr, "ERROR: Can't import %s, skipping its tests:" % mod |
|
sys.excepthook(*sys.exc_info()) |
|
else: |
|
# if there's a suite defined, incorporate its contents |
|
try: |
|
suite = getattr(module, 'suite') |
|
except AttributeError: |
|
# Look for individual tests |
|
moduleTests = testLoader.loadTestsFromModule(module) |
|
# unittest.TestSuite.addTests() doesn't work as advertised, |
|
# as it can't load tests from another TestSuite, so we have |
|
# to cheat: |
|
testSuite.addTest(moduleTests) |
|
continue |
|
if type(suite) == types.FunctionType: |
|
testSuite.addTest(suite()) |
|
elif isinstance(suite, unittest.TestSuite): |
|
testSuite.addTest(suite) |
|
else: |
|
raise AssertionError, "don't understand suite (%s)" % mod |
|
sys.path.pop(0) |
|
return testSuite |
|
|
|
def path2mod(path): |
|
"""Convert a file path to a dotted module name.""" |
|
return path[:-3].replace(os.sep, '.') |
|
|
|
def import_module(name): |
|
"""Import a dotted-path module name, and return the final component.""" |
|
mod = __import__(name) |
|
components = name.split('.') |
|
for comp in components[1:]: |
|
mod = getattr(mod, comp) |
|
return mod |
|
|
|
def main(suite=None): |
|
""" |
|
Shared `main` for any individual test_* file. |
|
|
|
suite -- TestSuite to run. If not specified, look for any globally defined |
|
tests and run them. |
|
""" |
|
parseArgs() |
|
if suite is None: |
|
# Load any globally defined tests. |
|
suite = unittest.defaultTestLoader.loadTestsFromModule( |
|
__import__('__main__')) |
|
if debug: |
|
print >>sys.stderr, "Debug: Suite=%s" % suite |
|
testRunner = unittest.TextTestRunner(verbosity=verbosity) |
|
# run suites (if we were called from test_all) or suite... |
|
if type(suite) == type([]): |
|
for s in suite: |
|
testRunner.run(s) |
|
else: |
|
return testRunner.run(suite)
|
|
|