@@ -3046,23 +3046,6 @@ class TestExceptionDetectionScenarios(RemoteInspectionTestBase):
30463046
30473047 4. finally_no_exception: Finally block with no exception raised
30483048 -> Should NOT have HAS_EXCEPTION (no exception state)
3049-
3050- 5. except_block_in_generator: Thread inside an except block running in a
3051- generator
3052- -> SHOULD have HAS_EXCEPTION (exc_info points at the generator's
3053- _PyErr_StackItem, not the thread's embedded exc_state)
3054-
3055- 6. except_block_in_genexpr_callee: Except block in a function called from a
3056- generator expression
3057- -> SHOULD have HAS_EXCEPTION (same reason as 5)
3058-
3059- 7. except_block_in_coroutine: Thread inside an except block running in a
3060- coroutine
3061- -> SHOULD have HAS_EXCEPTION (same reason as 5)
3062-
3063- 8. except_block_in_callee_from_coroutine: Except block in a function called
3064- from a coroutine
3065- -> SHOULD have HAS_EXCEPTION (same reason as 5)
30663049 """
30673050
30683051 def _make_single_scenario_script (self , port , scenario ):
@@ -3146,105 +3129,6 @@ def target_thread():
31463129 while True:
31473130 time.sleep(0.01)
31483131
3149- t = threading.Thread(target=target_thread)
3150- t.start()
3151- t.join()
3152- """ ,
3153- "except_block_in_generator" : f"""\
3154- import socket
3155- import threading
3156- import time
3157-
3158- def target_thread():
3159- '''Inside except block that runs in a generator'''
3160- conn = socket.create_connection(("localhost", { port } ))
3161- conn.sendall(b"ready:" + str(threading.get_native_id()).encode())
3162-
3163- def gen():
3164- try:
3165- raise ValueError("test")
3166- except ValueError:
3167- while True:
3168- time.sleep(0.01)
3169- yield
3170-
3171- for _ in gen():
3172- pass
3173-
3174- t = threading.Thread(target=target_thread)
3175- t.start()
3176- t.join()
3177- """ ,
3178- "except_block_in_genexpr_callee" : f"""\
3179- import socket
3180- import threading
3181- import time
3182-
3183- def target_thread():
3184- '''Inside except block in a function called from a generator expression'''
3185- conn = socket.create_connection(("localhost", { port } ))
3186- conn.sendall(b"ready:" + str(threading.get_native_id()).encode())
3187-
3188- def callee():
3189- try:
3190- raise ValueError("test")
3191- except ValueError:
3192- while True:
3193- time.sleep(0.01)
3194-
3195- list(callee() for _ in range(1))
3196-
3197- t = threading.Thread(target=target_thread)
3198- t.start()
3199- t.join()
3200- """ ,
3201- "except_block_in_coroutine" : f"""\
3202- import asyncio
3203- import socket
3204- import threading
3205- import time
3206-
3207- def target_thread():
3208- '''Inside except block that runs in a coroutine'''
3209- conn = socket.create_connection(("localhost", { port } ))
3210- conn.sendall(b"ready:" + str(threading.get_native_id()).encode())
3211-
3212- async def coro():
3213- try:
3214- raise ValueError("test")
3215- except ValueError:
3216- while True:
3217- time.sleep(0.01)
3218-
3219- asyncio.run(coro())
3220-
3221- t = threading.Thread(target=target_thread)
3222- t.start()
3223- t.join()
3224- """ ,
3225- "except_block_in_callee_from_coroutine" : f"""\
3226- import asyncio
3227- import socket
3228- import threading
3229- import time
3230-
3231- def target_thread():
3232- '''Inside except block in a function called from a coroutine'''
3233- conn = socket.create_connection(("localhost", { port } ))
3234- conn.sendall(b"ready:" + str(threading.get_native_id()).encode())
3235-
3236- def callee():
3237- try:
3238- raise ValueError("test")
3239- except ValueError:
3240- while True:
3241- time.sleep(0.01)
3242-
3243- async def coro():
3244- callee()
3245-
3246- asyncio.run(coro())
3247-
32483132t = threading.Thread(target=target_thread)
32493133t.start()
32503134t.join()
@@ -3408,72 +3292,6 @@ def test_finally_no_exception_no_flag(self):
34083292 self .assertIsNotNone (thread_tid , "Thread ID not received" )
34093293 self ._check_exception_status (p , thread_tid , expect_exception = False )
34103294
3411- @unittest .skipIf (
3412- sys .platform not in ("linux" , "darwin" , "win32" ),
3413- "Test only runs on supported platforms (Linux, macOS, or Windows)" ,
3414- )
3415- @unittest .skipIf (
3416- sys .platform == "android" , "Android raises Linux-specific exception"
3417- )
3418- def test_except_block_in_generator_has_exception (self ):
3419- """gh-158539: a handler running in a generator has HAS_EXCEPTION.
3420-
3421- Generators repoint ``tstate->exc_info`` at their own
3422- ``_PyErr_StackItem``, so the embedded ``exc_state`` stays empty and the
3423- profiler must follow ``exc_info`` to see the handled exception.
3424- """
3425- with self ._run_scenario_process ("except_block_in_generator" ) as (p , thread_tid ):
3426- self .assertIsNotNone (thread_tid , "Thread ID not received" )
3427- self ._check_exception_status (p , thread_tid , expect_exception = True )
3428-
3429- @unittest .skipIf (
3430- sys .platform not in ("linux" , "darwin" , "win32" ),
3431- "Test only runs on supported platforms (Linux, macOS, or Windows)" ,
3432- )
3433- @unittest .skipIf (
3434- sys .platform == "android" , "Android raises Linux-specific exception"
3435- )
3436- def test_except_block_in_genexpr_callee_has_exception (self ):
3437- """gh-158539: a handler in a function called from a generator expression.
3438-
3439- The handler itself lives in an ordinary function, but the generator
3440- expression on the stack means ``exc_info`` does not point at the
3441- thread's embedded ``exc_state``.
3442- """
3443- with self ._run_scenario_process (
3444- "except_block_in_genexpr_callee"
3445- ) as (p , thread_tid ):
3446- self .assertIsNotNone (thread_tid , "Thread ID not received" )
3447- self ._check_exception_status (p , thread_tid , expect_exception = True )
3448-
3449- @unittest .skipIf (
3450- sys .platform not in ("linux" , "darwin" , "win32" ),
3451- "Test only runs on supported platforms (Linux, macOS, or Windows)" ,
3452- )
3453- @unittest .skipIf (
3454- sys .platform == "android" , "Android raises Linux-specific exception"
3455- )
3456- def test_except_block_in_coroutine_has_exception (self ):
3457- """gh-158539: a handler running in a coroutine has HAS_EXCEPTION."""
3458- with self ._run_scenario_process ("except_block_in_coroutine" ) as (p , thread_tid ):
3459- self .assertIsNotNone (thread_tid , "Thread ID not received" )
3460- self ._check_exception_status (p , thread_tid , expect_exception = True )
3461-
3462- @unittest .skipIf (
3463- sys .platform not in ("linux" , "darwin" , "win32" ),
3464- "Test only runs on supported platforms (Linux, macOS, or Windows)" ,
3465- )
3466- @unittest .skipIf (
3467- sys .platform == "android" , "Android raises Linux-specific exception"
3468- )
3469- def test_except_block_in_callee_from_coroutine_has_exception (self ):
3470- """gh-158539: a handler in a function called from a coroutine."""
3471- with self ._run_scenario_process (
3472- "except_block_in_callee_from_coroutine"
3473- ) as (p , thread_tid ):
3474- self .assertIsNotNone (thread_tid , "Thread ID not received" )
3475- self ._check_exception_status (p , thread_tid , expect_exception = True )
3476-
34773295
34783296class TestExceptionDetectionInProcess (RemoteInspectionTestBase ):
34793297 """gh-158539: HAS_EXCEPTION for handlers running in generators/coroutines.
@@ -3488,7 +3306,7 @@ class TestExceptionDetectionInProcess(RemoteInspectionTestBase):
34883306 def setUpClass (cls ):
34893307 try :
34903308 RemoteUnwinder (os .getpid (), all_threads = True ).get_stack_trace ()
3491- except Exception as exc :
3309+ except PermissionError as exc :
34923310 raise unittest .SkipTest (f"self-inspection is unavailable: { exc } " )
34933311
34943312 def _check_running_handler (
@@ -3646,6 +3464,28 @@ def gen():
36463464
36473465 self ._check_running_handler (target , expect_exception = False )
36483466
3467+ def test_outer_handler_while_nested_generators_run (self ):
3468+ def target (ready , stop ):
3469+ def gen (depth ):
3470+ if depth :
3471+ yield from gen (depth - 1 )
3472+ else :
3473+ self ._busy_until_stopped (ready , stop )
3474+ yield
3475+
3476+ try :
3477+ raise ValueError ("outer" )
3478+ except ValueError :
3479+ for _ in gen (32 ):
3480+ pass
3481+
3482+ self ._check_running_handler (
3483+ target ,
3484+ expect_exception = True ,
3485+ mode = PROFILING_MODE_EXCEPTION ,
3486+ skip_non_matching_threads = True ,
3487+ )
3488+
36493489 def test_generator_finally_after_except (self ):
36503490 """The handled exception is cleared before the generator's finally."""
36513491 def target (ready , stop ):
0 commit comments