|
|
|
@ -0,0 +1,266 @@
|
|
|
|
|
From 364621f8489d85c29b26fa5e754aaf2a5f8279fe Mon Sep 17 00:00:00 2001
|
|
|
|
|
From: Matt Davis <mrd@redhat.com>
|
|
|
|
|
Date: Mon, 16 Oct 2023 17:53:28 -0700
|
|
|
|
|
Subject: [PATCH] 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/test_c.py | 217 +++++++++++++++---------------------------------
|
|
|
|
|
1 file changed, 69 insertions(+), 148 deletions(-)
|
|
|
|
|
|
|
|
|
|
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",
|