Skip to content

Instantly share code, notes, and snippets.

@ronaldoussoren
Last active June 4, 2018 13:22
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ronaldoussoren/fe4f80351a7ee72c245025df7b2ef1ed to your computer and use it in GitHub Desktop.
Crude hack for testing stable ABI
import unittest
import subprocess
import tempfile
import collections
import sys
import re
#
# Collect symbols tested for below
#
_STABLE_SYMBOLS = collections.defaultdict(set)
def stable_symbol(symbolname, py_ver=None):
def decorator(function):
_STABLE_SYMBOLS[py_ver].add(symbolname)
return function
return decorator
#
# Actual test cases
#
class TestPubliCAPI (unittest.TestCase):
def validate_symbols(self, api_version, *symbolnames):
with tempfile.NamedTemporaryFile(mode='w+', suffix='.c') as fp:
fp.write('#include "Python.h"\n')
fp.write('#include <stdio.h>\n')
fp.write('\n')
fp.write('int main(void) {\n')
for symbol in symbolnames:
fp.write(f' printf("%p\\n", &{symbol});\n')
fp.write(' return 0;\n')
fp.write('}\n')
fp.flush()
if api_version is None:
api_flags = "-DPy_LIMITED_API"
else:
version_num = tuple(int(x) for x in api_version.split('.'))
api_flags = "-DPy_LIMITED_API=0x%02x%02x0000"%version_num
subprocess.check_call(["cc", api_flags,
"-Werror=implicit-function-declaration",
"-I", "/Library/Frameworks/Python.framework/Versions/3.7/include/python3.7m/",
"-L", "/Library/Frameworks/Python.framework/Versions/3.7/lib/",
'-lpython3.7',
fp.name])
@stable_symbol('PyTuple_GetItem')
@stable_symbol('PyIndex_Check')
def test_abi(self):
self.validate_symbols(None, 'PyTuple_GetItem')
self.validate_symbols(None, 'PyIndex_Check')
@stable_symbol('PySlice_Unpack', '3.7')
@stable_symbol('PySlice_AdjustIndices', '3.7')
@stable_symbol('PyInterpreterState_GetID', '3.7')
def test_abi_37(self):
self.validate_symbols('3.7', 'PySlice_Unpack', 'PySlice_AdjustIndices', 'PyInterpreterState_GetID')
#
# The hard part, find a way to reliably extract symbols from headers (and on windows the stable abi library stub)
#
class TestExportedSymbols (unittest.TestCase):
def get_exported_names(self, api_version=None):
with tempfile.NamedTemporaryFile(mode='w+', suffix='.c') as fp:
fp.write('#include "Python.h"\n')
fp.flush()
if api_version is None:
output = subprocess.check_output(["cc", "-DPy_LIMITED_API", "-I", "/Library/Frameworks/Python.framework/Versions/3.7/include/python3.7m/", "-E", fp.name])
else:
version_num = tuple(int(x) for x in api_version.split('.'))
output = subprocess.check_output(["cc", "-DPy_LIMITED_API=0x%02x%02x0000"%version_num, "-I", "/Library/Frameworks/Python.framework/Versions/3.7/include/python3.7m/", "-E", fp.name])
python_file = False
symbols = set()
definitions = []
for ln in output.decode('utf-8').splitlines():
if not ln:
continue
elif ln.startswith('#') and ln.split(None, 2)[1].isdigit():
python_file = sys.prefix in ln.split(None,2)[-1]
else:
if python_file:
definitions.append(ln)
exported_names = set()
for definition in re.findall('[^;]*;', ' '.join(definitions), re.MULTILINE):
#print("BEFORE", definition)
definition = re.sub('__attribute__.*', '', definition)
definition = re.sub('\([^)]*\)', '', definition)
definition = re.sub('{[^)]*}', '', definition)
definition = definition.replace(';', '')
definition = definition.strip()
exported_names.add(definition.split()[-1].lstrip('*'))
return exported_names
def test_exported_names(self):
seen = set()
default_exported = self.get_exported_names()
with self.subTest("default"):
seen.update(default_exported)
self.assertEqual(len(_STABLE_SYMBOLS[None]), len(default_exported))
for version in ('3.%d'%(i,) for i in range(sys.version_info.minor+1)):
exported = self.get_exported_names(version) - seen
seen.update(exported)
with self.subTest(version):
self.assertEqual(_STABLE_SYMBOLS[version], exported)
if __name__ == "__main__":
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment