1+ from __future__ import annotations
2+
3+ import contextlib
4+ import traceback
5+ import unittest .mock
6+
17import pytest
28import sys
9+ import typing as t
310
411is_musl = False
512if sys .platform == 'linux' :
@@ -1337,27 +1344,37 @@ def cb(n):
13371344 e = pytest .raises (TypeError , f )
13381345 assert str (e .value ) == "'int(*)(int)' expects 1 arguments, got 0"
13391346
1347+ @contextlib .contextmanager
1348+ def _assert_unraisable (error_type : type [Exception ] | None , message : str = '' , traceback_tokens : list [str ] | None = None ):
1349+ """Assert sys.unraisablehook interaction (or an approximation for older Pythons) occurred while this context is active"""
1350+ raised_errors : list [Exception ] = []
1351+ raised_traceback : str = ''
1352+
1353+ # sys.unraisablehook is called more than once for chained exceptions; accumulate the errors and tracebacks for inspection
1354+ def _capture_unraisable_hook (ur_args ):
1355+ nonlocal raised_traceback
1356+ raised_errors .append (ur_args .exc_value )
1357+
1358+ # NB: need to use the old etype/value/tb form until 3.10 is the minimum
1359+ raised_traceback += (ur_args .err_msg or '' + '\n ' ) + '' .join (traceback .format_exception (None , ur_args .exc_value , ur_args .exc_traceback ))
1360+
1361+
1362+ with pytest .MonkeyPatch .context () as mp :
1363+ mp .setattr (sys , 'unraisablehook' , _capture_unraisable_hook )
1364+ yield
1365+
1366+ if error_type is None :
1367+ assert not raised_errors
1368+ assert not raised_traceback
1369+ return
1370+
1371+ assert any (type (raised_error ) is error_type for raised_error in raised_errors )
1372+ assert any (message in str (raised_error ) for raised_error in raised_errors )
1373+ for t in traceback_tokens or []:
1374+ assert t in raised_traceback
1375+
1376+
13401377def test_callback_exception ():
1341- try :
1342- import cStringIO
1343- except ImportError :
1344- import io as cStringIO # Python 3
1345- import linecache
1346- def matches (istr , ipattern , ipattern38 , ipattern311 = None ):
1347- if sys .version_info >= (3 , 8 ):
1348- ipattern = ipattern38
1349- if sys .version_info >= (3 , 11 ):
1350- ipattern = ipattern311 or ipattern38
1351- str , pattern = istr , ipattern
1352- while '$' in pattern :
1353- i = pattern .index ('$' )
1354- assert str [:i ] == pattern [:i ]
1355- j = str .find (pattern [i + 1 ], i )
1356- assert i + 1 <= j <= str .find ('\n ' , i )
1357- str = str [j :]
1358- pattern = pattern [i + 1 :]
1359- assert str == pattern
1360- return True
13611378 def check_value (x ):
13621379 if x == 10000 :
13631380 raise ValueError (42 )
@@ -1366,148 +1383,52 @@ def Zcb1(x):
13661383 return x * 3
13671384 BShort = new_primitive_type ("short" )
13681385 BFunc = new_function_type ((BShort ,), BShort , False )
1386+
13691387 f = callback (BFunc , Zcb1 , - 42 )
1370- #
13711388 seen = []
13721389 oops_result = None
13731390 def oops (* args ):
13741391 seen .append (args )
13751392 return oops_result
13761393 ff = callback (BFunc , Zcb1 , - 42 , oops )
1377- #
1378- orig_stderr = sys .stderr
1379- orig_getline = linecache .getline
1380- try :
1381- linecache .getline = lambda * args : 'LINE' # hack: speed up PyPy tests
1382- sys .stderr = cStringIO .StringIO ()
1383- if hasattr (sys , '__unraisablehook__' ): # work around pytest
1384- sys .unraisablehook = sys .__unraisablehook__ # on recent CPythons
1394+ with _assert_unraisable (None ):
13851395 assert f (100 ) == 300
1386- assert sys . stderr . getvalue () == ''
1396+ with _assert_unraisable ( ValueError , '42' , [ 'in Zcb1' , 'in check_value' ]):
13871397 assert f (10000 ) == - 42
1388- assert matches (sys .stderr .getvalue (), """\
1389- From cffi callback <function$Zcb1 at 0x$>:
1390- Traceback (most recent call last):
1391- File "$", line $, in Zcb1
1392- $
1393- File "$", line $, in check_value
1394- $
1395- ValueError: 42
1396- """ , """\
1397- Exception ignored from cffi callback <function$Zcb1 at 0x$>:
1398- Traceback (most recent call last):
1399- File "$", line $, in Zcb1
1400- $
1401- File "$", line $, in check_value
1402- $
1403- ValueError: 42
1404- """ )
1405- sys .stderr = cStringIO .StringIO ()
1406- bigvalue = 20000
1398+
1399+ bigvalue = 20000
1400+ with _assert_unraisable (OverflowError , "integer 60000 does not fit 'short'" , ['callback' , 'Zcb1' ]):
14071401 assert f (bigvalue ) == - 42
1408- assert matches (sys .stderr .getvalue (), """\
1409- From cffi callback <function$Zcb1 at 0x$>:
1410- Trying to convert the result back to C:
1411- OverflowError: integer 60000 does not fit 'short'
1412- """ , """\
1413- Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
1414- Traceback (most recent call last):
1415- File "$", line $, in test_callback_exception
1416- $
1417- OverflowError: integer 60000 does not fit 'short'
1418- """ )
1419- sys .stderr = cStringIO .StringIO ()
1420- bigvalue = 20000
1421- assert len (seen ) == 0
1402+ assert len (seen ) == 0
1403+
1404+ with _assert_unraisable (None ):
14221405 assert ff (bigvalue ) == - 42
1423- assert sys .stderr .getvalue () == ""
1424- assert len (seen ) == 1
1425- exc , val , tb = seen [0 ]
1426- assert exc is OverflowError
1427- assert str (val ) == "integer 60000 does not fit 'short'"
1428- #
1429- sys .stderr = cStringIO .StringIO ()
1430- bigvalue = 20000
1431- del seen [:]
1432- oops_result = 81
1406+ assert len (seen ) == 1
1407+ exc , val , tb = seen [0 ]
1408+ assert exc is OverflowError
1409+ assert str (val ) == "integer 60000 does not fit 'short'"
1410+
1411+ del seen [:]
1412+ oops_result = 81
1413+ with _assert_unraisable (None ):
14331414 assert ff (bigvalue ) == 81
1434- oops_result = None
1435- assert sys .stderr .getvalue () == ""
1436- assert len (seen ) == 1
1437- exc , val , tb = seen [0 ]
1438- assert exc is OverflowError
1439- assert str (val ) == "integer 60000 does not fit 'short'"
1440- #
1441- sys .stderr = cStringIO .StringIO ()
1442- bigvalue = 20000
1443- del seen [:]
1444- oops_result = "xy" # not None and not an int!
1415+
1416+ assert len (seen ) == 1
1417+ exc , val , tb = seen [0 ]
1418+ assert exc is OverflowError
1419+ assert str (val ) == "integer 60000 does not fit 'short'"
1420+
1421+ del seen [:]
1422+ oops_result = "xy" # not None and not an int!
1423+
1424+ with _assert_unraisable (TypeError , "an integer is required" , ["integer 60000 does not fit 'short'" ]):
14451425 assert ff (bigvalue ) == - 42
1446- oops_result = None
1447- assert matches (sys .stderr .getvalue (), """\
1448- From cffi callback <function$Zcb1 at 0x$>:
1449- Trying to convert the result back to C:
1450- OverflowError: integer 60000 does not fit 'short'
1451-
1452- During the call to 'onerror', another exception occurred:
1453-
1454- TypeError: $integer$
1455- """ , """\
1456- Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
1457- Traceback (most recent call last):
1458- File "$", line $, in test_callback_exception
1459- $
1460- OverflowError: integer 60000 does not fit 'short'
1461- Exception ignored during handling of the above exception by 'onerror':
1462- Traceback (most recent call last):
1463- File "$", line $, in test_callback_exception
1464- $
1465- TypeError: $integer$
1466- """ )
1467- #
1468- sys .stderr = cStringIO .StringIO ()
1469- seen = "not a list" # this makes the oops() function crash
1426+
1427+ seen = "not a list" # this makes the oops() function crash
1428+ oops_result = None
1429+ with _assert_unraisable (AttributeError , "'str' object has no attribute 'append" , ['Zcb1' , 'ff' , 'oops' ]):
14701430 assert ff (bigvalue ) == - 42
1471- # the $ after the AttributeError message are for the suggestions that
1472- # will be added in Python 3.10
1473- assert matches (sys .stderr .getvalue (), """\
1474- From cffi callback <function$Zcb1 at 0x$>:
1475- Trying to convert the result back to C:
1476- OverflowError: integer 60000 does not fit 'short'
1477-
1478- During the call to 'onerror', another exception occurred:
1479-
1480- Traceback (most recent call last):
1481- File "$", line $, in oops
1482- $
1483- AttributeError: 'str' object has no attribute 'append$
1484- """ , """\
1485- Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
1486- Traceback (most recent call last):
1487- File "$", line $, in test_callback_exception
1488- $
1489- OverflowError: integer 60000 does not fit 'short'
1490- Exception ignored during handling of the above exception by 'onerror':
1491- Traceback (most recent call last):
1492- File "$", line $, in oops
1493- $
1494- AttributeError: 'str' object has no attribute 'append$
1495- """ , """\
1496- Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C:
1497- Traceback (most recent call last):
1498- File "$", line $, in test_callback_exception
1499- $
1500- OverflowError: integer 60000 does not fit 'short'
1501- Exception ignored during handling of the above exception by 'onerror':
1502- Traceback (most recent call last):
1503- File "$", line $, in oops
1504- $
1505- $
1506- AttributeError: 'str' object has no attribute 'append$
1507- """ )
1508- finally :
1509- sys .stderr = orig_stderr
1510- linecache .getline = orig_getline
1431+
15111432
15121433def test_callback_return_type ():
15131434 for rettype in ["signed char" , "short" , "int" , "long" , "long long" ,
0 commit comments