diff --git a/.ci/fluent_test_runner.py b/.ci/fluent_test_runner.py index 54b35e19caa..d345d85766a 100644 --- a/.ci/fluent_test_runner.py +++ b/.ci/fluent_test_runner.py @@ -1,6 +1,7 @@ """Script to run Fluent test in Docker container.""" import argparse +import concurrent.futures import logging import os from pathlib import Path @@ -21,10 +22,10 @@ class FluentRuntimeError(RuntimeError): pass -def run_fluent_test( - src_test_dir: Path, journal_file: Path, launcher_args: str = "" +def _run_single_test( + src_test_dir: Path, journal_file: Path, launcher_args: str ) -> None: - """Run Fluent test. + """Run a single Fluent test. Parameters ---------- @@ -103,13 +104,29 @@ def run_fluent_test( sleep(1) logging.debug(container.logs(stderr=True).decode()) container.remove() - except docker.errors.NotFound: + except docker.errors.DockerException: pass MAX_TEST_PATH_LENGTH = 100 +def _run_single_test_with_status_print( + src_test_dir: Path, journal_file: Path, launcher_args: str, test_file_relpath: str +) -> bool: + try: + _run_single_test(src_test_dir, journal_file, launcher_args) + print( + f"{test_file_relpath}{(MAX_TEST_PATH_LENGTH + 10 - len(test_file_relpath)) * '·'}PASSED" + ) + except FluentRuntimeError as e: + print( + f"{test_file_relpath}{(MAX_TEST_PATH_LENGTH + 10 - len(test_file_relpath)) * '·'}FAILED" + ) + print(e) + return True + + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run Fluent test.") parser.add_argument( @@ -120,7 +137,8 @@ def run_fluent_test( test_dir = Path.cwd() / args.test_dir with TemporaryDirectory(ignore_cleanup_errors=True) as src_test_dir: copytree(test_dir, src_test_dir, dirs_exist_ok=True) - exception_occurred = False + statuses = [] + arguments = [] src_test_dir = Path(src_test_dir) for test_file in (src_test_dir / "fluent").rglob("*.py"): config_file = test_file.with_suffix(".yaml") @@ -129,17 +147,22 @@ def run_fluent_test( configs = yaml.safe_load(config_file.read_text()) launcher_args = configs.get("launcher_args", "") test_file_relpath = str(test_file.relative_to(src_test_dir)) - print(f"Running {test_file_relpath}", end="", flush=True) - try: - run_fluent_test(src_test_dir, test_file, launcher_args) - print( - f"{(MAX_TEST_PATH_LENGTH + 10 - len(test_file_relpath)) * '·'}PASSED" - ) - except FluentRuntimeError as e: - print( - f"{(MAX_TEST_PATH_LENGTH + 10 - len(test_file_relpath)) * '·'}FAILED" - ) - print(e) - exception_occurred = True - if exception_occurred: + arguments.append( + (src_test_dir, test_file, launcher_args, test_file_relpath) + ) + max_workers = int(os.getenv("MAX_WORKERS_FLUENT_TESTS", 4)) + if max_workers > 1: + with concurrent.futures.ThreadPoolExecutor( + max_workers=max_workers + ) as executor: + futures = [ + executor.submit(_run_single_test_with_status_print, *args) + for args in arguments + ] + for future in concurrent.futures.as_completed(futures): + statuses.append(future.result()) + else: + for args in arguments: + statuses.append(_run_single_test_with_status_print(*args)) + if any(statuses): exit(1) diff --git a/.github/workflows/test-fluent-journals.yml b/.github/workflows/test-fluent-journals.yml index f73cc2e3eb3..b96b5aa6b5a 100644 --- a/.github/workflows/test-fluent-journals.yml +++ b/.github/workflows/test-fluent-journals.yml @@ -83,3 +83,9 @@ jobs: - name: Run Fluent tests run: | make write-and-run-fluent-tests + env: + MAX_WORKERS_FLUENT_TESTS: 1 + + - name: Cleanup previous docker containers + if: always() + run: make cleanup-previous-docker-containers diff --git a/pyproject.toml b/pyproject.toml index 7fbf53054fe..abca3a70e31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,7 +108,6 @@ addopts = """ -v --durations=0 --show-capture=all --n 4 """ markers = [ "settings_only: Read and modify the case settings only, without loading the mesh, initializing, or solving the case",