1import faulthandler 2import locale 3import os 4import platform 5import random 6import re 7import sys 8import sysconfig 9import tempfile 10import time 11import unittest 12from test.libregrtest.cmdline import _parse_args 13from test.libregrtest.runtest import ( 14 findtests, runtest, get_abs_module, is_failed, 15 STDTESTS, NOTTESTS, PROGRESS_MIN_TIME, 16 Passed, Failed, EnvChanged, Skipped, ResourceDenied, Interrupted, 17 ChildError, DidNotRun) 18from test.libregrtest.setup import setup_tests 19from test.libregrtest.pgo import setup_pgo_tests 20from test.libregrtest.utils import removepy, count, format_duration, printlist 21from test import support 22from test.support import os_helper 23from test.support import threading_helper 24 25 26# bpo-38203: Maximum delay in seconds to exit Python (call Py_Finalize()). 27# Used to protect against threading._shutdown() hang. 28# Must be smaller than buildbot "1200 seconds without output" limit. 29EXIT_TIMEOUT = 120.0 30 31# gh-90681: When rerunning tests, we might need to rerun the whole 32# class or module suite if some its life-cycle hooks fail. 33# Test level hooks are not affected. 34_TEST_LIFECYCLE_HOOKS = frozenset(( 35 'setUpClass', 'tearDownClass', 36 'setUpModule', 'tearDownModule', 37)) 38 39EXITCODE_BAD_TEST = 2 40EXITCODE_INTERRUPTED = 130 41EXITCODE_ENV_CHANGED = 3 42EXITCODE_NO_TESTS_RAN = 4 43 44 45class Regrtest: 46 """Execute a test suite. 47 48 This also parses command-line options and modifies its behavior 49 accordingly. 50 51 tests -- a list of strings containing test names (optional) 52 testdir -- the directory in which to look for tests (optional) 53 54 Users other than the Python test suite will certainly want to 55 specify testdir; if it's omitted, the directory containing the 56 Python test suite is searched for. 57 58 If the tests argument is omitted, the tests listed on the 59 command-line will be used. If that's empty, too, then all *.py 60 files beginning with test_ will be used. 61 62 The other default arguments (verbose, quiet, exclude, 63 single, randomize, use_resources, trace, coverdir, 64 print_slow, and random_seed) allow programmers calling main() 65 directly to set the values that would normally be set by flags 66 on the command line. 67 """ 68 def __init__(self): 69 # Namespace of command line options 70 self.ns = None 71 72 # tests 73 self.tests = [] 74 self.selected = [] 75 76 # test results 77 self.good = [] 78 self.bad = [] 79 self.skipped = [] 80 self.resource_denieds = [] 81 self.environment_changed = [] 82 self.run_no_tests = [] 83 self.need_rerun = [] 84 self.rerun = [] 85 self.first_result = None 86 self.interrupted = False 87 88 # used by --slow 89 self.test_times = [] 90 91 # used by --coverage, trace.Trace instance 92 self.tracer = None 93 94 # used to display the progress bar "[ 3/100]" 95 self.start_time = time.monotonic() 96 self.test_count = '' 97 self.test_count_width = 1 98 99 # used by --single 100 self.next_single_test = None 101 self.next_single_filename = None 102 103 # used by --junit-xml 104 self.testsuite_xml = None 105 106 # misc 107 self.win_load_tracker = None 108 self.tmp_dir = None 109 self.worker_test_name = None 110 111 def get_executed(self): 112 return (set(self.good) | set(self.bad) | set(self.skipped) 113 | set(self.resource_denieds) | set(self.environment_changed) 114 | set(self.run_no_tests)) 115 116 def accumulate_result(self, result, rerun=False): 117 test_name = result.name 118 119 if not isinstance(result, (ChildError, Interrupted)) and not rerun: 120 self.test_times.append((result.duration_sec, test_name)) 121 122 if isinstance(result, Passed): 123 self.good.append(test_name) 124 elif isinstance(result, ResourceDenied): 125 self.skipped.append(test_name) 126 self.resource_denieds.append(test_name) 127 elif isinstance(result, Skipped): 128 self.skipped.append(test_name) 129 elif isinstance(result, EnvChanged): 130 self.environment_changed.append(test_name) 131 elif isinstance(result, Failed): 132 if not rerun: 133 self.bad.append(test_name) 134 self.need_rerun.append(result) 135 elif isinstance(result, DidNotRun): 136 self.run_no_tests.append(test_name) 137 elif isinstance(result, Interrupted): 138 self.interrupted = True 139 else: 140 raise ValueError("invalid test result: %r" % result) 141 142 if rerun and not isinstance(result, (Failed, Interrupted)): 143 self.bad.remove(test_name) 144 145 xml_data = result.xml_data 146 if xml_data: 147 import xml.etree.ElementTree as ET 148 for e in xml_data: 149 try: 150 self.testsuite_xml.append(ET.fromstring(e)) 151 except ET.ParseError: 152 print(xml_data, file=sys.__stderr__) 153 raise 154 155 def log(self, line=''): 156 empty = not line 157 158 # add the system load prefix: "load avg: 1.80 " 159 load_avg = self.getloadavg() 160 if load_avg is not None: 161 line = f"load avg: {load_avg:.2f} {line}" 162 163 # add the timestamp prefix: "0:01:05 " 164 test_time = time.monotonic() - self.start_time 165 166 mins, secs = divmod(int(test_time), 60) 167 hours, mins = divmod(mins, 60) 168 test_time = "%d:%02d:%02d" % (hours, mins, secs) 169 170 line = f"{test_time} {line}" 171 if empty: 172 line = line[:-1] 173 174 print(line, flush=True) 175 176 def display_progress(self, test_index, text): 177 if self.ns.quiet: 178 return 179 180 # "[ 51/405/1] test_tcl passed" 181 line = f"{test_index:{self.test_count_width}}{self.test_count}" 182 fails = len(self.bad) + len(self.environment_changed) 183 if fails and not self.ns.pgo: 184 line = f"{line}/{fails}" 185 self.log(f"[{line}] {text}") 186 187 def parse_args(self, kwargs): 188 ns = _parse_args(sys.argv[1:], **kwargs) 189 190 if ns.xmlpath: 191 support.junit_xml_list = self.testsuite_xml = [] 192 193 worker_args = ns.worker_args 194 if worker_args is not None: 195 from test.libregrtest.runtest_mp import parse_worker_args 196 ns, test_name = parse_worker_args(ns.worker_args) 197 ns.worker_args = worker_args 198 self.worker_test_name = test_name 199 200 # Strip .py extensions. 201 removepy(ns.args) 202 203 if ns.huntrleaks: 204 warmup, repetitions, _ = ns.huntrleaks 205 if warmup < 1 or repetitions < 1: 206 msg = ("Invalid values for the --huntrleaks/-R parameters. The " 207 "number of warmups and repetitions must be at least 1 " 208 "each (1:1).") 209 print(msg, file=sys.stderr, flush=True) 210 sys.exit(2) 211 212 if ns.tempdir: 213 ns.tempdir = os.path.expanduser(ns.tempdir) 214 215 self.ns = ns 216 217 def find_tests(self, tests): 218 self.tests = tests 219 220 if self.ns.single: 221 self.next_single_filename = os.path.join(self.tmp_dir, 'pynexttest') 222 try: 223 with open(self.next_single_filename, 'r') as fp: 224 next_test = fp.read().strip() 225 self.tests = [next_test] 226 except OSError: 227 pass 228 229 if self.ns.fromfile: 230 self.tests = [] 231 # regex to match 'test_builtin' in line: 232 # '0:00:00 [ 4/400] test_builtin -- test_dict took 1 sec' 233 regex = re.compile(r'\btest_[a-zA-Z0-9_]+\b') 234 with open(os.path.join(os_helper.SAVEDCWD, self.ns.fromfile)) as fp: 235 for line in fp: 236 line = line.split('#', 1)[0] 237 line = line.strip() 238 match = regex.search(line) 239 if match is not None: 240 self.tests.append(match.group()) 241 242 removepy(self.tests) 243 244 if self.ns.pgo: 245 # add default PGO tests if no tests are specified 246 setup_pgo_tests(self.ns) 247 248 stdtests = STDTESTS[:] 249 nottests = NOTTESTS.copy() 250 if self.ns.exclude: 251 for arg in self.ns.args: 252 if arg in stdtests: 253 stdtests.remove(arg) 254 nottests.add(arg) 255 self.ns.args = [] 256 257 # if testdir is set, then we are not running the python tests suite, so 258 # don't add default tests to be executed or skipped (pass empty values) 259 if self.ns.testdir: 260 alltests = findtests(self.ns.testdir, list(), set()) 261 else: 262 alltests = findtests(self.ns.testdir, stdtests, nottests) 263 264 if not self.ns.fromfile: 265 self.selected = self.tests or self.ns.args or alltests 266 else: 267 self.selected = self.tests 268 if self.ns.single: 269 self.selected = self.selected[:1] 270 try: 271 pos = alltests.index(self.selected[0]) 272 self.next_single_test = alltests[pos + 1] 273 except IndexError: 274 pass 275 276 # Remove all the selected tests that precede start if it's set. 277 if self.ns.start: 278 try: 279 del self.selected[:self.selected.index(self.ns.start)] 280 except ValueError: 281 print("Couldn't find starting test (%s), using all tests" 282 % self.ns.start, file=sys.stderr) 283 284 if self.ns.randomize: 285 if self.ns.random_seed is None: 286 self.ns.random_seed = random.randrange(10000000) 287 random.seed(self.ns.random_seed) 288 random.shuffle(self.selected) 289 290 def list_tests(self): 291 for name in self.selected: 292 print(name) 293 294 def _list_cases(self, suite): 295 for test in suite: 296 if isinstance(test, unittest.loader._FailedTest): 297 continue 298 if isinstance(test, unittest.TestSuite): 299 self._list_cases(test) 300 elif isinstance(test, unittest.TestCase): 301 if support.match_test(test): 302 print(test.id()) 303 304 def list_cases(self): 305 support.verbose = False 306 support.set_match_tests(self.ns.match_tests, self.ns.ignore_tests) 307 308 for test_name in self.selected: 309 abstest = get_abs_module(self.ns, test_name) 310 try: 311 suite = unittest.defaultTestLoader.loadTestsFromName(abstest) 312 self._list_cases(suite) 313 except unittest.SkipTest: 314 self.skipped.append(test_name) 315 316 if self.skipped: 317 print(file=sys.stderr) 318 print(count(len(self.skipped), "test"), "skipped:", file=sys.stderr) 319 printlist(self.skipped, file=sys.stderr) 320 321 def rerun_failed_tests(self): 322 self.log() 323 324 if self.ns.python: 325 # Temp patch for https://github.com/python/cpython/issues/94052 326 self.log( 327 "Re-running failed tests is not supported with --python " 328 "host runner option." 329 ) 330 return 331 332 self.ns.verbose = True 333 self.ns.failfast = False 334 self.ns.verbose3 = False 335 336 self.first_result = self.get_tests_result() 337 338 self.log("Re-running failed tests in verbose mode") 339 rerun_list = list(self.need_rerun) 340 self.need_rerun.clear() 341 for result in rerun_list: 342 test_name = result.name 343 self.rerun.append(test_name) 344 345 errors = result.errors or [] 346 failures = result.failures or [] 347 error_names = [ 348 self.normalize_test_name(test_full_name, is_error=True) 349 for (test_full_name, *_) in errors] 350 failure_names = [ 351 self.normalize_test_name(test_full_name) 352 for (test_full_name, *_) in failures] 353 self.ns.verbose = True 354 orig_match_tests = self.ns.match_tests 355 if errors or failures: 356 if self.ns.match_tests is None: 357 self.ns.match_tests = [] 358 self.ns.match_tests.extend(error_names) 359 self.ns.match_tests.extend(failure_names) 360 matching = "matching: " + ", ".join(self.ns.match_tests) 361 self.log(f"Re-running {test_name} in verbose mode ({matching})") 362 else: 363 self.log(f"Re-running {test_name} in verbose mode") 364 result = runtest(self.ns, test_name) 365 self.ns.match_tests = orig_match_tests 366 367 self.accumulate_result(result, rerun=True) 368 369 if isinstance(result, Interrupted): 370 break 371 372 if self.bad: 373 print(count(len(self.bad), 'test'), "failed again:") 374 printlist(self.bad) 375 376 self.display_result() 377 378 def normalize_test_name(self, test_full_name, *, is_error=False): 379 short_name = test_full_name.split(" ")[0] 380 if is_error and short_name in _TEST_LIFECYCLE_HOOKS: 381 # This means that we have a failure in a life-cycle hook, 382 # we need to rerun the whole module or class suite. 383 # Basically the error looks like this: 384 # ERROR: setUpClass (test.test_reg_ex.RegTest) 385 # or 386 # ERROR: setUpModule (test.test_reg_ex) 387 # So, we need to parse the class / module name. 388 lpar = test_full_name.index('(') 389 rpar = test_full_name.index(')') 390 return test_full_name[lpar + 1: rpar].split('.')[-1] 391 return short_name 392 393 def display_result(self): 394 # If running the test suite for PGO then no one cares about results. 395 if self.ns.pgo: 396 return 397 398 print() 399 print("== Tests result: %s ==" % self.get_tests_result()) 400 401 if self.interrupted: 402 print("Test suite interrupted by signal SIGINT.") 403 404 omitted = set(self.selected) - self.get_executed() 405 if omitted: 406 print() 407 print(count(len(omitted), "test"), "omitted:") 408 printlist(omitted) 409 410 if self.good and not self.ns.quiet: 411 print() 412 if (not self.bad 413 and not self.skipped 414 and not self.interrupted 415 and len(self.good) > 1): 416 print("All", end=' ') 417 print(count(len(self.good), "test"), "OK.") 418 419 if self.ns.print_slow: 420 self.test_times.sort(reverse=True) 421 print() 422 print("10 slowest tests:") 423 for test_time, test in self.test_times[:10]: 424 print("- %s: %s" % (test, format_duration(test_time))) 425 426 if self.bad: 427 print() 428 print(count(len(self.bad), "test"), "failed:") 429 printlist(self.bad) 430 431 if self.environment_changed: 432 print() 433 print("{} altered the execution environment:".format( 434 count(len(self.environment_changed), "test"))) 435 printlist(self.environment_changed) 436 437 if self.skipped and not self.ns.quiet: 438 print() 439 print(count(len(self.skipped), "test"), "skipped:") 440 printlist(self.skipped) 441 442 if self.rerun: 443 print() 444 print("%s:" % count(len(self.rerun), "re-run test")) 445 printlist(self.rerun) 446 447 if self.run_no_tests: 448 print() 449 print(count(len(self.run_no_tests), "test"), "run no tests:") 450 printlist(self.run_no_tests) 451 452 def run_tests_sequential(self): 453 if self.ns.trace: 454 import trace 455 self.tracer = trace.Trace(trace=False, count=True) 456 457 save_modules = sys.modules.keys() 458 459 msg = "Run tests sequentially" 460 if self.ns.timeout: 461 msg += " (timeout: %s)" % format_duration(self.ns.timeout) 462 self.log(msg) 463 464 previous_test = None 465 for test_index, test_name in enumerate(self.tests, 1): 466 start_time = time.monotonic() 467 468 text = test_name 469 if previous_test: 470 text = '%s -- %s' % (text, previous_test) 471 self.display_progress(test_index, text) 472 473 if self.tracer: 474 # If we're tracing code coverage, then we don't exit with status 475 # if on a false return value from main. 476 cmd = ('result = runtest(self.ns, test_name); ' 477 'self.accumulate_result(result)') 478 ns = dict(locals()) 479 self.tracer.runctx(cmd, globals=globals(), locals=ns) 480 result = ns['result'] 481 else: 482 result = runtest(self.ns, test_name) 483 self.accumulate_result(result) 484 485 if isinstance(result, Interrupted): 486 break 487 488 previous_test = str(result) 489 test_time = time.monotonic() - start_time 490 if test_time >= PROGRESS_MIN_TIME: 491 previous_test = "%s in %s" % (previous_test, format_duration(test_time)) 492 elif isinstance(result, Passed): 493 # be quiet: say nothing if the test passed shortly 494 previous_test = None 495 496 # Unload the newly imported modules (best effort finalization) 497 for module in sys.modules.keys(): 498 if module not in save_modules and module.startswith("test."): 499 support.unload(module) 500 501 if self.ns.failfast and is_failed(result, self.ns): 502 break 503 504 if previous_test: 505 print(previous_test) 506 507 def _test_forever(self, tests): 508 while True: 509 for test_name in tests: 510 yield test_name 511 if self.bad: 512 return 513 if self.ns.fail_env_changed and self.environment_changed: 514 return 515 516 def display_header(self): 517 # Print basic platform information 518 print("==", platform.python_implementation(), *sys.version.split()) 519 print("==", platform.platform(aliased=True), 520 "%s-endian" % sys.byteorder) 521 print("== cwd:", os.getcwd()) 522 cpu_count = os.cpu_count() 523 if cpu_count: 524 print("== CPU count:", cpu_count) 525 print("== encodings: locale=%s, FS=%s" 526 % (locale.getencoding(), sys.getfilesystemencoding())) 527 528 def get_tests_result(self): 529 result = [] 530 if self.bad: 531 result.append("FAILURE") 532 elif self.ns.fail_env_changed and self.environment_changed: 533 result.append("ENV CHANGED") 534 elif not any((self.good, self.bad, self.skipped, self.interrupted, 535 self.environment_changed)): 536 result.append("NO TEST RUN") 537 538 if self.interrupted: 539 result.append("INTERRUPTED") 540 541 if not result: 542 result.append("SUCCESS") 543 544 result = ', '.join(result) 545 if self.first_result: 546 result = '%s then %s' % (self.first_result, result) 547 return result 548 549 def run_tests(self): 550 # For a partial run, we do not need to clutter the output. 551 if (self.ns.header 552 or not(self.ns.pgo or self.ns.quiet or self.ns.single 553 or self.tests or self.ns.args)): 554 self.display_header() 555 556 if self.ns.huntrleaks: 557 warmup, repetitions, _ = self.ns.huntrleaks 558 if warmup < 3: 559 msg = ("WARNING: Running tests with --huntrleaks/-R and less than " 560 "3 warmup repetitions can give false positives!") 561 print(msg, file=sys.stdout, flush=True) 562 563 if self.ns.randomize: 564 print("Using random seed", self.ns.random_seed) 565 566 if self.ns.forever: 567 self.tests = self._test_forever(list(self.selected)) 568 self.test_count = '' 569 self.test_count_width = 3 570 else: 571 self.tests = iter(self.selected) 572 self.test_count = '/{}'.format(len(self.selected)) 573 self.test_count_width = len(self.test_count) - 1 574 575 if self.ns.use_mp: 576 from test.libregrtest.runtest_mp import run_tests_multiprocess 577 # If we're on windows and this is the parent runner (not a worker), 578 # track the load average. 579 if sys.platform == 'win32' and self.worker_test_name is None: 580 from test.libregrtest.win_utils import WindowsLoadTracker 581 582 try: 583 self.win_load_tracker = WindowsLoadTracker() 584 except PermissionError as error: 585 # Standard accounts may not have access to the performance 586 # counters. 587 print(f'Failed to create WindowsLoadTracker: {error}') 588 589 try: 590 run_tests_multiprocess(self) 591 finally: 592 if self.win_load_tracker is not None: 593 self.win_load_tracker.close() 594 self.win_load_tracker = None 595 else: 596 self.run_tests_sequential() 597 598 def finalize(self): 599 if self.next_single_filename: 600 if self.next_single_test: 601 with open(self.next_single_filename, 'w') as fp: 602 fp.write(self.next_single_test + '\n') 603 else: 604 os.unlink(self.next_single_filename) 605 606 if self.tracer: 607 r = self.tracer.results() 608 r.write_results(show_missing=True, summary=True, 609 coverdir=self.ns.coverdir) 610 611 print() 612 duration = time.monotonic() - self.start_time 613 print("Total duration: %s" % format_duration(duration)) 614 print("Tests result: %s" % self.get_tests_result()) 615 616 if self.ns.runleaks: 617 os.system("leaks %d" % os.getpid()) 618 619 def save_xml_result(self): 620 if not self.ns.xmlpath and not self.testsuite_xml: 621 return 622 623 import xml.etree.ElementTree as ET 624 root = ET.Element("testsuites") 625 626 # Manually count the totals for the overall summary 627 totals = {'tests': 0, 'errors': 0, 'failures': 0} 628 for suite in self.testsuite_xml: 629 root.append(suite) 630 for k in totals: 631 try: 632 totals[k] += int(suite.get(k, 0)) 633 except ValueError: 634 pass 635 636 for k, v in totals.items(): 637 root.set(k, str(v)) 638 639 xmlpath = os.path.join(os_helper.SAVEDCWD, self.ns.xmlpath) 640 with open(xmlpath, 'wb') as f: 641 for s in ET.tostringlist(root): 642 f.write(s) 643 644 def fix_umask(self): 645 if support.is_emscripten: 646 # Emscripten has default umask 0o777, which breaks some tests. 647 # see https://github.com/emscripten-core/emscripten/issues/17269 648 old_mask = os.umask(0) 649 if old_mask == 0o777: 650 os.umask(0o027) 651 else: 652 os.umask(old_mask) 653 654 def set_temp_dir(self): 655 if self.ns.tempdir: 656 self.tmp_dir = self.ns.tempdir 657 658 if not self.tmp_dir: 659 # When tests are run from the Python build directory, it is best practice 660 # to keep the test files in a subfolder. This eases the cleanup of leftover 661 # files using the "make distclean" command. 662 if sysconfig.is_python_build(): 663 self.tmp_dir = sysconfig.get_config_var('abs_builddir') 664 if self.tmp_dir is None: 665 # bpo-30284: On Windows, only srcdir is available. Using 666 # abs_builddir mostly matters on UNIX when building Python 667 # out of the source tree, especially when the source tree 668 # is read only. 669 self.tmp_dir = sysconfig.get_config_var('srcdir') 670 self.tmp_dir = os.path.join(self.tmp_dir, 'build') 671 else: 672 self.tmp_dir = tempfile.gettempdir() 673 674 self.tmp_dir = os.path.abspath(self.tmp_dir) 675 676 def create_temp_dir(self): 677 os.makedirs(self.tmp_dir, exist_ok=True) 678 679 # Define a writable temp dir that will be used as cwd while running 680 # the tests. The name of the dir includes the pid to allow parallel 681 # testing (see the -j option). 682 # Emscripten and WASI have stubbed getpid(), Emscripten has only 683 # milisecond clock resolution. Use randint() instead. 684 if sys.platform in {"emscripten", "wasi"}: 685 nounce = random.randint(0, 1_000_000) 686 else: 687 nounce = os.getpid() 688 if self.worker_test_name is not None: 689 test_cwd = 'test_python_worker_{}'.format(nounce) 690 else: 691 test_cwd = 'test_python_{}'.format(nounce) 692 test_cwd += os_helper.FS_NONASCII 693 test_cwd = os.path.join(self.tmp_dir, test_cwd) 694 return test_cwd 695 696 def cleanup(self): 697 import glob 698 699 path = os.path.join(glob.escape(self.tmp_dir), 'test_python_*') 700 print("Cleanup %s directory" % self.tmp_dir) 701 for name in glob.glob(path): 702 if os.path.isdir(name): 703 print("Remove directory: %s" % name) 704 os_helper.rmtree(name) 705 else: 706 print("Remove file: %s" % name) 707 os_helper.unlink(name) 708 709 def main(self, tests=None, **kwargs): 710 self.parse_args(kwargs) 711 712 self.set_temp_dir() 713 714 self.fix_umask() 715 716 if self.ns.cleanup: 717 self.cleanup() 718 sys.exit(0) 719 720 test_cwd = self.create_temp_dir() 721 722 try: 723 # Run the tests in a context manager that temporarily changes the CWD 724 # to a temporary and writable directory. If it's not possible to 725 # create or change the CWD, the original CWD will be used. 726 # The original CWD is available from os_helper.SAVEDCWD. 727 with os_helper.temp_cwd(test_cwd, quiet=True): 728 # When using multiprocessing, worker processes will use test_cwd 729 # as their parent temporary directory. So when the main process 730 # exit, it removes also subdirectories of worker processes. 731 self.ns.tempdir = test_cwd 732 733 self._main(tests, kwargs) 734 except SystemExit as exc: 735 # bpo-38203: Python can hang at exit in Py_Finalize(), especially 736 # on threading._shutdown() call: put a timeout 737 if threading_helper.can_start_thread: 738 faulthandler.dump_traceback_later(EXIT_TIMEOUT, exit=True) 739 740 sys.exit(exc.code) 741 742 def getloadavg(self): 743 if self.win_load_tracker is not None: 744 return self.win_load_tracker.getloadavg() 745 746 if hasattr(os, 'getloadavg'): 747 return os.getloadavg()[0] 748 749 return None 750 751 def _main(self, tests, kwargs): 752 if self.worker_test_name is not None: 753 from test.libregrtest.runtest_mp import run_tests_worker 754 run_tests_worker(self.ns, self.worker_test_name) 755 756 if self.ns.wait: 757 input("Press any key to continue...") 758 759 support.PGO = self.ns.pgo 760 support.PGO_EXTENDED = self.ns.pgo_extended 761 762 setup_tests(self.ns) 763 764 self.find_tests(tests) 765 766 if self.ns.list_tests: 767 self.list_tests() 768 sys.exit(0) 769 770 if self.ns.list_cases: 771 self.list_cases() 772 sys.exit(0) 773 774 self.run_tests() 775 self.display_result() 776 777 if self.ns.verbose2 and self.bad: 778 self.rerun_failed_tests() 779 780 self.finalize() 781 782 self.save_xml_result() 783 784 if self.bad: 785 sys.exit(2) 786 if self.interrupted: 787 sys.exit(130) 788 if self.ns.fail_env_changed and self.environment_changed: 789 sys.exit(3) 790 sys.exit(0) 791 792 793def main(tests=None, **kwargs): 794 """Run the Python suite.""" 795 Regrtest().main(tests=tests, **kwargs) 796