@@ -3534,5 +3534,104 @@ def test_get_stats_disabled_raises(self):
35343534 client_socket .sendall (b"done" )
35353535
35363536
3537+ class TestNonCodeExecutable (RemoteInspectionTestBase ):
3538+ @skip_if_not_supported
3539+ @unittest .skipIf (
3540+ sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
3541+ "Test only runs on Linux with process_vm_readv support" ,
3542+ )
3543+ def test_remote_stack_trace (self ):
3544+ port = find_unused_port ()
3545+ script = textwrap .dedent (
3546+ f"""\
3547+ import time, sys, socket, threading
3548+ import _testinternalcapi
3549+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
3550+ sock.connect(('localhost', { port } ))
3551+
3552+ def bar():
3553+ for x in range(100):
3554+ if x == 50:
3555+ _testinternalcapi.call_with_jit_frame(baz, foo, ())
3556+
3557+ def baz():
3558+ pass
3559+
3560+ def foo():
3561+ sock.sendall(b"ready:thread\\ n"); time.sleep(10_000)
3562+
3563+ t = threading.Thread(target=bar)
3564+ t.start()
3565+ sock.sendall(b"ready:main\\ n"); t.join()
3566+ """
3567+ )
3568+
3569+ with os_helper .temp_dir () as work_dir :
3570+ script_dir = os .path .join (work_dir , "script_pkg" )
3571+ os .mkdir (script_dir )
3572+
3573+ server_socket = _create_server_socket (port )
3574+ script_name = _make_test_script (script_dir , "script" , script )
3575+ client_socket = None
3576+
3577+ try :
3578+ with _managed_subprocess ([sys .executable , script_name ]) as p :
3579+ client_socket , _ = server_socket .accept ()
3580+ server_socket .close ()
3581+ server_socket = None
3582+
3583+ _wait_for_signal (
3584+ client_socket , [b"ready:main" , b"ready:thread" ]
3585+ )
3586+
3587+ try :
3588+ stack_trace = get_stack_trace (p .pid )
3589+ except PermissionError :
3590+ self .skipTest (
3591+ "Insufficient permissions to read the stack trace"
3592+ )
3593+
3594+ thread_expected_stack_trace = [
3595+ FrameInfo ([script_name , 15 , "foo" ]),
3596+ # external frame line number is function start
3597+ FrameInfo ([script_name , 11 , "baz" ]),
3598+ FrameInfo ([script_name , 9 , "bar" ]),
3599+ FrameInfo ([threading .__file__ , ANY , "Thread.run" ]),
3600+ FrameInfo (
3601+ [
3602+ threading .__file__ ,
3603+ ANY ,
3604+ "Thread._bootstrap_inner" ,
3605+ ]
3606+ ),
3607+ FrameInfo (
3608+ [threading .__file__ , ANY , "Thread._bootstrap" ]
3609+ ),
3610+ ]
3611+
3612+ # Find expected thread stack
3613+ found_thread = self ._find_thread_with_frame (
3614+ stack_trace ,
3615+ lambda f : f .funcname == "foo" and f .lineno == 15 ,
3616+ )
3617+ self .assertIsNotNone (
3618+ found_thread , "Expected thread stack trace not found"
3619+ )
3620+ self .assertEqual (
3621+ found_thread .frame_info , thread_expected_stack_trace
3622+ )
3623+
3624+ # Check main thread
3625+ main_frame = FrameInfo ([script_name , 19 , "<module>" ])
3626+ found_main = self ._find_frame_in_trace (
3627+ stack_trace , lambda f : f == main_frame
3628+ )
3629+ self .assertIsNotNone (
3630+ found_main , "Main thread stack trace not found"
3631+ )
3632+ finally :
3633+ _cleanup_sockets (client_socket , server_socket )
3634+
3635+
35373636if __name__ == "__main__" :
35383637 unittest .main ()
0 commit comments