Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(128006)

Side by Side Diff: Lib/unittest/loader.py

Issue 17457: Unittest discover fails with namespace packages and builtin modules
Patch Set: Created 5 years, 12 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | Lib/unittest/test/test_discovery.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 """Loading unittests.""" 1 """Loading unittests."""
2 2
3 import os 3 import os
4 import re 4 import re
5 import sys 5 import sys
6 import traceback 6 import traceback
7 import types 7 import types
8 import functools 8 import functools
9 9
10 from fnmatch import fnmatch 10 from fnmatch import fnmatch
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 193
194 if not top_level_dir in sys.path: 194 if not top_level_dir in sys.path:
195 # all test modules must be importable from the top level directory 195 # all test modules must be importable from the top level directory
196 # should we *unconditionally* put the start directory in first 196 # should we *unconditionally* put the start directory in first
197 # in sys.path to minimise likelihood of conflicts between installed 197 # in sys.path to minimise likelihood of conflicts between installed
198 # modules and development versions? 198 # modules and development versions?
199 sys.path.insert(0, top_level_dir) 199 sys.path.insert(0, top_level_dir)
200 self._top_level_dir = top_level_dir 200 self._top_level_dir = top_level_dir
201 201
202 is_not_importable = False 202 is_not_importable = False
203 is_namespace = False
204 tests = []
203 if os.path.isdir(os.path.abspath(start_dir)): 205 if os.path.isdir(os.path.abspath(start_dir)):
204 start_dir = os.path.abspath(start_dir) 206 start_dir = os.path.abspath(start_dir)
205 if start_dir != top_level_dir: 207 if start_dir != top_level_dir:
206 is_not_importable = not os.path.isfile(os.path.join(start_dir, ' __init__.py')) 208 is_not_importable = not os.path.isfile(os.path.join(start_dir, ' __init__.py'))
207 else: 209 else:
208 # support for discovery from dotted module names 210 # support for discovery from dotted module names
209 try: 211 try:
210 __import__(start_dir) 212 __import__(start_dir)
211 except ImportError: 213 except ImportError:
212 is_not_importable = True 214 is_not_importable = True
213 else: 215 else:
214 the_module = sys.modules[start_dir] 216 the_module = sys.modules[start_dir]
215 top_part = start_dir.split('.')[0] 217 top_part = start_dir.split('.')[0]
216 start_dir = os.path.abspath(os.path.dirname((the_module.__file__ ))) 218 try:
219 start_dir = os.path.abspath(
220 os.path.dirname((the_module.__file__)))
221 except AttributeError:
222 # look for namespace packages
223 spec = the_module.__spec__
eric.snow 2013/11/22 19:38:50 There is no guarantee that the_module has a __spec
224 if spec.loader is None:
225 if spec.submodule_search_locations is not None:
226 is_namespace = True
227
228 for path in the_module.__path__:
229 if (not set_implicit_top and
230 not path.startswith(top_level_dir)):
231 continue
232 self._top_level_dir = \
233 (path.split(the_module.__name__
234 .replace(".", os.path.sep))[0])
235 tests.extend(self._find_tests(path,
236 pattern,
237 namespace=True))
238 elif spec.name in sys.builtin_module_names:
eric.snow 2013/11/22 19:38:50 This should probably stay the_module.__name__, sin
239 # builtin module
240 raise TypeError('Can not use builtin modules '
241 'as dotted module names') from None
242
217 if set_implicit_top: 243 if set_implicit_top:
218 self._top_level_dir = self._get_directory_containing_module( top_part) 244 if not is_namespace:
219 sys.path.remove(top_level_dir) 245 self._top_level_dir = \
246 self._get_directory_containing_module(top_part)
247 sys.path.remove(top_level_dir)
248 else:
249 sys.path.remove(top_level_dir)
220 250
221 if is_not_importable: 251 if is_not_importable:
222 raise ImportError('Start directory is not importable: %r' % start_di r) 252 raise ImportError('Start directory is not importable: %r' % start_di r)
223 253
224 tests = list(self._find_tests(start_dir, pattern)) 254 if not is_namespace:
255 tests = list(self._find_tests(start_dir, pattern))
225 return self.suiteClass(tests) 256 return self.suiteClass(tests)
226 257
227 def _get_directory_containing_module(self, module_name): 258 def _get_directory_containing_module(self, module_name):
228 module = sys.modules[module_name] 259 module = sys.modules[module_name]
229 full_path = os.path.abspath(module.__file__) 260 full_path = os.path.abspath(module.__file__)
230 261
231 if os.path.basename(full_path).lower().startswith('__init__.py'): 262 if os.path.basename(full_path).lower().startswith('__init__.py'):
232 return os.path.dirname(os.path.dirname(full_path)) 263 return os.path.dirname(os.path.dirname(full_path))
233 else: 264 else:
234 # here we have been given a module rather than a package - so 265 # here we have been given a module rather than a package - so
(...skipping 12 matching lines...) Expand all
247 return name 278 return name
248 279
249 def _get_module_from_name(self, name): 280 def _get_module_from_name(self, name):
250 __import__(name) 281 __import__(name)
251 return sys.modules[name] 282 return sys.modules[name]
252 283
253 def _match_path(self, path, full_path, pattern): 284 def _match_path(self, path, full_path, pattern):
254 # override this method to use alternative matching strategy 285 # override this method to use alternative matching strategy
255 return fnmatch(path, pattern) 286 return fnmatch(path, pattern)
256 287
257 def _find_tests(self, start_dir, pattern): 288 def _find_tests(self, start_dir, pattern, namespace=False):
258 """Used by discovery. Yields test suites it loads.""" 289 """Used by discovery. Yields test suites it loads."""
259 paths = sorted(os.listdir(start_dir)) 290 paths = sorted(os.listdir(start_dir))
260 291
261 for path in paths: 292 for path in paths:
262 full_path = os.path.join(start_dir, path) 293 full_path = os.path.join(start_dir, path)
263 if os.path.isfile(full_path): 294 if os.path.isfile(full_path):
264 if not VALID_MODULE_NAME.match(path): 295 if not VALID_MODULE_NAME.match(path):
265 # valid Python identifiers only 296 # valid Python identifiers only
266 continue 297 continue
267 if not self._match_path(path, full_path, pattern): 298 if not self._match_path(path, full_path, pattern):
(...skipping 12 matching lines...) Expand all
280 fullpath_noext = _jython_aware_splitext(os.path.realpath(ful l_path)) 311 fullpath_noext = _jython_aware_splitext(os.path.realpath(ful l_path))
281 if realpath.lower() != fullpath_noext.lower(): 312 if realpath.lower() != fullpath_noext.lower():
282 module_dir = os.path.dirname(realpath) 313 module_dir = os.path.dirname(realpath)
283 mod_name = _jython_aware_splitext(os.path.basename(full_ path)) 314 mod_name = _jython_aware_splitext(os.path.basename(full_ path))
284 expected_dir = os.path.dirname(full_path) 315 expected_dir = os.path.dirname(full_path)
285 msg = ("%r module incorrectly imported from %r. Expected %r. " 316 msg = ("%r module incorrectly imported from %r. Expected %r. "
286 "Is this module globally installed?") 317 "Is this module globally installed?")
287 raise ImportError(msg % (mod_name, module_dir, expected_ dir)) 318 raise ImportError(msg % (mod_name, module_dir, expected_ dir))
288 yield self.loadTestsFromModule(module) 319 yield self.loadTestsFromModule(module)
289 elif os.path.isdir(full_path): 320 elif os.path.isdir(full_path):
290 if not os.path.isfile(os.path.join(full_path, '__init__.py')): 321 if (not namespace and
291 continue 322 not os.path.isfile(os.path.join(full_path, '__init__.py'))):
323 continue
292 324
293 load_tests = None 325 load_tests = None
294 tests = None 326 tests = None
295 if fnmatch(path, pattern): 327 if fnmatch(path, pattern):
296 # only check load_tests if the package directory itself matc hes the filter 328 # only check load_tests if the package directory itself matc hes the filter
297 name = self._get_name_from_path(full_path) 329 name = self._get_name_from_path(full_path)
298 package = self._get_module_from_name(name) 330 package = self._get_module_from_name(name)
299 load_tests = getattr(package, 'load_tests', None) 331 load_tests = getattr(package, 'load_tests', None)
300 tests = self.loadTestsFromModule(package, use_load_tests=Fal se) 332 tests = self.loadTestsFromModule(package, use_load_tests=Fal se)
301 333
302 if load_tests is None: 334 if load_tests is None:
303 if tests is not None: 335 if tests is not None:
304 # tests loaded from package file 336 # tests loaded from package file
305 yield tests 337 yield tests
306 # recurse into the package 338 # recurse into the package
307 yield from self._find_tests(full_path, pattern) 339 yield from self._find_tests(full_path, pattern,
340 namespace=namespace)
308 else: 341 else:
309 try: 342 try:
310 yield load_tests(self, tests, pattern) 343 yield load_tests(self, tests, pattern)
311 except Exception as e: 344 except Exception as e:
312 yield _make_failed_load_tests(package.__name__, e, 345 yield _make_failed_load_tests(package.__name__, e,
313 self.suiteClass) 346 self.suiteClass)
314 347
315 defaultTestLoader = TestLoader() 348 defaultTestLoader = TestLoader()
316 349
317 350
(...skipping 10 matching lines...) Expand all
328 361
329 def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp, 362 def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,
330 suiteClass=suite.TestSuite): 363 suiteClass=suite.TestSuite):
331 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase( 364 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(
332 testCaseClass) 365 testCaseClass)
333 366
334 def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp, 367 def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp,
335 suiteClass=suite.TestSuite): 368 suiteClass=suite.TestSuite):
336 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\ 369 return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\
337 module) 370 module)
OLDNEW
« no previous file with comments | « no previous file | Lib/unittest/test/test_discovery.py » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+