mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	importlib.abc.ExecutionLoader. PyLoader now inherits from this ABC instead of InspectLoader directly. Both PyLoader and PyPycLoader provide concrete implementations of get_filename in terms of source_path and bytecode_path.
		
			
				
	
	
		
			477 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			477 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import importlib
 | 
						|
from importlib import abc
 | 
						|
from .. import abc as testing_abc
 | 
						|
from .. import util
 | 
						|
from . import util as source_util
 | 
						|
import imp
 | 
						|
import marshal
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import types
 | 
						|
import unittest
 | 
						|
 | 
						|
 | 
						|
class PyLoaderMock(abc.PyLoader):
 | 
						|
 | 
						|
    # Globals that should be defined for all modules.
 | 
						|
    source = (b"_ = '::'.join([__name__, __file__, __package__, "
 | 
						|
              b"repr(__loader__)])")
 | 
						|
 | 
						|
    def __init__(self, data):
 | 
						|
        """Take a dict of 'module_name: path' pairings.
 | 
						|
 | 
						|
        Paths should have no file extension, allowing packages to be denoted by
 | 
						|
        ending in '__init__'.
 | 
						|
 | 
						|
        """
 | 
						|
        self.module_paths = data
 | 
						|
        self.path_to_module = {val:key for key,val in data.items()}
 | 
						|
 | 
						|
    def get_data(self, path):
 | 
						|
        if path not in self.path_to_module:
 | 
						|
            raise IOError
 | 
						|
        return self.source
 | 
						|
 | 
						|
    def is_package(self, name):
 | 
						|
        try:
 | 
						|
            return '__init__' in self.module_paths[name]
 | 
						|
        except KeyError:
 | 
						|
            raise ImportError
 | 
						|
 | 
						|
    def source_path(self, name):
 | 
						|
        try:
 | 
						|
            return self.module_paths[name]
 | 
						|
        except KeyError:
 | 
						|
            raise ImportError
 | 
						|
 | 
						|
 | 
						|
class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
 | 
						|
 | 
						|
    default_mtime = 1
 | 
						|
 | 
						|
    def __init__(self, source, bc={}):
 | 
						|
        """Initialize mock.
 | 
						|
 | 
						|
        'bc' is a dict keyed on a module's name. The value is dict with
 | 
						|
        possible keys of 'path', 'mtime', 'magic', and 'bc'. Except for 'path',
 | 
						|
        each of those keys control if any part of created bytecode is to
 | 
						|
        deviate from default values.
 | 
						|
 | 
						|
        """
 | 
						|
        super().__init__(source)
 | 
						|
        self.module_bytecode = {}
 | 
						|
        self.path_to_bytecode = {}
 | 
						|
        self.bytecode_to_path = {}
 | 
						|
        for name, data in bc.items():
 | 
						|
            self.path_to_bytecode[data['path']] = name
 | 
						|
            self.bytecode_to_path[name] = data['path']
 | 
						|
            magic = data.get('magic', imp.get_magic())
 | 
						|
            mtime = importlib._w_long(data.get('mtime', self.default_mtime))
 | 
						|
            if 'bc' in data:
 | 
						|
                bc = data['bc']
 | 
						|
            else:
 | 
						|
                bc = self.compile_bc(name)
 | 
						|
            self.module_bytecode[name] = magic + mtime + bc
 | 
						|
 | 
						|
    def compile_bc(self, name):
 | 
						|
        source_path = self.module_paths.get(name, '<test>') or '<test>'
 | 
						|
        code = compile(self.source, source_path, 'exec')
 | 
						|
        return marshal.dumps(code)
 | 
						|
 | 
						|
    def source_mtime(self, name):
 | 
						|
        if name in self.module_paths:
 | 
						|
            return self.default_mtime
 | 
						|
        elif name in self.module_bytecode:
 | 
						|
            return None
 | 
						|
        else:
 | 
						|
            raise ImportError
 | 
						|
 | 
						|
    def bytecode_path(self, name):
 | 
						|
        try:
 | 
						|
            return self.bytecode_to_path[name]
 | 
						|
        except KeyError:
 | 
						|
            if name in self.module_paths:
 | 
						|
                return None
 | 
						|
            else:
 | 
						|
                raise ImportError
 | 
						|
 | 
						|
    def write_bytecode(self, name, bytecode):
 | 
						|
        self.module_bytecode[name] = bytecode
 | 
						|
        return True
 | 
						|
 | 
						|
    def get_data(self, path):
 | 
						|
        if path in self.path_to_module:
 | 
						|
            return super().get_data(path)
 | 
						|
        elif path in self.path_to_bytecode:
 | 
						|
            name = self.path_to_bytecode[path]
 | 
						|
            return self.module_bytecode[name]
 | 
						|
        else:
 | 
						|
            raise IOError
 | 
						|
 | 
						|
    def is_package(self, name):
 | 
						|
        try:
 | 
						|
            return super().is_package(name)
 | 
						|
        except TypeError:
 | 
						|
            return '__init__' in self.bytecode_to_path[name]
 | 
						|
 | 
						|
 | 
						|
class PyLoaderTests(testing_abc.LoaderTests):
 | 
						|
 | 
						|
    """Tests for importlib.abc.PyLoader."""
 | 
						|
 | 
						|
    mocker = PyLoaderMock
 | 
						|
 | 
						|
    def eq_attrs(self, ob, **kwargs):
 | 
						|
        for attr, val in kwargs.items():
 | 
						|
            found = getattr(ob, attr)
 | 
						|
            self.assertEqual(found, val,
 | 
						|
                    "{} attribute: {} != {}".format(attr, found, val))
 | 
						|
 | 
						|
    def test_module(self):
 | 
						|
        name = '<module>'
 | 
						|
        path = os.path.join('', 'path', 'to', 'module')
 | 
						|
        mock = self.mocker({name: path})
 | 
						|
        with util.uncache(name):
 | 
						|
            module = mock.load_module(name)
 | 
						|
            self.assertTrue(name in sys.modules)
 | 
						|
        self.eq_attrs(module, __name__=name, __file__=path, __package__='',
 | 
						|
                        __loader__=mock)
 | 
						|
        self.assertTrue(not hasattr(module, '__path__'))
 | 
						|
        return mock, name
 | 
						|
 | 
						|
    def test_package(self):
 | 
						|
        name = '<pkg>'
 | 
						|
        path = os.path.join('path', 'to', name, '__init__')
 | 
						|
        mock = self.mocker({name: path})
 | 
						|
        with util.uncache(name):
 | 
						|
            module = mock.load_module(name)
 | 
						|
            self.assertTrue(name in sys.modules)
 | 
						|
        self.eq_attrs(module, __name__=name, __file__=path,
 | 
						|
                __path__=[os.path.dirname(path)], __package__=name,
 | 
						|
                __loader__=mock)
 | 
						|
        return mock, name
 | 
						|
 | 
						|
    def test_lacking_parent(self):
 | 
						|
        name = 'pkg.mod'
 | 
						|
        path = os.path.join('path', 'to', 'pkg', 'mod')
 | 
						|
        mock = self.mocker({name: path})
 | 
						|
        with util.uncache(name):
 | 
						|
            module = mock.load_module(name)
 | 
						|
            self.assertIn(name, sys.modules)
 | 
						|
        self.eq_attrs(module, __name__=name, __file__=path, __package__='pkg',
 | 
						|
                        __loader__=mock)
 | 
						|
        self.assertFalse(hasattr(module, '__path__'))
 | 
						|
        return mock, name
 | 
						|
 | 
						|
    def test_module_reuse(self):
 | 
						|
        name = 'mod'
 | 
						|
        path = os.path.join('path', 'to', 'mod')
 | 
						|
        module = imp.new_module(name)
 | 
						|
        mock = self.mocker({name: path})
 | 
						|
        with util.uncache(name):
 | 
						|
            sys.modules[name] = module
 | 
						|
            loaded_module = mock.load_module(name)
 | 
						|
            self.assertTrue(loaded_module is module)
 | 
						|
            self.assertTrue(sys.modules[name] is module)
 | 
						|
        return mock, name
 | 
						|
 | 
						|
    def test_state_after_failure(self):
 | 
						|
        name = "mod"
 | 
						|
        module = imp.new_module(name)
 | 
						|
        module.blah = None
 | 
						|
        mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
 | 
						|
        mock.source = b"1/0"
 | 
						|
        with util.uncache(name):
 | 
						|
            sys.modules[name] = module
 | 
						|
            with self.assertRaises(ZeroDivisionError):
 | 
						|
                mock.load_module(name)
 | 
						|
            self.assertTrue(sys.modules[name] is module)
 | 
						|
            self.assertTrue(hasattr(module, 'blah'))
 | 
						|
        return mock
 | 
						|
 | 
						|
    def test_unloadable(self):
 | 
						|
        name = "mod"
 | 
						|
        mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
 | 
						|
        mock.source = b"1/0"
 | 
						|
        with util.uncache(name):
 | 
						|
            with self.assertRaises(ZeroDivisionError):
 | 
						|
                mock.load_module(name)
 | 
						|
            self.assertTrue(name not in sys.modules)
 | 
						|
        return mock
 | 
						|
 | 
						|
 | 
						|
class PyLoaderInterfaceTests(unittest.TestCase):
 | 
						|
 | 
						|
    """Tests for importlib.abc.PyLoader to make sure that when source_path()
 | 
						|
    doesn't return a path everything works as expected."""
 | 
						|
 | 
						|
    def test_no_source_path(self):
 | 
						|
        # No source path should lead to ImportError.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyLoaderMock({})
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
    def test_source_path_is_None(self):
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyLoaderMock({name: None})
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
    def test_get_filename_with_source_path(self):
 | 
						|
        # get_filename() should return what source_path() returns.
 | 
						|
        name = 'mod'
 | 
						|
        path = os.path.join('path', 'to', 'source')
 | 
						|
        mock = PyLoaderMock({name: path})
 | 
						|
        with util.uncache(name):
 | 
						|
            self.assertEqual(mock.get_filename(name), path)
 | 
						|
 | 
						|
    def test_get_filename_no_source_path(self):
 | 
						|
        # get_filename() should raise ImportError if source_path returns None.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyLoaderMock({name: None})
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.get_filename(name)
 | 
						|
 | 
						|
 | 
						|
class PyLoaderGetSourceTests(unittest.TestCase):
 | 
						|
 | 
						|
    """Tests for importlib.abc.PyLoader.get_source()."""
 | 
						|
 | 
						|
    def test_default_encoding(self):
 | 
						|
        # Should have no problems with UTF-8 text.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyLoaderMock({name: os.path.join('path', 'to', 'mod')})
 | 
						|
        source = 'x = "ü"'
 | 
						|
        mock.source = source.encode('utf-8')
 | 
						|
        returned_source = mock.get_source(name)
 | 
						|
        self.assertEqual(returned_source, source)
 | 
						|
 | 
						|
    def test_decoded_source(self):
 | 
						|
        # Decoding should work.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyLoaderMock({name: os.path.join('path', 'to', 'mod')})
 | 
						|
        source = "# coding: Latin-1\nx='ü'"
 | 
						|
        assert source.encode('latin-1') != source.encode('utf-8')
 | 
						|
        mock.source = source.encode('latin-1')
 | 
						|
        returned_source = mock.get_source(name)
 | 
						|
        self.assertEqual(returned_source, source)
 | 
						|
 | 
						|
 | 
						|
class PyPycLoaderTests(PyLoaderTests):
 | 
						|
 | 
						|
    """Tests for importlib.abc.PyPycLoader."""
 | 
						|
 | 
						|
    mocker = PyPycLoaderMock
 | 
						|
 | 
						|
    @source_util.writes_bytecode_files
 | 
						|
    def verify_bytecode(self, mock, name):
 | 
						|
        assert name in mock.module_paths
 | 
						|
        self.assertIn(name, mock.module_bytecode)
 | 
						|
        magic = mock.module_bytecode[name][:4]
 | 
						|
        self.assertEqual(magic, imp.get_magic())
 | 
						|
        mtime = importlib._r_long(mock.module_bytecode[name][4:8])
 | 
						|
        self.assertEqual(mtime, 1)
 | 
						|
        bc = mock.module_bytecode[name][8:]
 | 
						|
        self.assertEqual(bc, mock.compile_bc(name))
 | 
						|
 | 
						|
    def test_module(self):
 | 
						|
        mock, name = super().test_module()
 | 
						|
        self.verify_bytecode(mock, name)
 | 
						|
 | 
						|
    def test_package(self):
 | 
						|
        mock, name = super().test_package()
 | 
						|
        self.verify_bytecode(mock, name)
 | 
						|
 | 
						|
    def test_lacking_parent(self):
 | 
						|
        mock, name = super().test_lacking_parent()
 | 
						|
        self.verify_bytecode(mock, name)
 | 
						|
 | 
						|
    def test_module_reuse(self):
 | 
						|
        mock, name = super().test_module_reuse()
 | 
						|
        self.verify_bytecode(mock, name)
 | 
						|
 | 
						|
    def test_state_after_failure(self):
 | 
						|
        super().test_state_after_failure()
 | 
						|
 | 
						|
    def test_unloadable(self):
 | 
						|
        super().test_unloadable()
 | 
						|
 | 
						|
 | 
						|
class PyPycLoaderInterfaceTests(unittest.TestCase):
 | 
						|
 | 
						|
    """Test for the interface of importlib.abc.PyPycLoader."""
 | 
						|
 | 
						|
    def get_filename_check(self, src_path, bc_path, expect):
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyPycLoaderMock({name: src_path}, {name: {'path': bc_path}})
 | 
						|
        with util.uncache(name):
 | 
						|
            assert mock.source_path(name) == src_path
 | 
						|
            assert mock.bytecode_path(name) == bc_path
 | 
						|
            self.assertEqual(mock.get_filename(name), expect)
 | 
						|
 | 
						|
    def test_filename_with_source_bc(self):
 | 
						|
        # When source and bytecode paths present, return the source path.
 | 
						|
        self.get_filename_check('source_path', 'bc_path', 'source_path')
 | 
						|
 | 
						|
    def test_filename_with_source_no_bc(self):
 | 
						|
        # With source but no bc, return source path.
 | 
						|
        self.get_filename_check('source_path', None, 'source_path')
 | 
						|
 | 
						|
    def test_filename_with_no_source_bc(self):
 | 
						|
        # With not source but bc, return the bc path.
 | 
						|
        self.get_filename_check(None, 'bc_path', 'bc_path')
 | 
						|
 | 
						|
    def test_filename_with_no_source_or_bc(self):
 | 
						|
        # With no source or bc, raise ImportError.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyPycLoaderMock({name: None}, {name: {'path': None}})
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.get_filename(name)
 | 
						|
 | 
						|
 | 
						|
class SkipWritingBytecodeTests(unittest.TestCase):
 | 
						|
 | 
						|
    """Test that bytecode is properly handled based on
 | 
						|
    sys.dont_write_bytecode."""
 | 
						|
 | 
						|
    @source_util.writes_bytecode_files
 | 
						|
    def run_test(self, dont_write_bytecode):
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
 | 
						|
        sys.dont_write_bytecode = dont_write_bytecode
 | 
						|
        with util.uncache(name):
 | 
						|
            mock.load_module(name)
 | 
						|
        self.assertTrue((name in mock.module_bytecode) is not
 | 
						|
                        dont_write_bytecode)
 | 
						|
 | 
						|
    def test_no_bytecode_written(self):
 | 
						|
        self.run_test(True)
 | 
						|
 | 
						|
    def test_bytecode_written(self):
 | 
						|
        self.run_test(False)
 | 
						|
 | 
						|
 | 
						|
class RegeneratedBytecodeTests(unittest.TestCase):
 | 
						|
 | 
						|
    """Test that bytecode is regenerated as expected."""
 | 
						|
 | 
						|
    @source_util.writes_bytecode_files
 | 
						|
    def test_different_magic(self):
 | 
						|
        # A different magic number should lead to new bytecode.
 | 
						|
        name = 'mod'
 | 
						|
        bad_magic = b'\x00\x00\x00\x00'
 | 
						|
        assert bad_magic != imp.get_magic()
 | 
						|
        mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
 | 
						|
                                {name: {'path': os.path.join('path', 'to',
 | 
						|
                                                    'mod.bytecode'),
 | 
						|
                                        'magic': bad_magic}})
 | 
						|
        with util.uncache(name):
 | 
						|
            mock.load_module(name)
 | 
						|
        self.assertTrue(name in mock.module_bytecode)
 | 
						|
        magic = mock.module_bytecode[name][:4]
 | 
						|
        self.assertEqual(magic, imp.get_magic())
 | 
						|
 | 
						|
    @source_util.writes_bytecode_files
 | 
						|
    def test_old_mtime(self):
 | 
						|
        # Bytecode with an older mtime should be regenerated.
 | 
						|
        name = 'mod'
 | 
						|
        old_mtime = PyPycLoaderMock.default_mtime - 1
 | 
						|
        mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
 | 
						|
                {name: {'path': 'path/to/mod.bytecode', 'mtime': old_mtime}})
 | 
						|
        with util.uncache(name):
 | 
						|
            mock.load_module(name)
 | 
						|
        self.assertTrue(name in mock.module_bytecode)
 | 
						|
        mtime = importlib._r_long(mock.module_bytecode[name][4:8])
 | 
						|
        self.assertEqual(mtime, PyPycLoaderMock.default_mtime)
 | 
						|
 | 
						|
 | 
						|
class BadBytecodeFailureTests(unittest.TestCase):
 | 
						|
 | 
						|
    """Test import failures when there is no source and parts of the bytecode
 | 
						|
    is bad."""
 | 
						|
 | 
						|
    def test_bad_magic(self):
 | 
						|
        # A bad magic number should lead to an ImportError.
 | 
						|
        name = 'mod'
 | 
						|
        bad_magic = b'\x00\x00\x00\x00'
 | 
						|
        bc = {name:
 | 
						|
                {'path': os.path.join('path', 'to', 'mod'),
 | 
						|
                 'magic': bad_magic}}
 | 
						|
        mock = PyPycLoaderMock({name: None}, bc)
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
    def test_no_bytecode(self):
 | 
						|
        # Missing code object bytecode should lead to an EOFError.
 | 
						|
        name = 'mod'
 | 
						|
        bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b''}}
 | 
						|
        mock = PyPycLoaderMock({name: None}, bc)
 | 
						|
        with util.uncache(name), self.assertRaises(EOFError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
    def test_bad_bytecode(self):
 | 
						|
        # Malformed code object bytecode should lead to a ValueError.
 | 
						|
        name = 'mod'
 | 
						|
        bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b'XXX'}}
 | 
						|
        mock = PyPycLoaderMock({name: None}, bc)
 | 
						|
        with util.uncache(name), self.assertRaises(ValueError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
 | 
						|
def raise_ImportError(*args, **kwargs):
 | 
						|
    raise ImportError
 | 
						|
 | 
						|
class MissingPathsTests(unittest.TestCase):
 | 
						|
 | 
						|
    """Test what happens when a source or bytecode path does not exist (either
 | 
						|
    from *_path returning None or raising ImportError)."""
 | 
						|
 | 
						|
    def test_source_path_None(self):
 | 
						|
        # Bytecode should be used when source_path returns None, along with
 | 
						|
        # __file__ being set to the bytecode path.
 | 
						|
        name = 'mod'
 | 
						|
        bytecode_path = 'path/to/mod'
 | 
						|
        mock = PyPycLoaderMock({name: None}, {name: {'path': bytecode_path}})
 | 
						|
        with util.uncache(name):
 | 
						|
            module = mock.load_module(name)
 | 
						|
        self.assertEqual(module.__file__, bytecode_path)
 | 
						|
 | 
						|
    # Testing for bytecode_path returning None handled by all tests where no
 | 
						|
    # bytecode initially exists.
 | 
						|
 | 
						|
    def test_all_paths_None(self):
 | 
						|
        # If all *_path methods return None, raise ImportError.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyPycLoaderMock({name: None})
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
    def test_source_path_ImportError(self):
 | 
						|
        # An ImportError from source_path should trigger an ImportError.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyPycLoaderMock({}, {name: {'path': os.path.join('path', 'to',
 | 
						|
                                                                'mod')}})
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
    def test_bytecode_path_ImportError(self):
 | 
						|
        # An ImportError from bytecode_path should trigger an ImportError.
 | 
						|
        name = 'mod'
 | 
						|
        mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
 | 
						|
        bad_meth = types.MethodType(raise_ImportError, mock)
 | 
						|
        mock.bytecode_path = bad_meth
 | 
						|
        with util.uncache(name), self.assertRaises(ImportError):
 | 
						|
            mock.load_module(name)
 | 
						|
 | 
						|
 | 
						|
def test_main():
 | 
						|
    from test.support import run_unittest
 | 
						|
    run_unittest(PyLoaderTests, PyLoaderInterfaceTests, PyLoaderGetSourceTests,
 | 
						|
                    PyPycLoaderTests, PyPycLoaderInterfaceTests,
 | 
						|
                    SkipWritingBytecodeTests, RegeneratedBytecodeTests,
 | 
						|
                    BadBytecodeFailureTests, MissingPathsTests)
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    test_main()
 |