Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1269,8 +1269,14 @@ class Spec:
pass
spec = Spec()

spec.name = "sys"
self.assertIs(_imp.create_builtin(spec), sys)
# Reloading a core module copies back the module dict snapshot taken
# when it was first initialized, in which __spec__ and __loader__ are
# still None. Swap them back so the rest of the suite sees a sys that
# carries its import metadata.
with (swap_attr(sys, '__spec__', sys.__spec__),
swap_attr(sys, '__loader__', sys.__loader__)):
spec.name = "sys"
self.assertIs(_imp.create_builtin(spec), sys)

spec.name = None
with self.assertRaisesRegex(TypeError, 'name must be string, not NoneType'):
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_importlib/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@
_interpreters = None


class ImportImportlibTests(unittest.TestCase):

def test_restores_missing_import_metadata(self):
# importlib._bootstrap._setup() installs the loader classes of the
# source copy on any module that has no __spec__ of its own.
for name in ('builtins', 'sys'):
module = sys.modules[name]
with self.subTest(module=name):
with (support.swap_attr(module, '__spec__', None),
support.swap_attr(module, '__loader__', None)):
util.import_importlib('importlib')

self.assertIsNone(module.__spec__)
self.assertIsNone(module.__loader__)


class DecodeSourceBytesTests:

source = "string ='ü'"
Expand Down
40 changes: 38 additions & 2 deletions Lib/test/test_importlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,48 @@ def _extension_details():
_extension_details()


_MISSING = object()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do note Python has proper sentinel support in Python 3.15.



@contextlib.contextmanager
def _restore_import_metadata():
"""Keep the source copy of importlib out of other modules' metadata.

Importing importlib with _frozen_importlib blocked runs
importlib._bootstrap._setup(), which fills in __spec__ and __loader__ on
every builtin and frozen module that lacks them. After the import, this
context restores the original values of those attributes on all modules that
were modified so those modifications don't leak into other tests.
"""
incomplete = {}
for name, module in list(sys.modules.items()):
if isinstance(module, types.ModuleType):
for attr in ('__spec__', '__loader__'):
value = getattr(module, attr, _MISSING)
if value is None or value is _MISSING:
incomplete[name, attr] = value
try:
yield
finally:
for (name, attr), value in incomplete.items():
module = sys.modules.get(name)
if module is not None and getattr(module, attr, _MISSING) is not value:
if value is _MISSING:
delattr(module, attr)
else:
setattr(module, attr, value)


def import_importlib(module_name):
"""Import a module from importlib both w/ and w/o _frozen_importlib."""
fresh = ('importlib',) if '.' in module_name else ()
frozen = import_helper.import_fresh_module(module_name)
source = import_helper.import_fresh_module(module_name, fresh=fresh,
blocked=('_frozen_importlib', '_frozen_importlib_external'))
with _restore_import_metadata():
source = import_helper.import_fresh_module(
module_name,
fresh=fresh,
blocked=('_frozen_importlib', '_frozen_importlib_external'),
)
return {'Frozen': frozen, 'Source': source}


Expand Down
Loading