bpo-45324: Capture data in FrozenImporter.find_spec() to use in exec_module(). (gh-28633)

Before this change we end up duplicating effort and throwing away data in FrozenImporter.find_spec().  Now we do the work once in find_spec() and the only thing we do in FrozenImporter.exec_module() is turn the raw frozen data into a code object and then exec it.

We've added _imp.find_frozen(), add an arg to _imp.get_frozen_object(), and updated FrozenImporter.  We've also moved some code around to reduce duplication, get a little more consistency in outcomes, and be more efficient.

Note that this change is mostly necessary if we want to set __file__ on frozen stdlib modules. (See https://bugs.python.org/issue21736.)

https://bugs.python.org/issue45324
This commit is contained in:
Eric Snow 2021-10-05 10:01:27 -06:00 committed by GitHub
parent b9bb74871b
commit c3d9ac8b34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 331 additions and 120 deletions

View file

@ -1,13 +1,15 @@
from .. import abc
import os.path
from .. import util
machinery = util.import_importlib('importlib.machinery')
import _imp
import marshal
import os.path
import unittest
import warnings
from test.support import import_helper
from test.support import import_helper, REPO_ROOT
class FindSpecTests(abc.FinderTests):
@ -19,39 +21,67 @@ class FindSpecTests(abc.FinderTests):
with import_helper.frozen_modules():
return finder.find_spec(name, **kwargs)
def check(self, spec, name):
def check_basic(self, spec, name, ispkg=False):
self.assertEqual(spec.name, name)
self.assertIs(spec.loader, self.machinery.FrozenImporter)
self.assertEqual(spec.origin, 'frozen')
self.assertFalse(spec.has_location)
if ispkg:
self.assertIsNotNone(spec.submodule_search_locations)
else:
self.assertIsNone(spec.submodule_search_locations)
self.assertIsNotNone(spec.loader_state)
def check_search_location(self, spec, source=None):
# Frozen packages do not have any path entries.
# (See https://bugs.python.org/issue21736.)
expected = []
self.assertListEqual(spec.submodule_search_locations, expected)
def check_data(self, spec, source=None, ispkg=None):
with import_helper.frozen_modules():
expected = _imp.get_frozen_object(spec.name)
data = spec.loader_state
# We can't compare the marshaled data directly because
# marshal.dumps() would mark "expected" as a ref, which slightly
# changes the output. (See https://bugs.python.org/issue34093.)
code = marshal.loads(data)
self.assertEqual(code, expected)
def test_module(self):
names = [
'__hello__',
'__hello_alias__',
'__hello_only__',
'__phello__.__init__',
'__phello__.spam',
'__phello__.ham.__init__',
'__phello__.ham.eggs',
]
for name in names:
modules = {
'__hello__': None,
'__phello__.__init__': None,
'__phello__.spam': None,
'__phello__.ham.__init__': None,
'__phello__.ham.eggs': None,
'__hello_alias__': '__hello__',
}
for name, source in modules.items():
with self.subTest(name):
spec = self.find(name)
self.check(spec, name)
self.assertEqual(spec.submodule_search_locations, None)
self.check_basic(spec, name)
self.check_data(spec, source)
def test_package(self):
names = [
'__phello__',
'__phello__.ham',
'__phello_alias__',
]
for name in names:
modules = {
'__phello__': None,
'__phello__.ham': None,
'__phello_alias__': '__hello__',
}
for name, source in modules.items():
with self.subTest(name):
spec = self.find(name)
self.check(spec, name)
self.assertEqual(spec.submodule_search_locations, [])
self.check_basic(spec, name, ispkg=True)
self.check_search_location(spec, source)
self.check_data(spec, source, ispkg=True)
def test_frozen_only(self):
name = '__hello_only__'
source = os.path.join(REPO_ROOT, 'Tools', 'freeze', 'flag.py')
spec = self.find(name)
self.check_basic(spec, name)
self.check_data(spec, source)
# These are covered by test_module() and test_package().
test_module_in_package = None

View file

@ -4,7 +4,9 @@ from .. import util
machinery = util.import_importlib('importlib.machinery')
from test.support import captured_stdout, import_helper
import _imp
import contextlib
import marshal
import types
import unittest
import warnings
@ -33,11 +35,14 @@ class ExecModuleTests(abc.LoaderTests):
def exec_module(self, name):
with import_helper.frozen_modules():
is_package = self.machinery.FrozenImporter.is_package(name)
code = _imp.get_frozen_object(name)
data = marshal.dumps(code)
spec = self.machinery.ModuleSpec(
name,
self.machinery.FrozenImporter,
origin='frozen',
is_package=is_package,
loader_state=data,
)
module = types.ModuleType(name)
module.__spec__ = spec
@ -61,6 +66,7 @@ class ExecModuleTests(abc.LoaderTests):
self.assertEqual(getattr(module, attr), value)
self.assertEqual(output, 'Hello world!\n')
self.assertTrue(hasattr(module, '__spec__'))
self.assertIsNone(module.__spec__.loader_state)
def test_package(self):
name = '__phello__'
@ -73,6 +79,7 @@ class ExecModuleTests(abc.LoaderTests):
name=name, attr=attr, given=attr_value,
expected=value))
self.assertEqual(output, 'Hello world!\n')
self.assertIsNone(module.__spec__.loader_state)
def test_lacking_parent(self):
name = '__phello__.spam'