summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--functional_tests/support/dir3/.hidden0
-rw-r--r--functional_tests/test_importer.py70
-rw-r--r--nose/importer.py37
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 = []