mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
bpo-40795: ctypes calls unraisablehook with an exception (GH-20452)
If ctypes fails to convert the result of a callback or if a ctypes callback function raises an exception, sys.unraisablehook is now called with an exception set. Previously, the error was logged into stderr by PyErr_Print().
This commit is contained in:
parent
e80697d687
commit
10228bad04
6 changed files with 79 additions and 41 deletions
|
@ -1,5 +1,7 @@
|
|||
import functools
|
||||
import unittest
|
||||
from test import support
|
||||
|
||||
from ctypes import *
|
||||
from ctypes.test import need_symbol
|
||||
import _ctypes_test
|
||||
|
@ -301,8 +303,22 @@ class SampleCallbacksTestCase(unittest.TestCase):
|
|||
with self.assertRaises(ArgumentError):
|
||||
cb(*args2)
|
||||
|
||||
def test_convert_result_error(self):
|
||||
def func():
|
||||
return ("tuple",)
|
||||
|
||||
proto = CFUNCTYPE(c_int)
|
||||
ctypes_func = proto(func)
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
# don't test the result since it is an uninitialized value
|
||||
result = ctypes_func()
|
||||
|
||||
self.assertIsInstance(cm.unraisable.exc_value, TypeError)
|
||||
self.assertEqual(cm.unraisable.err_msg,
|
||||
"Exception ignored on converting result "
|
||||
"of ctypes callback function")
|
||||
self.assertIs(cm.unraisable.object, func)
|
||||
|
||||
################################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
from ctypes import *
|
||||
import unittest, sys
|
||||
import contextlib
|
||||
from test import support
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
|
||||
def callback_func(arg):
|
||||
42 / arg
|
||||
|
@ -34,41 +38,40 @@ class CallbackTracbackTestCase(unittest.TestCase):
|
|||
# created, then a full traceback printed. When SystemExit is
|
||||
# raised in a callback function, the interpreter exits.
|
||||
|
||||
def capture_stderr(self, func, *args, **kw):
|
||||
# helper - call function 'func', and return the captured stderr
|
||||
import io
|
||||
old_stderr = sys.stderr
|
||||
logger = sys.stderr = io.StringIO()
|
||||
try:
|
||||
func(*args, **kw)
|
||||
finally:
|
||||
sys.stderr = old_stderr
|
||||
return logger.getvalue()
|
||||
@contextlib.contextmanager
|
||||
def expect_unraisable(self, exc_type, exc_msg=None):
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
yield
|
||||
|
||||
self.assertIsInstance(cm.unraisable.exc_value, exc_type)
|
||||
if exc_msg is not None:
|
||||
self.assertEqual(str(cm.unraisable.exc_value), exc_msg)
|
||||
self.assertEqual(cm.unraisable.err_msg,
|
||||
"Exception ignored on calling ctypes "
|
||||
"callback function")
|
||||
self.assertIs(cm.unraisable.object, callback_func)
|
||||
|
||||
def test_ValueError(self):
|
||||
cb = CFUNCTYPE(c_int, c_int)(callback_func)
|
||||
out = self.capture_stderr(cb, 42)
|
||||
self.assertEqual(out.splitlines()[-1],
|
||||
"ValueError: 42")
|
||||
with self.expect_unraisable(ValueError, '42'):
|
||||
cb(42)
|
||||
|
||||
def test_IntegerDivisionError(self):
|
||||
cb = CFUNCTYPE(c_int, c_int)(callback_func)
|
||||
out = self.capture_stderr(cb, 0)
|
||||
self.assertEqual(out.splitlines()[-1][:19],
|
||||
"ZeroDivisionError: ")
|
||||
with self.expect_unraisable(ZeroDivisionError):
|
||||
cb(0)
|
||||
|
||||
def test_FloatDivisionError(self):
|
||||
cb = CFUNCTYPE(c_int, c_double)(callback_func)
|
||||
out = self.capture_stderr(cb, 0.0)
|
||||
self.assertEqual(out.splitlines()[-1][:19],
|
||||
"ZeroDivisionError: ")
|
||||
with self.expect_unraisable(ZeroDivisionError):
|
||||
cb(0.0)
|
||||
|
||||
def test_TypeErrorDivisionError(self):
|
||||
cb = CFUNCTYPE(c_int, c_char_p)(callback_func)
|
||||
out = self.capture_stderr(cb, b"spam")
|
||||
self.assertEqual(out.splitlines()[-1],
|
||||
"TypeError: "
|
||||
"unsupported operand type(s) for /: 'int' and 'bytes'")
|
||||
err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'"
|
||||
with self.expect_unraisable(TypeError, err_msg):
|
||||
cb(b"spam")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -27,7 +27,6 @@ for typ in [c_short, c_int, c_long, c_longlong,
|
|||
class TestStructures(unittest.TestCase):
|
||||
def test_native(self):
|
||||
for typ in structures:
|
||||
## print typ.value
|
||||
self.assertEqual(typ.value.offset, 1)
|
||||
o = typ()
|
||||
o.value = 4
|
||||
|
@ -35,7 +34,6 @@ class TestStructures(unittest.TestCase):
|
|||
|
||||
def test_swapped(self):
|
||||
for typ in byteswapped_structures:
|
||||
## print >> sys.stderr, typ.value
|
||||
self.assertEqual(typ.value.offset, 1)
|
||||
o = typ()
|
||||
o.value = 4
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue