@@ -3534,5 +3534,89 @@ 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+ # Find expected thread stack by funcname
3595+ found_thread = self ._find_thread_with_frame (
3596+ stack_trace ,
3597+ lambda f : f .funcname == "foo" and f .location .lineno == 15 ,
3598+ )
3599+ self .assertIsNotNone (
3600+ found_thread , "Expected thread stack trace not found"
3601+ )
3602+ # Check the funcnames in order
3603+ funcnames = [f .funcname for f in found_thread .frame_info ]
3604+ self .assertEqual (
3605+ funcnames [:6 ],
3606+ ["foo" , "baz" , "bar" , "Thread.run" , "Thread._bootstrap_inner" , "Thread._bootstrap" ]
3607+ )
3608+
3609+ # Check main thread
3610+ found_main = self ._find_frame_in_trace (
3611+ stack_trace ,
3612+ lambda f : f .funcname == "<module>" and f .location .lineno == 19 ,
3613+ )
3614+ self .assertIsNotNone (
3615+ found_main , "Main thread stack trace not found"
3616+ )
3617+ finally :
3618+ _cleanup_sockets (client_socket , server_socket )
3619+
3620+
35373621if __name__ == "__main__" :
35383622 unittest .main ()
0 commit comments