@@ -35,17 +35,27 @@ def supports_trampoline_profiling():
3535
3636
3737class TestPerfTrampoline (unittest .TestCase ):
38- def setUp (self ):
39- super ().setUp ()
40- self .perf_files = set (pathlib .Path ("/tmp/" ).glob ("perf-*.map" ))
41-
42- def tearDown (self ) -> None :
43- super ().tearDown ()
44- files_to_delete = (
45- set (pathlib .Path ("/tmp/" ).glob ("perf-*.map" )) - self .perf_files
46- )
47- for file in files_to_delete :
48- file .unlink ()
38+ #
39+ # We've removed the buggy setUp and tearDown methods.
40+ # They caused race conditions by scanning /tmp for `perf-*.map` files.
41+ # Instead, we now use `addCleanup()` inside each test to delete
42+ # the *specific* files we know that test created.
43+ #
44+
45+ def _cleanup_perf_map (self , pid ):
46+ """
47+ Helper to safely delete a specific perf map file.
48+ We register this with addCleanup() to make sure it runs
49+ even if the test assertions fail.
50+ """
51+ perf_map = pathlib .Path (f"/tmp/perf-{ pid } .map" )
52+ try :
53+ if perf_map .exists ():
54+ perf_map .unlink ()
55+ except OSError :
56+ # Surpress errors, e.g., file already removed or
57+ # another kind of race condition.
58+ pass
4959
5060 @unittest .skipIf (support .check_bolt_optimized (), "fails on BOLT instrumented binaries" )
5161 def test_trampoline_works (self ):
@@ -64,13 +74,21 @@ def baz():
6474 with temp_dir () as script_dir :
6575 script = make_script (script_dir , "perftest" , code )
6676 env = {** os .environ , "PYTHON_JIT" : "0" }
67- with subprocess .Popen (
77+
78+ # Start the subprocess...
79+ process = subprocess .Popen (
6880 [sys .executable , "-Xperf" , script ],
6981 text = True ,
7082 stderr = subprocess .PIPE ,
7183 stdout = subprocess .PIPE ,
7284 env = env ,
73- ) as process :
85+ )
86+
87+ # ...and immediately register a cleanup task for its PID.
88+ # This is guaranteed to run after the test.
89+ self .addCleanup (self ._cleanup_perf_map , process .pid )
90+
91+ with process :
7492 stdout , stderr = process .communicate ()
7593
7694 self .assertEqual (stderr , "" )
@@ -116,9 +134,11 @@ def baz_fork():
116134 def foo():
117135 pid = os.fork()
118136 if pid == 0:
137+ # We're in the child process
119138 print(os.getpid())
120139 baz_fork()
121140 else:
141+ # We're in the parent process
122142 _, status = os.waitpid(-1, 0)
123143 sys.exit(status)
124144
@@ -140,11 +160,19 @@ def baz():
140160 stdout = subprocess .PIPE ,
141161 env = env ,
142162 ) as process :
163+ # Register cleanup for the PARENT process
164+ self .addCleanup (self ._cleanup_perf_map , process .pid )
143165 stdout , stderr = process .communicate ()
144166
145167 self .assertEqual (process .returncode , 0 )
146168 self .assertEqual (stderr , "" )
169+
170+ # The child process printed its PID to stdout
147171 child_pid = int (stdout .strip ())
172+
173+ # Register cleanup for the CHILD process
174+ self .addCleanup (self ._cleanup_perf_map , child_pid )
175+
148176 perf_file = pathlib .Path (f"/tmp/perf-{ process .pid } .map" )
149177 perf_child_file = pathlib .Path (f"/tmp/perf-{ child_pid } .map" )
150178 self .assertTrue (perf_file .exists ())
@@ -199,6 +227,8 @@ def baz():
199227 stdout = subprocess .PIPE ,
200228 env = env ,
201229 ) as process :
230+ # Register cleanup inside the loop for each process created
231+ self .addCleanup (self ._cleanup_perf_map , process .pid )
202232 stdout , stderr = process .communicate ()
203233
204234 self .assertEqual (stderr , "" )
@@ -308,6 +338,10 @@ def perf_command_works():
308338
309339
310340def run_perf (cwd , * args , use_jit = False , ** env_vars ):
341+ # This helper function runs perf and stores its output files *inside*
342+ # the 'cwd' (which is a temp_dir()). This is good! It means
343+ # those files (`perf_output.perf`, `jit_output.dump`)
344+ # are automatically cleaned up when the temp_dir() context manager exits.
311345 env = os .environ .copy ()
312346 if env_vars :
313347 env .update (env_vars )
@@ -434,23 +468,30 @@ def baz(n):
434468 "Unwinding is unreliable with frame pointers" ,
435469)
436470class TestPerfProfiler (unittest .TestCase , TestPerfProfilerMixin ):
471+ #
472+ # Just like in TestPerfTrampoline, we remove the racy
473+ # setUp/tearDown methods.
474+ #
475+
476+ def _cleanup_perf_map (self , pid ):
477+ """
478+ Helper to safely delete a specific perf map file.
479+ We need this for test_pre_fork_compile.
480+ """
481+ perf_map = pathlib .Path (f"/tmp/perf-{ pid } .map" )
482+ try :
483+ if perf_map .exists ():
484+ perf_map .unlink ()
485+ except OSError :
486+ pass
487+
437488 def run_perf (self , script_dir , script , activate_trampoline = True ):
489+ # The run_perf helper handles its own temp files inside script_dir
490+ # so we don't need to add cleanups for it here.
438491 if activate_trampoline :
439492 return run_perf (script_dir , sys .executable , "-Xperf" , script )
440493 return run_perf (script_dir , sys .executable , script )
441494
442- def setUp (self ):
443- super ().setUp ()
444- self .perf_files = set (pathlib .Path ("/tmp/" ).glob ("perf-*.map" ))
445-
446- def tearDown (self ) -> None :
447- super ().tearDown ()
448- files_to_delete = (
449- set (pathlib .Path ("/tmp/" ).glob ("perf-*.map" )) - self .perf_files
450- )
451- for file in files_to_delete :
452- file .unlink ()
453-
454495 def test_pre_fork_compile (self ):
455496 code = """if 1:
456497 import sys
@@ -499,11 +540,17 @@ def compile_trampolines_for_all_functions():
499540 stdout = subprocess .PIPE ,
500541 env = env ,
501542 ) as process :
543+ # This test also creates map files, so we clean them up
544+ # just like in TestPerfTrampoline.
545+ self .addCleanup (self ._cleanup_perf_map , process .pid )
502546 stdout , stderr = process .communicate ()
503547
504548 self .assertEqual (process .returncode , 0 )
505549 self .assertNotIn ("Error:" , stderr )
550+
506551 child_pid = int (stdout .strip ())
552+ self .addCleanup (self ._cleanup_perf_map , child_pid )
553+
507554 perf_file = pathlib .Path (f"/tmp/perf-{ process .pid } .map" )
508555 perf_child_file = pathlib .Path (f"/tmp/perf-{ child_pid } .map" )
509556 self .assertTrue (perf_file .exists ())
@@ -550,25 +597,24 @@ def _is_perf_version_at_least(major, minor):
550597 _is_perf_version_at_least (6 , 6 ), "perf command may not work due to a perf bug"
551598)
552599class TestPerfProfilerWithDwarf (unittest .TestCase , TestPerfProfilerMixin ):
600+ #
601+ # We've removed the racy setUp/tearDown methods from here, too.
602+ # The `run_perf` function (with use_jit=True) already creates its
603+ # .dump and .so files inside the `temp_dir()`, which gets
604+ # cleaned up automatically. The old setUp/tearDown were
605+ # trying to clean up /tmp, which was buggy and unreliable.
606+ #
607+
553608 def run_perf (self , script_dir , script , activate_trampoline = True ):
554609 if activate_trampoline :
555610 return run_perf (
556611 script_dir , sys .executable , "-Xperf_jit" , script , use_jit = True
557612 )
558613 return run_perf (script_dir , sys .executable , script , use_jit = True )
559614
560- def setUp (self ):
561- super ().setUp ()
562- self .perf_files = set (pathlib .Path ("/tmp/" ).glob ("jit*.dump" ))
563- self .perf_files |= set (pathlib .Path ("/tmp/" ).glob ("jitted-*.so" ))
564-
565- def tearDown (self ) -> None :
566- super ().tearDown ()
567- files_to_delete = set (pathlib .Path ("/tmp/" ).glob ("jit*.dump" ))
568- files_to_delete |= set (pathlib .Path ("/tmp/" ).glob ("jitted-*.so" ))
569- files_to_delete = files_to_delete - self .perf_files
570- for file in files_to_delete :
571- file .unlink ()
615+ #
616+ # No setUp or tearDown needed!
617+ #
572618
573619
574620if __name__ == "__main__" :
0 commit comments