blob: 808c50eb668193dd90830899c0118a24a1c22c80 [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
22def _make_failed_import_test(name, suiteClass):
Benjamin Peterson434ae772010-03-22 01:46:47 +000023 message = 'Failed to import test module: %s\n%s' % (name, traceback.format_exc())
Benjamin Peterson886af962010-03-21 23:13:07 +000024 return _make_failed_test('ModuleImportFailure', name, ImportError(message),
25 suiteClass)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000026
Benjamin Peterson886af962010-03-21 23:13:07 +000027def _make_failed_load_tests(name, exception, suiteClass):
28 return _make_failed_test('LoadTestsFailure', name, exception, suiteClass)
29
30def _make_failed_test(classname, methodname, exception, suiteClass):
31 def testFailure(self):
32 raise exception
33 attrs = {methodname: testFailure}
34 TestClass = type(classname, (case.TestCase,), attrs)
35 return suiteClass((TestClass(methodname),))
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000036
Ezio Melottieae2b382013-03-01 14:47:50 +020037def _make_skipped_test(methodname, exception, suiteClass):
38 @case.skip(str(exception))
39 def testSkipped(self):
40 pass
41 attrs = {methodname: testSkipped}
42 TestClass = type("ModuleSkipped", (case.TestCase,), attrs)
43 return suiteClass((TestClass(methodname),))
44
Michael Foorde01c62c2012-03-13 00:09:54 -070045def _jython_aware_splitext(path):
46 if path.lower().endswith('$py.class'):
47 return path[:-9]
48 return os.path.splitext(path)[0]
49
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000050
Benjamin Petersonbed7d042009-07-19 21:01:52 +000051class TestLoader(object):
52 """
53 This class is responsible for loading tests according to various criteria
54 and returning them wrapped in a TestSuite
55 """
56 testMethodPrefix = 'test'
57 sortTestMethodsUsing = staticmethod(util.three_way_cmp)
58 suiteClass = suite.TestSuite
59 _top_level_dir = None
60
61 def loadTestsFromTestCase(self, testCaseClass):
62 """Return a suite of all tests cases contained in testCaseClass"""
63 if issubclass(testCaseClass, suite.TestSuite):
Michael Foorde28bb152013-11-23 13:29:23 +000064 raise TypeError("Test cases should not be derived from "
65 "TestSuite. Maybe you meant to derive from "
66 "TestCase?")
Benjamin Petersonbed7d042009-07-19 21:01:52 +000067 testCaseNames = self.getTestCaseNames(testCaseClass)
68 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
69 testCaseNames = ['runTest']
70 loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
71 return loaded_suite
72
73 def loadTestsFromModule(self, module, use_load_tests=True):
74 """Return a suite of all tests cases contained in the given module"""
75 tests = []
76 for name in dir(module):
77 obj = getattr(module, name)
78 if isinstance(obj, type) and issubclass(obj, case.TestCase):
79 tests.append(self.loadTestsFromTestCase(obj))
80
81 load_tests = getattr(module, 'load_tests', None)
Michael Foord41647d62010-02-06 00:26:13 +000082 tests = self.suiteClass(tests)
Benjamin Petersonbed7d042009-07-19 21:01:52 +000083 if use_load_tests and load_tests is not None:
Benjamin Peterson886af962010-03-21 23:13:07 +000084 try:
85 return load_tests(self, tests, None)
86 except Exception as e:
87 return _make_failed_load_tests(module.__name__, e,
88 self.suiteClass)
Michael Foord41647d62010-02-06 00:26:13 +000089 return tests
Benjamin Petersonbed7d042009-07-19 21:01:52 +000090
91 def loadTestsFromName(self, name, module=None):
92 """Return a suite of all tests cases given a string specifier.
93
94 The name may resolve either to a module, a test case class, a
95 test method within a test case class, or a callable object which
96 returns a TestCase or TestSuite instance.
97
98 The method optionally resolves the names relative to a given module.
99 """
100 parts = name.split('.')
101 if module is None:
102 parts_copy = parts[:]
103 while parts_copy:
104 try:
105 module = __import__('.'.join(parts_copy))
106 break
107 except ImportError:
108 del parts_copy[-1]
109 if not parts_copy:
110 raise
111 parts = parts[1:]
112 obj = module
113 for part in parts:
114 parent, obj = obj, getattr(obj, part)
115
116 if isinstance(obj, types.ModuleType):
117 return self.loadTestsFromModule(obj)
118 elif isinstance(obj, type) and issubclass(obj, case.TestCase):
119 return self.loadTestsFromTestCase(obj)
120 elif (isinstance(obj, types.FunctionType) and
121 isinstance(parent, type) and
122 issubclass(parent, case.TestCase)):
R David Murray5e2f5932013-04-11 08:55:45 -0400123 name = parts[-1]
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000124 inst = parent(name)
125 # static methods follow a different path
126 if not isinstance(getattr(inst, name), types.FunctionType):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000127 return self.suiteClass([inst])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000128 elif isinstance(obj, suite.TestSuite):
129 return obj
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200130 if callable(obj):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000131 test = obj()
132 if isinstance(test, suite.TestSuite):
133 return test
134 elif isinstance(test, case.TestCase):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000135 return self.suiteClass([test])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000136 else:
137 raise TypeError("calling %s returned %s, not a test" %
138 (obj, test))
139 else:
140 raise TypeError("don't know how to make test from: %s" % obj)
141
142 def loadTestsFromNames(self, names, module=None):
143 """Return a suite of all tests cases found using the given sequence
144 of string specifiers. See 'loadTestsFromName()'.
145 """
146 suites = [self.loadTestsFromName(name, module) for name in names]
147 return self.suiteClass(suites)
148
149 def getTestCaseNames(self, testCaseClass):
150 """Return a sorted sequence of method names found within testCaseClass
151 """
152 def isTestMethod(attrname, testCaseClass=testCaseClass,
153 prefix=self.testMethodPrefix):
154 return attrname.startswith(prefix) and \
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200155 callable(getattr(testCaseClass, attrname))
Senthil Kumaranf27be5c2011-11-25 02:08:39 +0800156 testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000157 if self.sortTestMethodsUsing:
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000158 testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000159 return testFnNames
160
161 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
162 """Find and return all test modules from the specified start
Michael Foord6bcfade2010-11-20 17:22:21 +0000163 directory, recursing into subdirectories to find them and return all
164 tests found within them. Only test files that match the pattern will
165 be loaded. (Using shell style pattern matching.)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000166
167 All test modules must be importable from the top level of the project.
168 If the start directory is not the top level directory then the top
169 level directory must be specified separately.
170
171 If a test package name (directory with '__init__.py') matches the
172 pattern then the package will be checked for a 'load_tests' function. If
173 this exists then it will be called with loader, tests, pattern.
174
175 If load_tests exists then discovery does *not* recurse into the package,
176 load_tests is responsible for loading all tests in the package.
177
178 The pattern is deliberately not stored as a loader attribute so that
179 packages can continue discovery themselves. top_level_dir is stored so
180 load_tests does not need to pass this argument in to loader.discover().
Michael Foord80cbc9e2013-03-18 17:50:12 -0700181
182 Paths are sorted before being imported to ensure reproducible execution
183 order even on filesystems with non-alphabetical ordering like ext3/4.
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000184 """
Benjamin Petersonb48af542010-04-11 20:43:16 +0000185 set_implicit_top = False
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000186 if top_level_dir is None and self._top_level_dir is not None:
187 # make top_level_dir optional if called from load_tests in a package
188 top_level_dir = self._top_level_dir
189 elif top_level_dir is None:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000190 set_implicit_top = True
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000191 top_level_dir = start_dir
192
Benjamin Petersonb48af542010-04-11 20:43:16 +0000193 top_level_dir = os.path.abspath(top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000194
195 if not top_level_dir in sys.path:
196 # all test modules must be importable from the top level directory
Michael Foord3b2494f2010-05-07 23:42:40 +0000197 # should we *unconditionally* put the start directory in first
198 # in sys.path to minimise likelihood of conflicts between installed
199 # modules and development versions?
200 sys.path.insert(0, top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000201 self._top_level_dir = top_level_dir
202
Benjamin Petersonb48af542010-04-11 20:43:16 +0000203 is_not_importable = False
Michael Foorde28bb152013-11-23 13:29:23 +0000204 is_namespace = False
205 tests = []
Benjamin Petersonb48af542010-04-11 20:43:16 +0000206 if os.path.isdir(os.path.abspath(start_dir)):
207 start_dir = os.path.abspath(start_dir)
208 if start_dir != top_level_dir:
209 is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))
210 else:
211 # support for discovery from dotted module names
212 try:
213 __import__(start_dir)
214 except ImportError:
215 is_not_importable = True
216 else:
217 the_module = sys.modules[start_dir]
218 top_part = start_dir.split('.')[0]
Michael Foorde28bb152013-11-23 13:29:23 +0000219 try:
220 start_dir = os.path.abspath(
221 os.path.dirname((the_module.__file__)))
222 except AttributeError:
223 # look for namespace packages
224 try:
225 spec = the_module.__spec__
226 except AttributeError:
227 spec = None
228
229 if spec and spec.loader is None:
230 if spec.submodule_search_locations is not None:
231 is_namespace = True
232
233 for path in the_module.__path__:
234 if (not set_implicit_top and
235 not path.startswith(top_level_dir)):
236 continue
237 self._top_level_dir = \
238 (path.split(the_module.__name__
239 .replace(".", os.path.sep))[0])
240 tests.extend(self._find_tests(path,
241 pattern,
242 namespace=True))
243 elif the_module.__name__ in sys.builtin_module_names:
244 # builtin module
245 raise TypeError('Can not use builtin modules '
246 'as dotted module names') from None
247 else:
248 raise TypeError(
249 'don\'t know how to discover from {!r}'
250 .format(the_module)) from None
251
Benjamin Petersonb48af542010-04-11 20:43:16 +0000252 if set_implicit_top:
Michael Foorde28bb152013-11-23 13:29:23 +0000253 if not is_namespace:
254 self._top_level_dir = \
255 self._get_directory_containing_module(top_part)
256 sys.path.remove(top_level_dir)
257 else:
258 sys.path.remove(top_level_dir)
Benjamin Petersonb48af542010-04-11 20:43:16 +0000259
260 if is_not_importable:
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000261 raise ImportError('Start directory is not importable: %r' % start_dir)
262
Michael Foorde28bb152013-11-23 13:29:23 +0000263 if not is_namespace:
264 tests = list(self._find_tests(start_dir, pattern))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000265 return self.suiteClass(tests)
266
Benjamin Petersonb48af542010-04-11 20:43:16 +0000267 def _get_directory_containing_module(self, module_name):
268 module = sys.modules[module_name]
269 full_path = os.path.abspath(module.__file__)
270
271 if os.path.basename(full_path).lower().startswith('__init__.py'):
272 return os.path.dirname(os.path.dirname(full_path))
273 else:
274 # here we have been given a module rather than a package - so
275 # all we can do is search the *same* directory the module is in
276 # should an exception be raised instead
277 return os.path.dirname(full_path)
278
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000279 def _get_name_from_path(self, path):
Michael Foorde01c62c2012-03-13 00:09:54 -0700280 path = _jython_aware_splitext(os.path.normpath(path))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000281
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000282 _relpath = os.path.relpath(path, self._top_level_dir)
283 assert not os.path.isabs(_relpath), "Path must be within the project"
284 assert not _relpath.startswith('..'), "Path must be within the project"
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000285
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000286 name = _relpath.replace(os.path.sep, '.')
287 return name
288
289 def _get_module_from_name(self, name):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000290 __import__(name)
291 return sys.modules[name]
292
Michael Foord4107d312010-06-05 10:45:41 +0000293 def _match_path(self, path, full_path, pattern):
294 # override this method to use alternative matching strategy
295 return fnmatch(path, pattern)
296
Michael Foorde28bb152013-11-23 13:29:23 +0000297 def _find_tests(self, start_dir, pattern, namespace=False):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000298 """Used by discovery. Yields test suites it loads."""
Michael Foord80cbc9e2013-03-18 17:50:12 -0700299 paths = sorted(os.listdir(start_dir))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000300
301 for path in paths:
302 full_path = os.path.join(start_dir, path)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000303 if os.path.isfile(full_path):
304 if not VALID_MODULE_NAME.match(path):
305 # valid Python identifiers only
306 continue
Michael Foord4107d312010-06-05 10:45:41 +0000307 if not self._match_path(path, full_path, pattern):
308 continue
309 # if the test file matches, load it
310 name = self._get_name_from_path(full_path)
311 try:
312 module = self._get_module_from_name(name)
Ezio Melottieae2b382013-03-01 14:47:50 +0200313 except case.SkipTest as e:
314 yield _make_skipped_test(name, e, self.suiteClass)
Michael Foord4107d312010-06-05 10:45:41 +0000315 except:
316 yield _make_failed_import_test(name, self.suiteClass)
317 else:
318 mod_file = os.path.abspath(getattr(module, '__file__', full_path))
Antoine Pitroud5d0bc32013-10-23 19:11:29 +0200319 realpath = _jython_aware_splitext(os.path.realpath(mod_file))
320 fullpath_noext = _jython_aware_splitext(os.path.realpath(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000321 if realpath.lower() != fullpath_noext.lower():
322 module_dir = os.path.dirname(realpath)
Michael Foorde01c62c2012-03-13 00:09:54 -0700323 mod_name = _jython_aware_splitext(os.path.basename(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000324 expected_dir = os.path.dirname(full_path)
325 msg = ("%r module incorrectly imported from %r. Expected %r. "
326 "Is this module globally installed?")
327 raise ImportError(msg % (mod_name, module_dir, expected_dir))
328 yield self.loadTestsFromModule(module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000329 elif os.path.isdir(full_path):
Michael Foorde28bb152013-11-23 13:29:23 +0000330 if (not namespace and
331 not os.path.isfile(os.path.join(full_path, '__init__.py'))):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000332 continue
333
334 load_tests = None
335 tests = None
336 if fnmatch(path, pattern):
337 # only check load_tests if the package directory itself matches the filter
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000338 name = self._get_name_from_path(full_path)
339 package = self._get_module_from_name(name)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000340 load_tests = getattr(package, 'load_tests', None)
341 tests = self.loadTestsFromModule(package, use_load_tests=False)
342
343 if load_tests is None:
344 if tests is not None:
345 # tests loaded from package file
346 yield tests
347 # recurse into the package
Michael Foorde28bb152013-11-23 13:29:23 +0000348 yield from self._find_tests(full_path, pattern,
349 namespace=namespace)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000350 else:
Benjamin Peterson886af962010-03-21 23:13:07 +0000351 try:
352 yield load_tests(self, tests, pattern)
353 except Exception as e:
354 yield _make_failed_load_tests(package.__name__, e,
355 self.suiteClass)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000356
357defaultTestLoader = TestLoader()
358
359
360def _makeLoader(prefix, sortUsing, suiteClass=None):
361 loader = TestLoader()
362 loader.sortTestMethodsUsing = sortUsing
363 loader.testMethodPrefix = prefix
364 if suiteClass:
365 loader.suiteClass = suiteClass
366 return loader
367
368def getTestCaseNames(testCaseClass, prefix, sortUsing=util.three_way_cmp):
369 return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
370
371def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,
372 suiteClass=suite.TestSuite):
373 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(
374 testCaseClass)
375
376def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp,
377 suiteClass=suite.TestSuite):
378 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\
379 module)