blob: aaee52a1761823f60aa051ee380ef78f8262268a [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):
Robert Collinsf920c212014-10-20 13:24:05 +130024 message = 'Failed to import test module: %s\n%s' % (
25 name, traceback.format_exc())
Benjamin Peterson886af962010-03-21 23:13:07 +000026 return _make_failed_test('ModuleImportFailure', name, ImportError(message),
Robert Collinsf920c212014-10-20 13:24:05 +130027 suiteClass, message)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000028
Benjamin Peterson886af962010-03-21 23:13:07 +000029def _make_failed_load_tests(name, exception, suiteClass):
Robert Collinsf920c212014-10-20 13:24:05 +130030 message = 'Failed to call load_tests:\n%s' % (traceback.format_exc(),)
31 return _make_failed_test(
32 'LoadTestsFailure', name, exception, suiteClass, message)
Benjamin Peterson886af962010-03-21 23:13:07 +000033
Robert Collinsf920c212014-10-20 13:24:05 +130034def _make_failed_test(classname, methodname, exception, suiteClass, message):
Benjamin Peterson886af962010-03-21 23:13:07 +000035 def testFailure(self):
36 raise exception
37 attrs = {methodname: testFailure}
38 TestClass = type(classname, (case.TestCase,), attrs)
Robert Collinsf920c212014-10-20 13:24:05 +130039 return suiteClass((TestClass(methodname),)), message
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000040
Ezio Melottieae2b382013-03-01 14:47:50 +020041def _make_skipped_test(methodname, exception, suiteClass):
42 @case.skip(str(exception))
43 def testSkipped(self):
44 pass
45 attrs = {methodname: testSkipped}
46 TestClass = type("ModuleSkipped", (case.TestCase,), attrs)
47 return suiteClass((TestClass(methodname),))
48
Michael Foorde01c62c2012-03-13 00:09:54 -070049def _jython_aware_splitext(path):
50 if path.lower().endswith('$py.class'):
51 return path[:-9]
52 return os.path.splitext(path)[0]
53
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000054
Benjamin Petersonbed7d042009-07-19 21:01:52 +000055class TestLoader(object):
56 """
57 This class is responsible for loading tests according to various criteria
58 and returning them wrapped in a TestSuite
59 """
60 testMethodPrefix = 'test'
61 sortTestMethodsUsing = staticmethod(util.three_way_cmp)
62 suiteClass = suite.TestSuite
63 _top_level_dir = None
64
Robert Collinsf920c212014-10-20 13:24:05 +130065 def __init__(self):
66 super(TestLoader, self).__init__()
67 self.errors = []
68
Benjamin Petersonbed7d042009-07-19 21:01:52 +000069 def loadTestsFromTestCase(self, testCaseClass):
70 """Return a suite of all tests cases contained in testCaseClass"""
71 if issubclass(testCaseClass, suite.TestSuite):
Michael Foorde28bb152013-11-23 13:29:23 +000072 raise TypeError("Test cases should not be derived from "
73 "TestSuite. Maybe you meant to derive from "
74 "TestCase?")
Benjamin Petersonbed7d042009-07-19 21:01:52 +000075 testCaseNames = self.getTestCaseNames(testCaseClass)
76 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
77 testCaseNames = ['runTest']
78 loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
79 return loaded_suite
80
Barry Warsawd78742a2014-09-08 14:21:37 -040081 # XXX After Python 3.5, remove backward compatibility hacks for
82 # use_load_tests deprecation via *args and **kws. See issue 16662.
83 def loadTestsFromModule(self, module, *args, pattern=None, **kws):
Benjamin Petersonbed7d042009-07-19 21:01:52 +000084 """Return a suite of all tests cases contained in the given module"""
Barry Warsawd78742a2014-09-08 14:21:37 -040085 # This method used to take an undocumented and unofficial
86 # use_load_tests argument. For backward compatibility, we still
87 # accept the argument (which can also be the first position) but we
88 # ignore it and issue a deprecation warning if it's present.
Barry Warsawbb1e3f12014-09-08 17:29:02 -040089 if len(args) > 0 or 'use_load_tests' in kws:
Barry Warsawd78742a2014-09-08 14:21:37 -040090 warnings.warn('use_load_tests is deprecated and ignored',
91 DeprecationWarning)
92 kws.pop('use_load_tests', None)
93 if len(args) > 1:
Barry Warsawbb1e3f12014-09-08 17:29:02 -040094 # Complain about the number of arguments, but don't forget the
95 # required `module` argument.
96 complaint = len(args) + 1
97 raise TypeError('loadTestsFromModule() takes 1 positional argument but {} were given'.format(complaint))
Barry Warsawd78742a2014-09-08 14:21:37 -040098 if len(kws) != 0:
99 # Since the keyword arguments are unsorted (see PEP 468), just
100 # pick the alphabetically sorted first argument to complain about,
101 # if multiple were given. At least the error message will be
102 # predictable.
103 complaint = sorted(kws)[0]
104 raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000105 tests = []
106 for name in dir(module):
107 obj = getattr(module, name)
108 if isinstance(obj, type) and issubclass(obj, case.TestCase):
109 tests.append(self.loadTestsFromTestCase(obj))
110
111 load_tests = getattr(module, 'load_tests', None)
Michael Foord41647d62010-02-06 00:26:13 +0000112 tests = self.suiteClass(tests)
Barry Warsawd78742a2014-09-08 14:21:37 -0400113 if load_tests is not None:
Benjamin Peterson886af962010-03-21 23:13:07 +0000114 try:
Barry Warsawd78742a2014-09-08 14:21:37 -0400115 return load_tests(self, tests, pattern)
Benjamin Peterson886af962010-03-21 23:13:07 +0000116 except Exception as e:
Robert Collinsf920c212014-10-20 13:24:05 +1300117 error_case, error_message = _make_failed_load_tests(
118 module.__name__, e, self.suiteClass)
119 self.errors.append(error_message)
120 return error_case
Michael Foord41647d62010-02-06 00:26:13 +0000121 return tests
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000122
123 def loadTestsFromName(self, name, module=None):
124 """Return a suite of all tests cases given a string specifier.
125
126 The name may resolve either to a module, a test case class, a
127 test method within a test case class, or a callable object which
128 returns a TestCase or TestSuite instance.
129
130 The method optionally resolves the names relative to a given module.
131 """
132 parts = name.split('.')
133 if module is None:
134 parts_copy = parts[:]
135 while parts_copy:
136 try:
137 module = __import__('.'.join(parts_copy))
138 break
139 except ImportError:
140 del parts_copy[-1]
141 if not parts_copy:
142 raise
143 parts = parts[1:]
144 obj = module
145 for part in parts:
146 parent, obj = obj, getattr(obj, part)
147
148 if isinstance(obj, types.ModuleType):
149 return self.loadTestsFromModule(obj)
150 elif isinstance(obj, type) and issubclass(obj, case.TestCase):
151 return self.loadTestsFromTestCase(obj)
152 elif (isinstance(obj, types.FunctionType) and
153 isinstance(parent, type) and
154 issubclass(parent, case.TestCase)):
R David Murray5e2f5932013-04-11 08:55:45 -0400155 name = parts[-1]
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000156 inst = parent(name)
157 # static methods follow a different path
158 if not isinstance(getattr(inst, name), types.FunctionType):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000159 return self.suiteClass([inst])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000160 elif isinstance(obj, suite.TestSuite):
161 return obj
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200162 if callable(obj):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000163 test = obj()
164 if isinstance(test, suite.TestSuite):
165 return test
166 elif isinstance(test, case.TestCase):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000167 return self.suiteClass([test])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000168 else:
169 raise TypeError("calling %s returned %s, not a test" %
170 (obj, test))
171 else:
172 raise TypeError("don't know how to make test from: %s" % obj)
173
174 def loadTestsFromNames(self, names, module=None):
175 """Return a suite of all tests cases found using the given sequence
176 of string specifiers. See 'loadTestsFromName()'.
177 """
178 suites = [self.loadTestsFromName(name, module) for name in names]
179 return self.suiteClass(suites)
180
181 def getTestCaseNames(self, testCaseClass):
182 """Return a sorted sequence of method names found within testCaseClass
183 """
184 def isTestMethod(attrname, testCaseClass=testCaseClass,
185 prefix=self.testMethodPrefix):
186 return attrname.startswith(prefix) and \
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200187 callable(getattr(testCaseClass, attrname))
Senthil Kumaranf27be5c2011-11-25 02:08:39 +0800188 testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000189 if self.sortTestMethodsUsing:
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000190 testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000191 return testFnNames
192
193 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
194 """Find and return all test modules from the specified start
Michael Foord6bcfade2010-11-20 17:22:21 +0000195 directory, recursing into subdirectories to find them and return all
196 tests found within them. Only test files that match the pattern will
197 be loaded. (Using shell style pattern matching.)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000198
199 All test modules must be importable from the top level of the project.
200 If the start directory is not the top level directory then the top
201 level directory must be specified separately.
202
203 If a test package name (directory with '__init__.py') matches the
204 pattern then the package will be checked for a 'load_tests' function. If
205 this exists then it will be called with loader, tests, pattern.
206
207 If load_tests exists then discovery does *not* recurse into the package,
208 load_tests is responsible for loading all tests in the package.
209
210 The pattern is deliberately not stored as a loader attribute so that
211 packages can continue discovery themselves. top_level_dir is stored so
212 load_tests does not need to pass this argument in to loader.discover().
Michael Foord80cbc9e2013-03-18 17:50:12 -0700213
214 Paths are sorted before being imported to ensure reproducible execution
215 order even on filesystems with non-alphabetical ordering like ext3/4.
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000216 """
Benjamin Petersonb48af542010-04-11 20:43:16 +0000217 set_implicit_top = False
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000218 if top_level_dir is None and self._top_level_dir is not None:
219 # make top_level_dir optional if called from load_tests in a package
220 top_level_dir = self._top_level_dir
221 elif top_level_dir is None:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000222 set_implicit_top = True
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000223 top_level_dir = start_dir
224
Benjamin Petersonb48af542010-04-11 20:43:16 +0000225 top_level_dir = os.path.abspath(top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000226
227 if not top_level_dir in sys.path:
228 # all test modules must be importable from the top level directory
Michael Foord3b2494f2010-05-07 23:42:40 +0000229 # should we *unconditionally* put the start directory in first
230 # in sys.path to minimise likelihood of conflicts between installed
231 # modules and development versions?
232 sys.path.insert(0, top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000233 self._top_level_dir = top_level_dir
234
Benjamin Petersonb48af542010-04-11 20:43:16 +0000235 is_not_importable = False
Michael Foorde28bb152013-11-23 13:29:23 +0000236 is_namespace = False
237 tests = []
Benjamin Petersonb48af542010-04-11 20:43:16 +0000238 if os.path.isdir(os.path.abspath(start_dir)):
239 start_dir = os.path.abspath(start_dir)
240 if start_dir != top_level_dir:
241 is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))
242 else:
243 # support for discovery from dotted module names
244 try:
245 __import__(start_dir)
246 except ImportError:
247 is_not_importable = True
248 else:
249 the_module = sys.modules[start_dir]
250 top_part = start_dir.split('.')[0]
Michael Foorde28bb152013-11-23 13:29:23 +0000251 try:
252 start_dir = os.path.abspath(
253 os.path.dirname((the_module.__file__)))
254 except AttributeError:
255 # look for namespace packages
256 try:
257 spec = the_module.__spec__
258 except AttributeError:
259 spec = None
260
261 if spec and spec.loader is None:
262 if spec.submodule_search_locations is not None:
263 is_namespace = True
264
265 for path in the_module.__path__:
266 if (not set_implicit_top and
267 not path.startswith(top_level_dir)):
268 continue
269 self._top_level_dir = \
270 (path.split(the_module.__name__
271 .replace(".", os.path.sep))[0])
272 tests.extend(self._find_tests(path,
273 pattern,
274 namespace=True))
275 elif the_module.__name__ in sys.builtin_module_names:
276 # builtin module
277 raise TypeError('Can not use builtin modules '
278 'as dotted module names') from None
279 else:
280 raise TypeError(
281 'don\'t know how to discover from {!r}'
282 .format(the_module)) from None
283
Benjamin Petersonb48af542010-04-11 20:43:16 +0000284 if set_implicit_top:
Michael Foorde28bb152013-11-23 13:29:23 +0000285 if not is_namespace:
286 self._top_level_dir = \
287 self._get_directory_containing_module(top_part)
288 sys.path.remove(top_level_dir)
289 else:
290 sys.path.remove(top_level_dir)
Benjamin Petersonb48af542010-04-11 20:43:16 +0000291
292 if is_not_importable:
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000293 raise ImportError('Start directory is not importable: %r' % start_dir)
294
Michael Foorde28bb152013-11-23 13:29:23 +0000295 if not is_namespace:
296 tests = list(self._find_tests(start_dir, pattern))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000297 return self.suiteClass(tests)
298
Benjamin Petersonb48af542010-04-11 20:43:16 +0000299 def _get_directory_containing_module(self, module_name):
300 module = sys.modules[module_name]
301 full_path = os.path.abspath(module.__file__)
302
303 if os.path.basename(full_path).lower().startswith('__init__.py'):
304 return os.path.dirname(os.path.dirname(full_path))
305 else:
306 # here we have been given a module rather than a package - so
307 # all we can do is search the *same* directory the module is in
308 # should an exception be raised instead
309 return os.path.dirname(full_path)
310
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000311 def _get_name_from_path(self, path):
Michael Foorde01c62c2012-03-13 00:09:54 -0700312 path = _jython_aware_splitext(os.path.normpath(path))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000313
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000314 _relpath = os.path.relpath(path, self._top_level_dir)
315 assert not os.path.isabs(_relpath), "Path must be within the project"
316 assert not _relpath.startswith('..'), "Path must be within the project"
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000317
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000318 name = _relpath.replace(os.path.sep, '.')
319 return name
320
321 def _get_module_from_name(self, name):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000322 __import__(name)
323 return sys.modules[name]
324
Michael Foord4107d312010-06-05 10:45:41 +0000325 def _match_path(self, path, full_path, pattern):
326 # override this method to use alternative matching strategy
327 return fnmatch(path, pattern)
328
Michael Foorde28bb152013-11-23 13:29:23 +0000329 def _find_tests(self, start_dir, pattern, namespace=False):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000330 """Used by discovery. Yields test suites it loads."""
Michael Foord80cbc9e2013-03-18 17:50:12 -0700331 paths = sorted(os.listdir(start_dir))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000332
333 for path in paths:
334 full_path = os.path.join(start_dir, path)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000335 if os.path.isfile(full_path):
336 if not VALID_MODULE_NAME.match(path):
337 # valid Python identifiers only
338 continue
Michael Foord4107d312010-06-05 10:45:41 +0000339 if not self._match_path(path, full_path, pattern):
340 continue
341 # if the test file matches, load it
342 name = self._get_name_from_path(full_path)
343 try:
344 module = self._get_module_from_name(name)
Ezio Melottieae2b382013-03-01 14:47:50 +0200345 except case.SkipTest as e:
346 yield _make_skipped_test(name, e, self.suiteClass)
Michael Foord4107d312010-06-05 10:45:41 +0000347 except:
Robert Collinsf920c212014-10-20 13:24:05 +1300348 error_case, error_message = \
349 _make_failed_import_test(name, self.suiteClass)
350 self.errors.append(error_message)
351 yield error_case
Michael Foord4107d312010-06-05 10:45:41 +0000352 else:
353 mod_file = os.path.abspath(getattr(module, '__file__', full_path))
Antoine Pitroud5d0bc32013-10-23 19:11:29 +0200354 realpath = _jython_aware_splitext(os.path.realpath(mod_file))
355 fullpath_noext = _jython_aware_splitext(os.path.realpath(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000356 if realpath.lower() != fullpath_noext.lower():
357 module_dir = os.path.dirname(realpath)
Michael Foorde01c62c2012-03-13 00:09:54 -0700358 mod_name = _jython_aware_splitext(os.path.basename(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000359 expected_dir = os.path.dirname(full_path)
360 msg = ("%r module incorrectly imported from %r. Expected %r. "
361 "Is this module globally installed?")
362 raise ImportError(msg % (mod_name, module_dir, expected_dir))
Barry Warsawd78742a2014-09-08 14:21:37 -0400363 yield self.loadTestsFromModule(module, pattern=pattern)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000364 elif os.path.isdir(full_path):
Michael Foorde28bb152013-11-23 13:29:23 +0000365 if (not namespace and
366 not os.path.isfile(os.path.join(full_path, '__init__.py'))):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000367 continue
368
369 load_tests = None
370 tests = None
Barry Warsawd78742a2014-09-08 14:21:37 -0400371 name = self._get_name_from_path(full_path)
372 try:
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000373 package = self._get_module_from_name(name)
Barry Warsawd78742a2014-09-08 14:21:37 -0400374 except case.SkipTest as e:
375 yield _make_skipped_test(name, e, self.suiteClass)
376 except:
Robert Collinsf920c212014-10-20 13:24:05 +1300377 error_case, error_message = \
378 _make_failed_import_test(name, self.suiteClass)
379 self.errors.append(error_message)
380 yield error_case
Barry Warsawd78742a2014-09-08 14:21:37 -0400381 else:
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000382 load_tests = getattr(package, 'load_tests', None)
Barry Warsawd78742a2014-09-08 14:21:37 -0400383 tests = self.loadTestsFromModule(package, pattern=pattern)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000384 if tests is not None:
385 # tests loaded from package file
386 yield tests
Barry Warsawd78742a2014-09-08 14:21:37 -0400387
388 if load_tests is not None:
389 # loadTestsFromModule(package) has load_tests for us.
390 continue
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000391 # recurse into the package
Michael Foorde28bb152013-11-23 13:29:23 +0000392 yield from self._find_tests(full_path, pattern,
393 namespace=namespace)
Barry Warsawd78742a2014-09-08 14:21:37 -0400394
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000395
396defaultTestLoader = TestLoader()
397
398
399def _makeLoader(prefix, sortUsing, suiteClass=None):
400 loader = TestLoader()
401 loader.sortTestMethodsUsing = sortUsing
402 loader.testMethodPrefix = prefix
403 if suiteClass:
404 loader.suiteClass = suiteClass
405 return loader
406
407def getTestCaseNames(testCaseClass, prefix, sortUsing=util.three_way_cmp):
408 return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
409
410def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,
411 suiteClass=suite.TestSuite):
412 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(
413 testCaseClass)
414
415def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp,
416 suiteClass=suite.TestSuite):
417 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\
418 module)