66import operator
77import os
88import random
9+ import shutil
910import socket
1011import struct
1112import subprocess
@@ -1979,7 +1980,8 @@ def tearDown(self):
19791980 test .support .reap_children ()
19801981
19811982 def _run_remote_exec_test (self , script_code , python_args = None , env = None ,
1982- prologue = '' ,
1983+ python_executable = None , prologue = '' ,
1984+ after_ready = None ,
19831985 script_path = os_helper .TESTFN + '_remote.py' ):
19841986 # Create the script that will be remotely executed
19851987 self .addCleanup (os_helper .unlink , script_path )
@@ -2027,7 +2029,10 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20272029''' )
20282030
20292031 # Start the target process and capture its output
2030- cmd = [sys .executable ]
2032+ if python_executable is None :
2033+ python_executable = sys .executable
2034+
2035+ cmd = [python_executable ]
20312036 if python_args :
20322037 cmd .extend (python_args )
20332038 cmd .append (target )
@@ -2052,6 +2057,9 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20522057 response = client_socket .recv (1024 )
20532058 self .assertEqual (response , b"ready" )
20542059
2060+ if after_ready is not None :
2061+ after_ready (proc )
2062+
20552063 # Try remote exec on the target process
20562064 sys .remote_exec (proc .pid , script_path )
20572065
@@ -2074,6 +2082,19 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20742082 proc .terminate ()
20752083 proc .wait (timeout = SHORT_TIMEOUT )
20762084
2085+ def _run_remote_exec_with_deleted_mapping (self , deleted_path , ** kwargs ):
2086+ def delete_loaded_mapping (proc ):
2087+ os_helper .unlink (deleted_path )
2088+ with open (f'/proc/{ proc .pid } /maps' , encoding = 'utf-8' ) as maps :
2089+ self .assertIn (f'{ deleted_path } (deleted)' , maps .read ())
2090+
2091+ script = 'print("Remote script executed successfully!")'
2092+ returncode , stdout , stderr = self ._run_remote_exec_test (
2093+ script , after_ready = delete_loaded_mapping , ** kwargs )
2094+ self .assertEqual (returncode , 0 )
2095+ self .assertIn (b"Remote script executed successfully!" , stdout )
2096+ self .assertEqual (stderr , b"" )
2097+
20772098 def test_remote_exec (self ):
20782099 """Test basic remote exec functionality"""
20792100 script = 'print("Remote script executed successfully!")'
@@ -2200,6 +2221,75 @@ def test_remote_exec_invalid_script_path(self):
22002221 with self .assertRaises (OSError ):
22012222 sys .remote_exec (os .getpid (), "invalid_script_path" )
22022223
2224+ @unittest .skipUnless (sys .platform == 'linux' , 'Linux-only regression test' )
2225+ @unittest .skipUnless (
2226+ sysconfig .get_config_var ('Py_ENABLE_SHARED' ) == 1 ,
2227+ 'requires a shared libpython build' )
2228+ def test_remote_exec_deleted_libpython (self ):
2229+ """Test remote exec when the target libpython was deleted."""
2230+ build_dir = sysconfig .get_config_var ('abs_builddir' )
2231+ ldlibrary = sysconfig .get_config_var ('LDLIBRARY' )
2232+ instsoname = sysconfig .get_config_var ('INSTSONAME' )
2233+ if not build_dir or not ldlibrary or not instsoname :
2234+ self .skipTest ('cannot determine shared libpython location' )
2235+
2236+ source_libpython = os .path .join (build_dir , instsoname )
2237+ if not os .path .exists (source_libpython ):
2238+ self .skipTest (f'{ source_libpython !r} does not exist' )
2239+
2240+ with os_helper .temp_dir () as lib_dir :
2241+ copied_libpython = os .path .join (lib_dir , instsoname )
2242+ shutil .copy2 (source_libpython , copied_libpython )
2243+ if ldlibrary != instsoname :
2244+ os .symlink (instsoname , os .path .join (lib_dir , ldlibrary ))
2245+
2246+ env = os .environ .copy ()
2247+ ld_library_path = env .get ('LD_LIBRARY_PATH' )
2248+ env ['LD_LIBRARY_PATH' ] = lib_dir if not ld_library_path else (
2249+ lib_dir + os .pathsep + ld_library_path )
2250+
2251+ self ._run_remote_exec_with_deleted_mapping (copied_libpython ,
2252+ env = env )
2253+
2254+ @unittest .skipUnless (sys .platform == 'linux' , 'Linux-only regression test' )
2255+ @unittest .skipUnless (
2256+ sysconfig .get_config_var ('Py_ENABLE_SHARED' ) == 0 ,
2257+ 'requires a static Python build' )
2258+ def test_remote_exec_deleted_static_executable (self ):
2259+ """Test remote exec when the target static executable was deleted."""
2260+ build_dir = sysconfig .get_config_var ('abs_builddir' )
2261+ srcdir = sysconfig .get_config_var ('srcdir' )
2262+ if not build_dir or not srcdir :
2263+ self .skipTest ('cannot determine build-tree locations' )
2264+
2265+ pybuilddir_txt = os .path .join (build_dir , 'pybuilddir.txt' )
2266+ if not os .path .exists (pybuilddir_txt ):
2267+ self .skipTest (f'{ pybuilddir_txt !r} does not exist' )
2268+
2269+ with open (pybuilddir_txt , encoding = 'utf-8' ) as pybuilddir_file :
2270+ pybuilddir = pybuilddir_file .read ().strip ()
2271+ source_ext_dir = os .path .join (build_dir , pybuilddir )
2272+ if not os .path .isdir (source_ext_dir ):
2273+ self .skipTest (f'{ source_ext_dir !r} does not exist' )
2274+
2275+ with os_helper .temp_dir () as copied_root :
2276+ copied_build_dir = os .path .join (copied_root , 'build' )
2277+ copied_pybuilddir = os .path .join (copied_build_dir , pybuilddir )
2278+ os .makedirs (os .path .dirname (copied_pybuilddir ))
2279+ os .symlink (os .path .join (srcdir , 'Lib' ),
2280+ os .path .join (copied_root , 'Lib' ))
2281+ os .symlink (source_ext_dir , copied_pybuilddir )
2282+ shutil .copy2 (pybuilddir_txt ,
2283+ os .path .join (copied_build_dir , 'pybuilddir.txt' ))
2284+
2285+ copied_python = os .path .join (copied_build_dir ,
2286+ os .path .basename (sys .executable ))
2287+ shutil .copy2 (sys .executable , copied_python )
2288+
2289+ self ._run_remote_exec_with_deleted_mapping (
2290+ copied_python , python_args = ['-S' ],
2291+ python_executable = copied_python )
2292+
22032293 def test_remote_exec_in_process_without_debug_fails_envvar (self ):
22042294 """Test remote exec in a process without remote debugging enabled"""
22052295 script = os_helper .TESTFN + '_remote.py'
0 commit comments