From e6f5e2212362228605686c75c594caee2dba203c Mon Sep 17 00:00:00 2001 From: Michael Foord Date: Fri, 7 May 2010 23:39:38 +0000 Subject: [PATCH] Issue 8547 - detecting and reporting that modules have been imported from the wrong location under test discovery. --- Lib/unittest/loader.py | 15 +++++++++- Lib/unittest/test/test_discovery.py | 46 +++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py index 360a41ee4a7..a6fb95df331 100644 --- a/Lib/unittest/loader.py +++ b/Lib/unittest/loader.py @@ -173,7 +173,10 @@ class TestLoader(object): if not top_level_dir in sys.path: # all test modules must be importable from the top level directory - sys.path.append(top_level_dir) + # should we *unconditionally* put the start directory in first + # in sys.path to minimise likelihood of conflicts between installed + # modules and development versions? + sys.path.insert(0, top_level_dir) self._top_level_dir = top_level_dir is_not_importable = False @@ -246,6 +249,16 @@ class TestLoader(object): except: yield _make_failed_import_test(name, self.suiteClass) else: + mod_file = os.path.abspath(getattr(module, '__file__', full_path)) + realpath = os.path.splitext(mod_file)[0] + fullpath_noext = os.path.splitext(full_path)[0] + if realpath.lower() != fullpath_noext.lower(): + module_dir = os.path.dirname(realpath) + mod_name = os.path.splitext(os.path.basename(full_path))[0] + expected_dir = os.path.dirname(full_path) + msg = ("%r module incorrectly imported from %r. Expected %r. " + "Is this module globally installed?") + raise ImportError(msg % (mod_name, module_dir, expected_dir)) yield self.loadTestsFromModule(module) elif os.path.isdir(full_path): if not os.path.isfile(os.path.join(full_path, '__init__.py')): diff --git a/Lib/unittest/test/test_discovery.py b/Lib/unittest/test/test_discovery.py index de6096826aa..1f8b5c2dffc 100644 --- a/Lib/unittest/test/test_discovery.py +++ b/Lib/unittest/test/test_discovery.py @@ -1,4 +1,5 @@ import os +import re import sys import unittest @@ -54,8 +55,9 @@ class TestDiscovery(unittest.TestCase): loader._get_module_from_name = lambda path: path + ' module' loader.loadTestsFromModule = lambda module: module + ' tests' - loader._top_level_dir = '/foo' - suite = list(loader._find_tests('/foo', 'test*.py')) + top_level = os.path.abspath('/foo') + loader._top_level_dir = top_level + suite = list(loader._find_tests(top_level, 'test*.py')) expected = [name + ' module tests' for name in ('test1', 'test2')] @@ -298,6 +300,46 @@ class TestDiscovery(unittest.TestCase): self.assertTrue(program.failfast) self.assertTrue(program.catchbreak) + def test_detect_module_clash(self): + class Module(object): + __file__ = 'bar/foo.py' + sys.modules['foo'] = Module + full_path = os.path.abspath('foo') + original_listdir = os.listdir + original_isfile = os.path.isfile + original_isdir = os.path.isdir + + def cleanup(): + os.listdir = original_listdir + os.path.isfile = original_isfile + os.path.isdir = original_isdir + del sys.modules['foo'] + if full_path in sys.path: + sys.path.remove(full_path) + self.addCleanup(cleanup) + + def listdir(_): + return ['foo.py'] + def isfile(_): + return True + def isdir(_): + return True + os.listdir = listdir + os.path.isfile = isfile + os.path.isdir = isdir + + loader = unittest.TestLoader() + + mod_dir = os.path.abspath('bar') + expected_dir = os.path.abspath('foo') + msg = re.escape(r"'foo' module incorrectly imported from %r. Expected %r. " + "Is this module globally installed?" % (mod_dir, expected_dir)) + self.assertRaisesRegexp( + ImportError, '^%s$' % msg, loader.discover, + start_dir='foo', pattern='foo.py' + ) + self.assertEqual(sys.path[0], full_path) + if __name__ == '__main__': unittest.main()