diff options
-rw-r--r-- | functional_tests/support/dir3/.hidden | 0 | ||||
-rw-r--r-- | functional_tests/test_importer.py | 70 | ||||
-rw-r--r-- | nose/importer.py | 37 |
3 files changed, 84 insertions, 23 deletions
diff --git a/functional_tests/support/dir3/.hidden b/functional_tests/support/dir3/.hidden new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/functional_tests/support/dir3/.hidden diff --git a/functional_tests/test_importer.py b/functional_tests/test_importer.py index c24fdcf..20fb15d 100644 --- a/functional_tests/test_importer.py +++ b/functional_tests/test_importer.py @@ -2,6 +2,7 @@ import os import sys import unittest from nose.importer import Importer +from nose.plugins.skip import SkipTest class TestImporter(unittest.TestCase): @@ -16,7 +17,15 @@ class TestImporter(unittest.TestCase): sys.modules.pop('pak', None) sys.modules.pop('pak.mod', None) sys.modules.pop('pak.sub', None) - + try: + os.symlink( + os.path.abspath(os.path.join(self.dir, 'dir1', 'pak')), + os.path.join(self.dir, 'dir3', 'pak')) + except (AttributeError, NotImplementedError): + self.has_symlinks = False + else: + self.has_symlinks = True + def tearDown(self): to_del = [ m for m in sys.modules.keys() if m not in self._mods ] @@ -25,14 +34,16 @@ class TestImporter(unittest.TestCase): del sys.modules[mod] sys.modules.update(self._mods) sys.path = self._path[:] + if self.has_symlinks: + os.unlink(os.path.join(self.dir, 'dir3', 'pak')) def test_import_from_dir(self): imp = self.imp d1 = os.path.join(self.dir, 'dir1') d2 = os.path.join(self.dir, 'dir2') - - # simple name + + # simple name m1 = imp.importFromDir(d1, 'mod') m2 = imp.importFromDir(d2, 'mod') self.assertNotEqual(m1, m2) @@ -44,14 +55,34 @@ class TestImporter(unittest.TestCase): self.assertNotEqual(p1, p2) self.assertNotEqual(p1.__file__, p2.__file__) + def test_import_from_dirlink(self): + if not self.has_symlinks: + raise SkipTest("symlinks not available") + imp = self.imp + + d1 = os.path.join(self.dir, 'dir3') + d2 = os.path.join(self.dir, 'dir2') + + # simple name + m1 = imp.importFromDir(d1, 'pak') + m2 = imp.importFromDir(d2, 'pak') + self.assertNotEqual(m1, m2) + self.assertNotEqual(m1.__file__, m2.__file__) + + # dotted name + p1 = imp.importFromDir(d1, 'pak.mod') + p2 = imp.importFromDir(d2, 'pak.mod') + self.assertNotEqual(p1, p2) + self.assertNotEqual(p1.__file__, p2.__file__) + def test_import_from_path(self): imp = self.imp jn = os.path.join d1 = jn(self.dir, 'dir1') d2 = jn(self.dir, 'dir2') - - # simple name + + # simple name m1 = imp.importFromPath(jn(d1, 'mod.py'), 'mod') m2 = imp.importFromPath(jn(d2, 'mod.py'), 'mod') self.assertNotEqual(m1, m2) @@ -88,12 +119,12 @@ class TestImporter(unittest.TestCase): assert 'test_pak' in sys.modules, 'test_pak was not imported?' test_pak = sys.modules['test_pak'] assert hasattr(test_pak, 'test_sub'), "test_pak.test_sub was not set" - + def test_cached_no_reload(self): imp = self.imp d1 = os.path.join(self.dir, 'dir1') m1 = imp.importFromDir(d1, 'mod') - m2 = imp.importFromDir(d1, 'mod') + m2 = imp.importFromDir(d1, 'mod') assert m1 is m2, "%s is not %s" % (m1, m2) def test_cached_no_reload_dotted(self): @@ -133,13 +164,34 @@ class TestImporter(unittest.TestCase): assert mod_nose_imported2 != mod_sys_imported, \ "nose failed to reimport same name, different dir" + def test_sys_modules_symlinked_package_no_reload(self): + if not self.has_symlinks: + raise SkipTest("symlinks not available") + imp = self.imp + + d1 = os.path.join(self.dir, 'dir1') + d2 = os.path.join(self.dir, 'dir3') + sys.path.insert(0, d1) + # Symlinked package + mod_sys_imported = __import__('pak') + mod_nose_imported = imp.importFromDir(d2, 'pak') + assert mod_nose_imported is mod_sys_imported, \ + "nose reimported a module in sys.modules from the same file" + + # Module inside symlinked package + mod_sys_imported = __import__('pak.mod', fromlist=['mod']) + mod_nose_imported = imp.importFromDir(d2, 'pak.mod') + assert mod_nose_imported is mod_sys_imported, \ + ("nose reimported a module in sys.modules from the same file", + mod_sys_imported.__file__, mod_nose_imported.__file__) + def test_import_pkg_from_path_fpw(self): imp = self.imp imp.config.firstPackageWins = True jn = os.path.join d1 = jn(self.dir, 'dir1') d2 = jn(self.dir, 'dir2') - + # dotted name p1 = imp.importFromPath(jn(d1, 'pak', 'mod.py'), 'pak.mod') p2 = imp.importFromPath(jn(d2, 'pak', 'mod.py'), 'pak.mod') @@ -161,7 +213,7 @@ class TestImporter(unittest.TestCase): assert dp1.__path__ assert dp2.__path__ self.assertEqual(dp1.__path__, dp2.__path__) - + if __name__ == '__main__': import logging logging.basicConfig(level=logging.DEBUG) diff --git a/nose/importer.py b/nose/importer.py index c971b79..a2b261f 100644 --- a/nose/importer.py +++ b/nose/importer.py @@ -13,6 +13,13 @@ from imp import find_module, load_module, acquire_lock, release_lock log = logging.getLogger(__name__) +try: + _samefile = os.path.samefile +except AttributeError: + def _samefile(path, other): + return os.path.realpath(path) == os.path.realpath(other) + + class Importer(object): """An importer class that does only path-specific imports. That is, the given module is not searched for on sys.path, but only at @@ -36,7 +43,7 @@ class Importer(object): path_parts = path_parts[:-(len(name_parts))] dir_path = os.sep.join(path_parts) # then import fqname starting from that dir - return self.importFromDir(dir_path, fqname) + return self.importFromDir(dir_path, fqname) def importFromDir(self, dir, fqname): """Import a module *only* from path, ignoring sys.path and @@ -46,14 +53,14 @@ class Importer(object): log.debug("Import %s from %s", fqname, dir) # FIXME reimplement local per-dir cache? - + # special case for __main__ if fqname == '__main__': return sys.modules[fqname] - + if self.config.addPaths: add_path(dir, self.config) - + path = [dir] parts = fqname.split('.') part_fqname = '' @@ -95,27 +102,29 @@ class Importer(object): parent = mod return mod + def _dirname_if_file(self, filename): + # We only take the dirname if we have a path to a non-dir, + # because taking the dirname of a symlink to a directory does not + # give the actual directory parent. + return filename if os.path.isdir(filename) else os.path.dirname(filename) + def sameModule(self, mod, filename): mod_paths = [] if hasattr(mod, '__path__'): for path in mod.__path__: - mod_paths.append(os.path.dirname( - os.path.normpath( - os.path.abspath(path)))) + mod_paths.append(self._dirname_if_file(path)) elif hasattr(mod, '__file__'): - mod_paths.append(os.path.dirname( - os.path.normpath( - os.path.abspath(mod.__file__)))) + mod_paths.append(self._dirname_if_file(mod.__file__)) else: # builtin or other module-like object that # doesn't have __file__; must be new return False - new_path = os.path.dirname(os.path.normpath(filename)) + new_path = self._dirname_if_file(filename) for mod_path in mod_paths: log.debug( "module already loaded? mod: %s new: %s", mod_path, new_path) - if mod_path == new_path: + if _samefile(mod_path, new_path): return True return False @@ -126,8 +135,8 @@ def add_path(path, config=None): """ # FIXME add any src-looking dirs seen too... need to get config for that - - log.debug('Add path %s' % path) + + log.debug('Add path %s' % path) if not path: return [] added = [] |