blob: 68f954cde0bc8914783e4f5cb0ef4f62f2c8a7b4 [file] [log] [blame]
Benjamin Petersonbed7d042009-07-19 21:01:52 +00001"""Loading unittests."""
2
3import os
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +00004import re
Benjamin Petersonbed7d042009-07-19 21:01:52 +00005import sys
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +00006import traceback
Benjamin Petersonbed7d042009-07-19 21:01:52 +00007import types
8
9from fnmatch import fnmatch
10
11from . import case, suite, util
12
13
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000014# what about .pyc or .pyo (etc)
15# we would need to avoid loading the same tests multiple times
16# from '.py', '.pyc' *and* '.pyo'
17VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE)
18
19
20def _make_failed_import_test(name, suiteClass):
21 message = 'Failed to import test module: %s' % name
22 if hasattr(traceback, 'format_exc'):
23 # Python 2.3 compatibility
24 # format_exc returns two frames of discover.py as well
25 message += '\n%s' % traceback.format_exc()
26
27 def testImportFailure(self):
28 raise ImportError(message)
29 attrs = {name: testImportFailure}
30 ModuleImportFailure = type('ModuleImportFailure', (case.TestCase,), attrs)
31 return suiteClass((ModuleImportFailure(name),))
32
33
Benjamin Petersonbed7d042009-07-19 21:01:52 +000034class TestLoader(object):
35 """
36 This class is responsible for loading tests according to various criteria
37 and returning them wrapped in a TestSuite
38 """
39 testMethodPrefix = 'test'
40 sortTestMethodsUsing = staticmethod(util.three_way_cmp)
41 suiteClass = suite.TestSuite
42 _top_level_dir = None
43
44 def loadTestsFromTestCase(self, testCaseClass):
45 """Return a suite of all tests cases contained in testCaseClass"""
46 if issubclass(testCaseClass, suite.TestSuite):
47 raise TypeError("Test cases should not be derived from TestSuite." \
48 " Maybe you meant to derive from TestCase?")
49 testCaseNames = self.getTestCaseNames(testCaseClass)
50 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
51 testCaseNames = ['runTest']
52 loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
53 return loaded_suite
54
55 def loadTestsFromModule(self, module, use_load_tests=True):
56 """Return a suite of all tests cases contained in the given module"""
57 tests = []
58 for name in dir(module):
59 obj = getattr(module, name)
60 if isinstance(obj, type) and issubclass(obj, case.TestCase):
61 tests.append(self.loadTestsFromTestCase(obj))
62
63 load_tests = getattr(module, 'load_tests', None)
64 if use_load_tests and load_tests is not None:
65 return load_tests(self, tests, None)
66 return self.suiteClass(tests)
67
68 def loadTestsFromName(self, name, module=None):
69 """Return a suite of all tests cases given a string specifier.
70
71 The name may resolve either to a module, a test case class, a
72 test method within a test case class, or a callable object which
73 returns a TestCase or TestSuite instance.
74
75 The method optionally resolves the names relative to a given module.
76 """
77 parts = name.split('.')
78 if module is None:
79 parts_copy = parts[:]
80 while parts_copy:
81 try:
82 module = __import__('.'.join(parts_copy))
83 break
84 except ImportError:
85 del parts_copy[-1]
86 if not parts_copy:
87 raise
88 parts = parts[1:]
89 obj = module
90 for part in parts:
91 parent, obj = obj, getattr(obj, part)
92
93 if isinstance(obj, types.ModuleType):
94 return self.loadTestsFromModule(obj)
95 elif isinstance(obj, type) and issubclass(obj, case.TestCase):
96 return self.loadTestsFromTestCase(obj)
97 elif (isinstance(obj, types.FunctionType) and
98 isinstance(parent, type) and
99 issubclass(parent, case.TestCase)):
100 name = obj.__name__
101 inst = parent(name)
102 # static methods follow a different path
103 if not isinstance(getattr(inst, name), types.FunctionType):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000104 return self.suiteClass([inst])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000105 elif isinstance(obj, suite.TestSuite):
106 return obj
107 if hasattr(obj, '__call__'):
108 test = obj()
109 if isinstance(test, suite.TestSuite):
110 return test
111 elif isinstance(test, case.TestCase):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000112 return self.suiteClass([test])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000113 else:
114 raise TypeError("calling %s returned %s, not a test" %
115 (obj, test))
116 else:
117 raise TypeError("don't know how to make test from: %s" % obj)
118
119 def loadTestsFromNames(self, names, module=None):
120 """Return a suite of all tests cases found using the given sequence
121 of string specifiers. See 'loadTestsFromName()'.
122 """
123 suites = [self.loadTestsFromName(name, module) for name in names]
124 return self.suiteClass(suites)
125
126 def getTestCaseNames(self, testCaseClass):
127 """Return a sorted sequence of method names found within testCaseClass
128 """
129 def isTestMethod(attrname, testCaseClass=testCaseClass,
130 prefix=self.testMethodPrefix):
131 return attrname.startswith(prefix) and \
132 hasattr(getattr(testCaseClass, attrname), '__call__')
133 testFnNames = testFnNames = list(filter(isTestMethod,
134 dir(testCaseClass)))
135 if self.sortTestMethodsUsing:
136 testFnNames.sort(key=util.CmpToKey(self.sortTestMethodsUsing))
137 return testFnNames
138
139 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
140 """Find and return all test modules from the specified start
141 directory, recursing into subdirectories to find them. Only test files
142 that match the pattern will be loaded. (Using shell style pattern
143 matching.)
144
145 All test modules must be importable from the top level of the project.
146 If the start directory is not the top level directory then the top
147 level directory must be specified separately.
148
149 If a test package name (directory with '__init__.py') matches the
150 pattern then the package will be checked for a 'load_tests' function. If
151 this exists then it will be called with loader, tests, pattern.
152
153 If load_tests exists then discovery does *not* recurse into the package,
154 load_tests is responsible for loading all tests in the package.
155
156 The pattern is deliberately not stored as a loader attribute so that
157 packages can continue discovery themselves. top_level_dir is stored so
158 load_tests does not need to pass this argument in to loader.discover().
159 """
160 if top_level_dir is None and self._top_level_dir is not None:
161 # make top_level_dir optional if called from load_tests in a package
162 top_level_dir = self._top_level_dir
163 elif top_level_dir is None:
164 top_level_dir = start_dir
165
166 top_level_dir = os.path.abspath(os.path.normpath(top_level_dir))
167 start_dir = os.path.abspath(os.path.normpath(start_dir))
168
169 if not top_level_dir in sys.path:
170 # all test modules must be importable from the top level directory
171 sys.path.append(top_level_dir)
172 self._top_level_dir = top_level_dir
173
174 if start_dir != top_level_dir and not os.path.isfile(os.path.join(start_dir, '__init__.py')):
175 # what about __init__.pyc or pyo (etc)
176 raise ImportError('Start directory is not importable: %r' % start_dir)
177
178 tests = list(self._find_tests(start_dir, pattern))
179 return self.suiteClass(tests)
180
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000181 def _get_name_from_path(self, path):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000182 path = os.path.splitext(os.path.normpath(path))[0]
183
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000184 _relpath = os.path.relpath(path, self._top_level_dir)
185 assert not os.path.isabs(_relpath), "Path must be within the project"
186 assert not _relpath.startswith('..'), "Path must be within the project"
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000187
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000188 name = _relpath.replace(os.path.sep, '.')
189 return name
190
191 def _get_module_from_name(self, name):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000192 __import__(name)
193 return sys.modules[name]
194
195 def _find_tests(self, start_dir, pattern):
196 """Used by discovery. Yields test suites it loads."""
197 paths = os.listdir(start_dir)
198
199 for path in paths:
200 full_path = os.path.join(start_dir, path)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000201 if os.path.isfile(full_path):
202 if not VALID_MODULE_NAME.match(path):
203 # valid Python identifiers only
204 continue
205
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000206 if fnmatch(path, pattern):
207 # if the test file matches, load it
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000208 name = self._get_name_from_path(full_path)
209 try:
210 module = self._get_module_from_name(name)
211 except:
212 yield _make_failed_import_test(name, self.suiteClass)
213 else:
214 yield self.loadTestsFromModule(module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000215 elif os.path.isdir(full_path):
216 if not os.path.isfile(os.path.join(full_path, '__init__.py')):
217 continue
218
219 load_tests = None
220 tests = None
221 if fnmatch(path, pattern):
222 # only check load_tests if the package directory itself matches the filter
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000223 name = self._get_name_from_path(full_path)
224 package = self._get_module_from_name(name)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000225 load_tests = getattr(package, 'load_tests', None)
226 tests = self.loadTestsFromModule(package, use_load_tests=False)
227
228 if load_tests is None:
229 if tests is not None:
230 # tests loaded from package file
231 yield tests
232 # recurse into the package
233 for test in self._find_tests(full_path, pattern):
234 yield test
235 else:
236 yield load_tests(self, tests, pattern)
237
238defaultTestLoader = TestLoader()
239
240
241def _makeLoader(prefix, sortUsing, suiteClass=None):
242 loader = TestLoader()
243 loader.sortTestMethodsUsing = sortUsing
244 loader.testMethodPrefix = prefix
245 if suiteClass:
246 loader.suiteClass = suiteClass
247 return loader
248
249def getTestCaseNames(testCaseClass, prefix, sortUsing=util.three_way_cmp):
250 return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
251
252def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,
253 suiteClass=suite.TestSuite):
254 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(
255 testCaseClass)
256
257def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp,
258 suiteClass=suite.TestSuite):
259 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\
260 module)