@@ -3060,5 +3060,104 @@ def test_get_stats_disabled_raises(self):
30603060 client_socket .sendall (b"done" )
30613061
30623062
3063+ class TestNonCodeExecutable (RemoteInspectionTestBase ):
3064+ @skip_if_not_supported
3065+ @unittest .skipIf (
3066+ sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
3067+ "Test only runs on Linux with process_vm_readv support" ,
3068+ )
3069+ def test_remote_stack_trace (self ):
3070+ port = find_unused_port ()
3071+ script = textwrap .dedent (
3072+ f"""\
3073+ import time, sys, socket, threading
3074+ import _testinternalcapi
3075+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
3076+ sock.connect(('localhost', { port } ))
3077+
3078+ def bar():
3079+ for x in range(100):
3080+ if x == 50:
3081+ _testinternalcapi.call_with_jit_frame(baz, foo, ())
3082+
3083+ def baz():
3084+ pass
3085+
3086+ def foo():
3087+ sock.sendall(b"ready:thread\\ n"); time.sleep(10_000)
3088+
3089+ t = threading.Thread(target=bar)
3090+ t.start()
3091+ sock.sendall(b"ready:main\\ n"); t.join()
3092+ """
3093+ )
3094+
3095+ with os_helper .temp_dir () as work_dir :
3096+ script_dir = os .path .join (work_dir , "script_pkg" )
3097+ os .mkdir (script_dir )
3098+
3099+ server_socket = _create_server_socket (port )
3100+ script_name = _make_test_script (script_dir , "script" , script )
3101+ client_socket = None
3102+
3103+ try :
3104+ with _managed_subprocess ([sys .executable , script_name ]) as p :
3105+ client_socket , _ = server_socket .accept ()
3106+ server_socket .close ()
3107+ server_socket = None
3108+
3109+ _wait_for_signal (
3110+ client_socket , [b"ready:main" , b"ready:thread" ]
3111+ )
3112+
3113+ try :
3114+ stack_trace = get_stack_trace (p .pid )
3115+ except PermissionError :
3116+ self .skipTest (
3117+ "Insufficient permissions to read the stack trace"
3118+ )
3119+
3120+ thread_expected_stack_trace = [
3121+ FrameInfo ([script_name , 15 , "foo" ]),
3122+ # external frame line number is function start
3123+ FrameInfo ([script_name , 11 , "baz" ]),
3124+ FrameInfo ([script_name , 9 , "bar" ]),
3125+ FrameInfo ([threading .__file__ , ANY , "Thread.run" ]),
3126+ FrameInfo (
3127+ [
3128+ threading .__file__ ,
3129+ ANY ,
3130+ "Thread._bootstrap_inner" ,
3131+ ]
3132+ ),
3133+ FrameInfo (
3134+ [threading .__file__ , ANY , "Thread._bootstrap" ]
3135+ ),
3136+ ]
3137+
3138+ # Find expected thread stack
3139+ found_thread = self ._find_thread_with_frame (
3140+ stack_trace ,
3141+ lambda f : f .funcname == "foo" and f .lineno == 15 ,
3142+ )
3143+ self .assertIsNotNone (
3144+ found_thread , "Expected thread stack trace not found"
3145+ )
3146+ self .assertEqual (
3147+ found_thread .frame_info , thread_expected_stack_trace
3148+ )
3149+
3150+ # Check main thread
3151+ main_frame = FrameInfo ([script_name , 19 , "<module>" ])
3152+ found_main = self ._find_frame_in_trace (
3153+ stack_trace , lambda f : f == main_frame
3154+ )
3155+ self .assertIsNotNone (
3156+ found_main , "Main thread stack trace not found"
3157+ )
3158+ finally :
3159+ _cleanup_sockets (client_socket , server_socket )
3160+
3161+
30633162if __name__ == "__main__" :
30643163 unittest .main ()
0 commit comments