diff options
author | Michael Foord <fuzzyman@voidspace.org.uk> | 2009-09-13 19:07:03 (GMT) |
---|---|---|
committer | Michael Foord <fuzzyman@voidspace.org.uk> | 2009-09-13 19:07:03 (GMT) |
commit | e91ea56b307e026ba40140305d8f35f0e1c1143a (patch) | |
tree | 9fc09481fe1ff2b21bdfd56b25860a4e25a7cbd1 /Lib/unittest | |
parent | 6848d82a7c90ccf28ba0991129f99675e7b78e24 (diff) | |
download | cpython-e91ea56b307e026ba40140305d8f35f0e1c1143a.zip cpython-e91ea56b307e026ba40140305d8f35f0e1c1143a.tar.gz cpython-e91ea56b307e026ba40140305d8f35f0e1c1143a.tar.bz2 |
Test discovery in unittest will only attempt to import modules that are importable; i.e. their names are valid Python identifiers. If an import fails during discovery this will be recorded as an error and test discovery will continue. Issue 6568.
Diffstat (limited to 'Lib/unittest')
-rw-r--r-- | Lib/unittest/loader.py | 59 | ||||
-rw-r--r-- | Lib/unittest/suite.py | 3 |
2 files changed, 46 insertions, 16 deletions
diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py index 21520f5..31c343b 100644 --- a/Lib/unittest/loader.py +++ b/Lib/unittest/loader.py @@ -1,7 +1,9 @@ """Loading unittests.""" import os +import re import sys +import traceback import types from fnmatch import fnmatch @@ -19,6 +21,26 @@ def _CmpToKey(mycmp): return K +# what about .pyc or .pyo (etc) +# we would need to avoid loading the same tests multiple times +# from '.py', '.pyc' *and* '.pyo' +VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE) + + +def _make_failed_import_test(name, suiteClass): + message = 'Failed to import test module: %s' % name + if hasattr(traceback, 'format_exc'): + # Python 2.3 compatibility + # format_exc returns two frames of discover.py as well + message += '\n%s' % traceback.format_exc() + + def testImportFailure(self): + raise ImportError(message) + attrs = {name: testImportFailure} + ModuleImportFailure = type('ModuleImportFailure', (case.TestCase,), attrs) + return suiteClass((ModuleImportFailure(name),)) + + class TestLoader(object): """ This class is responsible for loading tests according to various criteria @@ -161,17 +183,17 @@ class TestLoader(object): tests = list(self._find_tests(start_dir, pattern)) return self.suiteClass(tests) - - def _get_module_from_path(self, path): - """Load a module from a path relative to the top-level directory - of a project. Used by discovery.""" + def _get_name_from_path(self, path): path = os.path.splitext(os.path.normpath(path))[0] - relpath = os.path.relpath(path, self._top_level_dir) - assert not os.path.isabs(relpath), "Path must be within the project" - assert not relpath.startswith('..'), "Path must be within the project" + _relpath = os.path.relpath(path, self._top_level_dir) + assert not os.path.isabs(_relpath), "Path must be within the project" + assert not _relpath.startswith('..'), "Path must be within the project" + + name = _relpath.replace(os.path.sep, '.') + return name - name = relpath.replace(os.path.sep, '.') + def _get_module_from_name(self, name): __import__(name) return sys.modules[name] @@ -181,14 +203,20 @@ class TestLoader(object): for path in paths: full_path = os.path.join(start_dir, path) - # what about __init__.pyc or pyo (etc) - # we would need to avoid loading the same tests multiple times - # from '.py', '.pyc' *and* '.pyo' - if os.path.isfile(full_path) and path.lower().endswith('.py'): + if os.path.isfile(full_path): + if not VALID_MODULE_NAME.match(path): + # valid Python identifiers only + continue + if fnmatch(path, pattern): # if the test file matches, load it - module = self._get_module_from_path(full_path) - yield self.loadTestsFromModule(module) + name = self._get_name_from_path(full_path) + try: + module = self._get_module_from_name(name) + except: + yield _make_failed_import_test(name, self.suiteClass) + else: + yield self.loadTestsFromModule(module) elif os.path.isdir(full_path): if not os.path.isfile(os.path.join(full_path, '__init__.py')): continue @@ -197,7 +225,8 @@ class TestLoader(object): tests = None if fnmatch(path, pattern): # only check load_tests if the package directory itself matches the filter - package = self._get_module_from_path(full_path) + name = self._get_name_from_path(full_path) + package = self._get_module_from_name(name) load_tests = getattr(package, 'load_tests', None) tests = self.loadTestsFromModule(package, use_load_tests=False) diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py index 5fba259..730b4d9 100644 --- a/Lib/unittest/suite.py +++ b/Lib/unittest/suite.py @@ -1,6 +1,7 @@ """TestSuite""" from . import case +from . import util class TestSuite(object): @@ -17,7 +18,7 @@ class TestSuite(object): self.addTests(tests) def __repr__(self): - return "<%s tests=%s>" % (_strclass(self.__class__), list(self)) + return "<%s tests=%s>" % (util.strclass(self.__class__), list(self)) def __eq__(self, other): if not isinstance(other, self.__class__): |