blob: a8c6492227ac21bcd03f420d5b45cc288b939ee8 [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
Barry Warsawd78742a2014-09-08 14:21:37 -04009import warnings
Benjamin Petersonbed7d042009-07-19 21:01:52 +000010
11from fnmatch import fnmatch
12
13from . import case, suite, util
14
Benjamin Petersondccc1fc2010-03-22 00:15:53 +000015__unittest = True
Benjamin Petersonbed7d042009-07-19 21:01:52 +000016
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000017# what about .pyc or .pyo (etc)
18# we would need to avoid loading the same tests multiple times
19# from '.py', '.pyc' *and* '.pyo'
20VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE)
21
22
23def _make_failed_import_test(name, suiteClass):
Benjamin Peterson434ae772010-03-22 01:46:47 +000024 message = 'Failed to import test module: %s\n%s' % (name, traceback.format_exc())
Benjamin Peterson886af962010-03-21 23:13:07 +000025 return _make_failed_test('ModuleImportFailure', name, ImportError(message),
26 suiteClass)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000027
Benjamin Peterson886af962010-03-21 23:13:07 +000028def _make_failed_load_tests(name, exception, suiteClass):
29 return _make_failed_test('LoadTestsFailure', name, exception, suiteClass)
30
31def _make_failed_test(classname, methodname, exception, suiteClass):
32 def testFailure(self):
33 raise exception
34 attrs = {methodname: testFailure}
35 TestClass = type(classname, (case.TestCase,), attrs)
36 return suiteClass((TestClass(methodname),))
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000037
Ezio Melottieae2b382013-03-01 14:47:50 +020038def _make_skipped_test(methodname, exception, suiteClass):
39 @case.skip(str(exception))
40 def testSkipped(self):
41 pass
42 attrs = {methodname: testSkipped}
43 TestClass = type("ModuleSkipped", (case.TestCase,), attrs)
44 return suiteClass((TestClass(methodname),))
45
Michael Foorde01c62c2012-03-13 00:09:54 -070046def _jython_aware_splitext(path):
47 if path.lower().endswith('$py.class'):
48 return path[:-9]
49 return os.path.splitext(path)[0]
50
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000051
Benjamin Petersonbed7d042009-07-19 21:01:52 +000052class TestLoader(object):
53 """
54 This class is responsible for loading tests according to various criteria
55 and returning them wrapped in a TestSuite
56 """
57 testMethodPrefix = 'test'
58 sortTestMethodsUsing = staticmethod(util.three_way_cmp)
59 suiteClass = suite.TestSuite
60 _top_level_dir = None
61
62 def loadTestsFromTestCase(self, testCaseClass):
63 """Return a suite of all tests cases contained in testCaseClass"""
64 if issubclass(testCaseClass, suite.TestSuite):
Michael Foorde28bb152013-11-23 13:29:23 +000065 raise TypeError("Test cases should not be derived from "
66 "TestSuite. Maybe you meant to derive from "
67 "TestCase?")
Benjamin Petersonbed7d042009-07-19 21:01:52 +000068 testCaseNames = self.getTestCaseNames(testCaseClass)
69 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
70 testCaseNames = ['runTest']
71 loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
72 return loaded_suite
73
Barry Warsawd78742a2014-09-08 14:21:37 -040074 # XXX After Python 3.5, remove backward compatibility hacks for
75 # use_load_tests deprecation via *args and **kws. See issue 16662.
76 def loadTestsFromModule(self, module, *args, pattern=None, **kws):
Benjamin Petersonbed7d042009-07-19 21:01:52 +000077 """Return a suite of all tests cases contained in the given module"""
Barry Warsawd78742a2014-09-08 14:21:37 -040078 # This method used to take an undocumented and unofficial
79 # use_load_tests argument. For backward compatibility, we still
80 # accept the argument (which can also be the first position) but we
81 # ignore it and issue a deprecation warning if it's present.
Barry Warsawbb1e3f12014-09-08 17:29:02 -040082 if len(args) > 0 or 'use_load_tests' in kws:
Barry Warsawd78742a2014-09-08 14:21:37 -040083 warnings.warn('use_load_tests is deprecated and ignored',
84 DeprecationWarning)
85 kws.pop('use_load_tests', None)
86 if len(args) > 1:
Barry Warsawbb1e3f12014-09-08 17:29:02 -040087 # Complain about the number of arguments, but don't forget the
88 # required `module` argument.
89 complaint = len(args) + 1
90 raise TypeError('loadTestsFromModule() takes 1 positional argument but {} were given'.format(complaint))
Barry Warsawd78742a2014-09-08 14:21:37 -040091 if len(kws) != 0:
92 # Since the keyword arguments are unsorted (see PEP 468), just
93 # pick the alphabetically sorted first argument to complain about,
94 # if multiple were given. At least the error message will be
95 # predictable.
96 complaint = sorted(kws)[0]
97 raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint))
Benjamin Petersonbed7d042009-07-19 21:01:52 +000098 tests = []
99 for name in dir(module):
100 obj = getattr(module, name)
101 if isinstance(obj, type) and issubclass(obj, case.TestCase):
102 tests.append(self.loadTestsFromTestCase(obj))
103
104 load_tests = getattr(module, 'load_tests', None)
Michael Foord41647d62010-02-06 00:26:13 +0000105 tests = self.suiteClass(tests)
Barry Warsawd78742a2014-09-08 14:21:37 -0400106 if load_tests is not None:
Benjamin Peterson886af962010-03-21 23:13:07 +0000107 try:
Barry Warsawd78742a2014-09-08 14:21:37 -0400108 return load_tests(self, tests, pattern)
Benjamin Peterson886af962010-03-21 23:13:07 +0000109 except Exception as e:
110 return _make_failed_load_tests(module.__name__, e,
111 self.suiteClass)
Michael Foord41647d62010-02-06 00:26:13 +0000112 return tests
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000113
114 def loadTestsFromName(self, name, module=None):
115 """Return a suite of all tests cases given a string specifier.
116
117 The name may resolve either to a module, a test case class, a
118 test method within a test case class, or a callable object which
119 returns a TestCase or TestSuite instance.
120
121 The method optionally resolves the names relative to a given module.
122 """
123 parts = name.split('.')
124 if module is None:
125 parts_copy = parts[:]
126 while parts_copy:
127 try:
128 module = __import__('.'.join(parts_copy))
129 break
130 except ImportError:
131 del parts_copy[-1]
132 if not parts_copy:
133 raise
134 parts = parts[1:]
135 obj = module
136 for part in parts:
137 parent, obj = obj, getattr(obj, part)
138
139 if isinstance(obj, types.ModuleType):
140 return self.loadTestsFromModule(obj)
141 elif isinstance(obj, type) and issubclass(obj, case.TestCase):
142 return self.loadTestsFromTestCase(obj)
143 elif (isinstance(obj, types.FunctionType) and
144 isinstance(parent, type) and
145 issubclass(parent, case.TestCase)):
R David Murray5e2f5932013-04-11 08:55:45 -0400146 name = parts[-1]
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000147 inst = parent(name)
148 # static methods follow a different path
149 if not isinstance(getattr(inst, name), types.FunctionType):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000150 return self.suiteClass([inst])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000151 elif isinstance(obj, suite.TestSuite):
152 return obj
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200153 if callable(obj):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000154 test = obj()
155 if isinstance(test, suite.TestSuite):
156 return test
157 elif isinstance(test, case.TestCase):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000158 return self.suiteClass([test])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000159 else:
160 raise TypeError("calling %s returned %s, not a test" %
161 (obj, test))
162 else:
163 raise TypeError("don't know how to make test from: %s" % obj)
164
165 def loadTestsFromNames(self, names, module=None):
166 """Return a suite of all tests cases found using the given sequence
167 of string specifiers. See 'loadTestsFromName()'.
168 """
169 suites = [self.loadTestsFromName(name, module) for name in names]
170 return self.suiteClass(suites)
171
172 def getTestCaseNames(self, testCaseClass):
173 """Return a sorted sequence of method names found within testCaseClass
174 """
175 def isTestMethod(attrname, testCaseClass=testCaseClass,
176 prefix=self.testMethodPrefix):
177 return attrname.startswith(prefix) and \
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200178 callable(getattr(testCaseClass, attrname))
Senthil Kumaranf27be5c2011-11-25 02:08:39 +0800179 testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000180 if self.sortTestMethodsUsing:
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000181 testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000182 return testFnNames
183
184 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
185 """Find and return all test modules from the specified start
Michael Foord6bcfade2010-11-20 17:22:21 +0000186 directory, recursing into subdirectories to find them and return all
187 tests found within them. Only test files that match the pattern will
188 be loaded. (Using shell style pattern matching.)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000189
190 All test modules must be importable from the top level of the project.
191 If the start directory is not the top level directory then the top
192 level directory must be specified separately.
193
194 If a test package name (directory with '__init__.py') matches the
195 pattern then the package will be checked for a 'load_tests' function. If
196 this exists then it will be called with loader, tests, pattern.
197
198 If load_tests exists then discovery does *not* recurse into the package,
199 load_tests is responsible for loading all tests in the package.
200
201 The pattern is deliberately not stored as a loader attribute so that
202 packages can continue discovery themselves. top_level_dir is stored so
203 load_tests does not need to pass this argument in to loader.discover().
Michael Foord80cbc9e2013-03-18 17:50:12 -0700204
205 Paths are sorted before being imported to ensure reproducible execution
206 order even on filesystems with non-alphabetical ordering like ext3/4.
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000207 """
Benjamin Petersonb48af542010-04-11 20:43:16 +0000208 set_implicit_top = False
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000209 if top_level_dir is None and self._top_level_dir is not None:
210 # make top_level_dir optional if called from load_tests in a package
211 top_level_dir = self._top_level_dir
212 elif top_level_dir is None:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000213 set_implicit_top = True
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000214 top_level_dir = start_dir
215
Benjamin Petersonb48af542010-04-11 20:43:16 +0000216 top_level_dir = os.path.abspath(top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000217
218 if not top_level_dir in sys.path:
219 # all test modules must be importable from the top level directory
Michael Foord3b2494f2010-05-07 23:42:40 +0000220 # should we *unconditionally* put the start directory in first
221 # in sys.path to minimise likelihood of conflicts between installed
222 # modules and development versions?
223 sys.path.insert(0, top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000224 self._top_level_dir = top_level_dir
225
Benjamin Petersonb48af542010-04-11 20:43:16 +0000226 is_not_importable = False
Michael Foorde28bb152013-11-23 13:29:23 +0000227 is_namespace = False
228 tests = []
Benjamin Petersonb48af542010-04-11 20:43:16 +0000229 if os.path.isdir(os.path.abspath(start_dir)):
230 start_dir = os.path.abspath(start_dir)
231 if start_dir != top_level_dir:
232 is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))
233 else:
234 # support for discovery from dotted module names
235 try:
236 __import__(start_dir)
237 except ImportError:
238 is_not_importable = True
239 else:
240 the_module = sys.modules[start_dir]
241 top_part = start_dir.split('.')[0]
Michael Foorde28bb152013-11-23 13:29:23 +0000242 try:
243 start_dir = os.path.abspath(
244 os.path.dirname((the_module.__file__)))
245 except AttributeError:
246 # look for namespace packages
247 try:
248 spec = the_module.__spec__
249 except AttributeError:
250 spec = None
251
252 if spec and spec.loader is None:
253 if spec.submodule_search_locations is not None:
254 is_namespace = True
255
256 for path in the_module.__path__:
257 if (not set_implicit_top and
258 not path.startswith(top_level_dir)):
259 continue
260 self._top_level_dir = \
261 (path.split(the_module.__name__
262 .replace(".", os.path.sep))[0])
263 tests.extend(self._find_tests(path,
264 pattern,
265 namespace=True))
266 elif the_module.__name__ in sys.builtin_module_names:
267 # builtin module
268 raise TypeError('Can not use builtin modules '
269 'as dotted module names') from None
270 else:
271 raise TypeError(
272 'don\'t know how to discover from {!r}'
273 .format(the_module)) from None
274
Benjamin Petersonb48af542010-04-11 20:43:16 +0000275 if set_implicit_top:
Michael Foorde28bb152013-11-23 13:29:23 +0000276 if not is_namespace:
277 self._top_level_dir = \
278 self._get_directory_containing_module(top_part)
279 sys.path.remove(top_level_dir)
280 else:
281 sys.path.remove(top_level_dir)
Benjamin Petersonb48af542010-04-11 20:43:16 +0000282
283 if is_not_importable:
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000284 raise ImportError('Start directory is not importable: %r' % start_dir)
285
Michael Foorde28bb152013-11-23 13:29:23 +0000286 if not is_namespace:
287 tests = list(self._find_tests(start_dir, pattern))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000288 return self.suiteClass(tests)
289
Benjamin Petersonb48af542010-04-11 20:43:16 +0000290 def _get_directory_containing_module(self, module_name):
291 module = sys.modules[module_name]
292 full_path = os.path.abspath(module.__file__)
293
294 if os.path.basename(full_path).lower().startswith('__init__.py'):
295 return os.path.dirname(os.path.dirname(full_path))
296 else:
297 # here we have been given a module rather than a package - so
298 # all we can do is search the *same* directory the module is in
299 # should an exception be raised instead
300 return os.path.dirname(full_path)
301
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000302 def _get_name_from_path(self, path):
Michael Foorde01c62c2012-03-13 00:09:54 -0700303 path = _jython_aware_splitext(os.path.normpath(path))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000304
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000305 _relpath = os.path.relpath(path, self._top_level_dir)
306 assert not os.path.isabs(_relpath), "Path must be within the project"
307 assert not _relpath.startswith('..'), "Path must be within the project"
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000308
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000309 name = _relpath.replace(os.path.sep, '.')
310 return name
311
312 def _get_module_from_name(self, name):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000313 __import__(name)
314 return sys.modules[name]
315
Michael Foord4107d312010-06-05 10:45:41 +0000316 def _match_path(self, path, full_path, pattern):
317 # override this method to use alternative matching strategy
318 return fnmatch(path, pattern)
319
Michael Foorde28bb152013-11-23 13:29:23 +0000320 def _find_tests(self, start_dir, pattern, namespace=False):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000321 """Used by discovery. Yields test suites it loads."""
Michael Foord80cbc9e2013-03-18 17:50:12 -0700322 paths = sorted(os.listdir(start_dir))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000323
324 for path in paths:
325 full_path = os.path.join(start_dir, path)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000326 if os.path.isfile(full_path):
327 if not VALID_MODULE_NAME.match(path):
328 # valid Python identifiers only
329 continue
Michael Foord4107d312010-06-05 10:45:41 +0000330 if not self._match_path(path, full_path, pattern):
331 continue
332 # if the test file matches, load it
333 name = self._get_name_from_path(full_path)
334 try:
335 module = self._get_module_from_name(name)
Ezio Melottieae2b382013-03-01 14:47:50 +0200336 except case.SkipTest as e:
337 yield _make_skipped_test(name, e, self.suiteClass)
Michael Foord4107d312010-06-05 10:45:41 +0000338 except:
339 yield _make_failed_import_test(name, self.suiteClass)
340 else:
341 mod_file = os.path.abspath(getattr(module, '__file__', full_path))
Antoine Pitroud5d0bc32013-10-23 19:11:29 +0200342 realpath = _jython_aware_splitext(os.path.realpath(mod_file))
343 fullpath_noext = _jython_aware_splitext(os.path.realpath(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000344 if realpath.lower() != fullpath_noext.lower():
345 module_dir = os.path.dirname(realpath)
Michael Foorde01c62c2012-03-13 00:09:54 -0700346 mod_name = _jython_aware_splitext(os.path.basename(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000347 expected_dir = os.path.dirname(full_path)
348 msg = ("%r module incorrectly imported from %r. Expected %r. "
349 "Is this module globally installed?")
350 raise ImportError(msg % (mod_name, module_dir, expected_dir))
Barry Warsawd78742a2014-09-08 14:21:37 -0400351 yield self.loadTestsFromModule(module, pattern=pattern)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000352 elif os.path.isdir(full_path):
Michael Foorde28bb152013-11-23 13:29:23 +0000353 if (not namespace and
354 not os.path.isfile(os.path.join(full_path, '__init__.py'))):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000355 continue
356
357 load_tests = None
358 tests = None
Barry Warsawd78742a2014-09-08 14:21:37 -0400359 name = self._get_name_from_path(full_path)
360 try:
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000361 package = self._get_module_from_name(name)
Barry Warsawd78742a2014-09-08 14:21:37 -0400362 except case.SkipTest as e:
363 yield _make_skipped_test(name, e, self.suiteClass)
364 except:
365 yield _make_failed_import_test(name, self.suiteClass)
366 else:
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000367 load_tests = getattr(package, 'load_tests', None)
Barry Warsawd78742a2014-09-08 14:21:37 -0400368 tests = self.loadTestsFromModule(package, pattern=pattern)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000369 if tests is not None:
370 # tests loaded from package file
371 yield tests
Barry Warsawd78742a2014-09-08 14:21:37 -0400372
373 if load_tests is not None:
374 # loadTestsFromModule(package) has load_tests for us.
375 continue
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000376 # recurse into the package
Michael Foorde28bb152013-11-23 13:29:23 +0000377 yield from self._find_tests(full_path, pattern,
378 namespace=namespace)
Barry Warsawd78742a2014-09-08 14:21:37 -0400379
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000380
381defaultTestLoader = TestLoader()
382
383
384def _makeLoader(prefix, sortUsing, suiteClass=None):
385 loader = TestLoader()
386 loader.sortTestMethodsUsing = sortUsing
387 loader.testMethodPrefix = prefix
388 if suiteClass:
389 loader.suiteClass = suiteClass
390 return loader
391
392def getTestCaseNames(testCaseClass, prefix, sortUsing=util.three_way_cmp):
393 return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
394
395def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,
396 suiteClass=suite.TestSuite):
397 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(
398 testCaseClass)
399
400def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp,
401 suiteClass=suite.TestSuite):
402 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\
403 module)