4646
4747@dataclasses .dataclass (slots = True )
4848class WorkerJob :
49- test_name : str
49+ runtests : RunTests
5050 namespace : Namespace
51- rerun : bool = False
5251 match_tests : FilterTuple | None = None
5352
5453
@@ -70,6 +69,7 @@ def default(self, o: Any) -> dict[str, Any]:
7069def _decode_worker_job (d : dict [str , Any ]) -> WorkerJob | dict [str , Any ]:
7170 if "__worker_job__" in d :
7271 d .pop ('__worker_job__' )
72+ d ['runtests' ] = RunTests (** d ['runtests' ])
7373 return WorkerJob (** d )
7474 if "__namespace__" in d :
7575 d .pop ('__namespace__' )
@@ -78,17 +78,16 @@ def _decode_worker_job(d: dict[str, Any]) -> WorkerJob | dict[str, Any]:
7878 return d
7979
8080
81- def _parse_worker_args (worker_json : str ) -> tuple [Namespace , str ]:
82- return json .loads (worker_json ,
83- object_hook = _decode_worker_job )
81+ def _parse_worker_json (worker_json : str ) -> tuple [Namespace , str ]:
82+ return json .loads (worker_json , object_hook = _decode_worker_job )
8483
8584
86- def run_test_in_subprocess (worker_job : WorkerJob ,
87- output_file : TextIO ,
88- tmp_dir : str | None = None ) -> subprocess .Popen :
85+ def create_worker_process (worker_job : WorkerJob ,
86+ output_file : TextIO ,
87+ tmp_dir : str | None = None ) -> subprocess .Popen :
8988 ns = worker_job .namespace
9089 python = ns .python
91- worker_args = json .dumps (worker_job , cls = _EncodeWorkerJob )
90+ worker_json = json .dumps (worker_job , cls = _EncodeWorkerJob )
9291
9392 if python is not None :
9493 executable = python
@@ -97,7 +96,7 @@ def run_test_in_subprocess(worker_job: WorkerJob,
9796 cmd = [* executable , * support .args_from_interpreter_flags (),
9897 '-u' , # Unbuffered stdout and stderr
9998 '-m' , 'test.regrtest' ,
100- '--worker-args ' , worker_args ]
99+ '--worker-json ' , worker_json ]
101100
102101 env = dict (os .environ )
103102 if tmp_dir is not None :
@@ -122,31 +121,32 @@ def run_test_in_subprocess(worker_job: WorkerJob,
122121 return subprocess .Popen (cmd , ** kw )
123122
124123
125- def run_tests_worker (worker_json : str ) -> NoReturn :
126- worker_job = _parse_worker_args (worker_json )
124+ def worker_process (worker_json : str ) -> NoReturn :
125+ worker_job = _parse_worker_json (worker_json )
126+ runtests = worker_job .runtests
127127 ns = worker_job .namespace
128- test_name = worker_job .test_name
129- rerun = worker_job .rerun
130- match_tests = worker_job .match_tests
128+ test_name = runtests .tests [0 ]
129+ match_tests : FilterTuple | None = worker_job .match_tests
131130
132131 setup_tests (ns )
133132
134- if rerun :
133+ if runtests . rerun :
135134 if match_tests :
136135 matching = "matching: " + ", " .join (match_tests )
137136 print (f"Re-running { test_name } in verbose mode ({ matching } )" , flush = True )
138137 else :
139138 print (f"Re-running { test_name } in verbose mode" , flush = True )
140139 ns .verbose = True
141140
142- if match_tests is not None :
143- ns .match_tests = match_tests
141+ if match_tests is not None :
142+ ns .match_tests = match_tests
144143
145144 result = runtest (ns , test_name )
146145 print () # Force a newline (just in case)
147146
148147 # Serialize TestResult as dict in JSON
149- print (json .dumps (result , cls = EncodeTestResult ), flush = True )
148+ json .dump (result , sys .stdout , cls = EncodeTestResult )
149+ sys .stdout .flush ()
150150 sys .exit (0 )
151151
152152
@@ -173,7 +173,8 @@ def stop(self):
173173 self .tests_iter = None
174174
175175
176- class MultiprocessResult (NamedTuple ):
176+ @dataclasses .dataclass (slots = True , frozen = True )
177+ class MultiprocessResult :
177178 result : TestResult
178179 # bpo-45410: stderr is written into stdout to keep messages order
179180 worker_stdout : str | None = None
@@ -198,7 +199,6 @@ def __init__(self, worker_id: int, runner: "MultiprocessTestRunner") -> None:
198199 self .ns = runner .ns
199200 self .timeout = runner .worker_timeout
200201 self .regrtest = runner .regrtest
201- self .rerun = runner .rerun
202202 self .current_test_name = None
203203 self .start_time = None
204204 self ._popen = None
@@ -264,9 +264,8 @@ def mp_result_error(
264264
265265 def _run_process (self , worker_job , output_file : TextIO ,
266266 tmp_dir : str | None = None ) -> int :
267- self .current_test_name = worker_job .test_name
268267 try :
269- popen = run_test_in_subprocess (worker_job , output_file , tmp_dir )
268+ popen = create_worker_process (worker_job , output_file , tmp_dir )
270269
271270 self ._killed = False
272271 self ._popen = popen
@@ -316,6 +315,8 @@ def _run_process(self, worker_job, output_file: TextIO,
316315 self .current_test_name = None
317316
318317 def _runtest (self , test_name : str ) -> MultiprocessResult :
318+ self .current_test_name = test_name
319+
319320 if sys .platform == 'win32' :
320321 # gh-95027: When stdout is not a TTY, Python uses the ANSI code
321322 # page for the sys.stdout encoding. If the main process runs in a
@@ -324,15 +325,20 @@ def _runtest(self, test_name: str) -> MultiprocessResult:
324325 else :
325326 encoding = sys .stdout .encoding
326327
327- match_tests = self .runtests .get_match_tests (test_name )
328+ tests = (test_name ,)
329+ if self .runtests .rerun :
330+ match_tests = self .runtests .get_match_tests (test_name )
331+ else :
332+ match_tests = None
333+ worker_runtests = self .runtests .copy (tests = tests )
334+ worker_job = WorkerJob (
335+ worker_runtests ,
336+ namespace = self .ns ,
337+ match_tests = match_tests )
328338
329339 # gh-94026: Write stdout+stderr to a tempfile as workaround for
330340 # non-blocking pipes on Emscripten with NodeJS.
331341 with tempfile .TemporaryFile ('w+' , encoding = encoding ) as stdout_file :
332- worker_job = WorkerJob (test_name ,
333- namespace = self .ns ,
334- rerun = self .rerun ,
335- match_tests = match_tests )
336342 # gh-93353: Check for leaked temporary files in the parent process,
337343 # since the deletion of temporary files can happen late during
338344 # Python finalization: too late for libregrtest.
0 commit comments