blob: af39216d2638b0ed907b470de6376cf8557af5c2 [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
Raymond Hettingerc50846a2010-04-05 18:56:31 +00008import functools
Benjamin Petersonbed7d042009-07-19 21:01:52 +00009
10from fnmatch import fnmatch
11
12from . import case, suite, util
13
Benjamin Petersondccc1fc2010-03-22 00:15:53 +000014__unittest = True
Benjamin Petersonbed7d042009-07-19 21:01:52 +000015
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000016# what about .pyc or .pyo (etc)
17# we would need to avoid loading the same tests multiple times
18# from '.py', '.pyc' *and* '.pyo'
19VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE)
20
21
Antoine Pitroud8337792015-03-18 23:56:46 +010022class _FailedTest(case.TestCase):
23 _testMethodName = None
24
25 def __init__(self, method_name, exception):
26 self._exception = exception
27 super(_FailedTest, self).__init__(method_name)
28
29 def __getattr__(self, name):
30 if name != self._testMethodName:
31 return super(_FailedTest, self).__getattr__(name)
32 def testFailure():
33 raise self._exception
34 return testFailure
35
36
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000037def _make_failed_import_test(name, suiteClass):
Benjamin Peterson434ae772010-03-22 01:46:47 +000038 message = 'Failed to import test module: %s\n%s' % (name, traceback.format_exc())
Antoine Pitroud8337792015-03-18 23:56:46 +010039 return _make_failed_test(name, ImportError(message), suiteClass)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000040
Benjamin Peterson886af962010-03-21 23:13:07 +000041def _make_failed_load_tests(name, exception, suiteClass):
Antoine Pitroud8337792015-03-18 23:56:46 +010042 return _make_failed_test(name, exception, suiteClass)
Benjamin Peterson886af962010-03-21 23:13:07 +000043
Antoine Pitroud8337792015-03-18 23:56:46 +010044def _make_failed_test(methodname, exception, suiteClass):
45 test = _FailedTest(methodname, exception)
46 return suiteClass((test,))
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000047
Ezio Melottieae2b382013-03-01 14:47:50 +020048def _make_skipped_test(methodname, exception, suiteClass):
49 @case.skip(str(exception))
50 def testSkipped(self):
51 pass
52 attrs = {methodname: testSkipped}
53 TestClass = type("ModuleSkipped", (case.TestCase,), attrs)
54 return suiteClass((TestClass(methodname),))
55
Michael Foorde01c62c2012-03-13 00:09:54 -070056def _jython_aware_splitext(path):
57 if path.lower().endswith('$py.class'):
58 return path[:-9]
59 return os.path.splitext(path)[0]
60
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000061
Benjamin Petersonbed7d042009-07-19 21:01:52 +000062class TestLoader(object):
63 """
64 This class is responsible for loading tests according to various criteria
65 and returning them wrapped in a TestSuite
66 """
67 testMethodPrefix = 'test'
68 sortTestMethodsUsing = staticmethod(util.three_way_cmp)
69 suiteClass = suite.TestSuite
70 _top_level_dir = None
71
72 def loadTestsFromTestCase(self, testCaseClass):
73 """Return a suite of all tests cases contained in testCaseClass"""
74 if issubclass(testCaseClass, suite.TestSuite):
Michael Foorde28bb152013-11-23 13:29:23 +000075 raise TypeError("Test cases should not be derived from "
76 "TestSuite. Maybe you meant to derive from "
77 "TestCase?")
Benjamin Petersonbed7d042009-07-19 21:01:52 +000078 testCaseNames = self.getTestCaseNames(testCaseClass)
79 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
80 testCaseNames = ['runTest']
81 loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
82 return loaded_suite
83
84 def loadTestsFromModule(self, module, use_load_tests=True):
85 """Return a suite of all tests cases contained in the given module"""
86 tests = []
87 for name in dir(module):
88 obj = getattr(module, name)
89 if isinstance(obj, type) and issubclass(obj, case.TestCase):
90 tests.append(self.loadTestsFromTestCase(obj))
91
92 load_tests = getattr(module, 'load_tests', None)
Michael Foord41647d62010-02-06 00:26:13 +000093 tests = self.suiteClass(tests)
Benjamin Petersonbed7d042009-07-19 21:01:52 +000094 if use_load_tests and load_tests is not None:
Benjamin Peterson886af962010-03-21 23:13:07 +000095 try:
96 return load_tests(self, tests, None)
97 except Exception as e:
98 return _make_failed_load_tests(module.__name__, e,
99 self.suiteClass)
Michael Foord41647d62010-02-06 00:26:13 +0000100 return tests
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000101
102 def loadTestsFromName(self, name, module=None):
103 """Return a suite of all tests cases given a string specifier.
104
105 The name may resolve either to a module, a test case class, a
106 test method within a test case class, or a callable object which
107 returns a TestCase or TestSuite instance.
108
109 The method optionally resolves the names relative to a given module.
110 """
111 parts = name.split('.')
112 if module is None:
113 parts_copy = parts[:]
114 while parts_copy:
115 try:
116 module = __import__('.'.join(parts_copy))
117 break
118 except ImportError:
119 del parts_copy[-1]
120 if not parts_copy:
121 raise
122 parts = parts[1:]
123 obj = module
124 for part in parts:
125 parent, obj = obj, getattr(obj, part)
126
127 if isinstance(obj, types.ModuleType):
128 return self.loadTestsFromModule(obj)
129 elif isinstance(obj, type) and issubclass(obj, case.TestCase):
130 return self.loadTestsFromTestCase(obj)
131 elif (isinstance(obj, types.FunctionType) and
132 isinstance(parent, type) and
133 issubclass(parent, case.TestCase)):
R David Murray5e2f5932013-04-11 08:55:45 -0400134 name = parts[-1]
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000135 inst = parent(name)
136 # static methods follow a different path
137 if not isinstance(getattr(inst, name), types.FunctionType):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000138 return self.suiteClass([inst])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000139 elif isinstance(obj, suite.TestSuite):
140 return obj
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200141 if callable(obj):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000142 test = obj()
143 if isinstance(test, suite.TestSuite):
144 return test
145 elif isinstance(test, case.TestCase):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000146 return self.suiteClass([test])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000147 else:
148 raise TypeError("calling %s returned %s, not a test" %
149 (obj, test))
150 else:
151 raise TypeError("don't know how to make test from: %s" % obj)
152
153 def loadTestsFromNames(self, names, module=None):
154 """Return a suite of all tests cases found using the given sequence
155 of string specifiers. See 'loadTestsFromName()'.
156 """
157 suites = [self.loadTestsFromName(name, module) for name in names]
158 return self.suiteClass(suites)
159
160 def getTestCaseNames(self, testCaseClass):
161 """Return a sorted sequence of method names found within testCaseClass
162 """
163 def isTestMethod(attrname, testCaseClass=testCaseClass,
164 prefix=self.testMethodPrefix):
165 return attrname.startswith(prefix) and \
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200166 callable(getattr(testCaseClass, attrname))
Senthil Kumaranf27be5c2011-11-25 02:08:39 +0800167 testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000168 if self.sortTestMethodsUsing:
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000169 testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000170 return testFnNames
171
172 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
173 """Find and return all test modules from the specified start
Michael Foord6bcfade2010-11-20 17:22:21 +0000174 directory, recursing into subdirectories to find them and return all
175 tests found within them. Only test files that match the pattern will
176 be loaded. (Using shell style pattern matching.)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000177
178 All test modules must be importable from the top level of the project.
179 If the start directory is not the top level directory then the top
180 level directory must be specified separately.
181
182 If a test package name (directory with '__init__.py') matches the
183 pattern then the package will be checked for a 'load_tests' function. If
184 this exists then it will be called with loader, tests, pattern.
185
186 If load_tests exists then discovery does *not* recurse into the package,
187 load_tests is responsible for loading all tests in the package.
188
189 The pattern is deliberately not stored as a loader attribute so that
190 packages can continue discovery themselves. top_level_dir is stored so
191 load_tests does not need to pass this argument in to loader.discover().
Michael Foord80cbc9e2013-03-18 17:50:12 -0700192
193 Paths are sorted before being imported to ensure reproducible execution
194 order even on filesystems with non-alphabetical ordering like ext3/4.
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000195 """
Benjamin Petersonb48af542010-04-11 20:43:16 +0000196 set_implicit_top = False
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000197 if top_level_dir is None and self._top_level_dir is not None:
198 # make top_level_dir optional if called from load_tests in a package
199 top_level_dir = self._top_level_dir
200 elif top_level_dir is None:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000201 set_implicit_top = True
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000202 top_level_dir = start_dir
203
Benjamin Petersonb48af542010-04-11 20:43:16 +0000204 top_level_dir = os.path.abspath(top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000205
206 if not top_level_dir in sys.path:
207 # all test modules must be importable from the top level directory
Michael Foord3b2494f2010-05-07 23:42:40 +0000208 # should we *unconditionally* put the start directory in first
209 # in sys.path to minimise likelihood of conflicts between installed
210 # modules and development versions?
211 sys.path.insert(0, top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000212 self._top_level_dir = top_level_dir
213
Benjamin Petersonb48af542010-04-11 20:43:16 +0000214 is_not_importable = False
Michael Foorde28bb152013-11-23 13:29:23 +0000215 is_namespace = False
216 tests = []
Benjamin Petersonb48af542010-04-11 20:43:16 +0000217 if os.path.isdir(os.path.abspath(start_dir)):
218 start_dir = os.path.abspath(start_dir)
219 if start_dir != top_level_dir:
220 is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))
221 else:
222 # support for discovery from dotted module names
223 try:
224 __import__(start_dir)
225 except ImportError:
226 is_not_importable = True
227 else:
228 the_module = sys.modules[start_dir]
229 top_part = start_dir.split('.')[0]
Michael Foorde28bb152013-11-23 13:29:23 +0000230 try:
231 start_dir = os.path.abspath(
232 os.path.dirname((the_module.__file__)))
233 except AttributeError:
234 # look for namespace packages
235 try:
236 spec = the_module.__spec__
237 except AttributeError:
238 spec = None
239
240 if spec and spec.loader is None:
241 if spec.submodule_search_locations is not None:
242 is_namespace = True
243
244 for path in the_module.__path__:
245 if (not set_implicit_top and
246 not path.startswith(top_level_dir)):
247 continue
248 self._top_level_dir = \
249 (path.split(the_module.__name__
250 .replace(".", os.path.sep))[0])
251 tests.extend(self._find_tests(path,
252 pattern,
253 namespace=True))
254 elif the_module.__name__ in sys.builtin_module_names:
255 # builtin module
256 raise TypeError('Can not use builtin modules '
257 'as dotted module names') from None
258 else:
259 raise TypeError(
260 'don\'t know how to discover from {!r}'
261 .format(the_module)) from None
262
Benjamin Petersonb48af542010-04-11 20:43:16 +0000263 if set_implicit_top:
Michael Foorde28bb152013-11-23 13:29:23 +0000264 if not is_namespace:
265 self._top_level_dir = \
266 self._get_directory_containing_module(top_part)
267 sys.path.remove(top_level_dir)
268 else:
269 sys.path.remove(top_level_dir)
Benjamin Petersonb48af542010-04-11 20:43:16 +0000270
271 if is_not_importable:
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000272 raise ImportError('Start directory is not importable: %r' % start_dir)
273
Michael Foorde28bb152013-11-23 13:29:23 +0000274 if not is_namespace:
275 tests = list(self._find_tests(start_dir, pattern))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000276 return self.suiteClass(tests)
277
Benjamin Petersonb48af542010-04-11 20:43:16 +0000278 def _get_directory_containing_module(self, module_name):
279 module = sys.modules[module_name]
280 full_path = os.path.abspath(module.__file__)
281
282 if os.path.basename(full_path).lower().startswith('__init__.py'):
283 return os.path.dirname(os.path.dirname(full_path))
284 else:
285 # here we have been given a module rather than a package - so
286 # all we can do is search the *same* directory the module is in
287 # should an exception be raised instead
288 return os.path.dirname(full_path)
289
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000290 def _get_name_from_path(self, path):
Michael Foorde01c62c2012-03-13 00:09:54 -0700291 path = _jython_aware_splitext(os.path.normpath(path))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000292
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000293 _relpath = os.path.relpath(path, self._top_level_dir)
294 assert not os.path.isabs(_relpath), "Path must be within the project"
295 assert not _relpath.startswith('..'), "Path must be within the project"
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000296
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000297 name = _relpath.replace(os.path.sep, '.')
298 return name
299
300 def _get_module_from_name(self, name):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000301 __import__(name)
302 return sys.modules[name]
303
Michael Foord4107d312010-06-05 10:45:41 +0000304 def _match_path(self, path, full_path, pattern):
305 # override this method to use alternative matching strategy
306 return fnmatch(path, pattern)
307
Michael Foorde28bb152013-11-23 13:29:23 +0000308 def _find_tests(self, start_dir, pattern, namespace=False):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000309 """Used by discovery. Yields test suites it loads."""
Michael Foord80cbc9e2013-03-18 17:50:12 -0700310 paths = sorted(os.listdir(start_dir))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000311
312 for path in paths:
313 full_path = os.path.join(start_dir, path)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000314 if os.path.isfile(full_path):
315 if not VALID_MODULE_NAME.match(path):
316 # valid Python identifiers only
317 continue
Michael Foord4107d312010-06-05 10:45:41 +0000318 if not self._match_path(path, full_path, pattern):
319 continue
320 # if the test file matches, load it
321 name = self._get_name_from_path(full_path)
322 try:
323 module = self._get_module_from_name(name)
Ezio Melottieae2b382013-03-01 14:47:50 +0200324 except case.SkipTest as e:
325 yield _make_skipped_test(name, e, self.suiteClass)
Michael Foord4107d312010-06-05 10:45:41 +0000326 except:
327 yield _make_failed_import_test(name, self.suiteClass)
328 else:
329 mod_file = os.path.abspath(getattr(module, '__file__', full_path))
Antoine Pitroud5d0bc32013-10-23 19:11:29 +0200330 realpath = _jython_aware_splitext(os.path.realpath(mod_file))
331 fullpath_noext = _jython_aware_splitext(os.path.realpath(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000332 if realpath.lower() != fullpath_noext.lower():
333 module_dir = os.path.dirname(realpath)
Michael Foorde01c62c2012-03-13 00:09:54 -0700334 mod_name = _jython_aware_splitext(os.path.basename(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000335 expected_dir = os.path.dirname(full_path)
336 msg = ("%r module incorrectly imported from %r. Expected %r. "
337 "Is this module globally installed?")
338 raise ImportError(msg % (mod_name, module_dir, expected_dir))
339 yield self.loadTestsFromModule(module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000340 elif os.path.isdir(full_path):
Michael Foorde28bb152013-11-23 13:29:23 +0000341 if (not namespace and
342 not os.path.isfile(os.path.join(full_path, '__init__.py'))):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000343 continue
344
345 load_tests = None
346 tests = None
347 if fnmatch(path, pattern):
348 # only check load_tests if the package directory itself matches the filter
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000349 name = self._get_name_from_path(full_path)
350 package = self._get_module_from_name(name)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000351 load_tests = getattr(package, 'load_tests', None)
352 tests = self.loadTestsFromModule(package, use_load_tests=False)
353
354 if load_tests is None:
355 if tests is not None:
356 # tests loaded from package file
357 yield tests
358 # recurse into the package
Michael Foorde28bb152013-11-23 13:29:23 +0000359 yield from self._find_tests(full_path, pattern,
360 namespace=namespace)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000361 else:
Benjamin Peterson886af962010-03-21 23:13:07 +0000362 try:
363 yield load_tests(self, tests, pattern)
364 except Exception as e:
365 yield _make_failed_load_tests(package.__name__, e,
366 self.suiteClass)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000367
368defaultTestLoader = TestLoader()
369
370
371def _makeLoader(prefix, sortUsing, suiteClass=None):
372 loader = TestLoader()
373 loader.sortTestMethodsUsing = sortUsing
374 loader.testMethodPrefix = prefix
375 if suiteClass:
376 loader.suiteClass = suiteClass
377 return loader
378
379def getTestCaseNames(testCaseClass, prefix, sortUsing=util.three_way_cmp):
380 return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
381
382def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,
383 suiteClass=suite.TestSuite):
384 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(
385 testCaseClass)
386
387def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp,
388 suiteClass=suite.TestSuite):
389 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\
390 module)