| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  | import dataclasses | 
					
						
							| 
									
										
										
										
											2016-03-23 02:04:32 +01:00
										 |  |  | import faulthandler | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2022-06-14 13:43:02 +02:00
										 |  |  | import os.path | 
					
						
							| 
									
										
										
										
											2015-09-30 03:05:43 +02:00
										 |  |  | import queue | 
					
						
							| 
									
										
										
										
											2019-10-18 15:49:08 +02:00
										 |  |  | import signal | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | import subprocess | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2022-06-16 21:48:26 +02:00
										 |  |  | import tempfile | 
					
						
							| 
									
										
										
										
											2017-09-07 18:56:24 +02:00
										 |  |  | import threading | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | import traceback | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  | from typing import NoReturn, Literal, Any, TextIO | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | from test import support | 
					
						
							| 
									
										
										
										
											2020-06-30 21:46:06 +08:00
										 |  |  | from test.support import os_helper | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  | from test.support import TestStats | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  | from test.libregrtest.main import Regrtest | 
					
						
							| 
									
										
										
										
											2016-03-23 12:14:10 +01:00
										 |  |  | from test.libregrtest.runtest import ( | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |     run_single_test, TestResult, State, PROGRESS_MIN_TIME, | 
					
						
							| 
									
										
										
										
											2023-09-10 03:07:05 +02:00
										 |  |  |     FilterTuple, RunTests, StrPath, StrJSON, TestName) | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  | from test.libregrtest.setup import setup_tests, setup_test_dir | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  | from test.libregrtest.results import TestResults | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  | from test.libregrtest.utils import format_duration, print_warning | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 16:21:36 +02:00
										 |  |  | if sys.platform == 'win32': | 
					
						
							|  |  |  |     import locale | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-30 03:05:43 +02:00
										 |  |  | # Display the running tests if nothing happened last N seconds | 
					
						
							| 
									
										
										
										
											2015-11-04 09:03:53 +01:00
										 |  |  | PROGRESS_UPDATE = 30.0   # seconds | 
					
						
							| 
									
										
										
										
											2019-09-17 10:08:19 +02:00
										 |  |  | assert PROGRESS_UPDATE >= PROGRESS_MIN_TIME | 
					
						
							| 
									
										
										
										
											2015-09-30 03:05:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  | # Kill the main process after 5 minutes. It is supposed to write an update | 
					
						
							|  |  |  | # every PROGRESS_UPDATE seconds. Tolerate 5 minutes for Python slowest | 
					
						
							|  |  |  | # buildbot workers. | 
					
						
							|  |  |  | MAIN_PROCESS_TIMEOUT = 5 * 60.0 | 
					
						
							|  |  |  | assert MAIN_PROCESS_TIMEOUT >= PROGRESS_UPDATE | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  | # Time to wait until a worker completes: should be immediate | 
					
						
							|  |  |  | JOIN_TIMEOUT = 30.0   # seconds | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-18 15:49:08 +02:00
										 |  |  | USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg")) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-24 12:04:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  | @dataclasses.dataclass(slots=True) | 
					
						
							|  |  |  | class WorkerJob: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |     runtests: RunTests | 
					
						
							| 
									
										
										
										
											2019-05-14 15:49:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  | def create_worker_process(runtests: RunTests, | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |                           output_file: TextIO, | 
					
						
							| 
									
										
										
										
											2023-09-10 03:07:05 +02:00
										 |  |  |                           tmp_dir: StrPath | None = None) -> subprocess.Popen: | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  |     python_cmd = runtests.python_cmd | 
					
						
							|  |  |  |     worker_json = runtests.as_json() | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  |     if python_cmd is not None: | 
					
						
							|  |  |  |         executable = python_cmd | 
					
						
							| 
									
										
										
										
											2022-05-02 15:51:34 -07:00
										 |  |  |     else: | 
					
						
							|  |  |  |         executable = [sys.executable] | 
					
						
							|  |  |  |     cmd = [*executable, *support.args_from_interpreter_flags(), | 
					
						
							| 
									
										
										
										
											2016-09-21 17:12:50 +02:00
										 |  |  |            '-u',    # Unbuffered stdout and stderr | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  |            '-m', 'test.regrtest', | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |            '--worker-json', worker_json] | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-14 13:43:02 +02:00
										 |  |  |     env = dict(os.environ) | 
					
						
							| 
									
										
										
										
											2022-06-14 18:04:53 +02:00
										 |  |  |     if tmp_dir is not None: | 
					
						
							|  |  |  |         env['TMPDIR'] = tmp_dir | 
					
						
							|  |  |  |         env['TEMP'] = tmp_dir | 
					
						
							|  |  |  |         env['TMP'] = tmp_dir | 
					
						
							| 
									
										
										
										
											2022-06-14 13:43:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  |     # Running the child from the same working directory as regrtest's original | 
					
						
							|  |  |  |     # invocation ensures that TEMPDIR for the child is the same when | 
					
						
							|  |  |  |     # sysconfig.is_python_build() is true. See issue 15300. | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |     kw = dict( | 
					
						
							|  |  |  |         env=env, | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         stdout=output_file, | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |         # bpo-45410: Write stderr into stdout to keep messages order | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         stderr=output_file, | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |         text=True, | 
					
						
							|  |  |  |         close_fds=(os.name != 'nt'), | 
					
						
							|  |  |  |         cwd=os_helper.SAVEDCWD, | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2019-10-18 15:49:08 +02:00
										 |  |  |     if USE_PROCESS_GROUP: | 
					
						
							|  |  |  |         kw['start_new_session'] = True | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |     return subprocess.Popen(cmd, **kw) | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 03:07:05 +02:00
										 |  |  | def worker_process(worker_json: StrJSON) -> NoReturn: | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  |     runtests = RunTests.from_json(worker_json) | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |     test_name = runtests.tests[0] | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |     match_tests: FilterTuple | None = runtests.match_tests | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-09 11:18:14 +02:00
										 |  |  |     setup_test_dir(runtests.test_dir) | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  |     setup_tests(runtests) | 
					
						
							| 
									
										
										
										
											2015-09-30 01:32:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |     if runtests.rerun: | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         if match_tests: | 
					
						
							|  |  |  |             matching = "matching: " + ", ".join(match_tests) | 
					
						
							|  |  |  |             print(f"Re-running {test_name} in verbose mode ({matching})", flush=True) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             print(f"Re-running {test_name} in verbose mode", flush=True) | 
					
						
							| 
									
										
										
										
											2019-05-14 15:49:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  |     result = run_single_test(test_name, runtests) | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  |     print()   # Force a newline (just in case) | 
					
						
							| 
									
										
										
										
											2019-05-14 15:49:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     # Serialize TestResult as dict in JSON | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |     json.dump(result, sys.stdout, cls=EncodeTestResult) | 
					
						
							|  |  |  |     sys.stdout.flush() | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  |     sys.exit(0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # We do not use a generator so multiple threads can call next(). | 
					
						
							|  |  |  | class MultiprocessIterator: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     """A thread-safe iterator over tests for multiprocess mode.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-13 19:17:54 +02:00
										 |  |  |     def __init__(self, tests_iter): | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  |         self.lock = threading.Lock() | 
					
						
							| 
									
										
										
										
											2019-05-13 19:17:54 +02:00
										 |  |  |         self.tests_iter = tests_iter | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __iter__(self): | 
					
						
							|  |  |  |         return self | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __next__(self): | 
					
						
							|  |  |  |         with self.lock: | 
					
						
							| 
									
										
										
										
											2019-05-13 19:17:54 +02:00
										 |  |  |             if self.tests_iter is None: | 
					
						
							|  |  |  |                 raise StopIteration | 
					
						
							|  |  |  |             return next(self.tests_iter) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def stop(self): | 
					
						
							|  |  |  |         with self.lock: | 
					
						
							|  |  |  |             self.tests_iter = None | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  | @dataclasses.dataclass(slots=True, frozen=True) | 
					
						
							|  |  |  | class MultiprocessResult: | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     result: TestResult | 
					
						
							| 
									
										
										
										
											2021-10-08 17:14:37 +02:00
										 |  |  |     # bpo-45410: stderr is written into stdout to keep messages order | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |     worker_stdout: str | None = None | 
					
						
							|  |  |  |     err_msg: str | None = None | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ExcStr = str | 
					
						
							|  |  |  | QueueOutput = tuple[Literal[False], MultiprocessResult] | tuple[Literal[True], ExcStr] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-26 04:08:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  | class ExitThread(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  | class WorkerThread(threading.Thread): | 
					
						
							|  |  |  |     def __init__(self, worker_id: int, runner: "RunWorkers") -> None: | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  |         super().__init__() | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         self.worker_id = worker_id | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         self.runtests = runner.runtests | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |         self.pending = runner.pending | 
					
						
							|  |  |  |         self.output = runner.output | 
					
						
							|  |  |  |         self.timeout = runner.worker_timeout | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |         self.log = runner.log | 
					
						
							| 
									
										
										
										
											2019-04-26 04:08:53 +02:00
										 |  |  |         self.current_test_name = None | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  |         self.start_time = None | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         self._popen = None | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  |         self._killed = False | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         self._stopped = False | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def __repr__(self) -> str: | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         info = [f'WorkerThread #{self.worker_id}'] | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  |         if self.is_alive(): | 
					
						
							| 
									
										
										
										
											2019-10-02 13:35:11 +02:00
										 |  |  |             info.append("running") | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         else: | 
					
						
							|  |  |  |             info.append('stopped') | 
					
						
							|  |  |  |         test = self.current_test_name | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  |         if test: | 
					
						
							|  |  |  |             info.append(f'test={test}') | 
					
						
							|  |  |  |         popen = self._popen | 
					
						
							| 
									
										
										
										
											2019-10-02 13:35:11 +02:00
										 |  |  |         if popen is not None: | 
					
						
							|  |  |  |             dt = time.monotonic() - self.start_time | 
					
						
							|  |  |  |             info.extend((f'pid={self._popen.pid}', | 
					
						
							|  |  |  |                          f'time={format_duration(dt)}')) | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  |         return '<%s>' % ' '.join(info) | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def _kill(self) -> None: | 
					
						
							| 
									
										
										
										
											2019-08-21 10:59:20 +01:00
										 |  |  |         popen = self._popen | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         if popen is None: | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2019-08-21 10:59:20 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-02 13:35:11 +02:00
										 |  |  |         if self._killed: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         self._killed = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-18 15:49:08 +02:00
										 |  |  |         if USE_PROCESS_GROUP: | 
					
						
							|  |  |  |             what = f"{self} process group" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             what = f"{self}" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         print(f"Kill {what}", file=sys.stderr, flush=True) | 
					
						
							| 
									
										
										
										
											2019-08-21 10:59:20 +01:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2019-10-18 15:49:08 +02:00
										 |  |  |             if USE_PROCESS_GROUP: | 
					
						
							|  |  |  |                 os.killpg(popen.pid, signal.SIGKILL) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 popen.kill() | 
					
						
							| 
									
										
										
										
											2019-10-17 00:29:12 +02:00
										 |  |  |         except ProcessLookupError: | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |             # popen.kill(): the process completed, the WorkerThread thread | 
					
						
							| 
									
										
										
										
											2019-10-18 15:49:08 +02:00
										 |  |  |             # read its exit status, but Popen.send_signal() read the returncode | 
					
						
							|  |  |  |             # just before Popen.wait() set returncode. | 
					
						
							| 
									
										
										
										
											2019-10-17 00:29:12 +02:00
										 |  |  |             pass | 
					
						
							| 
									
										
										
										
											2019-08-21 10:59:20 +01:00
										 |  |  |         except OSError as exc: | 
					
						
							| 
									
										
										
										
											2019-10-18 15:49:08 +02:00
										 |  |  |             print_warning(f"Failed to kill {what}: {exc!r}") | 
					
						
							| 
									
										
										
										
											2019-08-21 10:59:20 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def stop(self) -> None: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         # Method called from a different thread to stop this thread | 
					
						
							|  |  |  |         self._stopped = True | 
					
						
							|  |  |  |         self._kill() | 
					
						
							| 
									
										
										
										
											2019-08-14 14:18:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def mp_result_error( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |         test_result: TestResult, | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |         stdout: str | None = None, | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |         err_msg=None | 
					
						
							|  |  |  |     ) -> MultiprocessResult: | 
					
						
							| 
									
										
										
										
											2021-10-08 17:14:37 +02:00
										 |  |  |         return MultiprocessResult(test_result, stdout, err_msg) | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |     def _run_process(self, worker_job, output_file: TextIO, | 
					
						
							| 
									
										
										
										
											2023-09-10 03:07:05 +02:00
										 |  |  |                      tmp_dir: StrPath | None = None) -> int: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |             popen = create_worker_process(worker_job, output_file, tmp_dir) | 
					
						
							| 
									
										
										
										
											2019-10-02 13:35:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             self._killed = False | 
					
						
							| 
									
										
										
										
											2019-10-02 13:35:11 +02:00
										 |  |  |             self._popen = popen | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         except: | 
					
						
							|  |  |  |             self.current_test_name = None | 
					
						
							|  |  |  |             raise | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             if self._stopped: | 
					
						
							|  |  |  |                 # If kill() has been called before self._popen is set, | 
					
						
							|  |  |  |                 # self._popen is still running. Call again kill() | 
					
						
							|  |  |  |                 # to ensure that the process is killed. | 
					
						
							|  |  |  |                 self._kill() | 
					
						
							|  |  |  |                 raise ExitThread | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-21 10:59:20 +01:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |                 # gh-94026: stdout+stderr are written to tempfile | 
					
						
							|  |  |  |                 retcode = popen.wait(timeout=self.timeout) | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  |                 assert retcode is not None | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |                 return retcode | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             except subprocess.TimeoutExpired: | 
					
						
							|  |  |  |                 if self._stopped: | 
					
						
							| 
									
										
										
										
											2021-10-08 17:14:37 +02:00
										 |  |  |                     # kill() has been called: communicate() fails on reading | 
					
						
							|  |  |  |                     # closed stdout | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |                     raise ExitThread | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  |                 # On timeout, kill the process | 
					
						
							|  |  |  |                 self._kill() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # None means TIMEOUT for the caller | 
					
						
							|  |  |  |                 retcode = None | 
					
						
							|  |  |  |                 # bpo-38207: Don't attempt to call communicate() again: on it | 
					
						
							| 
									
										
										
										
											2021-10-08 17:14:37 +02:00
										 |  |  |                 # can hang until all child processes using stdout | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  |                 # pipes completes. | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             except OSError: | 
					
						
							|  |  |  |                 if self._stopped: | 
					
						
							|  |  |  |                     # kill() has been called: communicate() fails | 
					
						
							| 
									
										
										
										
											2021-10-08 17:14:37 +02:00
										 |  |  |                     # on reading closed stdout | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |                     raise ExitThread | 
					
						
							|  |  |  |                 raise | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             self._kill() | 
					
						
							|  |  |  |             raise | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  |         finally: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             self._wait_completed() | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |             self._popen = None | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             self.current_test_name = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 03:07:05 +02:00
										 |  |  |     def _runtest(self, test_name: TestName) -> MultiprocessResult: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |         self.current_test_name = test_name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 16:21:36 +02:00
										 |  |  |         if sys.platform == 'win32': | 
					
						
							|  |  |  |             # gh-95027: When stdout is not a TTY, Python uses the ANSI code | 
					
						
							|  |  |  |             # page for the sys.stdout encoding. If the main process runs in a | 
					
						
							|  |  |  |             # terminal, sys.stdout uses WindowsConsoleIO with UTF-8 encoding. | 
					
						
							|  |  |  |             encoding = locale.getencoding() | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             encoding = sys.stdout.encoding | 
					
						
							| 
									
										
										
										
											2023-06-28 04:26:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-09 03:03:39 +02:00
										 |  |  |         tests = (test_name,) | 
					
						
							|  |  |  |         if self.runtests.rerun: | 
					
						
							|  |  |  |             match_tests = self.runtests.get_match_tests(test_name) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             match_tests = None | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |         kwargs = {} | 
					
						
							|  |  |  |         if match_tests: | 
					
						
							|  |  |  |             kwargs['match_tests'] = match_tests | 
					
						
							|  |  |  |         worker_runtests = self.runtests.copy(tests=tests, **kwargs) | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |         # gh-94026: Write stdout+stderr to a tempfile as workaround for | 
					
						
							|  |  |  |         # non-blocking pipes on Emscripten with NodeJS. | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         with tempfile.TemporaryFile('w+', encoding=encoding) as stdout_file: | 
					
						
							| 
									
										
										
										
											2022-06-14 18:04:53 +02:00
										 |  |  |             # gh-93353: Check for leaked temporary files in the parent process, | 
					
						
							|  |  |  |             # since the deletion of temporary files can happen late during | 
					
						
							|  |  |  |             # Python finalization: too late for libregrtest. | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |             if not support.is_wasi: | 
					
						
							|  |  |  |                 # Don't check for leaked temporary files and directories if Python is | 
					
						
							|  |  |  |                 # run on WASI. WASI don't pass environment variables like TMPDIR to | 
					
						
							|  |  |  |                 # worker processes. | 
					
						
							|  |  |  |                 tmp_dir = tempfile.mkdtemp(prefix="test_python_") | 
					
						
							|  |  |  |                 tmp_dir = os.path.abspath(tmp_dir) | 
					
						
							|  |  |  |                 try: | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  |                     retcode = self._run_process(worker_runtests, stdout_file, tmp_dir) | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |                 finally: | 
					
						
							|  |  |  |                     tmp_files = os.listdir(tmp_dir) | 
					
						
							|  |  |  |                     os_helper.rmtree(tmp_dir) | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2023-09-10 01:41:21 +02:00
										 |  |  |                 retcode = self._run_process(worker_runtests, stdout_file) | 
					
						
							| 
									
										
										
										
											2022-06-29 10:05:16 +02:00
										 |  |  |                 tmp_files = () | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |             stdout_file.seek(0) | 
					
						
							| 
									
										
										
										
											2023-06-28 04:26:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |                 stdout = stdout_file.read().strip() | 
					
						
							| 
									
										
										
										
											2023-06-28 04:26:52 +02:00
										 |  |  |             except Exception as exc: | 
					
						
							|  |  |  |                 # gh-101634: Catch UnicodeDecodeError if stdout cannot be | 
					
						
							|  |  |  |                 # decoded from encoding | 
					
						
							|  |  |  |                 err_msg = f"Cannot read process stdout: {exc}" | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |                 result = TestResult(test_name, state=State.MULTIPROCESSING_ERROR) | 
					
						
							|  |  |  |                 return self.mp_result_error(result, err_msg=err_msg) | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  |         if retcode is None: | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |             result = TestResult(test_name, state=State.TIMEOUT) | 
					
						
							|  |  |  |             return self.mp_result_error(result, stdout) | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         err_msg = None | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  |         if retcode != 0: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |             err_msg = "Exit code %s" % retcode | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |             stdout, _, worker_json = stdout.rpartition("\n") | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |             stdout = stdout.rstrip() | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |             if not worker_json: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                 err_msg = "Failed to parse worker stdout" | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     # deserialize run_tests_worker() output | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |                     result = json.loads(worker_json, | 
					
						
							|  |  |  |                                         object_hook=decode_test_result) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                 except Exception as exc: | 
					
						
							|  |  |  |                     err_msg = "Failed to parse worker JSON: %s" % exc | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |         if err_msg: | 
					
						
							|  |  |  |             result = TestResult(test_name, state=State.MULTIPROCESSING_ERROR) | 
					
						
							|  |  |  |             return self.mp_result_error(result, stdout, err_msg) | 
					
						
							| 
									
										
										
										
											2015-09-30 00:33:29 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-14 13:43:02 +02:00
										 |  |  |         if tmp_files: | 
					
						
							|  |  |  |             msg = (f'\n\n' | 
					
						
							| 
									
										
										
										
											2022-06-14 18:04:53 +02:00
										 |  |  |                    f'Warning -- {test_name} leaked temporary files ' | 
					
						
							|  |  |  |                    f'({len(tmp_files)}): {", ".join(sorted(tmp_files))}') | 
					
						
							| 
									
										
										
										
											2022-06-14 13:43:02 +02:00
										 |  |  |             stdout += msg | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |             result.set_env_changed() | 
					
						
							| 
									
										
										
										
											2022-06-14 13:43:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |         return MultiprocessResult(result, stdout) | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def run(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |         fail_fast = self.runtests.fail_fast | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         fail_env_changed = self.runtests.fail_env_changed | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         while not self._stopped: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |             try: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     test_name = next(self.pending) | 
					
						
							|  |  |  |                 except StopIteration: | 
					
						
							|  |  |  |                     break | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |                 self.start_time = time.monotonic() | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                 mp_result = self._runtest(test_name) | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |                 mp_result.result.duration = time.monotonic() - self.start_time | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                 self.output.put((False, mp_result)) | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |                 if mp_result.result.must_stop(fail_fast, fail_env_changed): | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                     break | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  |             except ExitThread: | 
					
						
							|  |  |  |                 break | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |             except BaseException: | 
					
						
							|  |  |  |                 self.output.put((True, traceback.format_exc())) | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def _wait_completed(self) -> None: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         popen = self._popen | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             popen.wait(JOIN_TIMEOUT) | 
					
						
							|  |  |  |         except (subprocess.TimeoutExpired, OSError) as exc: | 
					
						
							|  |  |  |             print_warning(f"Failed to wait for {self} completion " | 
					
						
							|  |  |  |                           f"(timeout={format_duration(JOIN_TIMEOUT)}): " | 
					
						
							|  |  |  |                           f"{exc!r}") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def wait_stopped(self, start_time: float) -> None: | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         # bpo-38207: RunWorkers.stop_workers() called self.stop() | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  |         # which killed the process. Sometimes, killing the process from the | 
					
						
							|  |  |  |         # main thread does not interrupt popen.communicate() in | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         # WorkerThread thread. This loop with a timeout is a workaround | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  |         # for that. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # Moreover, if this method fails to join the thread, it is likely | 
					
						
							|  |  |  |         # that Python will hang at exit while calling threading._shutdown() | 
					
						
							|  |  |  |         # which tries again to join the blocked thread. Regrtest.main() | 
					
						
							|  |  |  |         # uses EXIT_TIMEOUT to workaround this second bug. | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         while True: | 
					
						
							|  |  |  |             # Write a message every second | 
					
						
							|  |  |  |             self.join(1.0) | 
					
						
							|  |  |  |             if not self.is_alive(): | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             dt = time.monotonic() - start_time | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |             self.log(f"Waiting for {self} thread for {format_duration(dt)}") | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             if dt > JOIN_TIMEOUT: | 
					
						
							|  |  |  |                 print_warning(f"Failed to join {self} in {format_duration(dt)}") | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  | def get_running(workers: list[WorkerThread]) -> list[str]: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |     running = [] | 
					
						
							| 
									
										
										
										
											2015-09-29 23:15:38 +02:00
										 |  |  |     for worker in workers: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         current_test_name = worker.current_test_name | 
					
						
							|  |  |  |         if not current_test_name: | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         dt = time.monotonic() - worker.start_time | 
					
						
							|  |  |  |         if dt >= PROGRESS_MIN_TIME: | 
					
						
							|  |  |  |             text = '%s (%s)' % (current_test_name, format_duration(dt)) | 
					
						
							|  |  |  |             running.append(text) | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |     if not running: | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  |     return f"running ({len(running)}): {', '.join(running)}" | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  | class RunWorkers: | 
					
						
							|  |  |  |     def __init__(self, regrtest: Regrtest, runtests: RunTests, num_workers: int) -> None: | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |         self.results: TestResults = regrtest.results | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         self.log = regrtest.log | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |         self.display_progress = regrtest.display_progress | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         self.num_workers = num_workers | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         self.runtests = runtests | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |         self.output: queue.Queue[QueueOutput] = queue.Queue() | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         tests_iter = runtests.iter_tests() | 
					
						
							|  |  |  |         self.pending = MultiprocessIterator(tests_iter) | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |         self.timeout = runtests.timeout | 
					
						
							|  |  |  |         if self.timeout is not None: | 
					
						
							| 
									
										
										
										
											2020-04-14 18:29:44 +02:00
										 |  |  |             # Rely on faulthandler to kill a worker process. This timouet is | 
					
						
							|  |  |  |             # when faulthandler fails to kill a worker process. Give a maximum | 
					
						
							|  |  |  |             # of 5 minutes to faulthandler to kill the worker. | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |             self.worker_timeout = min(self.timeout * 1.5, self.timeout + 5 * 60) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2019-08-14 14:18:51 +02:00
										 |  |  |             self.worker_timeout = None | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         self.workers = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def start_workers(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         self.workers = [WorkerThread(index, self) | 
					
						
							|  |  |  |                         for index in range(1, self.num_workers + 1)] | 
					
						
							| 
									
										
										
										
											2020-04-14 18:29:44 +02:00
										 |  |  |         msg = f"Run tests in parallel using {len(self.workers)} child processes" | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |         if self.timeout: | 
					
						
							| 
									
										
										
										
											2020-04-14 18:29:44 +02:00
										 |  |  |             msg += (" (timeout: %s, worker timeout: %s)" | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |                     % (format_duration(self.timeout), | 
					
						
							| 
									
										
										
										
											2020-04-14 18:29:44 +02:00
										 |  |  |                        format_duration(self.worker_timeout))) | 
					
						
							|  |  |  |         self.log(msg) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         for worker in self.workers: | 
					
						
							|  |  |  |             worker.start() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def stop_workers(self) -> None: | 
					
						
							| 
									
										
										
										
											2019-05-14 03:47:32 +02:00
										 |  |  |         start_time = time.monotonic() | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         for worker in self.workers: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             worker.stop() | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         for worker in self.workers: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             worker.wait_stopped(start_time) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def _get_result(self) -> QueueOutput | None: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |         pgo = self.runtests.pgo | 
					
						
							|  |  |  |         use_faulthandler = (self.timeout is not None) | 
					
						
							| 
									
										
										
										
											2022-01-10 22:03:09 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # bpo-46205: check the status of workers every iteration to avoid | 
					
						
							|  |  |  |         # waiting forever on an empty queue. | 
					
						
							|  |  |  |         while any(worker.is_alive() for worker in self.workers): | 
					
						
							| 
									
										
										
										
											2019-09-17 10:08:19 +02:00
										 |  |  |             if use_faulthandler: | 
					
						
							| 
									
										
										
										
											2019-10-08 18:45:43 +02:00
										 |  |  |                 faulthandler.dump_traceback_later(MAIN_PROCESS_TIMEOUT, | 
					
						
							|  |  |  |                                                   exit=True) | 
					
						
							| 
									
										
										
										
											2016-03-23 02:04:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |             # wait for a thread | 
					
						
							| 
									
										
										
										
											2015-09-30 03:05:43 +02:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |                 return self.output.get(timeout=PROGRESS_UPDATE) | 
					
						
							| 
									
										
										
										
											2015-09-30 03:05:43 +02:00
										 |  |  |             except queue.Empty: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                 pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |             if not pgo: | 
					
						
							|  |  |  |                 # display progress | 
					
						
							|  |  |  |                 running = get_running(self.workers) | 
					
						
							|  |  |  |                 if running: | 
					
						
							|  |  |  |                     self.log(running) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 22:03:09 -05:00
										 |  |  |         # all worker threads are done: consume pending results | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self.output.get(timeout=0) | 
					
						
							|  |  |  |         except queue.Empty: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def display_result(self, mp_result: MultiprocessResult) -> None: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         result = mp_result.result | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |         pgo = self.runtests.pgo | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |         text = str(result) | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |         if mp_result.err_msg: | 
					
						
							|  |  |  |             # MULTIPROCESSING_ERROR | 
					
						
							|  |  |  |             text += ' (%s)' % mp_result.err_msg | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         elif (result.duration >= PROGRESS_MIN_TIME and not pgo): | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |             text += ' (%s)' % format_duration(result.duration) | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         if not pgo: | 
					
						
							|  |  |  |             running = get_running(self.workers) | 
					
						
							|  |  |  |             if running: | 
					
						
							|  |  |  |                 text += f' -- {running}' | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |         self.display_progress(self.test_index, text) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     def _process_result(self, item: QueueOutput) -> bool: | 
					
						
							|  |  |  |         """Returns True if test runner must stop.""" | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         if item[0]: | 
					
						
							|  |  |  |             # Thread got an exception | 
					
						
							|  |  |  |             format_exc = item[1] | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |             print_warning(f"regrtest worker thread failed: {format_exc}") | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |             result = TestResult("<regrtest worker>", state=State.MULTIPROCESSING_ERROR) | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |             self.results.accumulate_result(result, self.runtests) | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |             return result | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.test_index += 1 | 
					
						
							|  |  |  |         mp_result = item[1] | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         result = mp_result.result | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |         self.results.accumulate_result(result, self.runtests) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         self.display_result(mp_result) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |         if mp_result.worker_stdout: | 
					
						
							|  |  |  |             print(mp_result.worker_stdout, flush=True) | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |         return result | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |     def run(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |         fail_fast = self.runtests.fail_fast | 
					
						
							| 
									
										
										
										
											2023-09-10 02:24:38 +02:00
										 |  |  |         fail_env_changed = self.runtests.fail_env_changed | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         self.start_workers() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.test_index = 0 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             while True: | 
					
						
							|  |  |  |                 item = self._get_result() | 
					
						
							|  |  |  |                 if item is None: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  |                 result = self._process_result(item) | 
					
						
							|  |  |  |                 if result.must_stop(fail_fast, fail_env_changed): | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                     break | 
					
						
							|  |  |  |         except KeyboardInterrupt: | 
					
						
							|  |  |  |             print() | 
					
						
							| 
									
										
										
										
											2023-09-10 04:30:43 +02:00
										 |  |  |             self.results.interrupted = True | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |         finally: | 
					
						
							| 
									
										
										
										
											2023-09-09 03:37:48 +02:00
										 |  |  |             if self.timeout is not None: | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  |                 faulthandler.cancel_dump_traceback_later() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             # Always ensure that all worker processes are no longer | 
					
						
							|  |  |  |             # worker when we exit this function | 
					
						
							|  |  |  |             self.pending.stop() | 
					
						
							|  |  |  |             self.stop_workers() | 
					
						
							| 
									
										
										
										
											2019-04-26 08:40:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  | class EncodeTestResult(json.JSONEncoder): | 
					
						
							|  |  |  |     """Encode a TestResult (sub)class object into a JSON dict.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def default(self, o: Any) -> dict[str, Any]: | 
					
						
							|  |  |  |         if isinstance(o, TestResult): | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |             result = dataclasses.asdict(o) | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |             result["__test_result__"] = o.__class__.__name__ | 
					
						
							|  |  |  |             return result | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return super().default(o) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 23:37:15 +02:00
										 |  |  | def decode_test_result(d: dict[str, Any]) -> TestResult | dict[str, Any]: | 
					
						
							| 
									
										
										
										
											2021-07-22 20:25:58 +02:00
										 |  |  |     """Decode a TestResult (sub)class object from a JSON dict.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if "__test_result__" not in d: | 
					
						
							|  |  |  |         return d | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-02 18:09:36 +02:00
										 |  |  |     d.pop('__test_result__') | 
					
						
							|  |  |  |     if d['stats'] is not None: | 
					
						
							|  |  |  |         d['stats'] = TestStats(**d['stats']) | 
					
						
							|  |  |  |     return TestResult(**d) |