add note on revision numbering (feature request #50)
[docutils.git] / test / package_unittest.py
blob995547c19e021365dfbc3cf8b8b2c6023617755b
1 #! /usr/bin/env python
3 # $Id$
4 # Author: Garth Kidd <garth@deadlybloodyserious.com>
5 # Copyright: This module has been placed in the public domain.
7 """
8 This module extends unittest.py with `loadTestModules()`, by loading multiple
9 test modules from a directory. Optionally, test packages are also loaded,
10 recursively.
11 """
13 import sys
14 import os
15 import getopt
16 import types
17 import unittest
18 import re
21 # So that individual test modules can share a bit of state,
22 # `package_unittest` acts as an intermediary for the following
23 # variables:
24 debug = False
25 verbosity = 1
27 USAGE = """\
28 Usage: test_whatever [options]
30 Options:
31 -h, --help Show this message
32 -v, --verbose Verbose output
33 -q, --quiet Minimal output
34 -d, --debug Debug mode
35 """
37 def usageExit(msg=None):
38 """Print usage and exit."""
39 if msg:
40 print msg
41 print USAGE
42 sys.exit(2)
44 def parseArgs(argv=sys.argv):
45 """Parse command line arguments and set TestFramework state.
47 State is to be acquired by test_* modules by a grotty hack:
48 ``from TestFramework import *``. For this stylistic
49 transgression, I expect to be first up against the wall
50 when the revolution comes. --Garth"""
51 global verbosity, debug
52 try:
53 options, args = getopt.getopt(argv[1:], 'hHvqd',
54 ['help', 'verbose', 'quiet', 'debug'])
55 for opt, value in options:
56 if opt in ('-h', '-H', '--help'):
57 usageExit()
58 if opt in ('-q', '--quiet'):
59 verbosity = 0
60 if opt in ('-v', '--verbose'):
61 verbosity = 2
62 if opt in ('-d', '--debug'):
63 debug =1
64 if len(args) != 0:
65 usageExit("No command-line arguments supported yet.")
66 except getopt.error, msg:
67 usageExit(msg)
69 def loadTestModules(path, name='', packages=None):
70 """
71 Return a test suite composed of all the tests from modules in a directory.
73 Search for modules in directory `path`, beginning with `name`. If
74 `packages` is true, search subdirectories (also beginning with `name`)
75 recursively. Subdirectories must be Python packages; they must contain an
76 '__init__.py' module.
77 """
78 testLoader = unittest.defaultTestLoader
79 testSuite = unittest.TestSuite()
80 testModules = []
81 path = os.path.abspath(path) # current working dir if `path` empty
82 paths = [path]
83 while paths:
84 p = paths.pop(0)
85 files = os.listdir(p)
86 for filename in files:
87 if filename.startswith(name):
88 fullpath = os.path.join(p, filename)
89 if filename.endswith('.py'):
90 fullpath = fullpath[len(path)+1:]
91 testModules.append(path2mod(fullpath))
92 elif packages and os.path.isdir(fullpath) and \
93 os.path.isfile(os.path.join(fullpath, '__init__.py')):
94 paths.append(fullpath)
95 # Import modules and add their tests to the suite.
96 sys.path.insert(0, path)
97 for mod in testModules:
98 if debug:
99 print >>sys.stderr, "importing %s" % mod
100 try:
101 module = import_module(mod)
102 except ImportError:
103 print >>sys.stderr, "ERROR: Can't import %s, skipping its tests:" % mod
104 sys.excepthook(*sys.exc_info())
105 else:
106 # if there's a suite defined, incorporate its contents
107 try:
108 suite = getattr(module, 'suite')
109 except AttributeError:
110 # Look for individual tests
111 moduleTests = testLoader.loadTestsFromModule(module)
112 # unittest.TestSuite.addTests() doesn't work as advertised,
113 # as it can't load tests from another TestSuite, so we have
114 # to cheat:
115 testSuite.addTest(moduleTests)
116 continue
117 if type(suite) == types.FunctionType:
118 testSuite.addTest(suite())
119 elif isinstance(suite, unittest.TestSuite):
120 testSuite.addTest(suite)
121 else:
122 raise AssertionError, "don't understand suite (%s)" % mod
123 sys.path.pop(0)
124 return testSuite
126 def path2mod(path):
127 """Convert a file path to a dotted module name."""
128 return path[:-3].replace(os.sep, '.')
130 def import_module(name):
131 """Import a dotted-path module name, and return the final component."""
132 mod = __import__(name)
133 components = name.split('.')
134 for comp in components[1:]:
135 mod = getattr(mod, comp)
136 return mod
138 def main(suite=None):
140 Shared `main` for any individual test_* file.
142 suite -- TestSuite to run. If not specified, look for any globally defined
143 tests and run them.
145 parseArgs()
146 if suite is None:
147 # Load any globally defined tests.
148 suite = unittest.defaultTestLoader.loadTestsFromModule(
149 __import__('__main__'))
150 if debug:
151 print >>sys.stderr, "Debug: Suite=%s" % suite
152 testRunner = unittest.TextTestRunner(verbosity=verbosity)
153 # run suites (if we were called from test_all) or suite...
154 if type(suite) == type([]):
155 for s in suite:
156 testRunner.run(s)
157 else:
158 return testRunner.run(suite)