77import time
88import trace
99
10- from test import support
11- from test .support import os_helper , MS_WINDOWS
10+ from test .support import os_helper , MS_WINDOWS , flush_std_streams
1211
1312from .cmdline import _parse_args , Namespace
1413from .findtests import findtests , split_test_packages , list_cases
@@ -73,6 +72,7 @@ def __init__(self, ns: Namespace, _add_python_opts: bool = False):
7372 self .want_cleanup : bool = ns .cleanup
7473 self .want_rerun : bool = ns .rerun
7574 self .want_run_leaks : bool = ns .runleaks
75+ self .want_bisect : bool = ns .bisect
7676
7777 self .ci_mode : bool = (ns .fast_ci or ns .slow_ci )
7878 self .want_add_python_opts : bool = (_add_python_opts
@@ -273,6 +273,55 @@ def rerun_failed_tests(self, runtests: RunTests):
273273
274274 self .display_result (rerun_runtests )
275275
276+ def _run_bisect (self , runtests : RunTests , test : str , progress : str ) -> bool :
277+ print ()
278+ title = f"Bisect { test } "
279+ if progress :
280+ title = f"{ title } ({ progress } )"
281+ print (title )
282+ print ("#" * len (title ))
283+ print ()
284+
285+ cmd = runtests .create_python_cmd ()
286+ cmd .extend ([
287+ "-u" , "-m" , "test.bisect_cmd" ,
288+ # Limit to 25 iterations (instead of 100) to not abuse CI resources
289+ "--max-iter" , "25" ,
290+ "-v" ,
291+ # runtests.match_tests is not used (yet) for bisect_cmd -i arg
292+ ])
293+ cmd .extend (runtests .bisect_cmd_args ())
294+ cmd .append (test )
295+ print ("+" , shlex .join (cmd ), flush = True )
296+
297+ flush_std_streams ()
298+
299+ import subprocess
300+ proc = subprocess .run (cmd , timeout = runtests .timeout )
301+ exitcode = proc .returncode
302+
303+ title = f"{ title } : exit code { exitcode } "
304+ print (title )
305+ print ("#" * len (title ))
306+ print (flush = True )
307+
308+ if exitcode :
309+ print (f"Bisect failed with exit code { exitcode } " )
310+ return False
311+
312+ return True
313+
314+ def run_bisect (self , runtests : RunTests ) -> None :
315+ tests , _ = self .results .prepare_rerun (clear = False )
316+
317+ for index , name in enumerate (tests , 1 ):
318+ if len (tests ) > 1 :
319+ progress = f"{ index } /{ len (tests )} "
320+ else :
321+ progress = ""
322+ if not self ._run_bisect (runtests , name , progress ):
323+ return
324+
276325 def display_result (self , runtests ):
277326 # If running the test suite for PGO then no one cares about results.
278327 if runtests .pgo :
@@ -466,7 +515,7 @@ def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int:
466515
467516 setup_process ()
468517
469- if self .hunt_refleak and not self .num_workers :
518+ if ( runtests .hunt_refleak is not None ) and ( not self .num_workers ) :
470519 # gh-109739: WindowsLoadTracker thread interfers with refleak check
471520 use_load_tracker = False
472521 else :
@@ -486,6 +535,9 @@ def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int:
486535
487536 if self .want_rerun and self .results .need_rerun ():
488537 self .rerun_failed_tests (runtests )
538+
539+ if self .want_bisect and self .results .need_rerun ():
540+ self .run_bisect (runtests )
489541 finally :
490542 if use_load_tracker :
491543 self .logger .stop_load_tracker ()
0 commit comments