15
15
from test .libregrtest .logger import Logger
16
16
from test .libregrtest .result import State
17
17
from test .libregrtest .runtests import RunTests , HuntRefleak
18
- from test .libregrtest .setup import setup_tests , setup_test_dir
18
+ from test .libregrtest .setup import setup_process , setup_test_dir
19
19
from test .libregrtest .single import run_single_test , PROGRESS_MIN_TIME
20
20
from test .libregrtest .pgo import setup_pgo_tests
21
21
from test .libregrtest .results import TestResults
22
22
from test .libregrtest .utils import (
23
- StrPath , StrJSON , TestName , TestList , FilterTuple ,
23
+ StrPath , StrJSON , TestName , TestList , TestTuple , FilterTuple ,
24
24
strip_py_suffix , count , format_duration ,
25
25
printlist , get_build_info , get_temp_dir , get_work_dir , exit_timeout ,
26
26
abs_module_name )
@@ -51,7 +51,7 @@ class Regrtest:
51
51
"""
52
52
def __init__ (self , ns : Namespace ):
53
53
# Log verbosity
54
- self .verbose : bool = ns .verbose
54
+ self .verbose : int = int ( ns .verbose )
55
55
self .quiet : bool = ns .quiet
56
56
self .pgo : bool = ns .pgo
57
57
self .pgo_extended : bool = ns .pgo_extended
@@ -122,8 +122,6 @@ def __init__(self, ns: Namespace):
122
122
self .tmp_dir : StrPath | None = ns .tempdir
123
123
124
124
# tests
125
- self .tests = []
126
- self .selected : TestList = []
127
125
self .first_runtests : RunTests | None = None
128
126
129
127
# used by --slowest
@@ -140,18 +138,18 @@ def __init__(self, ns: Namespace):
140
138
def log (self , line = '' ):
141
139
self .logger .log (line )
142
140
143
- def find_tests (self ) :
141
+ def find_tests (self , tests : TestList | None = None ) -> tuple [ TestTuple , TestList | None ] :
144
142
if self .single_test_run :
145
143
self .next_single_filename = os .path .join (self .tmp_dir , 'pynexttest' )
146
144
try :
147
145
with open (self .next_single_filename , 'r' ) as fp :
148
146
next_test = fp .read ().strip ()
149
- self . tests = [next_test ]
147
+ tests = [next_test ]
150
148
except OSError :
151
149
pass
152
150
153
151
if self .fromfile :
154
- self . tests = []
152
+ tests = []
155
153
# regex to match 'test_builtin' in line:
156
154
# '0:00:00 [ 4/400] test_builtin -- test_dict took 1 sec'
157
155
regex = re .compile (r'\btest_[a-zA-Z0-9_]+\b' )
@@ -161,9 +159,9 @@ def find_tests(self):
161
159
line = line .strip ()
162
160
match = regex .search (line )
163
161
if match is not None :
164
- self . tests .append (match .group ())
162
+ tests .append (match .group ())
165
163
166
- strip_py_suffix (self . tests )
164
+ strip_py_suffix (tests )
167
165
168
166
if self .pgo :
169
167
# add default PGO tests if no tests are specified
@@ -179,26 +177,26 @@ def find_tests(self):
179
177
exclude = exclude_tests )
180
178
181
179
if not self .fromfile :
182
- self . selected = self . tests or self .cmdline_args
183
- if self . selected :
184
- self . selected = split_test_packages (self . selected )
180
+ selected = tests or self .cmdline_args
181
+ if selected :
182
+ selected = split_test_packages (selected )
185
183
else :
186
- self . selected = alltests
184
+ selected = alltests
187
185
else :
188
- self . selected = self . tests
186
+ selected = tests
189
187
190
188
if self .single_test_run :
191
- self . selected = self . selected [:1 ]
189
+ selected = selected [:1 ]
192
190
try :
193
- pos = alltests .index (self . selected [0 ])
191
+ pos = alltests .index (selected [0 ])
194
192
self .next_single_test = alltests [pos + 1 ]
195
193
except IndexError :
196
194
pass
197
195
198
196
# Remove all the selected tests that precede start if it's set.
199
197
if self .starting_test :
200
198
try :
201
- del self . selected [:self . selected .index (self .starting_test )]
199
+ del selected [:selected .index (self .starting_test )]
202
200
except ValueError :
203
201
print (f"Cannot find starting test: { self .starting_test } " )
204
202
sys .exit (1 )
@@ -207,10 +205,12 @@ def find_tests(self):
207
205
if self .random_seed is None :
208
206
self .random_seed = random .randrange (100_000_000 )
209
207
random .seed (self .random_seed )
210
- random .shuffle (self .selected )
208
+ random .shuffle (selected )
209
+
210
+ return (tuple (selected ), tests )
211
211
212
212
@staticmethod
213
- def list_tests (tests : TestList ):
213
+ def list_tests (tests : TestTuple ):
214
214
for name in tests :
215
215
print (name )
216
216
@@ -224,12 +224,12 @@ def _list_cases(self, suite):
224
224
if support .match_test (test ):
225
225
print (test .id ())
226
226
227
- def list_cases (self ):
227
+ def list_cases (self , tests : TestTuple ):
228
228
support .verbose = False
229
229
support .set_match_tests (self .match_tests , self .ignore_tests )
230
230
231
231
skipped = []
232
- for test_name in self . selected :
232
+ for test_name in tests :
233
233
module_name = abs_module_name (test_name , self .test_dir )
234
234
try :
235
235
suite = unittest .defaultTestLoader .loadTestsFromName (module_name )
@@ -247,6 +247,10 @@ def list_cases(self):
247
247
def _rerun_failed_tests (self , runtests : RunTests ):
248
248
# Configure the runner to re-run tests
249
249
if self .num_workers == 0 :
250
+ # Always run tests in fresh processes to have more deterministic
251
+ # initial state. Don't re-run tests in parallel but limit to a
252
+ # single worker process to have side effects (on the system load
253
+ # and timings) between tests.
250
254
self .num_workers = 1
251
255
252
256
tests , match_tests_dict = self .results .prepare_rerun ()
@@ -294,7 +298,8 @@ def display_result(self, runtests):
294
298
print ()
295
299
print (f"== Tests result: { state } ==" )
296
300
297
- self .results .display_result (self .selected , self .quiet , self .print_slowest )
301
+ self .results .display_result (runtests .tests ,
302
+ self .quiet , self .print_slowest )
298
303
299
304
def run_test (self , test_name : TestName , runtests : RunTests , tracer ):
300
305
if tracer is not None :
@@ -404,7 +409,7 @@ def get_state(self):
404
409
return state
405
410
406
411
def _run_tests_mp (self , runtests : RunTests , num_workers : int ) -> None :
407
- from test .libregrtest .runtest_mp import RunWorkers
412
+ from test .libregrtest .run_workers import RunWorkers
408
413
RunWorkers (num_workers , runtests , self .logger , self .results ).run ()
409
414
410
415
def finalize_tests (self , tracer ):
@@ -454,39 +459,9 @@ def cleanup_temp_dir(tmp_dir: StrPath):
454
459
print ("Remove file: %s" % name )
455
460
os_helper .unlink (name )
456
461
457
- def main (self , tests : TestList | None = None ):
458
- if self .junit_filename and not os .path .isabs (self .junit_filename ):
459
- self .junit_filename = os .path .abspath (self .junit_filename )
460
-
461
- self .tests = tests
462
-
463
- strip_py_suffix (self .cmdline_args )
464
-
465
- self .tmp_dir = get_temp_dir (self .tmp_dir )
466
-
467
- if self .want_cleanup :
468
- self .cleanup_temp_dir (self .tmp_dir )
469
- sys .exit (0 )
470
-
471
- os .makedirs (self .tmp_dir , exist_ok = True )
472
- work_dir = get_work_dir (parent_dir = self .tmp_dir )
473
-
474
- with exit_timeout ():
475
- # Run the tests in a context manager that temporarily changes the
476
- # CWD to a temporary and writable directory. If it's not possible
477
- # to create or change the CWD, the original CWD will be used.
478
- # The original CWD is available from os_helper.SAVEDCWD.
479
- with os_helper .temp_cwd (work_dir , quiet = True ):
480
- # When using multiprocessing, worker processes will use
481
- # work_dir as their parent temporary directory. So when the
482
- # main process exit, it removes also subdirectories of worker
483
- # processes.
484
-
485
- self ._main ()
486
-
487
- def create_run_tests (self ):
462
+ def create_run_tests (self , tests : TestTuple ):
488
463
return RunTests (
489
- tuple ( self . selected ) ,
464
+ tests ,
490
465
fail_fast = self .fail_fast ,
491
466
match_tests = self .match_tests ,
492
467
ignore_tests = self .ignore_tests ,
@@ -506,7 +481,7 @@ def create_run_tests(self):
506
481
python_cmd = self .python_cmd ,
507
482
)
508
483
509
- def run_tests (self ) -> int :
484
+ def _run_tests (self , selected : TestTuple , tests : TestList | None ) -> int :
510
485
if self .hunt_refleak and self .hunt_refleak .warmups < 3 :
511
486
msg = ("WARNING: Running tests with --huntrleaks/-R and "
512
487
"less than 3 warmup repetitions can give false positives!" )
@@ -520,17 +495,17 @@ def run_tests(self) -> int:
520
495
# For a partial run, we do not need to clutter the output.
521
496
if (self .want_header
522
497
or not (self .pgo or self .quiet or self .single_test_run
523
- or self . tests or self .cmdline_args )):
498
+ or tests or self .cmdline_args )):
524
499
self .display_header ()
525
500
526
501
if self .randomize :
527
502
print ("Using random seed" , self .random_seed )
528
503
529
- runtests = self .create_run_tests ()
504
+ runtests = self .create_run_tests (selected )
530
505
self .first_runtests = runtests
531
506
self .logger .set_tests (runtests )
532
507
533
- setup_tests ( runtests )
508
+ setup_process ( )
534
509
535
510
self .logger .start_load_tracker ()
536
511
try :
@@ -553,20 +528,48 @@ def run_tests(self) -> int:
553
528
return self .results .get_exitcode (self .fail_env_changed ,
554
529
self .fail_rerun )
555
530
556
- def _main (self ):
531
+ def run_tests (self , selected : TestTuple , tests : TestList | None ) -> int :
532
+ os .makedirs (self .tmp_dir , exist_ok = True )
533
+ work_dir = get_work_dir (parent_dir = self .tmp_dir )
534
+
535
+ # Put a timeout on Python exit
536
+ with exit_timeout ():
537
+ # Run the tests in a context manager that temporarily changes the
538
+ # CWD to a temporary and writable directory. If it's not possible
539
+ # to create or change the CWD, the original CWD will be used.
540
+ # The original CWD is available from os_helper.SAVEDCWD.
541
+ with os_helper .temp_cwd (work_dir , quiet = True ):
542
+ # When using multiprocessing, worker processes will use
543
+ # work_dir as their parent temporary directory. So when the
544
+ # main process exit, it removes also subdirectories of worker
545
+ # processes.
546
+ return self ._run_tests (selected , tests )
547
+
548
+ def main (self , tests : TestList | None = None ):
549
+ if self .junit_filename and not os .path .isabs (self .junit_filename ):
550
+ self .junit_filename = os .path .abspath (self .junit_filename )
551
+
552
+ strip_py_suffix (self .cmdline_args )
553
+
554
+ self .tmp_dir = get_temp_dir (self .tmp_dir )
555
+
556
+ if self .want_cleanup :
557
+ self .cleanup_temp_dir (self .tmp_dir )
558
+ sys .exit (0 )
559
+
557
560
if self .want_wait :
558
561
input ("Press any key to continue..." )
559
562
560
563
setup_test_dir (self .test_dir )
561
- self .find_tests ()
564
+ selected , tests = self .find_tests (tests )
562
565
563
566
exitcode = 0
564
567
if self .want_list_tests :
565
- self .list_tests (self . selected )
568
+ self .list_tests (selected )
566
569
elif self .want_list_cases :
567
- self .list_cases ()
570
+ self .list_cases (selected )
568
571
else :
569
- exitcode = self .run_tests ()
572
+ exitcode = self .run_tests (selected , tests )
570
573
571
574
sys .exit (exitcode )
572
575
0 commit comments