From da6c1ee25711421b6a1351feb9cc282d47638492 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 20 Jul 2026 13:08:05 -0400 Subject: [PATCH] test_importlib: restore builtins after import Our CI (https://github.com/facebookincubator/cinder/actions/runs/29382592631/job/87249270535) was failing on test_pickle and test_pickletool with: ``` PicklingError: Can't pickle : it's not the same object as importlib._bootstrap.BuiltinImporter ``` I believe this is happening because we run multiple test modules in the same interpreter, so the following sequence happens: * `test_importlib.util.import_importlib()` imports a source copy of `importlib` while blocking `_frozen_importlib`. During this import, `importlib._bootstrap._setup()` initializes import metadata on existing built-in modules. If `builtins.__loader__` or `builtins.__spec__` was originally absent, the source copy installs its own `BuiltinImporter`. * Although `import_fresh_module()` restores `sys.modules`, it does not restore attributes mutated on existing module objects. Consequently, `builtins.__loader__` continues to reference the temporary source `BuiltinImporter`, while `importlib._bootstrap.BuiltinImporter` resolves to the restored frozen class. * Pickle serializes classes by module and qualified name and verifies that the resolved global is the same object. The two `BuiltinImporter` class objects therefore cause the identity check to fail. To fix this we instead snapshot `__loader__` and `__spec__` before importing `importlib` and then restore them to their original values after. --- Lib/test/test_importlib/test_util.py | 26 ++++++++++++++++++++++++++ Lib/test/test_importlib/util.py | 20 ++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 8ec2dcd69e58b63..3a5a251f4a50259 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -33,6 +33,32 @@ _interpreters = None +class ImportImportlibTests(unittest.TestCase): + + def test_restores_missing_builtin_import_metadata(self): + missing = object() + attrs = { + attr: getattr(util.builtins, attr, missing) + for attr in ('__loader__', '__spec__') + } + try: + for attr in attrs: + if hasattr(util.builtins, attr): + delattr(util.builtins, attr) + + util.import_importlib('importlib') + + for attr in attrs: + self.assertFalse(hasattr(util.builtins, attr)) + finally: + for attr, value in attrs.items(): + if value is missing: + if hasattr(util.builtins, attr): + delattr(util.builtins, attr) + else: + setattr(util.builtins, attr, value) + + class DecodeSourceBytesTests: source = "string ='ΓΌ'" diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py index 6399f952f9e912b..9b1cdaaca3500c2 100644 --- a/Lib/test/test_importlib/util.py +++ b/Lib/test/test_importlib/util.py @@ -68,8 +68,24 @@ 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')) + missing = object() + builtin_attrs = { + attr: getattr(builtins, attr, missing) + for attr in ('__loader__', '__spec__') + } + try: + source = import_helper.import_fresh_module( + module_name, + fresh=fresh, + blocked=('_frozen_importlib', '_frozen_importlib_external'), + ) + finally: + for attr, value in builtin_attrs.items(): + if value is missing: + if hasattr(builtins, attr): + delattr(builtins, attr) + else: + setattr(builtins, attr, value) return {'Frozen': frozen, 'Source': source}