import importlib

from tests import mock_importlib
from tests.py_help import TestPyPycPackages
from importlib import _r_long

import imp
import marshal
import os
import StringIO
import sys
from test.test_support import run_unittest


class IntegrationTests(TestPyPycPackages):
    
    """Tests that verify the default semantics are what is expected.

    Tests should verify that:
    * The proper module was returned.
    * All expected modules were added to sys.modules.
    * All modules imported by the call have the proper attributes.
    
    There are currently no tests for the import lock.
    
    Need to test (both relative and absolute imports as needed):
    * import module
    * import package
    * import package.module
    * from module import attribute
    * from package import module
    * from package import attribute
    * from package.module import attribute
    * from package import *
        + With __all__

    """

    def setUp(self):
        TestPyPycPackages.setUp(self, False)
        self.import_ = importlib.Import()
        self.cached_modules = []
        if self.module_name in sys.modules:
            del sys.modules[self.module_name]
        if self.pkg_name in sys.modules:
            del sys.modules[self.pkg_name]
        if self.pkg_module_name in sys.modules:
            del sys.modules[self.pkg_module_name]

    def tearDown(self):
        TestPyPycPackages.tearDown(self)
        for module_name, module in self.cached_modules:
            sys.modules[module_name] = module

    def clear_sys_modules(self, *modules):
        """Remove specified modules from sys.modules for a test and put back
        during teardown."""
        for module_name in modules:
            try:
                self.cached_modules.append((module_name,
                    sys.modules[module_name]))
                del sys.modules[module_name]
            except KeyError:
                pass

    def test_builtin(self):
        # Test importing a built-in module.
        # ``import sys``
        self.clear_sys_modules('sys')
        module = self.import_('sys')
        self.failUnlessEqual(module.__name__, 'sys')
        # Only test for attributes that are not set during interpreter
        # creation!
        self.failUnless(hasattr(module, 'exc_info'))
        self.failUnless(hasattr(module, 'version'))

    def test_frozen(self):
        # Importing a frozen module should work.
        # ``import __hello__``
        self.clear_sys_modules('__hello__')
        faked_stdout = StringIO.StringIO()
        sys.stdout = faked_stdout
        try:
            module = self.import_('__hello__')
            self.failUnlessEqual(module.__name__, '__hello__')
            self.failUnless('frozen' in module.__file__)
        finally:
            sys.stdout = sys.__stdout__

    def test_extension(self):
        # Should be able to import extension modules.
        # ``import time``
        module = self.import_('time')
        self.failUnlessEqual(module.__name__, 'time')
        self.failUnless(hasattr(module, 'time'))

    def test_pyc_w_py(self):
        # Should be able to import a .pyc file when a .py is also present.
        # ``import pyc`` with a corresponding .py .
        module = self.import_(self.module_name)
        self.verify_module(module, self.pyc_path)

    def test_pyc_wo_py(self):
        # Importing just a .pyc file (w/ no .py) should be okay.
        # ``import pyc`` from a .pyc .
        os.remove(self.py_path)
        assert os.path.exists(self.pyc_path)
        module = self.import_(self.module_name)
        self.verify_module(module, self.pyc_path)

    def test_sys_modules(self):
        # Should be able to pull from sys.modules even if a file does not exist
        # for the module.
        # ``import module`` from sys.modules.
        test_module_name = '<' + self.module_name + '>'
        test_module = mock_importlib.MockModule(test_module_name)
        sys.modules[test_module_name] = test_module
        try:
            module = self.import_(test_module_name)
            self.failUnless(module is test_module)
        finally:
            del sys.modules[test_module_name]

    def test_py_creating_pyc(self):
        # Importing a .py file should work and generate a .pyc file.
        # ``import py`` creating a .pyc .
        os.remove(self.pyc_path)
        module = self.import_(self.module_name)
        self.verify_module(module)
        self.failUnless(os.path.exists(self.pyc_path))
        with open(self.pyc_path, 'rb') as pyc_file:
            data = pyc_file.read()
        self.failUnlessEqual(data[:4], imp.get_magic())
        py_mod = int(os.stat(self.py_path).st_mtime)
        # XXX Using importer's _r_long.
        pyc_mod = _r_long(data[4:8])
        self.failUnlessEqual(py_mod, pyc_mod)
        code = marshal.loads(data[8:])
        module = mock_importlib.MockModule(self.module_name)
        exec code in module.__dict__
        self.verify_module(module)

    def test_top_level_package(self):
        # Should be able to import a top-level package.
        # ``import package``
        module = self.import_(self.pkg_name)
        self.verify_package(module)

    def test_package_module(self):
        # A module within a top-level package should work with the package not
        # already imported.
        # ``import package.module``
        assert '.' in self.pkg_module_name
        module = self.import_(self.pkg_module_name)
        self.verify_package(module, self.pkg_module_name)

    def test_sub_package(self):
        # A package within a package should work.
        # ``import package.subpackage``
        module = self.import_(self.sub_pkg_name)
        self.verify_package(module, self.sub_pkg_name)

    def test_sub_package_module(self):
        # A module contained within a sub-package should work.
        # ``import package.subpackage.module``
        module = self.import_(self.sub_pkg_module_name)
        self.verify_package(module, self.sub_pkg_module_name)

    def test_classic_relative_import_in_package_init(self):
        # Importing within a package's __init__ file using a relative name
        # should work properly.
        # ``import module`` for 'package' where 'module' is 'package.module'.
        package_globals = {'__name__':self.pkg_name, '__path__':['some_path']}
        module = self.import_(self.module_name, package_globals, level=-1)
        self.verify_package(module, self.pkg_module_name)

    def test_classic_relative_import_in_module(self):
        # Importing using a relative name in a module in a package should work.
        # ``import module`` for 'package.some_module' where 'module' is
        # 'package.module'.
        module_globals = {'__name__':self.pkg_name + '.' + 'another_module'}
        module = self.import_(self.module_name, module_globals, level=-1)
        self.verify_package(module, self.pkg_module_name)

    def test_absolute_name_in_classic_relative_context(self):
        # Importing a module that happens to be ambiguous in terms of being
        # relative or absolute, but only exists in an absolute name context,
        # should work.  It should also lead to a value for None in sys.modules
        # for the resolved relative name.
        # ``import module`` for 'package' where 'module' is 'module'.
        package_module_globals = {'__name__':self.pkg_module_name}
        module = self.import_(self.top_level_module_name,
                                package_module_globals, level=-1)
        resolved_name = self.pkg_name + '.' + self.top_level_module_name
        self.verify_package(module)
        self.failUnlessEqual(module.__name__, self.top_level_module_name)
        self.failUnless(sys.modules[resolved_name] is None)

    def test_relative_import_in_package_init(self):
        # Importing a module with a relative name in a package's __init__ file
        # should work.
        # ``from . import module`` for 'package'.
        caller_globals = {'__name__':self.pkg_name, '__path__':[self.pkg_path]}
        module = self.import_('', caller_globals, fromlist=[self.module_name],
                                level=1)
        self.verify_package(module, self.pkg_name)

    def test_relative_import_in_package(self):
        # Importing a module with a relative name in another module should
        # work.
        # ``from . import module`` for 'package.module'.
        caller_globals = {'__name__':self.pkg_module_name}
        module = self.import_('', caller_globals, fromlist=[self.module_name],
                                level=1)
        self.verify_package(module, self.pkg_name)
        
    def test_relative_import_in_subpackages(self):
        # ``from .. import module`` in 'package.subpackage'.
        caller_globals = {'__name__':self.sub_pkg_name,
                            '__path__':[self.sub_pkg_path]}
        module = self.import_('', caller_globals, fromlist=[self.module_name],
                                level=2)
        self.verify_package(module, self.pkg_name)
        
    def test_relative_import_of_package(self):
        # ``from ..subpackage import module`` in 'package.subpackage'.
        # XXX
        caller_globals = {'__name__':self.sub_pkg_name,
                            '__path__':[self.sub_pkg_path]}
        module = self.import_(self.sub_pkg_tail_name, caller_globals,
                                fromlist=[self.module_name], level=2)
        self.verify_package(module, self.sub_pkg_name)
        
    def test_relative_import_return(self):
        # When importing from a relative name, the module up to the first dot
        # of that relative name (made absolute) should be returned.
        # ``import subpackage.module`` for 'package.module'.
        module_globals = {'__name__':self.pkg_module_name}
        relative_name = self.sub_pkg_tail_name + '.' + self.module_name
        module = self.import_(relative_name, module_globals)
        self.verify_package(module, self.sub_pkg_name)


def test_main():
    run_unittest(IntegrationTests)



if __name__ == '__main__':
    test_main()

