[Python-checkins] r52575 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py

brett.cannon python-checkins at python.org
Wed Nov 1 23:57:04 CET 2006


Author: brett.cannon
Date: Wed Nov  1 23:57:03 2006
New Revision: 52575

Modified:
   sandbox/trunk/import_in_py/importer.py
   sandbox/trunk/import_in_py/mock_importer.py
   sandbox/trunk/import_in_py/test_importer.py
Log:
Refactor unit tests to use mock objects.  Tests now run faster and are more
isolated.

Also begin initial work on package support (mostly untested).


Modified: sandbox/trunk/import_in_py/importer.py
==============================================================================
--- sandbox/trunk/import_in_py/importer.py	(original)
+++ sandbox/trunk/import_in_py/importer.py	Wed Nov  1 23:57:03 2006
@@ -255,6 +255,7 @@
     def __init__(self, path_entry, *handlers):
         self.path_entry = path_entry
         self.handlers = handlers
+        self.loader = FileSystemLoader
 
     def find_module(self, fullname, path=None):
         """Determine if this path entry can handle this import, and if so,
@@ -262,15 +263,19 @@
         registered."""
         # XXX Ignores 'path' at the moment.
         # XXX Does not worry about case-insensitive filesystems.
-        try:
-            for handler in self.handlers:
-                for file_ext in handler.handles:
-                    file_name = fullname + file_ext
-                    file_path = os.path.join(self.path_entry, file_name)
-                    if os.path.isfile(file_path):
-                        raise StopIteration("file found")
-        except StopIteration:
-            return FileSystemLoader(file_path, handler)
+        for handler in self.handlers:
+            for file_ext in handler.handles:
+                # XXX Backwards-incompatible to use extension modules for __init__?
+                package_entry = os.path.join(self.path_entry, fullname)
+                package_name = os.path.join(package_entry,
+                                            '__init__'+file_ext)
+                file_path = os.path.join(self.path_entry, package_name)
+                if os.path.isfile(file_path):
+                    return FileSystemLoader(file_path, handler, package_entry)
+                file_name = fullname + file_ext
+                file_path = os.path.join(self.path_entry, file_name)
+                if os.path.isfile(file_path):
+                    return self.loader(file_path, handler)
         else:
             return None
 
@@ -283,10 +288,11 @@
     
     """
 
-    def __init__(self, file_path, handler):
+    def __init__(self, file_path, handler, package=None):
         """Store arguments on to the instance."""
         self.file_path = file_path
         self.handler = handler
+        self.package = package
 
     def load_module(self, fullname, path=None):
         """Load the module from self.path using self.handler.
@@ -305,6 +311,8 @@
                 if fullname in sys.modules:                    
                     del sys.modules[fullname]
                 raise
+            if self.package is not None:
+                module.__path__ = [self.package]
             return module
             
     def mod_time(self, path):

Modified: sandbox/trunk/import_in_py/mock_importer.py
==============================================================================
--- sandbox/trunk/import_in_py/mock_importer.py	(original)
+++ sandbox/trunk/import_in_py/mock_importer.py	Wed Nov  1 23:57:03 2006
@@ -11,6 +11,19 @@
     return log_and_call
 
 
+class MockHandler(object):
+    
+    """Mock a handler."""
+
+    def __init__(self, *handles):
+        self.handles = handles
+    
+    def handle_code(self, loader, mod_name, path):
+        """Mock implementation of a handler."""
+        called_with = loader, mod_name, path
+        sys.modules[mod_name] = called_with
+        return called_with
+
 class MockPyPycLoader(object):
     
     """Mock loader object for testing importer.PyPycHandler.
@@ -22,9 +35,9 @@
     
     def __init__(self, file_path, handler, package=None):
         """Store arguments passed into the constructor for verification."""
-        self.init_file_path = file_path
-        self.init_handler = handler
-        self.init_package = package
+        self.file_path = file_path
+        self.handler = handler
+        self.package = package
 
     @classmethod
     def setup(cls, good_magic=True, good_timestamp=True, pyc_exists=True,
@@ -89,7 +102,8 @@
         return True
             
     def load_module(self, fullname, path=None):
-        raise NotImplementedError
+        """Return what the method was called with."""
+        return fullname, path
     
     @log_call
     def split_path(self, path):
@@ -149,9 +163,11 @@
         return None
 
 
+# Mock Importers (with optional path_hooks support).
+
 class ErrorImporter(object):
 
-    """Helper class to have a guaranteed error point."""
+    """Mock importer to have a guaranteed error point."""
 
     def find_module(self, fullname, path=None):
         self.find_request = fullname, path
@@ -168,7 +184,11 @@
 
 class PassImporter(object):
 
-    """Always pass on importing a module."""
+    """Mock importer that always pass on importing a module."""
+    
+    def __call__(self, path_entry):
+        """Always pass when asked to create an importer."""
+        raise ImportError
 
     def find_module(self, fullname, path=None):
         self.find_request = fullname, path
@@ -185,7 +205,7 @@
 
 class SucceedImporter(object):
 
-    """Always succeed by returning 'self'."""
+    """Mock importer that always succeed by returning 'self'."""
 
     module = 42
 

Modified: sandbox/trunk/import_in_py/test_importer.py
==============================================================================
--- sandbox/trunk/import_in_py/test_importer.py	(original)
+++ sandbox/trunk/import_in_py/test_importer.py	Wed Nov  1 23:57:03 2006
@@ -1,3 +1,4 @@
+"""XXX separate medium/large tests into a single test class."""
 from __future__ import with_statement
 import importer
 
@@ -166,23 +167,37 @@
         self.failUnlessEqual(getattr(module, self.attr_name), self.attr_value)
 
 
-class FileSystemImporterTests(PyPycFileHelper):
+class FileSystemImporterTests(unittest.TestCase):
 
     """Test the filesystem importer."""
 
     def setUp(self):
         """Create a basic importer."""
-        super(FileSystemImporterTests, self).setUp()
-        source_handler = importer.PyPycHandler(bytecode_handles=tuple())
+        self.directory = tempfile.gettempdir()
+        self.module_name = 'importer_test'
+        self.file_ext = '.test'
+        self.file_path = os.path.join(self.directory,
+                                        self.module_name + self.file_ext)
+        with open(self.file_path, 'w') as test_file:
+            test_file.write("A file for testing the 'importer' module.")
+        mock_handler = mock_importer.MockHandler(self.file_ext)
         self.importer = importer.FileSystemImporter(self.directory,
-                                                    source_handler)
+                                                    mock_handler)
+        self.importer.loader = mock_importer.MockPyPycLoader
+        
+    def tearDown(self):
+        """Clean up the created file."""
+        try:
+            os.remove(self.file_path)
+        except OSError:
+            pass
 
     def test_find_module_single_handler(self):
         # Having a single handler should work without issue.
-        loader = self.importer.find_module(self.module)
-        self.failUnless(isinstance(loader, importer.FileSystemLoader))
-        self.failUnlessEqual(loader.file_path, self.source_path)
-        self.failUnless(isinstance(loader.handler, importer.PyPycHandler))
+        loader = self.importer.find_module(self.module_name)
+        self.failUnless(isinstance(loader, mock_importer.MockPyPycLoader))
+        self.failUnlessEqual(loader.file_path, self.file_path)
+        self.failUnless(isinstance(loader.handler, mock_importer.MockHandler))
 
     def test_find_module_cannot_find(self):
         # Should return None if it can't find the module.
@@ -191,48 +206,58 @@
 
     def test_find_module_multiple_handlers(self):
         # Modules should be found based on the order of the handlers.
-        source_handler = importer.PyPycHandler(bytecode_handles=tuple())
-        bytecode_handler = importer.PyPycHandler(source_handles=tuple())
+        # mock
+        first_handler = mock_importer.MockHandler('.not_found')
+        second_handler = mock_importer.MockHandler(self.file_ext)
         fs_importer = importer.FileSystemImporter(self.directory,
-                                                  bytecode_handler, source_handler)
-        loader = fs_importer.find_module(self.module)
-        self.failUnless(isinstance(loader, importer.FileSystemLoader))
-        self.failUnlessEqual(loader.file_path, self.bytecode_path)
-        self.failUnless(isinstance(loader.handler, importer.PyPycHandler))
-
-    def test_find_to_load(self):
-        # Make sure that one can go from find_module() to getting a module
-        # imported.
-        loader = self.importer.find_module(self.module)
-        self.failUnless(loader)
-        module = loader.load_module(self.module)
-        self.verify_module(module, self.source_path)
-        self.failUnlessEqual(module, sys.modules[self.module])
-
+                                                  first_handler,
+                                                  second_handler)
+        fs_importer.loader = mock_importer.MockPyPycLoader
+        loader = fs_importer.find_module(self.module_name)
+        self.failUnless(isinstance(loader, mock_importer.MockPyPycLoader))
+        self.failUnlessEqual(loader.file_path, self.file_path)
+        self.failUnless(isinstance(loader.handler, mock_importer.MockHandler))
 
-class FileSystemLoaderBlackBoxTests(PyPycFileHelper):
 
-    """Test the filesystem loader's PEP 302 API compliance."""
+class FileSystemLoaderMockEnv(unittest.TestCase):
+    
+    """Helper for settting up mock environment for testing
+    importer.FileSystemLoader."""
 
     def setUp(self):
         """Create a fresh loader per run."""
-        super(FileSystemLoaderBlackBoxTests, self).setUp()
-        source_handler = importer.PyPycHandler(bytecode_handles=tuple())
-        self.loader = importer.FileSystemLoader(self.source_path,
-                                                source_handler)
+        # XXX mock
+        mock_handler = mock_importer.MockHandler()
+        self.test_path = "<test path>"
+        self.module_name = "test_module_name"
+        self.loader = importer.FileSystemLoader(self.test_path,
+                                                mock_handler)
+                                                
+    def tearDown(self):
+        """Make sure that there is no straggling modules in sys.modules."""
+        try:
+            del sys.modules[self.module_name]
+        except KeyError:
+            pass
+
+
+class FileSystemLoaderBlackBoxTests(FileSystemLoaderMockEnv):
+
+    """Test the filesystem loader's PEP 302 API compliance."""
 
     def test_load_module_fresh(self):
         # Test a basic module load where there is no sys.modules entry.
         # PyPycFileHelper.setUp() clears sys.modules for us.
-        new_module = self.loader.load_module(self.module)
-        self.verify_module(new_module, self.source_path)
+        new_module = self.loader.load_module(self.module_name)
+        expected = self.loader, self.module_name, self.test_path
+        self.failUnlessEqual(new_module, expected)
 
     def test_load_module_sys_modules(self):
         # Make sure that the loader returns the module from sys.modules if it
         # is there.
-        sys.modules[self.module] = self.module_object
-        loaded_module = self.loader.load_module(self.module)
-        self.failUnless(loaded_module is self.module_object)
+        sys.modules[self.module_name] = self.module_name
+        loaded_module = self.loader.load_module(self.module_name)
+        self.failUnless(loaded_module is self.module_name)
         
     def test_sys_module_cleared_on_error(self):
         # Any entry made for module into sys.modules should be cleared upon error.
@@ -240,64 +265,77 @@
             def handle_code(*args):
                 raise ImportError
                 
-        loader = importer.FileSystemLoader(self.source_path, RaiseErrorHandler())
+        loader = importer.FileSystemLoader(self.test_path, RaiseErrorHandler())
         try:
-            loader.load_module(self.module)
+            loader.load_module(self.module_name)
         except ImportError:
-            self.failUnless(self.module not in sys.modules)
+            self.failUnless(self.module_name not in sys.modules)
+            
+    def test_package_init(self):
+        # Test that instantiating a loader for handling a package works
+        # properly.
+        # XXX
+        pass
             
             
-class FileSystemLoaderWhiteBoxTests(PyPycFileHelper):
+class FileSystemLoaderWhiteBoxTests(FileSystemLoaderMockEnv):
     
     """Test the filesystem loader's non-PEP 302 API."""
     
     def setUp(self):
-        """Create a loader."""
+        """Set up a test file."""
         super(FileSystemLoaderWhiteBoxTests, self).setUp()
-        source_handler = importer.PyPycHandler(bytecode_handles=tuple())
-        self.loader = importer.FileSystemLoader(self.source_path,
-                                                source_handler)
+        self.directory = tempfile.gettempdir()
+        self.file_ext = ".test"
+        self.module_name = "import_test"
+        self.file_name = self.module_name + self.file_ext
+        self.file_path = os.path.join(self.directory, self.file_name)
+        self.data = "File used for testing 'importer' module.\r\n42"
+        with open(self.file_path, 'w') as test_file:
+            test_file.write(self.data)
+            
+    def tearDown(self):
+        """Delete the test file."""
+        super(FileSystemLoaderWhiteBoxTests, self).tearDown()
+        try:
+            os.remove(self.file_path)
+        except OSError:
+            pass
 
     def test_mod_time(self):
         # Modification time for the passed-in path should match the file.
-        true_mod_time = os.stat(self.source_path).st_mtime
-        mod_time = self.loader.mod_time(self.source_path)
+        true_mod_time = os.stat(self.file_path).st_mtime
+        mod_time = self.loader.mod_time(self.file_path)
         self.failUnlessEqual(mod_time, true_mod_time)
         
     def test_split_path(self):
         # Should split a path just like os.path.splitext().
-        true_split_path = os.path.splitext(self.source_path)
-        split_path = self.loader.split_path(self.source_path)
+        true_split_path = os.path.splitext(self.file_path)
+        split_path = self.loader.split_path(self.file_path)
         self.failUnlessEqual(split_path, true_split_path)
         
     def test_create_path(self):
         # Should work like concatenating two strings.
-        true_path = ''.join(self.loader.split_path(self.source_path))
+        true_path = self.file_path
         path = self.loader.create_path(*self.loader.split_path(
-                                                        self.source_path))
+                                                        self.file_path))
         self.failUnlessEqual(path, true_path)
         
     def test_read_data(self):
         # Should be able to read a file.
-        source = self.loader.read_data(self.source_path, False)
-        self.failUnlessEqual(source, self.source)
+        data = self.loader.read_data(self.file_path, False)
+        self.failUnlessEqual(data, self.data.replace('\r', ''))
+        data = self.loader.read_data(self.file_path, True)
+        self.failUnlessEqual(data, self.data)
         test_text = '1\r\n2'
-        try:
-            with open(test_support.TESTFN, 'wb') as test_file:
-                test_file.write(test_text)
-            result = self.loader.read_data(test_support.TESTFN, binary=True)
-            self.failUnlessEqual(result, test_text)
-            result = self.loader.read_data(test_support.TESTFN, binary=False)
-            self.failUnlessEqual(result, test_text.replace('\r', ''))
-        finally:
-            os.remove(test_support.TESTFN)
         
     def test_write_data(self):
         # Should be able to write a file.
-        self.loader.write_data(self.source, self.source_path, False)
-        read_source = self.loader.read_data(self.source_path, False)
-        self.failUnlessEqual(read_source, self.source)
-        # XXX How to test data written with 'b'?
+        self.loader.write_data(self.data, self.file_path, True)
+        with open(self.file_path, 'rb') as test_file:
+            data = test_file.read()
+        self.failUnlessEqual(data, self.data)
+        # XXX How to test data written with/without 'b'?
 
 
 class PyPycHandlerSupportingMethodTests(PyPycFileHelper):
@@ -436,7 +474,7 @@
             raise test_support.TestSkipped("not extension modules found")
         self.ext_path = os.path.join(entry, ext_paths[0])
         self.module_name = os.path.splitext(os.path.split(self.ext_path)[1])[0]
-        self.loader = importer.FileSystemLoader(self.ext_path, self.handler)
+        self.loader = mock_importer.MockHandler()
         
     def test_handle_code(self):
         # Make sure an extension module can be loaded.
@@ -465,7 +503,8 @@
         sys.path_hooks = []
         self.old_path_importer_cache = sys.path_importer_cache.copy()
         sys.path_importer_cache.clear()
-        self.import_ = importer.Importer()
+        self.default_importer = mock_importer.PassImporter()
+        self.importer = importer.Importer(self.default_importer, tuple())
         
     def tearDown(self):
         """Restore backup of import-related attributes in 'sys'."""
@@ -486,46 +525,21 @@
 
 class ImporterMiscTests(ImporterHelper):
 
-    """Test miscellaneous parts of the Importer class."""
+    """Test miscellaneous parts of the Importer class.
+    
+    Tests of the default values for __init__ are handled in the integration
+    tests.
+
+    """
 
     def test_sys_module_return(self):
         # A module found in sys.modules should be returned immediately.
-        sys.path_importer_cache.clear()
-        sys.path = []
         test_module = '<test>'
         sys.modules[test_module] = test_module
-        module = self.import_('<test>')
+        module = self.importer.import_('<test>')
+        del sys.modules[test_module]
         self.failUnlessEqual(module, test_module)
-
-    def test_default_init(self):
-        # The default initialization should work with a None entry for every
-        # sys.path entry in sys.path_importer_cache.  It should also lead to
-        # built-in, frozen, extension, .pyc, and .py files being imported if
-        # desired.
-        sys.path_importer_cache = dict((entry, None) for entry in sys.path)
-        self.clear_sys_module('sys', '__hello__', 'time', 'token')
-        # Restore sys.path for 'time' import.
-        sys.path = self.old_sys_path
-        # Built-ins.
-        module = self.import_('sys')
-        self.failUnlessEqual(module.__name__, 'sys')
-        self.failUnless(hasattr(sys, 'version'))
-        # Frozen modules.
-        try:
-            sys.stdout = StringIO.StringIO()
-            module = self.import_('__hello__')
-        finally:
-            sys.stdout = sys.__stdout__
-        self.failUnlessEqual(module.__name__, '__hello__')
-        # Extension modules.
-        module = self.import_('time')
-        self.failUnlessEqual(module.__name__, 'time')
-        self.failUnless(hasattr(module, 'sleep'))
-        # .py/.pyc files.
-        module = self.import_('token')
-        self.failUnlessEqual(module.__name__, 'token')
-        self.failUnless(hasattr(module, 'ISTERMINAL'))
-        
+    
     def test_empty_fromlist(self):
         # An empty fromlist means that the root module is returned.
         # XXX
@@ -545,15 +559,13 @@
         # Test search method of sys.meta_path.
         # Should raise ImportError on error.
         self.clear_sys_module('sys')
-        import_ = importer.Importer(extended_meta_path=())
-        sys.meta_path = []
-        self.failUnlessRaises(ImportError, import_.search_meta_path,
+        self.failUnlessRaises(ImportError, self.importer.search_meta_path,
                                 'sys')
         # Verify call order.
         meta_path = (mock_importer.PassImporter(),
                         mock_importer.SucceedImporter())
         sys.meta_path = meta_path
-        loader = import_.search_meta_path('sys')
+        loader = self.importer.search_meta_path('sys')
         for entry in meta_path:
             self.failUnlessEqual(entry.find_request, ('sys', None))
         self.failUnless(loader is meta_path[-1])
@@ -565,8 +577,8 @@
         pass_importer = mock_importer.PassImporter()
         sys.meta_path = [pass_importer]
         succeed_importer = mock_importer.SucceedImporter()
-        import_ = importer.Importer(extended_meta_path=(succeed_importer,))
-        module = import_('sys')
+        importer_ = importer.Importer(extended_meta_path=(succeed_importer,))
+        module = importer_.import_('sys')
         for meta_importer in (pass_importer, succeed_importer):
             self.failUnlessEqual(meta_importer.find_request, ('sys', None))
         self.failUnless(module is mock_importer.SucceedImporter.module)
@@ -581,22 +593,22 @@
         # when sys.path_importer_cache has a value of None.
         self.clear_sys_module('sys')
         succeed_importer = mock_importer.SucceedImporter()
-        import_ = importer.Importer(succeed_importer, ())
+        importer_ = importer.Importer(succeed_importer, tuple())
         sys.meta_path = []
         sys.path = ['<succeed>']
         sys.path_importer_cache['<succeed>'] = None
-        module = import_('sys')
+        module = importer_.import_('sys')
         self.failUnlessEqual(succeed_importer.find_request, ('sys', None))
         self.failUnless(module is mock_importer.SucceedImporter.module)
 
     def test_search_sys_path(self):
         # Test sys.path searching for a loader.
         self.clear_sys_module('sys')
-        import_ = importer.Importer(extended_meta_path=())
+        importer_ = importer.Importer(extended_meta_path=())
         sys.path = []
         sys_path = (mock_importer.PassImporter.set_on_sys_path(),
                     mock_importer.SucceedImporter.set_on_sys_path())
-        module = import_('token')
+        module = importer_.import_('token')
         for entry in sys_path:
             self.failUnlessEqual(entry.find_request, ('token', None))
         self.failUnless(module is mock_importer.SucceedImporter.module)
@@ -607,7 +619,7 @@
         self.clear_sys_module('sys')
         sys.path = []
         succeed_importer = mock_importer.SucceedImporter.set_on_sys_path()
-        loader = self.import_.search_sys_path('sys')
+        loader = self.importer.search_sys_path('sys')
         self.failUnless(loader is succeed_importer)
         
     def test_importer_cache_from_path_hooks(self):
@@ -620,7 +632,7 @@
         sys.path = [path_entry]
         sys.path_importer_cache.clear()
         sys.path_hooks = [succeed_importer]
-        loader = self.import_.search_sys_path('sys')
+        loader = self.importer.search_sys_path('sys')
         self.failUnless(loader is succeed_importer)
         self.failUnless(sys.path_importer_cache[path_entry] is
                         succeed_importer)
@@ -633,8 +645,52 @@
         sys.path = [path_entry]
         sys.path_hooks = []
         sys.path_importer_cache.clear()
-        self.failUnlessRaises(ImportError, self.import_.search_sys_path, 'sys')
+        self.failUnlessRaises(ImportError, self.importer.search_sys_path, 'sys')
         self.failUnless(sys.path_importer_cache[path_entry] is None)
+        
+        
+class IntegrationTests(unittest.TestCase):
+    
+    """Hold tests that involve multiple classes from 'importer' and verify
+    integration semantics.
+    
+    XXX
+    * file loader <-> py/pyc handler
+    * file loader <-> extension handler
+    * file importer -> file loader
+    * file importer -> file loader <-> py/pyc handler
+    * Importer -> importer -> loader
+    
+    """
+    def XXX_test_default_import(self):
+        # The default initialization should work with a None entry for every
+        # sys.path entry in sys.path_importer_cache.  It should also lead to
+        # built-in, frozen, extension, .pyc, and .py files being imported if
+        # desired.
+        sys.path_importer_cache = dict((entry, None) for entry in sys.path)
+        self.clear_sys_module('sys', '__hello__', 'time', 'token')
+        # Restore sys.path for 'time' import.
+        sys.path = self.old_sys_path
+        import_ = importer.Importer()
+        # Built-ins.
+        module = import_('sys')
+        self.failUnlessEqual(module.__name__, 'sys')
+        self.failUnless(hasattr(sys, 'version'))
+        # Frozen modules.
+        try:
+            sys.stdout = StringIO.StringIO()
+            module = import_('__hello__')
+        finally:
+            sys.stdout = sys.__stdout__
+        self.failUnlessEqual(module.__name__, '__hello__')
+        # Extension modules.
+        module = import_('time')
+        self.failUnlessEqual(module.__name__, 'time')
+        self.failUnless(hasattr(module, 'sleep'))
+        # .py/.pyc files.
+        module = import_('token')
+        self.failUnlessEqual(module.__name__, 'token')
+        self.failUnless(hasattr(module, 'ISTERMINAL'))
 
 
 def test_main():


More information about the Python-checkins mailing list