summaryrefslogtreecommitdiff
path: root/test/package_unittest.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/package_unittest.py')
-rw-r--r--test/package_unittest.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/test/package_unittest.py b/test/package_unittest.py
new file mode 100644
index 000000000..43d724cd1
--- /dev/null
+++ b/test/package_unittest.py
@@ -0,0 +1,161 @@
+#! /usr/bin/env python
+
+# Author: Garth Kidd
+# Contact: garth@deadlybloodyserious.com
+# Revision: $Revision$
+# Date: $Date$
+# 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 = 0
+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 type(suite) == types.InstanceType \
+ and 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:
+ testRunner.run(suite)