Skip to content
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

Python 3.13 compatibility #24

Merged
merged 2 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions src/c/_cffi_backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@
# define PyText_Check PyUnicode_Check
# define PyTextAny_Check PyUnicode_Check
# define PyText_FromFormat PyUnicode_FromFormat
# define PyText_AsUTF8 _PyUnicode_AsString /* PyUnicode_AsUTF8 in Py3.3 */
# define PyText_AS_UTF8 _PyUnicode_AsString
# define PyText_AsUTF8 PyUnicode_AsUTF8
# define PyText_AS_UTF8 PyUnicode_AsUTF8
# if PY_VERSION_HEX >= 0x03030000
# define PyText_GetSize PyUnicode_GetLength
# else
Expand Down
4 changes: 3 additions & 1 deletion src/c/misc_thread_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,9 @@ PyAPI_DATA(void *volatile) _PyThreadState_Current;

static PyThreadState *get_current_ts(void)
{
#if PY_VERSION_HEX >= 0x03060000
#if PY_VERSION_HEX >= 0x030D0000
return PyThreadState_GetUnchecked();
#elif PY_VERSION_HEX >= 0x03060000
return _PyThreadState_UncheckedGet();
#elif defined(_Py_atomic_load_relaxed)
return (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current);
Expand Down
217 changes: 69 additions & 148 deletions src/c/test_c.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
from __future__ import annotations

import contextlib
import traceback
import unittest.mock

import pytest
import sys
import typing as t

is_musl = False
if sys.platform == 'linux':
Expand Down Expand Up @@ -1337,27 +1344,37 @@ def cb(n):
e = pytest.raises(TypeError, f)
assert str(e.value) == "'int(*)(int)' expects 1 arguments, got 0"

@contextlib.contextmanager
def _assert_unraisable(error_type: type[Exception] | None, message: str = '', traceback_tokens: list[str] | None = None):
"""Assert that a given sys.unraisablehook interaction occurred (or did not occur, if error_type is None) while this context was active"""
raised_errors: list[Exception] = []
raised_traceback: str = ''

# sys.unraisablehook is called more than once for chained exceptions; accumulate the errors and tracebacks for inspection
def _capture_unraisable_hook(ur_args):
nonlocal raised_traceback
raised_errors.append(ur_args.exc_value)

# NB: need to use the old etype/value/tb form until 3.10 is the minimum
raised_traceback += (ur_args.err_msg or '' + '\n') + ''.join(traceback.format_exception(None, ur_args.exc_value, ur_args.exc_traceback))


with pytest.MonkeyPatch.context() as mp:
mp.setattr(sys, 'unraisablehook', _capture_unraisable_hook)
yield

if error_type is None:
assert not raised_errors
assert not raised_traceback
return

assert any(type(raised_error) is error_type for raised_error in raised_errors)
assert any(message in str(raised_error) for raised_error in raised_errors)
for t in traceback_tokens or []:
assert t in raised_traceback


def test_callback_exception():
try:
import cStringIO
except ImportError:
import io as cStringIO # Python 3
import linecache
def matches(istr, ipattern, ipattern38, ipattern311=None):
if sys.version_info >= (3, 8):
ipattern = ipattern38
if sys.version_info >= (3, 11):
ipattern = ipattern311 or ipattern38
str, pattern = istr, ipattern
while '$' in pattern:
i = pattern.index('$')
assert str[:i] == pattern[:i]
j = str.find(pattern[i+1], i)
assert i + 1 <= j <= str.find('\n', i)
str = str[j:]
pattern = pattern[i+1:]
assert str == pattern
return True
def check_value(x):
if x == 10000:
raise ValueError(42)
Expand All @@ -1366,148 +1383,52 @@ def Zcb1(x):
return x * 3
BShort = new_primitive_type("short")
BFunc = new_function_type((BShort,), BShort, False)

f = callback(BFunc, Zcb1, -42)
#
seen = []
oops_result = None
def oops(*args):
seen.append(args)
return oops_result
ff = callback(BFunc, Zcb1, -42, oops)
#
orig_stderr = sys.stderr
orig_getline = linecache.getline
try:
linecache.getline = lambda *args: 'LINE' # hack: speed up PyPy tests
sys.stderr = cStringIO.StringIO()
if hasattr(sys, '__unraisablehook__'): # work around pytest
sys.unraisablehook = sys.__unraisablehook__ # on recent CPythons
with _assert_unraisable(None):
assert f(100) == 300
assert sys.stderr.getvalue() == ''
with _assert_unraisable(ValueError, '42', ['in Zcb1', 'in check_value']):
assert f(10000) == -42
assert matches(sys.stderr.getvalue(), """\
From cffi callback <function$Zcb1 at 0x$>:
Traceback (most recent call last):
File "$", line $, in Zcb1
$
File "$", line $, in check_value
$
ValueError: 42
""", """\
Exception ignored from cffi callback <function$Zcb1 at 0x$>:
Traceback (most recent call last):
File "$", line $, in Zcb1
$
File "$", line $, in check_value
$
ValueError: 42
""")
sys.stderr = cStringIO.StringIO()
bigvalue = 20000

bigvalue = 20000
with _assert_unraisable(OverflowError, "integer 60000 does not fit 'short'", ['callback', 'Zcb1']):
assert f(bigvalue) == -42
assert matches(sys.stderr.getvalue(), """\
From cffi callback <function$Zcb1 at 0x$>:
Trying to convert the result back to C:
OverflowError: integer 60000 does not fit 'short'
""", """\
Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
Traceback (most recent call last):
File "$", line $, in test_callback_exception
$
OverflowError: integer 60000 does not fit 'short'
""")
sys.stderr = cStringIO.StringIO()
bigvalue = 20000
assert len(seen) == 0
assert len(seen) == 0

with _assert_unraisable(None):
assert ff(bigvalue) == -42
assert sys.stderr.getvalue() == ""
assert len(seen) == 1
exc, val, tb = seen[0]
assert exc is OverflowError
assert str(val) == "integer 60000 does not fit 'short'"
#
sys.stderr = cStringIO.StringIO()
bigvalue = 20000
del seen[:]
oops_result = 81
assert len(seen) == 1
exc, val, tb = seen[0]
assert exc is OverflowError
assert str(val) == "integer 60000 does not fit 'short'"

del seen[:]
oops_result = 81
with _assert_unraisable(None):
assert ff(bigvalue) == 81
oops_result = None
assert sys.stderr.getvalue() == ""
assert len(seen) == 1
exc, val, tb = seen[0]
assert exc is OverflowError
assert str(val) == "integer 60000 does not fit 'short'"
#
sys.stderr = cStringIO.StringIO()
bigvalue = 20000
del seen[:]
oops_result = "xy" # not None and not an int!

assert len(seen) == 1
exc, val, tb = seen[0]
assert exc is OverflowError
assert str(val) == "integer 60000 does not fit 'short'"

del seen[:]
oops_result = "xy" # not None and not an int!

with _assert_unraisable(TypeError, "an integer is required", ["integer 60000 does not fit 'short'"]):
assert ff(bigvalue) == -42
oops_result = None
assert matches(sys.stderr.getvalue(), """\
From cffi callback <function$Zcb1 at 0x$>:
Trying to convert the result back to C:
OverflowError: integer 60000 does not fit 'short'

During the call to 'onerror', another exception occurred:

TypeError: $integer$
""", """\
Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
Traceback (most recent call last):
File "$", line $, in test_callback_exception
$
OverflowError: integer 60000 does not fit 'short'
Exception ignored during handling of the above exception by 'onerror':
Traceback (most recent call last):
File "$", line $, in test_callback_exception
$
TypeError: $integer$
""")
#
sys.stderr = cStringIO.StringIO()
seen = "not a list" # this makes the oops() function crash

seen = "not a list" # this makes the oops() function crash
oops_result = None
with _assert_unraisable(AttributeError, "'str' object has no attribute 'append", ['Zcb1', 'ff', 'oops']):
assert ff(bigvalue) == -42
# the $ after the AttributeError message are for the suggestions that
# will be added in Python 3.10
assert matches(sys.stderr.getvalue(), """\
From cffi callback <function$Zcb1 at 0x$>:
Trying to convert the result back to C:
OverflowError: integer 60000 does not fit 'short'

During the call to 'onerror', another exception occurred:

Traceback (most recent call last):
File "$", line $, in oops
$
AttributeError: 'str' object has no attribute 'append$
""", """\
Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
Traceback (most recent call last):
File "$", line $, in test_callback_exception
$
OverflowError: integer 60000 does not fit 'short'
Exception ignored during handling of the above exception by 'onerror':
Traceback (most recent call last):
File "$", line $, in oops
$
AttributeError: 'str' object has no attribute 'append$
""", """\
Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
Traceback (most recent call last):
File "$", line $, in test_callback_exception
$
OverflowError: integer 60000 does not fit 'short'
Exception ignored during handling of the above exception by 'onerror':
Traceback (most recent call last):
File "$", line $, in oops
$
$
AttributeError: 'str' object has no attribute 'append$
""")
finally:
sys.stderr = orig_stderr
linecache.getline = orig_getline


def test_callback_return_type():
for rettype in ["signed char", "short", "int", "long", "long long",
Expand Down