1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import traceback
5
+ import unittest .mock
6
+
1
7
import pytest
2
8
import sys
9
+ import typing as t
3
10
4
11
is_musl = False
5
12
if sys .platform == 'linux' :
@@ -1337,27 +1344,37 @@ def cb(n):
1337
1344
e = pytest .raises (TypeError , f )
1338
1345
assert str (e .value ) == "'int(*)(int)' expects 1 arguments, got 0"
1339
1346
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
+
1340
1377
def 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
1361
1378
def check_value (x ):
1362
1379
if x == 10000 :
1363
1380
raise ValueError (42 )
@@ -1366,148 +1383,52 @@ def Zcb1(x):
1366
1383
return x * 3
1367
1384
BShort = new_primitive_type ("short" )
1368
1385
BFunc = new_function_type ((BShort ,), BShort , False )
1386
+
1369
1387
f = callback (BFunc , Zcb1 , - 42 )
1370
- #
1371
1388
seen = []
1372
1389
oops_result = None
1373
1390
def oops (* args ):
1374
1391
seen .append (args )
1375
1392
return oops_result
1376
1393
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 ):
1385
1395
assert f (100 ) == 300
1386
- assert sys . stderr . getvalue () == ''
1396
+ with _assert_unraisable ( ValueError , '42' , [ 'in Zcb1' , 'in check_value' ]):
1387
1397
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' ]):
1407
1401
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 ):
1422
1405
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 ):
1433
1414
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'" ]):
1445
1425
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' ]):
1470
1430
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
+
1511
1432
1512
1433
def test_callback_return_type ():
1513
1434
for rettype in ["signed char" , "short" , "int" , "long" , "long long" ,
0 commit comments