|
9 | 9 | import builtins |
10 | 10 | import unittest |
11 | 11 | import unittest.mock |
| 12 | +import os |
12 | 13 | import re |
13 | 14 | import tempfile |
14 | 15 | import random |
15 | 16 | import string |
| 17 | +import importlib.machinery |
| 18 | +import sysconfig |
16 | 19 | from test import support |
17 | 20 | import shutil |
18 | 21 | from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ, |
19 | 22 | requires_debug_ranges, has_no_debug_ranges, |
20 | | - requires_subprocess) |
| 23 | + requires_subprocess, os_helper) |
21 | 24 | from test.support.os_helper import TESTFN, temp_dir, unlink |
22 | 25 | from test.support.script_helper import assert_python_ok, assert_python_failure, make_script |
23 | 26 | from test.support.import_helper import forget |
@@ -5194,6 +5197,56 @@ def test_windows_only_module_error(self): |
5194 | 5197 | else: |
5195 | 5198 | self.fail("ModuleNotFoundError was not raised") |
5196 | 5199 |
|
| 5200 | + @unittest.skipIf(not importlib.machinery.EXTENSION_SUFFIXES, 'Platform does not support extension modules') |
| 5201 | + def test_find_incompatible_extension_modules(self): |
| 5202 | + """_find_incompatible_extension_modules assumes the last extension in |
| 5203 | + importlib.machinery.EXTENSION_SUFFIXES (defined in Python/dynload_*.c) |
| 5204 | + is untagged (eg. .so, .pyd). |
| 5205 | +
|
| 5206 | + This test exists to make sure that assumption is correct. |
| 5207 | + """ |
| 5208 | + last_extension_suffix = importlib.machinery.EXTENSION_SUFFIXES[-1] |
| 5209 | + if shlib_suffix := sysconfig.get_config_var('SHLIB_SUFFIX'): |
| 5210 | + self.assertEqual(last_extension_suffix, shlib_suffix) |
| 5211 | + else: |
| 5212 | + before_dot, *extensions = last_extension_suffix.split('.') |
| 5213 | + expected_prefixes = [''] |
| 5214 | + if os.name == 'nt': |
| 5215 | + # Windows puts the debug tag in the module file stem (eg. foo_d.pyd) |
| 5216 | + expected_prefixes.append('_d') |
| 5217 | + self.assertIn(before_dot, expected_prefixes, msg=( |
| 5218 | + f'Unexpected prefix {before_dot!r} in extension module ' |
| 5219 | + f'suffix {last_extension_suffix!r}. ' |
| 5220 | + 'traceback._find_incompatible_extension_module needs to be ' |
| 5221 | + 'updated to take this into account!' |
| 5222 | + )) |
| 5223 | + # if SHLIB_SUFFIX is not define, we assume the native |
| 5224 | + # shared library suffix only contains one extension |
| 5225 | + # (eg. '.so', bad eg. '.cpython-315-x86_64-linux-gnu.so') |
| 5226 | + self.assertEqual(len(extensions), 1, msg=( |
| 5227 | + 'The last suffix in importlib.machinery.EXTENSION_SUFFIXES ' |
| 5228 | + 'contains more than one extension, so it is probably different ' |
| 5229 | + 'than SHLIB_SUFFIX. It probably contains an ABI tag! ' |
| 5230 | + 'If this is a false positive, define SHLIB_SUFFIX in sysconfig.' |
| 5231 | + )) |
| 5232 | + |
| 5233 | + @unittest.skipIf(not importlib.machinery.EXTENSION_SUFFIXES, 'Platform does not support extension modules') |
| 5234 | + def test_incompatible_extension_modules_hint(self): |
| 5235 | + untagged_suffix = importlib.machinery.EXTENSION_SUFFIXES[-1] |
| 5236 | + with os_helper.temp_dir() as tmp: |
| 5237 | + # create a module with a incompatible ABI tag |
| 5238 | + incompatible_module = f'foo.some-abi{untagged_suffix}' |
| 5239 | + open(os.path.join(tmp, incompatible_module), "wb").close() |
| 5240 | + # try importing it |
| 5241 | + code = f''' |
| 5242 | + import sys |
| 5243 | + sys.path.insert(0, {tmp!r}) |
| 5244 | + import foo |
| 5245 | + ''' |
| 5246 | + _, _, stderr = assert_python_failure('-c', code, __cwd=tmp) |
| 5247 | + hint = f'Although a module with this name was found for a different Python version ({incompatible_module}).' |
| 5248 | + self.assertIn(hint, stderr.decode()) |
| 5249 | + |
5197 | 5250 |
|
5198 | 5251 | class TestColorizedTraceback(unittest.TestCase): |
5199 | 5252 | maxDiff = None |
|
0 commit comments