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
Prev Previous commit
Next Next commit
Add a test for the various settings and overrides for a singlephase e…
…xtension.
  • Loading branch information
ericsnowcurrently committed Jan 12, 2023
commit dc8d877b250d3408b0cd23cba7e60ecc2e8f19cf
110 changes: 79 additions & 31 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1418,9 +1418,16 @@ def pipe(self):
os.set_blocking(r, False)
return (r, w)

def import_script(self, name, fd):
def import_script(self, name, fd, check_override=None):
override_text = ''
if check_override is not None:
override_text = f'''
import _imp
_imp._override_multi_interp_extensions_check({check_override})
'''
return textwrap.dedent(f'''
import os, sys
{override_text}
try:
import {name}
except ImportError as exc:
Expand All @@ -1430,47 +1437,54 @@ def import_script(self, name, fd):
os.write({fd}, text.encode('utf-8'))
''')

def check_compatible_shared(self, name, *, strict=False):
# 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.
#
# "strict" determines whether or not the interpreter will be
# configured to check for modules that are not compatible
# with use in multiple interpreters.

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

def run_shared(self, name, *,
check_singlephase_setting=False,
check_singlephase_override=None,
):
"""
Try importing the named module 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.

"check_singlephase_setting" determines whether or not
the interpreter will be configured to check for modules
that are not compatible with use in multiple interpreters.

This should always return "okay" for all modules if the
setting is False (with no override).
"""
__import__(name)

r, w = self.pipe()
ret = run_in_subinterp_with_config(
self.import_script(name, w),
kwargs = dict(
**self.RUN_KWARGS,
check_multi_interp_extensions=strict,
check_multi_interp_extensions=check_singlephase_setting,
)

r, w = self.pipe()
script = self.import_script(name, w, check_singlephase_override)

ret = run_in_subinterp_with_config(script, **kwargs)
self.assertEqual(ret, 0)
out = os.read(r, 100)
return os.read(r, 100)

def check_compatible_shared(self, name, *, strict=False):
# Verify that the named module may be imported in a subinterpreter.
# (See run_shared() for more info.)
out = self.run_shared(name, check_singlephase_setting=strict)
self.assertEqual(out, b'okay')

def check_incompatible_shared(self, name):
# Differences from check_compatible_shared():
# * verify that import fails
# * "strict" is always True
__import__(name)

r, w = self.pipe()
ret = run_in_subinterp_with_config(
self.import_script(name, w),
**self.RUN_KWARGS,
check_multi_interp_extensions=True,
out = self.run_shared(name, check_singlephase_setting=True)
self.assertEqual(
out.decode('utf-8'),
f'ImportError: module {name} does not support loading in subinterpreters',
)
self.assertEqual(ret, 0)
out = os.read(r, 100).decode('utf-8')
self.assertEqual(out, f'ImportError: module {name} does not support loading in subinterpreters')

def check_compatible_isolated(self, name, *, strict=False):
# Differences from check_compatible_shared():
Expand Down Expand Up @@ -1530,7 +1544,7 @@ def test_frozen_compat(self):
with self.subTest(f'{module}: strict, shared'):
self.check_compatible_shared(module, strict=True)

@unittest.skipIf(_testsinglephase is None, "test requires _testsinglphase module")
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
def test_single_init_extension_compat(self):
module = '_testsinglephase'
with self.subTest(f'{module}: not strict'):
Expand Down Expand Up @@ -1561,6 +1575,40 @@ def test_python_compat(self):
with self.subTest(f'{module}: strict, isolated'):
self.check_compatible_isolated(module, strict=True)

@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
def test_singlephase_check_with_setting_and_override(self):
module = '_testsinglephase'

def check_compatible(setting, override):
out = self.run_shared(
module,
check_singlephase_setting=setting,
check_singlephase_override=override,
)
self.assertEqual(out, b'okay')

def check_incompatible(setting, override):
out = self.run_shared(
module,
check_singlephase_setting=setting,
check_singlephase_override=override,
)
self.assertNotEqual(out, b'okay')

with self.subTest('config: check enabled; override: enabled'):
check_incompatible(True, 1)
with self.subTest('config: check enabled; override: use config'):
check_incompatible(True, 0)
with self.subTest('config: check enabled; override: disabled'):
check_compatible(True, -1)

with self.subTest('config: check disabled; override: enabled'):
check_incompatible(False, 1)
with self.subTest('config: check disabled; override: use config'):
check_compatible(False, 0)
with self.subTest('config: check disabled; override: disabled'):
check_compatible(False, -1)


if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
Expand Down
0