66import operator
77import os
88import random
9+ import shutil
910import socket
1011import struct
1112import subprocess
@@ -2013,7 +2014,8 @@ def tearDown(self):
20132014 test .support .reap_children ()
20142015
20152016 def _run_remote_exec_test (self , script_code , python_args = None , env = None ,
2016- prologue = '' ,
2017+ python_executable = None , prologue = '' ,
2018+ after_ready = None ,
20172019 script_path = os_helper .TESTFN + '_remote.py' ):
20182020 # Create the script that will be remotely executed
20192021 self .addCleanup (os_helper .unlink , script_path )
@@ -2061,7 +2063,10 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20612063''' )
20622064
20632065 # Start the target process and capture its output
2064- cmd = [sys .executable ]
2066+ if python_executable is None :
2067+ python_executable = sys .executable
2068+
2069+ cmd = [python_executable ]
20652070 if python_args :
20662071 cmd .extend (python_args )
20672072 cmd .append (target )
@@ -2086,6 +2091,9 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
20862091 response = client_socket .recv (1024 )
20872092 self .assertEqual (response , b"ready" )
20882093
2094+ if after_ready is not None :
2095+ after_ready (proc )
2096+
20892097 # Try remote exec on the target process
20902098 sys .remote_exec (proc .pid , script_path )
20912099
@@ -2108,6 +2116,19 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
21082116 proc .terminate ()
21092117 proc .wait (timeout = SHORT_TIMEOUT )
21102118
2119+ def _run_remote_exec_with_deleted_mapping (self , deleted_path , ** kwargs ):
2120+ def delete_loaded_mapping (proc ):
2121+ os_helper .unlink (deleted_path )
2122+ with open (f'/proc/{ proc .pid } /maps' , encoding = 'utf-8' ) as maps :
2123+ self .assertIn (f'{ deleted_path } (deleted)' , maps .read ())
2124+
2125+ script = 'print("Remote script executed successfully!")'
2126+ returncode , stdout , stderr = self ._run_remote_exec_test (
2127+ script , after_ready = delete_loaded_mapping , ** kwargs )
2128+ self .assertEqual (returncode , 0 )
2129+ self .assertIn (b"Remote script executed successfully!" , stdout )
2130+ self .assertEqual (stderr , b"" )
2131+
21112132 def test_remote_exec (self ):
21122133 """Test basic remote exec functionality"""
21132134 script = 'print("Remote script executed successfully!")'
@@ -2234,6 +2255,75 @@ def test_remote_exec_invalid_script_path(self):
22342255 with self .assertRaises (OSError ):
22352256 sys .remote_exec (os .getpid (), "invalid_script_path" )
22362257
2258+ @unittest .skipUnless (sys .platform == 'linux' , 'Linux-only regression test' )
2259+ @unittest .skipUnless (
2260+ sysconfig .get_config_var ('Py_ENABLE_SHARED' ) == 1 ,
2261+ 'requires a shared libpython build' )
2262+ def test_remote_exec_deleted_libpython (self ):
2263+ """Test remote exec when the target libpython was deleted."""
2264+ build_dir = sysconfig .get_config_var ('abs_builddir' )
2265+ ldlibrary = sysconfig .get_config_var ('LDLIBRARY' )
2266+ instsoname = sysconfig .get_config_var ('INSTSONAME' )
2267+ if not build_dir or not ldlibrary or not instsoname :
2268+ self .skipTest ('cannot determine shared libpython location' )
2269+
2270+ source_libpython = os .path .join (build_dir , instsoname )
2271+ if not os .path .exists (source_libpython ):
2272+ self .skipTest (f'{ source_libpython !r} does not exist' )
2273+
2274+ with os_helper .temp_dir () as lib_dir :
2275+ copied_libpython = os .path .join (lib_dir , instsoname )
2276+ shutil .copy2 (source_libpython , copied_libpython )
2277+ if ldlibrary != instsoname :
2278+ os .symlink (instsoname , os .path .join (lib_dir , ldlibrary ))
2279+
2280+ env = os .environ .copy ()
2281+ ld_library_path = env .get ('LD_LIBRARY_PATH' )
2282+ env ['LD_LIBRARY_PATH' ] = lib_dir if not ld_library_path else (
2283+ lib_dir + os .pathsep + ld_library_path )
2284+
2285+ self ._run_remote_exec_with_deleted_mapping (copied_libpython ,
2286+ env = env )
2287+
2288+ @unittest .skipUnless (sys .platform == 'linux' , 'Linux-only regression test' )
2289+ @unittest .skipUnless (
2290+ sysconfig .get_config_var ('Py_ENABLE_SHARED' ) == 0 ,
2291+ 'requires a static Python build' )
2292+ def test_remote_exec_deleted_static_executable (self ):
2293+ """Test remote exec when the target static executable was deleted."""
2294+ build_dir = sysconfig .get_config_var ('abs_builddir' )
2295+ srcdir = sysconfig .get_config_var ('srcdir' )
2296+ if not build_dir or not srcdir :
2297+ self .skipTest ('cannot determine build-tree locations' )
2298+
2299+ pybuilddir_txt = os .path .join (build_dir , 'pybuilddir.txt' )
2300+ if not os .path .exists (pybuilddir_txt ):
2301+ self .skipTest (f'{ pybuilddir_txt !r} does not exist' )
2302+
2303+ with open (pybuilddir_txt , encoding = 'utf-8' ) as pybuilddir_file :
2304+ pybuilddir = pybuilddir_file .read ().strip ()
2305+ source_ext_dir = os .path .join (build_dir , pybuilddir )
2306+ if not os .path .isdir (source_ext_dir ):
2307+ self .skipTest (f'{ source_ext_dir !r} does not exist' )
2308+
2309+ with os_helper .temp_dir () as copied_root :
2310+ copied_build_dir = os .path .join (copied_root , 'build' )
2311+ copied_pybuilddir = os .path .join (copied_build_dir , pybuilddir )
2312+ os .makedirs (os .path .dirname (copied_pybuilddir ))
2313+ os .symlink (os .path .join (srcdir , 'Lib' ),
2314+ os .path .join (copied_root , 'Lib' ))
2315+ os .symlink (source_ext_dir , copied_pybuilddir )
2316+ shutil .copy2 (pybuilddir_txt ,
2317+ os .path .join (copied_build_dir , 'pybuilddir.txt' ))
2318+
2319+ copied_python = os .path .join (copied_build_dir ,
2320+ os .path .basename (sys .executable ))
2321+ shutil .copy2 (sys .executable , copied_python )
2322+
2323+ self ._run_remote_exec_with_deleted_mapping (
2324+ copied_python , python_args = ['-S' ],
2325+ python_executable = copied_python )
2326+
22372327 def test_remote_exec_in_process_without_debug_fails_envvar (self ):
22382328 """Test remote exec in a process without remote debugging enabled"""
22392329 script = os_helper .TESTFN + '_remote.py'
0 commit comments