8000 gh-98627: Add an Optional Check for Extension Module Subinterpreter Compatibility by ericsnowcurrently · Pull Request #99040 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-98627: Add an Optional Check for Extension Module Subinterpreter Compatibility #99040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
eb9d241
Add tests for extension module subinterpreter compatibility.
ericsnowcurrently Oct 24, 2022
49b4895
Add _PyInterpreterConfig.check_multi_interp_extensions and Py_RTFLAGS…
ericsnowcurrently Oct 22, 2022
ed505a6
Add _PyImport_CheckSubinterpIncompatibleExtensionAllowed().
ericsnowcurrently Oct 24, 2022
64e1dc8
Raise ImportError in subinterpreters for incompatible single-phase in…
ericsnowcurrently Oct 24, 2022
72ab9b6
Add a NEWS entry.
ericsnowcurrently Nov 3, 2022
9c24b34
Add PyInterpreterState.override_multi_interp_extensions_check.
ericsnowcurrently Nov 21, 2022
3c084eb
Add check_multi_interp_extensions().
ericsnowcurrently Nov 21, 2022
a3d3a65
Add _imp._override_multi_interp_extensions_check().
ericsnowcurrently Nov 21, 2022
ad3fe36
Add test.support.import_helper.multi_interp_extensions_check().
ericsnowcurrently Nov 21, 2022
1defec3
Add a test.
ericsnowcurrently Nov 21, 2022
99f3371
Merge branch 'main' into HEAD
ericsnowcurrently Jan 12, 2023
3c3ed2b
Fix a typo.
ericsnowcurrently Jan 12, 2023
de6c791
Add some extra diagnostic info.
ericsnowcurrently Jan 12, 2023
af114f2
Clarify various names (e.g. data keys) in the test.
ericsnowcurrently Jan 12, 2023
282e6d3
Allow long test output.
ericsnowcurrently Jan 12, 2023
3cb8645
Do not show the noop values unless different.
ericsnowcurrently Jan 12, 2023
81abbfb
Add comments to the expected values.
ericsnowcurrently Jan 12, 2023
db5d35a
Tweak the subtest labels.
ericsnowcurrently Jan 12, 2023
e0c55ad
Fix the expected results.
ericsnowcurrently Jan 12, 2023
3b2dd6d
Add a test just for how the setting is used.
ericsnowcurrently Jan 12, 2023
d648a7b
Revert "Add a test just for how the setting is used."
ericsnowcurrently Jan 12, 2023
dc8d877
Add a test for the various settings and overrides for a singlephase e…
ericsnowcurrently Jan 12, 2023
5fba674
Fix check_config.py.
ericsnowcurrently Feb 3, 2023
35d322d
Merge branch 'main' into interpreter-multi-interp-extensions-check
ericsnowcurrently Feb 6, 2023
ddf01fb
Merge branch 'main' into interpreter-multi-interp-extensions-check
ericsnowcurrently Feb 15, 2023
ee2cd3c
Merge branch 'main' into interpreter-multi-interp-extensions-check
ericsnowcurrently Feb 15, 2023
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
Next Next commit
Add tests for extension module subinterpreter compatibility.
  • Loading branch information
ericsnowcurrently committed Jan 11, 2023
commit eb9d2413cb27e7dc06a645be37a9f1b5b49d6c60
138 changes: 137 additions & 1 deletion Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from test.support import os_helper
from test.support import (
STDLIB_DIR, swap_attr, swap_item, cpython_only, is_emscripten,
is_wasi)
is_wasi, run_in_subinterp_with_config)
from test.support.import_helper import (
forget, make_legacy_pyc, unlink, unload, DirsOnSysPath, CleanImport)
from test.support.os_helper import (
Expand All @@ -30,6 +30,14 @@
from test.support import threading_helper
from test.test_importlib.util import uncache
from types import ModuleType
try:
import _testsinglephase
except ImportError:
_testsinglephase = None
try:
import _testmultiphase
except ImportError:
_testmultiphase = None


skip_if_dont_write_bytecode = unittest.skipIf(
Expand Down Expand Up @@ -1392,6 +1400,134 @@ def test_unwritable_module(self):
unwritable.x = 42


class SubinterpImportTests(unittest.TestCase):

RUN_KWARGS = dict(
allow_fork=False,
allow_exec=False,
allow_threads=True,
allow_daemon_threads=False,
)

@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
def pipe(self):
r, w = os.pipe()
self.addCleanup(os.close, r)
self.addCleanup(os.close, w)
if hasattr(os, 'set_blocking'):
os.set_blocking(r, False)
return (r, w)

def import_script(self, name, fd):
return textwrap.dedent(f'''
import os, sys
try:
import {name}
except ImportError as exc:
text = 'ImportError: ' + str(exc)
else:
text = 'okay'
os.write({fd}, text.encode('utf-8'))
''')

def check_compatible_shared(self, name):
# Verify that the named module may be imported in a subinterpreter.
#
# The subinterpreter will be in the current process.
# The module will have already been imported in the main interpreter.
# Thus, for extension/builtin modules, the module definition will
# have been loaded already and cached globally.

# This check should always pass for all modules if not strict.

__import__(name)

r, w = self.pipe()
ret = run_in_subinterp_with_config(
self.import_script(name, w),
**self.RUN_KWARGS,
)
self.assertEqual(ret, 0)
out = os.read(r, 100)
self.assertEqual(out, b'okay')

def check_compatible_isolated(self, name):
# Differences from check_compatible_shared():
# * subinterpreter in a new process
# * module has never been imported before in that process
# * this tests importing the module for the first time
_, out, err = script_helper.assert_python_ok('-c', textwrap.dedent(f'''
import _testcapi, sys
assert (
{name!r} in sys.builtin_module_names or
{name!r} not in sys.modules
), repr({name!r})
ret = _testcapi.run_in_subinterp_with_config(
{self.import_script(name, "sys.stdout.fileno()")!r},
**{self.RUN_KWARGS},
)
assert ret == 0, ret
'''))
self.assertEqual(err, b'')
self.assertEqual(out, b'okay')

def check_incompatible_isolated(self, name):
# Differences from check_compatible_isolated():
# * verify that import fails
_, out, err = script_helper.assert_python_ok('-c', textwrap.dedent(f'''
import _testcapi, sys
assert {name!r} not in sys.modules, {name!r}
ret = _testcapi.run_in_subinterp_with_config(
{self.import_script(name, "sys.stdout.fileno()")!r},
**{self.RUN_KWARGS},
)
assert ret == 0, ret
'''))
self.assertEqual(err, b'')
self.assertEqual(
out.decode('utf-8'),
f'ImportError: module {name} does not support loading in subinterpreters',
)

def test_builtin_compat(self):
module = 'sys'
with self.subTest(f'{module}: shared'):
self.check_compatible_shared(module)

@cpython_only
def test_frozen_compat(self):
module = '_frozen_importlib'
if __import__(module).__spec__.origin != 'frozen':
raise unittest.SkipTest(f'{module} is unexpectedly not frozen')
with self.subTest(f'{module}: shared'):
self.check_compatible_shared(module)

@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
def test_single_init_extension_compat(self):
module = '_testsinglephase'
with self.subTest(f'{module}: shared'):
self.check_compatible_shared(module)
with self.subTest(f'{module}: isolated'):
self.check_compatible_isolated(module)

@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
def test_multi_init_extension_compat(self):
module = '_testmultiphase'
with self.subTest(f'{module}: shared'):
self.check_compatible_shared(module)
with self.subTest(f'{module}: isolated'):
self.check_compatible_isolated(module)

def test_python_compat(self):
module = 'threading'
if __import__(module).__spec__.origin == 'frozen':
raise unittest.SkipTest(f'{module} is unexpectedly frozen')
with self.subTest(f'{module}: shared'):
self.check_compatible_shared(module)
with self.subTest(f'{module}: isolated'):
self.check_compatible_isolated(module)


if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
unittest.main()
0