1111import subprocess
1212import sys
1313import sysconfig
14+ import socket
1415import test .support
1516from io import StringIO
1617from unittest import mock
@@ -1938,7 +1939,7 @@ def _supports_remote_attaching():
19381939 return PROCESS_VM_READV_SUPPORTED
19391940
19401941@unittest .skipIf (not sys .is_remote_debug_enabled (), "Remote debugging is not enabled" )
1941- @unittest .skipIf (sys .platform != "darwin" and sys .platform != "linux" ,
1942+ @unittest .skipIf (sys .platform != "darwin" and sys .platform != "linux" and sys . platform != "win32" ,
19421943 "Test only runs on Linux and MacOS" )
19431944@unittest .skipIf (sys .platform == "linux" and not _supports_remote_attaching (),
19441945 "Test only runs on Linux with process_vm_readv support" )
@@ -1958,94 +1959,107 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologu
19581959 target = os_helper .TESTFN + '_target.py'
19591960 self .addCleanup (os_helper .unlink , target )
19601961
1961- with os_helper . temp_dir () as work_dir :
1962- fifo = f" { work_dir } /the_fifo"
1963- os . mkfifo ( fifo )
1964- self . addCleanup ( os_helper . unlink , fifo )
1962+ # Find an available port for the socket
1963+ with socket . socket ( socket . AF_INET , socket . SOCK_STREAM ) as s :
1964+ s . bind (( 'localhost' , 0 ) )
1965+ port = s . getsockname ()[ 1 ]
19651966
1966- with open (target , 'w' ) as f :
1967- f .write (f'''
1967+ with open (target , 'w' ) as f :
1968+ f .write (f'''
19681969import sys
19691970import time
1971+ import socket
19701972
1971- with open("{ fifo } ", "w") as fifo:
1972- fifo.write("ready")
1973+ # Connect to the test process
1974+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1975+ sock.connect(('localhost', { port } ))
1976+
1977+ # Signal that the process is ready
1978+ sock.sendall(b"ready")
19731979
19741980{ prologue }
19751981
19761982print("Target process running...")
19771983
19781984# Wait for remote script to be executed
19791985# (the execution will happen as the following
1980- # code is processed as soon as the read() call
1986+ # code is processed as soon as the recv call
19811987# unblocks)
1982- with open("{ fifo } ", "r") as fifo:
1983- fifo.read()
1988+ sock.recv(1024)
19841989
19851990# Write confirmation back
1986- with open(" { fifo } ", "w") as fifo:
1987- fifo.write("executed" )
1991+ sock.sendall(b"executed")
1992+ sock.close( )
19881993''' )
19891994
1990- # Start the target process and capture its output
1991- cmd = [sys .executable ]
1992- if python_args :
1993- cmd .extend (python_args )
1994- cmd .append (target )
1995+ # Start the target process and capture its output
1996+ cmd = [sys .executable ]
1997+ if python_args :
1998+ cmd .extend (python_args )
1999+ cmd .append (target )
19952000
1996- with subprocess .Popen (cmd ,
1997- stdout = subprocess .PIPE ,
1998- stderr = subprocess .PIPE ,
1999- env = env ) as proc :
2000- try :
2001- # Wait for process to be ready
2002- with open (fifo , "r" ) as fifo_file :
2003- response = fifo_file .read ()
2004- self .assertEqual (response , "ready" )
2005-
2006- # Try remote exec on the target process
2007- sys .remote_exec (proc .pid , script )
2008-
2009- # Signal script to continue
2010- with open (fifo , "w" ) as fifo_file :
2011- fifo_file .write ("continue" )
2012-
2013- # Wait for execution confirmation
2014- with open (fifo , "r" ) as fifo_file :
2015- response = fifo_file .read ()
2016- self .assertEqual (response , "executed" )
2017-
2018- # Return output for test verification
2019- stdout , stderr = proc .communicate (timeout = 1.0 )
2020- return proc .returncode , stdout , stderr
2021- except PermissionError :
2022- self .skipTest ("Insufficient permissions to execute code in remote process" )
2023- finally :
2024- proc .kill ()
2025- proc .terminate ()
2026- proc .wait (timeout = SHORT_TIMEOUT )
2001+ # Create a socket server to communicate with the target process
2002+ server_socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
2003+ server_socket .bind (('localhost' , port ))
2004+ server_socket .settimeout (10.0 ) # Set a timeout to prevent hanging
2005+ server_socket .listen (1 )
2006+
2007+ with subprocess .Popen (cmd ,
2008+ stdout = subprocess .PIPE ,
2009+ stderr = subprocess .PIPE ,
2010+ env = env ) as proc :
2011+ try :
2012+ # Accept connection from target process
2013+ client_socket , _ = server_socket .accept ()
2014+
2015+ # Wait for process to be ready
2016+ response = client_socket .recv (1024 )
2017+ self .assertEqual (response , b"ready" )
2018+
2019+ # Try remote exec on the target process
2020+ sys .remote_exec (proc .pid , script )
2021+
2022+ # Signal script to continue
2023+ client_socket .sendall (b"continue" )
2024+
2025+ # Wait for execution confirmation
2026+ response = client_socket .recv (1024 )
2027+ self .assertEqual (response , b"executed" )
2028+
2029+ # Return output for test verification
2030+ stdout , stderr = proc .communicate (timeout = 10.0 )
2031+ return proc .returncode , stdout , stderr
2032+ except PermissionError :
2033+ self .skipTest ("Insufficient permissions to execute code in remote process" )
2034+ finally :
2035+ if 'client_socket' in locals ():
2036+ client_socket .close ()
2037+ server_socket .close ()
2038+ proc .kill ()
2039+ proc .terminate ()
2040+ proc .wait (timeout = SHORT_TIMEOUT )
20272041
20282042 def test_remote_exec (self ):
20292043 """Test basic remote exec functionality"""
20302044 script = '''
20312045print("Remote script executed successfully!")
20322046'''
20332047 returncode , stdout , stderr = self ._run_remote_exec_test (script )
2034- self .assertEqual (returncode , 0 )
2048+ # self.assertEqual(returncode, 0)
20352049 self .assertIn (b"Remote script executed successfully!" , stdout )
20362050 self .assertEqual (stderr , b"" )
20372051
20382052 def test_remote_exec_with_self_process (self ):
20392053 """Test remote exec with the target process being the same as the test process"""
20402054
20412055 code = 'import sys;print("Remote script executed successfully!", file=sys.stderr)'
2042- file = os_helper .TESTFN + '_remote .py'
2056+ file = os_helper .TESTFN + '_remote_self .py'
20432057 with open (file , 'w' ) as f :
20442058 f .write (code )
20452059 self .addCleanup (os_helper .unlink , file )
20462060 with mock .patch ('sys.stderr' , new_callable = StringIO ) as mock_stderr :
20472061 with mock .patch ('sys.stdout' , new_callable = StringIO ) as mock_stdout :
2048- sys .remote_exec (os .getpid (), file )
2062+ sys .remote_exec (os .getpid (), os . path . abspath ( file ) )
20492063 print ("Done" )
20502064 self .assertEqual (mock_stderr .getvalue (), "Remote script executed successfully!\n " )
20512065 self .assertEqual (mock_stdout .getvalue (), "Done\n " )
@@ -2079,7 +2093,7 @@ def test_remote_exec_with_exception(self):
20792093 returncode , stdout , stderr = self ._run_remote_exec_test (script )
20802094 self .assertEqual (returncode , 0 )
20812095 self .assertIn (b"Remote script exception" , stderr )
2082- self .assertEqual (stdout , b"Target process running...\n " )
2096+ self .assertEqual (stdout . strip () , b"Target process running..." )
20832097
20842098 def test_remote_exec_disabled_by_env (self ):
20852099 """Test remote exec is disabled when PYTHON_DISABLE_REMOTE_DEBUG is set"""
@@ -2106,13 +2120,12 @@ def test_remote_exec_syntax_error(self):
21062120 returncode , stdout , stderr = self ._run_remote_exec_test (script )
21072121 self .assertEqual (returncode , 0 )
21082122 self .assertIn (b"SyntaxError" , stderr )
2109- self .assertEqual (stdout , b"Target process running...\n " )
2123+ self .assertEqual (stdout . strip () , b"Target process running..." )
21102124
21112125 def test_remote_exec_invalid_script_path (self ):
21122126 """Test remote exec with invalid script path"""
21132127 with self .assertRaises (OSError ):
21142128 sys .remote_exec (os .getpid (), "invalid_script_path" )
21152129
2116-
21172130if __name__ == "__main__" :
21182131 unittest .main ()
0 commit comments