2 new revisions:

Revision: 6c8fb623fb76
Author:   Pekka Klärck
Date:     Tue Jan  3 14:32:16 2012
Log: Importing by path: Only remove modules from sys.modules when absolute ...
http://code.google.com/p/robotframework/source/detail?r=6c8fb623fb76

Revision: 72654cec0803
Author:   Pekka Klärck
Date:     Tue Jan  3 14:41:00 2012
Log:      cleanup and ooops fix
http://code.google.com/p/robotframework/source/detail?r=72654cec0803

==============================================================================
Revision: 6c8fb623fb76
Author:   Pekka Klärck
Date:     Tue Jan  3 14:32:16 2012
Log: Importing by path: Only remove modules from sys.modules when absolute must to prevent corrupting imported modules.

Update issue 979
Now modules are removed from sys.modules only when needed. The logic works similarly as with RF 2.6.x but now also modules implemented as directories are handled correctly.
http://code.google.com/p/robotframework/source/detail?r=6c8fb623fb76

Modified:
 /src/robot/utils/importer.py
 /utest/utils/test_importer_util.py

=======================================
--- /src/robot/utils/importer.py        Tue Jan  3 06:07:16 2012
+++ /src/robot/utils/importer.py        Tue Jan  3 14:32:16 2012
@@ -22,11 +22,10 @@
 from robot.errors import DataError

 from .error import get_error_details
-from .robotpath import abspath
+from .robotpath import abspath, normpath


 # TODO:
-# - acceptance tests for issue 979
# - test can variable files be implemented with java/python classes nowadays
 #   (possibly returning class when importing by path is bwic anyway)

@@ -157,6 +156,7 @@

     def import_(self, path):
         self._verify_import_path(path)
+        self._remove_wrong_module_from_sys_modules(path)
         module = self._import_by_path(path)
         imported = self._get_class_from_module(module) or module
         return self._verify_type(imported), path
@@ -169,25 +169,42 @@
         if not os.path.splitext(path)[1] in self._valid_import_extensions:
             raise DataError('Not a valid file or directory to import.')

-    def _import_by_path(self, path):
-        module_dir, module_name = self._split_path_to_module(path)
-        sys.path.insert(0, module_dir)
-        try:
-            return self._import_fresh_module(module_name)
-        finally:
-            sys.path.pop(0)
+    def _remove_wrong_module_from_sys_modules(self, path):
+        importing_from, name = self._split_path_to_module(path)
+        importing_package = os.path.splitext(path)[1] == ''
+ if self._wrong_module_imported(name, importing_from, importing_package):
+            del sys.modules[name]
+ self._logger.info("Removed module '%s' from sys.modules to import "
+                              "fresh module." % name)

     def _split_path_to_module(self, path):
         module_dir, module_file = os.path.split(abspath(path))
         module_name = os.path.splitext(module_file)[0]
+        if module_name.endswith('$py'):
+            module_name = module_name[:-3]
         return module_dir, module_name

-    def _import_fresh_module(self, name):
-        if name in sys.modules:
-            del sys.modules[name]
- self._logger.info("Removed module '%s' from sys.modules to import "
-                              "fresh module." % name)
-        return self._import(name)
+ def _wrong_module_imported(self, name, importing_from, importing_package):
+        module = sys.modules.get(name)
+        if not module:
+            return False
+        source = getattr(module, '__file__', None)
+ if not source: # play safe (occurs at least with java based modules)
+            return True
+        imported_from, imported_file = self._split_path_to_module(source)
+        imported_package = imported_file == '__init__'
+        if importing_package:
+            imported_from = os.path.dirname(imported_from)
+        return (normpath(importing_from) != normpath(imported_from) or
+                importing_package != imported_package)
+
+    def _import_by_path(self, path):
+        module_dir, module_name = self._split_path_to_module(path)
+        sys.path.insert(0, module_dir)
+        try:
+            return self._import(module_name)
+        finally:
+            sys.path.pop(0)


 class NonDottedImporter(_Importer):
=======================================
--- /utest/utils/test_importer_util.py  Tue Jan  3 06:05:18 2012
+++ /utest/utils/test_importer_util.py  Tue Jan  3 14:32:16 2012
@@ -6,10 +6,10 @@
 import sys
 import os
 import re
-from os.path import abspath, dirname, exists, isabs, join, normpath
+from os.path import abspath, basename, dirname, exists, join, normpath

 from robot.errors import DataError
-from robot.utils.importer import Importer
+from robot.utils.importer import Importer, ByPathImporter
 from robot.utils.asserts import (assert_equals, assert_true, assert_raises,
                                  assert_raises_with_msg)

@@ -49,7 +49,7 @@
                 msg = msg.replace(ext, '')
         self.messages.append(msg)

-    def assert_message(self, msg, index=-1):
+    def assert_message(self, msg, index=0):
         assert_equals(self.messages[index], msg)


@@ -64,77 +64,90 @@

     def test_python_file(self):
         path = create_temp_file('test.py')
-        self._import_and_verify(path)
+        self._import_and_verify(path, remove='test')
+        self._assert_imported_message('test', path)

     def test_python_directory(self):
         create_temp_file('__init__.py')
-        self._import_and_verify(TESTDIR + os.sep)
+        module_name = basename(TESTDIR)
+        self._import_and_verify(TESTDIR, remove=module_name)
+        self._assert_imported_message(module_name, TESTDIR)
+
+    def test_import_same_file_multiple_times(self):
+        path = create_temp_file('test.py')
+        self._import_and_verify(path, remove='test')
+        self._assert_imported_message('test', path)
+        self._import_and_verify(path)
+        self._assert_imported_message('test', path)
+        self._import_and_verify(path, name='library')
+        self._assert_imported_message('test', path, type='library module')

     def test_import_different_file_and_directory_with_same_name(self):
- removed = "Removed module 'test' from sys.modules to import fresh module."
-        imported = "Imported module 'test' from '%s'."
         path1 = create_temp_file('test.py', attr=1)
-        self._import_and_verify(path1, attr=1)
-        self.logger.assert_message(imported % path1)
+        self._import_and_verify(path1, attr=1, remove='test')
+        self._assert_imported_message('test', path1)
         path2 = join(TESTDIR, 'test')
         os.mkdir(path2)
         create_temp_file(join(path2, '__init__.py'), attr=2)
         self._import_and_verify(path2, attr=2, directory=path2)
-        self.logger.assert_message(removed, index=0)
-        self.logger.assert_message(imported % path2, index=1)
+        self._assert_removed_message('test')
+        self._assert_imported_message('test', path2, index=1)
         path3 = create_temp_file(join(path2, 'test.py'), attr=3)
         self._import_and_verify(path3, attr=3, directory=path2)
-        self.logger.assert_message(removed, index=0)
-        self.logger.assert_message(imported % path3, index=1)
+        self._assert_removed_message('test')
+        self._assert_imported_message('test', path3, index=1)

     def test_import_class_from_file(self):
path = create_temp_file('test.py', extra_content='class test:\n def m(s): return 1')
-        klass = Importer().import_class_or_module_by_path(path)
+        klass = self._import(path, remove='test')
+        self._assert_imported_message('test', path, type='class')
         assert_true(inspect.isclass(klass))
         assert_equals(klass.__name__, 'test')
         assert_equals(klass().m(), 1)

     def test_invalid_python_file(self):
         path = create_temp_file('test.py', extra_content='invalid content')
-        error = assert_raises(DataError, self._import_and_verify, path)
+ error = assert_raises(DataError, self._import_and_verify, path, remove='test')
         assert_prefix(error, "Importing '%s' failed: SyntaxError:" % path)

-    def test_logging_when_importing_module(self):
-        path = join(LIBDIR, 'classes.py')
-        self._import(path, name='lib')
- self.logger.assert_message("Imported lib module 'classes' from '%s'." % path)
-
-    def test_logging_when_importing_python_class(self):
-        path = join(LIBDIR, 'ExampleLibrary.py')
-        self._import(path)
- self.logger.assert_message("Imported class 'ExampleLibrary' from '%s'." % path)
-
     if sys.platform.startswith('java'):

-        def test_java_class(self):
-            self._import_and_verify(join(CURDIR, 'ImportByPath.java'))
-            self._import_and_verify(join(CURDIR, 'ImportByPath.class'))
+        def test_java_class_with_java_extension(self):
+            path = join(CURDIR, 'ImportByPath.java')
+            self._import_and_verify(path, remove='ImportByPath')
+ self._assert_imported_message('ImportByPath', path, type='class')
+
+        def test_java_class_with_class_extension(self):
+            path = join(CURDIR, 'ImportByPath.class')
+ self._import_and_verify(path, remove='ImportByPath', name='java') + self._assert_imported_message('ImportByPath', path, type='java class')

         def test_importing_java_package_fails(self):
-            path = join(LIBDIR, 'javapkg') + os.sep
+            path = join(LIBDIR, 'javapkg')
             assert_raises_with_msg(DataError,
"Importing '%s' failed: Expected class or "
                                    "module, got <javapackage>." % path,
-                                   self._import, path)
-
-        def test_logging_when_importing_java_class(self):
+                                   self._import, path, remove='javapkg')
+
+ def test_removing_from_sys_modules_when_importing_multiple_times(self):
             path = join(CURDIR, 'ImportByPath.java')
-            self._import(path, name='java')
- self.logger.assert_message("Imported java class 'ImportByPath' from '%s'." % path)
-
-    def _import_and_verify(self, path, attr=42, directory=TESTDIR):
-        module = self._import(path)
+            self._import(path, name='java', remove='ImportByPath')
+ self._assert_imported_message('ImportByPath', path, 'java class')
+            self._import(path)
+            self._assert_removed_message('ImportByPath')
+ self._assert_imported_message('ImportByPath', path, 'class', index=1)
+
+    def _import_and_verify(self, path, attr=42, directory=TESTDIR,
+                           name=None, remove=None):
+        module = self._import(path, name, remove)
         assert_equals(module.attr, attr)
         assert_equals(module.func(), attr)
         if hasattr(module, '__file__'):
             assert_equals(dirname(abspath(module.__file__)), directory)

-    def _import(self, path, name=None):
+    def _import(self, path, name=None, remove=None):
+        if remove and remove in sys.modules:
+            del sys.modules[remove]
         self.logger = LoggerStub()
         importer = Importer(name, self.logger)
         sys_path_before = sys.path[:]
@@ -143,6 +156,14 @@
         finally:
             assert_equals(sys.path, sys_path_before)

+ def _assert_imported_message(self, name, source, type='module', index=0):
+        msg = "Imported %s '%s' from '%s'." % (type, name, source)
+        self.logger.assert_message(msg, index=index)
+
+    def _assert_removed_message(self, name, index=0):
+ msg = "Removed module '%s' from sys.modules to import fresh module." % name
+        self.logger.assert_message(msg, index=index)
+

 class TestInvalidImportPath(unittest.TestCase):

@@ -307,7 +328,7 @@
         return klass

     def _import(self, name, type=None, logger=None):
-        return Importer(type, logger).import_class_or_module(name)
+ return Importer(type, logger or LoggerStub()).import_class_or_module(name)


 class TestErrorDetails(unittest.TestCase):
@@ -376,5 +397,26 @@
                 yield line


+class TestSplitPathToModule(unittest.TestCase):
+
+    def _verify(self, file_name, expected_name):
+        path = abspath(file_name)
+        actual = ByPathImporter(None)._split_path_to_module(path)
+        assert_equals(actual, (dirname(path), expected_name))
+
+    def test_normal_file(self):
+        self._verify('hello.py', 'hello')
+        self._verify('hello.class', 'hello')
+        self._verify('hello.world.java', 'hello.world')
+
+    def test_jython_class_file(self):
+        self._verify('hello$py.class', 'hello')
+        self._verify('__init__$py.class', '__init__')
+
+    def test_directory(self):
+        self._verify('hello', 'hello')
+        self._verify('hello'+os.sep, 'hello')
+
+
 if __name__ == '__main__':
     unittest.main()

==============================================================================
Revision: 72654cec0803
Author:   Pekka Klärck
Date:     Tue Jan  3 14:41:00 2012
Log:      cleanup and ooops fix
http://code.google.com/p/robotframework/source/detail?r=72654cec0803

Modified:
 /src/robot/utils/importer.py

=======================================
--- /src/robot/utils/importer.py        Tue Jan  3 14:32:16 2012
+++ /src/robot/utils/importer.py        Tue Jan  3 14:41:00 2012
@@ -26,6 +26,7 @@


 # TODO:
+# - don't import test library modules if library is in cache
# - test can variable files be implemented with java/python classes nowadays
 #   (possibly returning class when importing by path is bwic anyway)

@@ -191,12 +192,16 @@
         source = getattr(module, '__file__', None)
if not source: # play safe (occurs at least with java based modules)
             return True
+ imported_from, imported_package = self._get_import_information(source)
+        return ((normpath(importing_from), importing_package) !=
+                (normpath(imported_from), imported_package))
+
+    def _get_import_information(self, source):
         imported_from, imported_file = self._split_path_to_module(source)
         imported_package = imported_file == '__init__'
-        if importing_package:
+        if imported_package:
             imported_from = os.path.dirname(imported_from)
-        return (normpath(importing_from) != normpath(imported_from) or
-                importing_package != imported_package)
+        return imported_from, imported_package

     def _import_by_path(self, path):
         module_dir, module_name = self._split_path_to_module(path)

Reply via email to