blob: a5ac737fc26b4801a475075d2b9ac1c5a536da0d [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
Michael Foorde01c62c2012-03-13 00:09:54 -070037def _jython_aware_splitext(path):
38 if path.lower().endswith('$py.class'):
39 return path[:-9]
40 return os.path.splitext(path)[0]
41
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000042
Benjamin Petersonbed7d042009-07-19 21:01:52 +000043class TestLoader(object):
44 """
45 This class is responsible for loading tests according to various criteria
46 and returning them wrapped in a TestSuite
47 """
48 testMethodPrefix = 'test'
49 sortTestMethodsUsing = staticmethod(util.three_way_cmp)
50 suiteClass = suite.TestSuite
51 _top_level_dir = None
52
53 def loadTestsFromTestCase(self, testCaseClass):
54 """Return a suite of all tests cases contained in testCaseClass"""
55 if issubclass(testCaseClass, suite.TestSuite):
56 raise TypeError("Test cases should not be derived from TestSuite." \
57 " Maybe you meant to derive from TestCase?")
58 testCaseNames = self.getTestCaseNames(testCaseClass)
59 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
60 testCaseNames = ['runTest']
61 loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
62 return loaded_suite
63
64 def loadTestsFromModule(self, module, use_load_tests=True):
65 """Return a suite of all tests cases contained in the given module"""
66 tests = []
67 for name in dir(module):
68 obj = getattr(module, name)
69 if isinstance(obj, type) and issubclass(obj, case.TestCase):
70 tests.append(self.loadTestsFromTestCase(obj))
71
72 load_tests = getattr(module, 'load_tests', None)
Michael Foord41647d62010-02-06 00:26:13 +000073 tests = self.suiteClass(tests)
Benjamin Petersonbed7d042009-07-19 21:01:52 +000074 if use_load_tests and load_tests is not None:
Benjamin Peterson886af962010-03-21 23:13:07 +000075 try:
76 return load_tests(self, tests, None)
77 except Exception as e:
78 return _make_failed_load_tests(module.__name__, e,
79 self.suiteClass)
Michael Foord41647d62010-02-06 00:26:13 +000080 return tests
Benjamin Petersonbed7d042009-07-19 21:01:52 +000081
82 def loadTestsFromName(self, name, module=None):
83 """Return a suite of all tests cases given a string specifier.
84
85 The name may resolve either to a module, a test case class, a
86 test method within a test case class, or a callable object which
87 returns a TestCase or TestSuite instance.
88
89 The method optionally resolves the names relative to a given module.
90 """
91 parts = name.split('.')
92 if module is None:
93 parts_copy = parts[:]
94 while parts_copy:
95 try:
96 module = __import__('.'.join(parts_copy))
97 break
98 except ImportError:
99 del parts_copy[-1]
100 if not parts_copy:
101 raise
102 parts = parts[1:]
103 obj = module
104 for part in parts:
105 parent, obj = obj, getattr(obj, part)
106
107 if isinstance(obj, types.ModuleType):
108 return self.loadTestsFromModule(obj)
109 elif isinstance(obj, type) and issubclass(obj, case.TestCase):
110 return self.loadTestsFromTestCase(obj)
111 elif (isinstance(obj, types.FunctionType) and
112 isinstance(parent, type) and
113 issubclass(parent, case.TestCase)):
114 name = obj.__name__
115 inst = parent(name)
116 # static methods follow a different path
117 if not isinstance(getattr(inst, name), types.FunctionType):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000118 return self.suiteClass([inst])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000119 elif isinstance(obj, suite.TestSuite):
120 return obj
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200121 if callable(obj):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000122 test = obj()
123 if isinstance(test, suite.TestSuite):
124 return test
125 elif isinstance(test, case.TestCase):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000126 return self.suiteClass([test])
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000127 else:
128 raise TypeError("calling %s returned %s, not a test" %
129 (obj, test))
130 else:
131 raise TypeError("don't know how to make test from: %s" % obj)
132
133 def loadTestsFromNames(self, names, module=None):
134 """Return a suite of all tests cases found using the given sequence
135 of string specifiers. See 'loadTestsFromName()'.
136 """
137 suites = [self.loadTestsFromName(name, module) for name in names]
138 return self.suiteClass(suites)
139
140 def getTestCaseNames(self, testCaseClass):
141 """Return a sorted sequence of method names found within testCaseClass
142 """
143 def isTestMethod(attrname, testCaseClass=testCaseClass,
144 prefix=self.testMethodPrefix):
145 return attrname.startswith(prefix) and \
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200146 callable(getattr(testCaseClass, attrname))
Senthil Kumaranf27be5c2011-11-25 02:08:39 +0800147 testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000148 if self.sortTestMethodsUsing:
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000149 testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000150 return testFnNames
151
152 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
153 """Find and return all test modules from the specified start
Michael Foord6bcfade2010-11-20 17:22:21 +0000154 directory, recursing into subdirectories to find them and return all
155 tests found within them. Only test files that match the pattern will
156 be loaded. (Using shell style pattern matching.)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000157
158 All test modules must be importable from the top level of the project.
159 If the start directory is not the top level directory then the top
160 level directory must be specified separately.
161
162 If a test package name (directory with '__init__.py') matches the
163 pattern then the package will be checked for a 'load_tests' function. If
164 this exists then it will be called with loader, tests, pattern.
165
166 If load_tests exists then discovery does *not* recurse into the package,
167 load_tests is responsible for loading all tests in the package.
168
169 The pattern is deliberately not stored as a loader attribute so that
170 packages can continue discovery themselves. top_level_dir is stored so
171 load_tests does not need to pass this argument in to loader.discover().
172 """
Benjamin Petersonb48af542010-04-11 20:43:16 +0000173 set_implicit_top = False
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000174 if top_level_dir is None and self._top_level_dir is not None:
175 # make top_level_dir optional if called from load_tests in a package
176 top_level_dir = self._top_level_dir
177 elif top_level_dir is None:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000178 set_implicit_top = True
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000179 top_level_dir = start_dir
180
Benjamin Petersonb48af542010-04-11 20:43:16 +0000181 top_level_dir = os.path.abspath(top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000182
183 if not top_level_dir in sys.path:
184 # all test modules must be importable from the top level directory
Michael Foord3b2494f2010-05-07 23:42:40 +0000185 # should we *unconditionally* put the start directory in first
186 # in sys.path to minimise likelihood of conflicts between installed
187 # modules and development versions?
188 sys.path.insert(0, top_level_dir)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000189 self._top_level_dir = top_level_dir
190
Benjamin Petersonb48af542010-04-11 20:43:16 +0000191 is_not_importable = False
192 if os.path.isdir(os.path.abspath(start_dir)):
193 start_dir = os.path.abspath(start_dir)
194 if start_dir != top_level_dir:
195 is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))
196 else:
197 # support for discovery from dotted module names
198 try:
199 __import__(start_dir)
200 except ImportError:
201 is_not_importable = True
202 else:
203 the_module = sys.modules[start_dir]
204 top_part = start_dir.split('.')[0]
205 start_dir = os.path.abspath(os.path.dirname((the_module.__file__)))
206 if set_implicit_top:
207 self._top_level_dir = self._get_directory_containing_module(top_part)
208 sys.path.remove(top_level_dir)
209
210 if is_not_importable:
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000211 raise ImportError('Start directory is not importable: %r' % start_dir)
212
213 tests = list(self._find_tests(start_dir, pattern))
214 return self.suiteClass(tests)
215
Benjamin Petersonb48af542010-04-11 20:43:16 +0000216 def _get_directory_containing_module(self, module_name):
217 module = sys.modules[module_name]
218 full_path = os.path.abspath(module.__file__)
219
220 if os.path.basename(full_path).lower().startswith('__init__.py'):
221 return os.path.dirname(os.path.dirname(full_path))
222 else:
223 # here we have been given a module rather than a package - so
224 # all we can do is search the *same* directory the module is in
225 # should an exception be raised instead
226 return os.path.dirname(full_path)
227
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000228 def _get_name_from_path(self, path):
Michael Foorde01c62c2012-03-13 00:09:54 -0700229 path = _jython_aware_splitext(os.path.normpath(path))
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000230
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000231 _relpath = os.path.relpath(path, self._top_level_dir)
232 assert not os.path.isabs(_relpath), "Path must be within the project"
233 assert not _relpath.startswith('..'), "Path must be within the project"
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000234
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000235 name = _relpath.replace(os.path.sep, '.')
236 return name
237
238 def _get_module_from_name(self, name):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000239 __import__(name)
240 return sys.modules[name]
241
Michael Foord4107d312010-06-05 10:45:41 +0000242 def _match_path(self, path, full_path, pattern):
243 # override this method to use alternative matching strategy
244 return fnmatch(path, pattern)
245
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000246 def _find_tests(self, start_dir, pattern):
247 """Used by discovery. Yields test suites it loads."""
248 paths = os.listdir(start_dir)
249
250 for path in paths:
251 full_path = os.path.join(start_dir, path)
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000252 if os.path.isfile(full_path):
253 if not VALID_MODULE_NAME.match(path):
254 # valid Python identifiers only
255 continue
Michael Foord4107d312010-06-05 10:45:41 +0000256 if not self._match_path(path, full_path, pattern):
257 continue
258 # if the test file matches, load it
259 name = self._get_name_from_path(full_path)
260 try:
261 module = self._get_module_from_name(name)
262 except:
263 yield _make_failed_import_test(name, self.suiteClass)
264 else:
265 mod_file = os.path.abspath(getattr(module, '__file__', full_path))
Michael Foorde01c62c2012-03-13 00:09:54 -0700266 realpath = _jython_aware_splitext(mod_file)
267 fullpath_noext = _jython_aware_splitext(full_path)
Michael Foord4107d312010-06-05 10:45:41 +0000268 if realpath.lower() != fullpath_noext.lower():
269 module_dir = os.path.dirname(realpath)
Michael Foorde01c62c2012-03-13 00:09:54 -0700270 mod_name = _jython_aware_splitext(os.path.basename(full_path))
Michael Foord4107d312010-06-05 10:45:41 +0000271 expected_dir = os.path.dirname(full_path)
272 msg = ("%r module incorrectly imported from %r. Expected %r. "
273 "Is this module globally installed?")
274 raise ImportError(msg % (mod_name, module_dir, expected_dir))
275 yield self.loadTestsFromModule(module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000276 elif os.path.isdir(full_path):
277 if not os.path.isfile(os.path.join(full_path, '__init__.py')):
278 continue
279
280 load_tests = None
281 tests = None
282 if fnmatch(path, pattern):
283 # only check load_tests if the package directory itself matches the filter
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000284 name = self._get_name_from_path(full_path)
285 package = self._get_module_from_name(name)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000286 load_tests = getattr(package, 'load_tests', None)
287 tests = self.loadTestsFromModule(package, use_load_tests=False)
288
289 if load_tests is None:
290 if tests is not None:
291 # tests loaded from package file
292 yield tests
293 # recurse into the package
Andrew Svetlov7d140152012-10-06 17:11:45 +0300294 yield from self._find_tests(full_path, pattern)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000295 else:
Benjamin Peterson886af962010-03-21 23:13:07 +0000296 try:
297 yield load_tests(self, tests, pattern)
298 except Exception as e:
299 yield _make_failed_load_tests(package.__name__, e,
300 self.suiteClass)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000301
302defaultTestLoader = TestLoader()
303
304
305def _makeLoader(prefix, sortUsing, suiteClass=None):
306 loader = TestLoader()
307 loader.sortTestMethodsUsing = sortUsing
308 loader.testMethodPrefix = prefix
309 if suiteClass:
310 loader.suiteClass = suiteClass
311 return loader
312
313def getTestCaseNames(testCaseClass, prefix, sortUsing=util.three_way_cmp):
314 return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
315
316def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,
317 suiteClass=suite.TestSuite):
318 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(
319 testCaseClass)
320
321def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp,
322 suiteClass=suite.TestSuite):
323 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\
324 module)