cpython/Lib/test/test_capi/test_modsupport.py
Petr Viktorin 0c74fc8af0
gh-137210: Add a struct, slot & function for checking an extension's ABI (GH-137212)
Co-authored-by: Steve Dower <steve.dower@microsoft.com>
2025-09-05 16:23:18 +02:00

154 lines
6.3 KiB
Python

import sys
import unittest
import sysconfig
from test.support import subTests
from test.support import import_helper
_testcapi = import_helper.import_module('_testcapi')
class Test_ABIInfo_Check(unittest.TestCase):
@subTests('modname', (None, 'test_mod'))
def test_zero(self, modname):
_testcapi.pyabiinfo_check(modname, 0, 0, 0, 0, 0)
_testcapi.pyabiinfo_check(modname, 1, 0, 0, 0, 0)
def test_large_major_version(self):
with self.assertRaisesRegex(ImportError,
'^PyABIInfo version too high$'):
_testcapi.pyabiinfo_check(None, 2, 0, 0, 0, 0)
with self.assertRaisesRegex(ImportError,
'^test_mod: PyABIInfo version too high$'):
_testcapi.pyabiinfo_check("test_mod", 2, 0, 0, 0, 0)
@subTests('modname', (None, 'test_mod'))
def test_large_minor_version(self, modname):
_testcapi.pyabiinfo_check(modname, 1, 2, 0, 0, 0)
@subTests('modname', (None, 'test_mod'))
@subTests('major', (0, 1))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
def test_positive_regular(self, modname, major, minor, build):
ver = sys.hexversion
truncated = ver & 0xffff0000
filled = truncated | 0x12b8
maxed = truncated | 0xffff
for abi_version in (0, ver, truncated, filled, maxed):
with self.subTest(abi_version=abi_version):
_testcapi.pyabiinfo_check(modname, major, minor, 0,
build, abi_version)
@subTests('modname', (None, 'test_mod'))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
@subTests('offset', (+0x00010000, -0x00010000))
def test_negative_regular(self, modname, minor, build, offset):
ver = sys.hexversion + offset
truncated = ver & 0xffff0000
filled = truncated | 0x12b8
maxed = truncated | 0xffff
for abi_version in (ver, truncated, filled, maxed):
with self.subTest(abi_version=abi_version):
with self.assertRaisesRegex(
ImportError,
r'incompatible ABI version \(3\.\d+\)$'):
_testcapi.pyabiinfo_check(modname, 1, minor, 0,
build,
abi_version)
@subTests('modname', (None, 'test_mod'))
@subTests('major', (0, 1))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
@subTests('abi_version', (
0,
0x03020000,
sys.hexversion,
sys.hexversion & 0xffff0000,
sys.hexversion - 0x00010000,
))
def test_positive_stable(self, modname, major, minor, build, abi_version):
_testcapi.pyabiinfo_check(modname, major, minor,
_testcapi.PyABIInfo_STABLE,
build,
abi_version)
@subTests('modname', (None, 'test_mod'))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
@subTests('abi_version_and_msg', (
(1, 'invalid'),
(3, 'invalid'),
(0x0301ffff, 'invalid'),
((sys.hexversion & 0xffff0000) + 0x00010000, 'incompatible future'),
(sys.hexversion + 0x00010000, 'incompatible future'),
(0x04000000, 'incompatible future'),
))
def test_negative_stable(self, modname, minor, build, abi_version_and_msg):
abi_version, msg = abi_version_and_msg
with self.assertRaisesRegex(
ImportError,
rf'{msg} stable ABI version \(\d+\.\d+\)$'):
_testcapi.pyabiinfo_check(modname, 1, minor,
_testcapi.PyABIInfo_STABLE,
build,
abi_version)
@subTests('modname', (None, 'test_mod'))
@subTests('major', (0, 1))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
@subTests('abi_version', (0, sys.hexversion))
def test_positive_internal(self, modname, major, minor, build, abi_version):
_testcapi.pyabiinfo_check(modname, major, minor,
_testcapi.PyABIInfo_INTERNAL,
build,
abi_version)
@subTests('modname', (None, 'test_mod'))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
@subTests('abi_version', (
sys.hexversion - 0x00010000,
sys.hexversion - 1,
sys.hexversion + 1,
sys.hexversion + 0x00010000,
))
def test_negative_internal(self, modname, minor, build, abi_version):
with self.assertRaisesRegex(
ImportError,
r'incompatible internal ABI \(0x[\da-f]+ != 0x[\da-f]+\)$'):
_testcapi.pyabiinfo_check(modname, 1, minor,
_testcapi.PyABIInfo_INTERNAL,
build,
abi_version)
@subTests('modname', (None, 'test_mod'))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
@subTests('ft_flag', (
0,
(_testcapi.PyABIInfo_FREETHREADED
if sysconfig.get_config_var("Py_GIL_DISABLED")
else _testcapi.PyABIInfo_GIL),
_testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
))
def test_positive_freethreading(self, modname, minor, build, ft_flag):
self.assertEqual(ft_flag & _testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
ft_flag)
_testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)
@subTests('modname', (None, 'test_mod'))
@subTests('minor', (0, 1, 9))
@subTests('build', (0, sys.hexversion))
def test_negative_freethreading(self, modname, minor, build):
if sysconfig.get_config_var("Py_GIL_DISABLED"):
ft_flag = _testcapi.PyABIInfo_GIL
msg = "incompatible with free-threaded CPython"
else:
ft_flag = _testcapi.PyABIInfo_FREETHREADED
msg = "only compatible with free-threaded CPython"
with self.assertRaisesRegex(ImportError, msg):
_testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)