Index: Lib/unittest/loader.py =================================================================== --- Lib/unittest/loader.py (revision 77287) +++ Lib/unittest/loader.py (working copy) @@ -85,20 +85,27 @@ The method optionally resolves the names relative to a given module. """ parts = name.split('.') + import_excp = None if module is None: parts_copy = parts[:] while parts_copy: try: module = __import__('.'.join(parts_copy)) break - except ImportError: + except ImportError, e: + import_excp = e del parts_copy[-1] if not parts_copy: raise parts = parts[1:] obj = module for part in parts: - parent, obj = obj, getattr(obj, part) + try: + parent, obj = obj, getattr(obj, part) + except AttributeError, e: + if import_excp: + raise import_excp + raise if isinstance(obj, types.ModuleType): return self.loadTestsFromModule(obj) Index: Lib/test/test_unittest.py =================================================================== --- Lib/test/test_unittest.py (revision 77287) +++ Lib/test/test_unittest.py (working copy) @@ -18,6 +18,7 @@ from copy import deepcopy from cStringIO import StringIO import pickle +from contextlib import contextmanager ### Support code ################################################################ @@ -350,11 +351,43 @@ try: loader.loadTestsFromName('unittest.sdasfasfasdf') - except AttributeError, e: - self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + except ImportError, e: + self.assertEqual(str(e), "No module named sdasfasfasdf") else: - self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + self.fail("TestLoader.loadTestsFromName failed to raise ImportError") + + # "The specifier is a name that resolves to a module which imports a non-existent + # module. + # + # What happens when a module is found but it has a bad import statement? + def test_loadTestsFromName__badimport(self): + @contextmanager + def newdirinpath(dir): + os.mkdir(dir) + sys.path.insert(0, dir) + yield + sys.path.pop(0) + test_support.rmtree(dir) + + with newdirinpath(test_support.TESTFN), test_support.EnvironmentVarGuard() as env: + env['PYTHONPATH'] = test_support.TESTFN + fullmodname = os.path.join(test_support.TESTFN, 'foofoo') + sourcefn = fullmodname + os.extsep + "py" + f = open(sourcefn, 'w') + f.write('import barbar') + f.close() + + loader = unittest.TestLoader() + + try: + loader.loadTestsFromName('foofoo') + except ImportError, e: + self.assertEqual(str(e), "No module named barbar") + else: + self.fail("TestLoader.loadTestsFromName failed to raise ImportError") + + # "The specifier name is a ``dotted name'' that may resolve either to # a module, a test case class, a TestSuite instance, a test method # within a test case class, or a callable object which returns a @@ -735,11 +768,42 @@ try: loader.loadTestsFromNames(['unittest.sdasfasfasdf', 'unittest']) - except AttributeError, e: - self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + except ImportError, e: + self.assertEqual(str(e), "No module named sdasfasfasdf") else: - self.fail("TestLoader.loadTestsFromNames failed to raise AttributeError") + self.fail("TestLoader.loadTestsFromNames failed to raise ImportError") + + # "The specifier is a name that resolves to a module which imports a non-existent + # module. + # + # What happens when a module is found but it has a bad import statement? + def test_loadTestsFromNames__badimport(self): + @contextmanager + def newdirinpath(dir): + os.mkdir(dir) + sys.path.insert(0, dir) + yield + sys.path.pop(0) + test_support.rmtree(dir) + + with newdirinpath(test_support.TESTFN), test_support.EnvironmentVarGuard() as env: + env['PYTHONPATH'] = test_support.TESTFN + fullmodname = os.path.join(test_support.TESTFN, 'foofoo') + sourcefn = fullmodname + os.extsep + "py" + f = open(sourcefn, 'w') + f.write('import barbar') + f.close() + + loader = unittest.TestLoader() + + try: + loader.loadTestsFromNames(['foofoo', 'unittest']) + except ImportError, e: + self.assertEqual(str(e), "No module named barbar") + else: + self.fail("TestLoader.loadTestsFromName failed to raise ImportError") + # "The specifier name is a ``dotted name'' that may resolve either to # a module, a test case class, a TestSuite instance, a test method # within a test case class, or a callable object which returns a