diff --git a/changelog-entries/848.md b/changelog-entries/848.md new file mode 100644 index 000000000..92e11e03c --- /dev/null +++ b/changelog-entries/848.md @@ -0,0 +1 @@ +- Added optional per-component `build_timeout` in `components.yaml` for the system test Docker build step ([#848](https://github.com/precice/tutorials/pull/848)). diff --git a/tools/tests/README.md b/tools/tests/README.md index aa53133e3..28cf367d5 100644 --- a/tools/tests/README.md +++ b/tools/tests/README.md @@ -330,8 +330,10 @@ This template defines: ### Timeouts -A `GLOBAL_TIMEOUT` is used for all operations. Its default value is 600s (5min), it is set in the beginning of [`Systemtests.py`](https://github.com/precice/tutorials/blob/develop/tools/tests/systemtests/Systemtest.py), and it can be overridden via the `PRECICE_SYSTEMTESTS_TIMEOUT` environment variable. +The build and the run/compare steps use separate timeouts. -Tests can define a different `timeout` in their `tests.yaml` entry, which applies to the running and results comparison steps. +**Build:** Each component in `components.yaml` may set a `build_timeout` (default: 480s (8min)). The build step runs `docker compose build` once for all participant images, with one wall-clock subprocess timeout. That limit is the maximum `build_timeout` among the distinct components of the test (so that the slowest component is not cut off too early). You can override the default via the `PRECICE_SYSTEMTESTS_BUILD_TIMEOUT` environment variable. + +**Run and compare:** Each test in `tests.yaml` may set a `timeout` (default: 180s (3min)), which applies to the running and results comparison steps only. You can override the default via the `PRECICE_SYSTEMTESTS_TIMEOUT` environment variable. diff --git a/tools/tests/components.yaml b/tools/tests/components.yaml index c7a4de2b0..1357e51c7 100644 --- a/tools/tests/components.yaml +++ b/tools/tests/components.yaml @@ -50,6 +50,7 @@ dealii-adapter: dumux-adapter: template: component-templates/dumux-adapter.yaml + build_timeout: 600 build_arguments: PLATFORM: default: "ubuntu_2404" @@ -195,6 +196,7 @@ python-bindings: su2-adapter: template: component-templates/su2-adapter.yaml + build_timeout: 600 build_arguments: PLATFORM: default: "ubuntu_2404" diff --git a/tools/tests/metadata_parser/metdata.py b/tools/tests/metadata_parser/metdata.py index 1c9b98d55..882fcca2a 100644 --- a/tools/tests/metadata_parser/metdata.py +++ b/tools/tests/metadata_parser/metdata.py @@ -97,6 +97,7 @@ class Component: name: str template: str parameters: BuildArguments + build_timeout: int | None = None def __eq__(self, other): if isinstance(other, Component): @@ -130,11 +131,17 @@ def from_yaml(cls, path): with open(path, 'r') as f: data = yaml.safe_load(f) for component_name in data: - parameters = BuildArguments.from_components_yaml( - data[component_name]) - template = data[component_name]["template"] + component_data = data[component_name] + parameters = BuildArguments.from_components_yaml(component_data) + template = component_data["template"] + build_timeout = component_data.get("build_timeout", None) + if build_timeout is not None: + if not isinstance(build_timeout, int) or build_timeout <= 0: + raise ValueError( + f"build_timeout must be a positive integer for component " + f"'{component_name}', got {build_timeout!r}") components.append( - Component(component_name, template, parameters)) + Component(component_name, template, parameters, build_timeout)) return cls(components) diff --git a/tools/tests/systemtests/Systemtest.py b/tools/tests/systemtests/Systemtest.py index f2d4559a1..3ffb74df0 100644 --- a/tools/tests/systemtests/Systemtest.py +++ b/tools/tests/systemtests/Systemtest.py @@ -21,7 +21,9 @@ import os -GLOBAL_TIMEOUT = int(os.environ.get("PRECICE_SYSTEMTESTS_TIMEOUT", 600)) +GLOBAL_TIMEOUT = int(os.environ.get("PRECICE_SYSTEMTESTS_TIMEOUT", 180)) +DEFAULT_BUILD_TIMEOUT = int( + os.environ.get("PRECICE_SYSTEMTESTS_BUILD_TIMEOUT", 480)) SHORT_TIMEOUT = 10 DIFF_RESULTS_DIR = "diff-results" @@ -238,6 +240,27 @@ def __hash__(self) -> int: def __post_init__(self): self.__init_args_to_use() self.env = {} + self.build_timeout = self._resolve_build_timeout() + + def _resolve_build_timeout(self) -> int: + """ + Wall-clock limit for the single ``docker compose build`` subprocess. + + Uses the maximum build_timeout of the distinct components in this test, + so the step can run long enough for the slowest adapter. Components + without build_timeout use DEFAULT_BUILD_TIMEOUT. + """ + timeouts = [] + seen_components = set() + for case in self.case_combination.cases: + if case.component.name in seen_components: + continue + seen_components.add(case.component.name) + if case.component.build_timeout is not None: + timeouts.append(case.component.build_timeout) + else: + timeouts.append(DEFAULT_BUILD_TIMEOUT) + return max(timeouts) if timeouts else DEFAULT_BUILD_TIMEOUT def __init_args_to_use(self): """ @@ -847,6 +870,8 @@ def _build_docker(self): Builds the docker image """ logging.debug(f"Building docker image for {self}") + logging.info( + f"Using build timeout {self.build_timeout}s for {self}") time_start = time.perf_counter() docker_compose_content = self.__get_docker_compose_file() with open(self.system_test_dir / "docker-compose.tutorial.yaml", 'w') as file: @@ -862,7 +887,7 @@ def _build_docker(self): 'build', ], "build", - GLOBAL_TIMEOUT, + self.build_timeout, ) elapsed_time = time.perf_counter() - time_start return DockerComposeResult(exit_code, stdout_data, stderr_data, self, elapsed_time)