diff --git a/src/ert/config/model_config.py b/src/ert/config/model_config.py index 1ccdd22c1cb..711cbef21e5 100644 --- a/src/ert/config/model_config.py +++ b/src/ert/config/model_config.py @@ -3,6 +3,7 @@ import contextlib import logging import os.path +import re import shutil from pathlib import Path @@ -25,6 +26,8 @@ DEFAULT_JOBNAME_FORMAT = "-" DEFAULT_ECLBASE_FORMAT = "ECLBASE" +_TOKEN_REGEX = re.compile(r"<[^<>]+>") + FULL_DISK_PERCENTAGE_THRESHOLD = 0.97 MINIMUM_BYTES_LEFT_ON_DISK_THRESHOLD = 200 * 1000**3 # 200 GB # We give warning if free disk space is less than MINIMUM_BYTES_LEFT_ON_DISK_THRESHOLD @@ -74,6 +77,9 @@ def validate_runpath(cls, runpath_format_string: str) -> str: ) ConfigWarning.warn(msg) logger.warning(msg) + + cls._validate_static_parts_exists(result) + with contextlib.suppress(Exception): mount_dir = get_mount_directory(Path(runpath_format_string)) total_space, used_space, free_space = shutil.disk_usage(mount_dir) @@ -127,6 +133,35 @@ def from_dict(cls, config_dict: ConfigDict) -> ModelConfig: ), ) + @staticmethod + def _validate_static_parts_exists(runpath_format: str) -> None: + """ + Splits the provided runpath format into parts + and verifies that the static prefix, if present, + consists of directories. + + Static parts after dynamic parts are not validated, + as dynamic parts are resolved later in the application. + """ + + parts = Path(runpath_format).parts + + static_parts = [] + for part in parts: + if _TOKEN_REGEX.search(part): + break + static_parts.append(part) + + if not static_parts: + return + + static_dir = Path(*static_parts) + if static_dir.exists() and not static_dir.is_dir(): + raise ConfigValidationError( + f"Invalid runpath format; {static_dir} exists, but is not a directory." + ) + return + def _replace_runpath_format(format_string: str) -> str: format_string = format_string.replace("%d", "", 1) diff --git a/tests/ert/unit_tests/config/test_model_config.py b/tests/ert/unit_tests/config/test_model_config.py index 763ee1e35b6..df6040c4521 100644 --- a/tests/ert/unit_tests/config/test_model_config.py +++ b/tests/ert/unit_tests/config/test_model_config.py @@ -57,6 +57,25 @@ def test_model_config_jobname_and_eclbase(extra_config, expected): assert ModelConfig.from_dict(config_dict).jobname_format_string == expected +def test_that_runpath_validation_raises_error_when_static_prefix_is_not_a_dir(tmp_path): + file = tmp_path / "not_a_dir" + file.write_text("This is a file") + + with pytest.raises(ValueError, match="not a directory"): + ModelConfig(runpath_format_string=str(file / "" / "")) + + +def test_that_runpath_validation_does_not_raise_error_when_static_prefix_is_a_dir( + tmp_path, +): + static_dir = tmp_path / "dir" + static_dir.mkdir() + + ModelConfig( + runpath_format_string=str(static_dir / "" / "static_part" / "") + ) + + @pytest.mark.parametrize( ("total_space", "used_space", "to_warn", "expected_warning"), [