11import  faulthandler 
22import  json 
3- import  os 
3+ import  os . path 
44import  queue 
55import  shlex 
66import  signal 
1717from  test .libregrtest .cmdline  import  Namespace 
1818from  test .libregrtest .main  import  Regrtest 
1919from  test .libregrtest .runtest  import  (
20-     runtest , is_failed , TestResult , Interrupted , Timeout , ChildError , PROGRESS_MIN_TIME )
20+     runtest , is_failed , TestResult , Interrupted , Timeout , ChildError ,
21+     PROGRESS_MIN_TIME , Passed , EnvChanged )
2122from  test .libregrtest .setup  import  setup_tests 
2223from  test .libregrtest .utils  import  format_duration , print_warning 
2324
@@ -52,7 +53,7 @@ def parse_worker_args(worker_args) -> tuple[Namespace, str]:
5253    return  (ns , test_name )
5354
5455
55- def  run_test_in_subprocess (testname : str , ns : Namespace ) ->  subprocess .Popen :
56+ def  run_test_in_subprocess (testname : str , ns : Namespace ,  tmp_dir :  str ) ->  subprocess .Popen :
5657    ns_dict  =  vars (ns )
5758    worker_args  =  (ns_dict , testname )
5859    worker_args  =  json .dumps (worker_args )
@@ -66,10 +67,14 @@ def run_test_in_subprocess(testname: str, ns: Namespace) -> subprocess.Popen:
6667           '-m' , 'test.regrtest' ,
6768           '--worker-args' , worker_args ]
6869
70+     env  =  dict (os .environ )
71+     env ['TMPDIR' ] =  tmp_dir 
72+     env ['TEMPDIR' ] =  tmp_dir 
73+ 
6974    # Running the child from the same working directory as regrtest's original 
7075    # invocation ensures that TEMPDIR for the child is the same when 
7176    # sysconfig.is_python_build() is true. See issue 15300. 
72-     kw  =  {}
77+     kw  =  {'env' :  env }
7378    if  USE_PROCESS_GROUP :
7479        kw ['start_new_session' ] =  True 
7580    return  subprocess .Popen (cmd ,
@@ -206,12 +211,12 @@ def mp_result_error(
206211        test_result .duration_sec  =  time .monotonic () -  self .start_time 
207212        return  MultiprocessResult (test_result , stdout , err_msg )
208213
209-     def  _run_process (self , test_name : str ) ->  tuple [int , str , str ]:
214+     def  _run_process (self , test_name : str ,  tmp_dir :  str ) ->  tuple [int , str , str ]:
210215        self .start_time  =  time .monotonic ()
211216
212217        self .current_test_name  =  test_name 
213218        try :
214-             popen  =  run_test_in_subprocess (test_name , self .ns )
219+             popen  =  run_test_in_subprocess (test_name , self .ns ,  tmp_dir )
215220
216221            self ._killed  =  False 
217222            self ._popen  =  popen 
@@ -266,7 +271,17 @@ def _run_process(self, test_name: str) -> tuple[int, str, str]:
266271            self .current_test_name  =  None 
267272
268273    def  _runtest (self , test_name : str ) ->  MultiprocessResult :
269-         retcode , stdout  =  self ._run_process (test_name )
274+         # gh-93353: Check for leaked temporary files in the parent process, 
275+         # since the deletion of temporary files can happen late during 
276+         # Python finalization: too late for libregrtest. 
277+         tmp_dir  =  os .getcwd () +  '_tmpdir' 
278+         tmp_dir  =  os .path .abspath (tmp_dir )
279+         try :
280+             os .mkdir (tmp_dir )
281+             retcode , stdout  =  self ._run_process (test_name , tmp_dir )
282+         finally :
283+             tmp_files  =  os .listdir (tmp_dir )
284+             os_helper .rmtree (tmp_dir )
270285
271286        if  retcode  is  None :
272287            return  self .mp_result_error (Timeout (test_name ), stdout )
@@ -289,6 +304,14 @@ def _runtest(self, test_name: str) -> MultiprocessResult:
289304        if  err_msg  is  not None :
290305            return  self .mp_result_error (ChildError (test_name ), stdout , err_msg )
291306
307+         if  tmp_files :
308+             msg  =  (f'\n \n ' 
309+                    f'Warning -- Test leaked temporary files ({ len (tmp_files )}  
310+                    f'{ ", " .join (sorted (tmp_files ))}  )
311+             stdout  +=  msg 
312+             if  isinstance (result , Passed ):
313+                 result  =  EnvChanged .from_passed (result )
314+ 
292315        return  MultiprocessResult (result , stdout , err_msg )
293316
294317    def  run (self ) ->  None :
0 commit comments