File py313-compat.patch of Package python-cffi

From 14723b0bbd127790c450945099db31018d80fa83 Mon Sep 17 00:00:00 2001
From: Matt Davis <6775756+nitzmahone@users.noreply.github.com>
Date: Thu, 30 Nov 2023 18:27:52 -0800
Subject: [PATCH] Python 3.13 compatibility (#24)

* fix obsolete private API aliases for 3.13 compat

* update unraisable tests to use sys.unraisablehook

* assert shape of calls to unraisablehook and sanity check traceback contents instead of (varying)  stderr output from default unraisablehook impl
---
 src/c/_cffi_backend.c      |   4 +-
 src/c/misc_thread_common.h |   4 +-
 src/c/test_c.py            | 217 ++++++++++++-------------------------
 3 files changed, 74 insertions(+), 151 deletions(-)

diff --git a/src/c/_cffi_backend.c b/src/c/_cffi_backend.c
index 5e284e00..7c72ffe0 100644
--- a/src/c/_cffi_backend.c
+++ b/src/c/_cffi_backend.c
@@ -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
diff --git a/src/c/misc_thread_common.h b/src/c/misc_thread_common.h
index ead9c83c..7d29634b 100644
--- a/src/c/misc_thread_common.h
+++ b/src/c/misc_thread_common.h
@@ -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);
diff --git a/src/c/test_c.py b/src/c/test_c.py
index 1cdab10f..10cc35cf 100644
--- a/src/c/test_c.py
+++ b/src/c/test_c.py
@@ -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':
@@ -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)
@@ -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",
openSUSE Build Service is sponsored by