Skip to content

Commit e6f5e22

Browse files
author
Michael Foord
committed
Issue 8547 - detecting and reporting that modules have been imported from the wrong location under test discovery.
1 parent 1a0ce68 commit e6f5e22

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

Lib/unittest/loader.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
173173

174174
if not top_level_dir in sys.path:
175175
# all test modules must be importable from the top level directory
176-
sys.path.append(top_level_dir)
176+
# should we *unconditionally* put the start directory in first
177+
# in sys.path to minimise likelihood of conflicts between installed
178+
# modules and development versions?
179+
sys.path.insert(0, top_level_dir)
177180
self._top_level_dir = top_level_dir
178181

179182
is_not_importable = False
@@ -246,6 +249,16 @@ def _find_tests(self, start_dir, pattern):
246249
except:
247250
yield _make_failed_import_test(name, self.suiteClass)
248251
else:
252+
mod_file = os.path.abspath(getattr(module, '__file__', full_path))
253+
realpath = os.path.splitext(mod_file)[0]
254+
fullpath_noext = os.path.splitext(full_path)[0]
255+
if realpath.lower() != fullpath_noext.lower():
256+
module_dir = os.path.dirname(realpath)
257+
mod_name = os.path.splitext(os.path.basename(full_path))[0]
258+
expected_dir = os.path.dirname(full_path)
259+
msg = ("%r module incorrectly imported from %r. Expected %r. "
260+
"Is this module globally installed?")
261+
raise ImportError(msg % (mod_name, module_dir, expected_dir))
249262
yield self.loadTestsFromModule(module)
250263
elif os.path.isdir(full_path):
251264
if not os.path.isfile(os.path.join(full_path, '__init__.py')):

Lib/unittest/test/test_discovery.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import re
23
import sys
34

45
import unittest
@@ -54,8 +55,9 @@ def isfile(path):
5455
loader._get_module_from_name = lambda path: path + ' module'
5556
loader.loadTestsFromModule = lambda module: module + ' tests'
5657

57-
loader._top_level_dir = '/foo'
58-
suite = list(loader._find_tests('/foo', 'test*.py'))
58+
top_level = os.path.abspath('/foo')
59+
loader._top_level_dir = top_level
60+
suite = list(loader._find_tests(top_level, 'test*.py'))
5961

6062
expected = [name + ' module tests' for name in
6163
('test1', 'test2')]
@@ -298,6 +300,46 @@ def discover(self, start_dir, pattern, top_level_dir):
298300
self.assertTrue(program.failfast)
299301
self.assertTrue(program.catchbreak)
300302

303+
def test_detect_module_clash(self):
304+
class Module(object):
305+
__file__ = 'bar/foo.py'
306+
sys.modules['foo'] = Module
307+
full_path = os.path.abspath('foo')
308+
original_listdir = os.listdir
309+
original_isfile = os.path.isfile
310+
original_isdir = os.path.isdir
311+
312+
def cleanup():
313+
os.listdir = original_listdir
314+
os.path.isfile = original_isfile
315+
os.path.isdir = original_isdir
316+
del sys.modules['foo']
317+
if full_path in sys.path:
318+
sys.path.remove(full_path)
319+
self.addCleanup(cleanup)
320+
321+
def listdir(_):
322+
return ['foo.py']
323+
def isfile(_):
324+
return True
325+
def isdir(_):
326+
return True
327+
os.listdir = listdir
328+
os.path.isfile = isfile
329+
os.path.isdir = isdir
330+
331+
loader = unittest.TestLoader()
332+
333+
mod_dir = os.path.abspath('bar')
334+
expected_dir = os.path.abspath('foo')
335+
msg = re.escape(r"'foo' module incorrectly imported from %r. Expected %r. "
336+
"Is this module globally installed?" % (mod_dir, expected_dir))
337+
self.assertRaisesRegexp(
338+
ImportError, '^%s$' % msg, loader.discover,
339+
start_dir='foo', pattern='foo.py'
340+
)
341+
self.assertEqual(sys.path[0], full_path)
342+
301343

302344
if __name__ == '__main__':
303345
unittest.main()

0 commit comments

Comments
 (0)