Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-109817: Add --single-process-per-case option to libregrtest#151689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base:main
Are you sure you want to change the base?
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -70,6 +70,26 @@ def stop(self): | ||
| with self.lock: | ||
| self.tests_iter = None | ||
| class GroupedMultiprocessIterator: | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cannot | ||
| """Provide test groups safely across multiple worker threads.""" | ||
| def __init__(self, groups_iter): | ||
| self.lock = threading.Lock() | ||
| self.groups_iter = groups_iter | ||
| def next_group(self): | ||
| with self.lock: | ||
| if self.groups_iter is None: | ||
| return None | ||
| try: | ||
| return next(self.groups_iter) | ||
| except StopIteration: | ||
| return None | ||
| def stop(self): | ||
| with self.lock: | ||
| self.groups_iter = None | ||
| @dataclasses.dataclass(slots=True, frozen=True) | ||
| class MultiprocessResult: | ||
| @@ -269,16 +289,24 @@ def create_json_file(self, stack: contextlib.ExitStack) -> tuple[JsonFile, TextI | ||
| json_file = JsonFile(json_fd, JsonFileType.UNIX_FD) | ||
| return (json_file, json_tmpfile) | ||
| def create_worker_runtests(self, test_name: TestName, json_file: JsonFile) -> WorkerRunTests: | ||
| tests = (test_name,) | ||
| if self.runtests.rerun: | ||
| match_tests = self.runtests.get_match_tests(test_name) | ||
| def create_worker_runtests(self, test_name: TestName, | ||
| json_file: JsonFile, | ||
| module_name: TestName | None = None, | ||
| ) -> WorkerRunTests: | ||
| kwargs: dict[str, Any] = {} | ||
| if module_name is not None: | ||
| # single_process_per_case mode: run a single test case | ||
| # (test_name is a case ID) inside its module. | ||
| tests = (module_name,) | ||
| kwargs['match_tests'] = [(test_name, True)] | ||
| else: | ||
| match_tests = None | ||
| tests = (test_name,) | ||
| if self.runtests.rerun: | ||
| match_tests = self.runtests.get_match_tests(test_name) | ||
| if match_tests: | ||
| kwargs['match_tests'] = [(test, True) for test in match_tests] | ||
| kwargs: dict[str, Any] = {} | ||
| if match_tests: | ||
| kwargs['match_tests'] = [(test, True) for test in match_tests] | ||
| if self.runtests.output_on_failure: | ||
| kwargs['verbose'] = True | ||
| kwargs['output_on_failure'] = False | ||
| @@ -356,11 +384,13 @@ def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None, | ||
| return (result, stdout) | ||
| def _runtest(self, test_name: TestName) -> MultiprocessResult: | ||
| def _runtest(self, test_name: TestName, | ||
| module_name: TestName | None = None) -> MultiprocessResult: | ||
| with contextlib.ExitStack() as stack: | ||
| stdout_file = self.create_stdout(stack) | ||
| json_file, json_tmpfile = self.create_json_file(stack) | ||
| worker_runtests = self.create_worker_runtests(test_name, json_file) | ||
| worker_runtests = self.create_worker_runtests( | ||
| test_name, json_file, module_name=module_name) | ||
| retcode: str | int | None | ||
| retcode, tmp_files = self.run_tmp_files(worker_runtests, | ||
| @@ -391,6 +421,14 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult: | ||
| return MultiprocessResult(result, stdout) | ||
| def run(self) -> None: | ||
| if self.runtests.single_process_per_case: | ||
| self._run_grouped() | ||
| else: | ||
| self._run_flat() | ||
| def _run_flat(self) -> None: | ||
| """Original behavior: one test name (module) per iteration.""" | ||
| assert isinstance(self.pending, MultiprocessIterator) | ||
| fail_fast = self.runtests.fail_fast | ||
| fail_env_changed = self.runtests.fail_env_changed | ||
| try: | ||
| @@ -420,6 +458,52 @@ def run(self) -> None: | ||
| finally: | ||
| self.output.put(WorkerThreadExited()) | ||
| def _run_grouped(self) -> None: | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
| ||
| """Execute all tests in a group on the same thread before moving on.""" | ||
| assert isinstance(self.pending, GroupedMultiprocessIterator) | ||
| fail_fast = self.runtests.fail_fast | ||
| fail_env_changed = self.runtests.fail_env_changed | ||
| try: | ||
| while not self._stopped: | ||
| group = self.pending.next_group() | ||
| if group is None: | ||
| break | ||
| module_name, case_ids = group | ||
| must_stop = False | ||
| for test_name in case_ids: | ||
| if self._stopped: | ||
| break | ||
| self.start_time = time.monotonic() | ||
| self.test_name = test_name | ||
| try: | ||
| mp_result = self._runtest(test_name, module_name) | ||
| except WorkerError as exc: | ||
| mp_result = exc.mp_result | ||
| finally: | ||
| self.test_name = _NOT_RUNNING | ||
| mp_result = dataclasses.replace( | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
| ||
| mp_result, | ||
| result=dataclasses.replace( | ||
| mp_result.result, | ||
| test_name=test_name, | ||
| duration=time.monotonic() - self.start_time)) | ||
| self.output.put((False, mp_result)) | ||
| if mp_result.result.must_stop(fail_fast, fail_env_changed): | ||
| must_stop = True | ||
| break | ||
| if must_stop: | ||
| break | ||
| except ExitThread: | ||
| pass | ||
| except BaseException: | ||
| self.output.put((True, traceback.format_exc())) | ||
| finally: | ||
| self.output.put(WorkerThreadExited()) | ||
| def _wait_completed(self) -> None: | ||
| popen = self._popen | ||
| # only needed for mypy: | ||
| @@ -489,8 +573,13 @@ def __init__(self, num_workers: int, runtests: RunTests, | ||
| self.live_worker_count = 0 | ||
| self.output: queue.Queue[QueueContent] = queue.Queue() | ||
| tests_iter = runtests.iter_tests() | ||
| self.pending = MultiprocessIterator(tests_iter) | ||
| self.pending: MultiprocessIterator | GroupedMultiprocessIterator | ||
| if runtests.single_process_per_case: | ||
| groups_iter = runtests.iter_case_groups() | ||
| self.pending = GroupedMultiprocessIterator(groups_iter) | ||
| else: | ||
| tests_iter = runtests.iter_tests() | ||
| self.pending = MultiprocessIterator(tests_iter) | ||
| self.timeout = runtests.timeout | ||
| if self.timeout is not None: | ||
| # Rely on faulthandler to kill a worker process. This timouet is | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set_match_tests()is called both inlist_cases()and incollect_cases().