66import operator
77import os
88import random
9+ import shutil
910import socket
1011import struct
1112import subprocess
@@ -2003,7 +2004,8 @@ def tearDown(self):
20032004 test .support .reap_children ()
20042005
20052006 def _run_remote_exec_test (self , script_code , python_args = None , env = None ,
2006- prologue = '' ,
2007+ python_executable = None , prologue = '' ,
2008+ after_ready = None ,
20072009 script_path = os_helper .TESTFN + '_remote.py' ):
20082010 # Create the script that will be remotely executed
20092011 self .addCleanup (os_helper .unlink , script_path )
@@ -2051,7 +2053,10 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20512053''' )
20522054
20532055 # Start the target process and capture its output
2054- cmd = [sys .executable ]
2056+ if python_executable is None :
2057+ python_executable = sys .executable
2058+
2059+ cmd = [python_executable ]
20552060 if python_args :
20562061 cmd .extend (python_args )
20572062 cmd .append (target )
@@ -2076,6 +2081,9 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20762081 response = client_socket .recv (1024 )
20772082 self .assertEqual (response , b"ready" )
20782083
2084+ if after_ready is not None :
2085+ after_ready (proc )
2086+
20792087 # Try remote exec on the target process
20802088 sys .remote_exec (proc .pid , script_path )
20812089
@@ -2098,6 +2106,19 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20982106 proc .terminate ()
20992107 proc .wait (timeout = SHORT_TIMEOUT )
21002108
2109+ def _run_remote_exec_with_deleted_mapping (self , deleted_path , ** kwargs ):
2110+ def delete_loaded_mapping (proc ):
2111+ os_helper .unlink (deleted_path )
2112+ with open (f'/proc/{ proc .pid } /maps' , encoding = 'utf-8' ) as maps :
2113+ self .assertIn (f'{ deleted_path } (deleted)' , maps .read ())
2114+
2115+ script = 'print("Remote script executed successfully!")'
2116+ returncode , stdout , stderr = self ._run_remote_exec_test (
2117+ script , after_ready = delete_loaded_mapping , ** kwargs )
2118+ self .assertEqual (returncode , 0 )
2119+ self .assertIn (b"Remote script executed successfully!" , stdout )
2120+ self .assertEqual (stderr , b"" )
2121+
21012122 def test_remote_exec (self ):
21022123 """Test basic remote exec functionality"""
21032124 script = 'print("Remote script executed successfully!")'
@@ -2224,6 +2245,75 @@ def test_remote_exec_invalid_script_path(self):
22242245 with self .assertRaises (OSError ):
22252246 sys .remote_exec (os .getpid (), "invalid_script_path" )
22262247
2248+ @unittest .skipUnless (sys .platform == 'linux' , 'Linux-only regression test' )
2249+ @unittest .skipUnless (
2250+ sysconfig .get_config_var ('Py_ENABLE_SHARED' ) == 1 ,
2251+ 'requires a shared libpython build' )
2252+ def test_remote_exec_deleted_libpython (self ):
2253+ """Test remote exec when the target libpython was deleted."""
2254+ build_dir = sysconfig .get_config_var ('abs_builddir' )
2255+ ldlibrary = sysconfig .get_config_var ('LDLIBRARY' )
2256+ instsoname = sysconfig .get_config_var ('INSTSONAME' )
2257+ if not build_dir or not ldlibrary or not instsoname :
2258+ self .skipTest ('cannot determine shared libpython location' )
2259+
2260+ source_libpython = os .path .join (build_dir , instsoname )
2261+ if not os .path .exists (source_libpython ):
2262+ self .skipTest (f'{ source_libpython !r} does not exist' )
2263+
2264+ with os_helper .temp_dir () as lib_dir :
2265+ copied_libpython = os .path .join (lib_dir , instsoname )
2266+ shutil .copy2 (source_libpython , copied_libpython )
2267+ if ldlibrary != instsoname :
2268+ os .symlink (instsoname , os .path .join (lib_dir , ldlibrary ))
2269+
2270+ env = os .environ .copy ()
2271+ ld_library_path = env .get ('LD_LIBRARY_PATH' )
2272+ env ['LD_LIBRARY_PATH' ] = lib_dir if not ld_library_path else (
2273+ lib_dir + os .pathsep + ld_library_path )
2274+
2275+ self ._run_remote_exec_with_deleted_mapping (copied_libpython ,
2276+ env = env )
2277+
2278+ @unittest .skipUnless (sys .platform == 'linux' , 'Linux-only regression test' )
2279+ @unittest .skipUnless (
2280+ sysconfig .get_config_var ('Py_ENABLE_SHARED' ) == 0 ,
2281+ 'requires a static Python build' )
2282+ def test_remote_exec_deleted_static_executable (self ):
2283+ """Test remote exec when the target static executable was deleted."""
2284+ build_dir = sysconfig .get_config_var ('abs_builddir' )
2285+ srcdir = sysconfig .get_config_var ('srcdir' )
2286+ if not build_dir or not srcdir :
2287+ self .skipTest ('cannot determine build-tree locations' )
2288+
2289+ pybuilddir_txt = os .path .join (build_dir , 'pybuilddir.txt' )
2290+ if not os .path .exists (pybuilddir_txt ):
2291+ self .skipTest (f'{ pybuilddir_txt !r} does not exist' )
2292+
2293+ with open (pybuilddir_txt , encoding = 'utf-8' ) as pybuilddir_file :
2294+ pybuilddir = pybuilddir_file .read ().strip ()
2295+ source_ext_dir = os .path .join (build_dir , pybuilddir )
2296+ if not os .path .isdir (source_ext_dir ):
2297+ self .skipTest (f'{ source_ext_dir !r} does not exist' )
2298+
2299+ with os_helper .temp_dir () as copied_root :
2300+ copied_build_dir = os .path .join (copied_root , 'build' )
2301+ copied_pybuilddir = os .path .join (copied_build_dir , pybuilddir )
2302+ os .makedirs (os .path .dirname (copied_pybuilddir ))
2303+ os .symlink (os .path .join (srcdir , 'Lib' ),
2304+ os .path .join (copied_root , 'Lib' ))
2305+ os .symlink (source_ext_dir , copied_pybuilddir )
2306+ shutil .copy2 (pybuilddir_txt ,
2307+ os .path .join (copied_build_dir , 'pybuilddir.txt' ))
2308+
2309+ copied_python = os .path .join (copied_build_dir ,
2310+ os .path .basename (sys .executable ))
2311+ shutil .copy2 (sys .executable , copied_python )
2312+
2313+ self ._run_remote_exec_with_deleted_mapping (
2314+ copied_python , python_args = ['-S' ],
2315+ python_executable = copied_python )
2316+
22272317 def test_remote_exec_in_process_without_debug_fails_envvar (self ):
22282318 """Test remote exec in a process without remote debugging enabled"""
22292319 script = os_helper .TESTFN + '_remote.py'
0 commit comments