diff --git a/src/ert/config/ert_config.py b/src/ert/config/ert_config.py index df80c6110c3..bfb4d5ba10b 100644 --- a/src/ert/config/ert_config.py +++ b/src/ert/config/ert_config.py @@ -766,10 +766,15 @@ def _create_list_of_forward_model_steps_to_run( cls, installed_steps: Dict[str, ForwardModelStep], substitutions: Substitutions, - config_dict, + config_dict: dict, ) -> List[ForwardModelStep]: errors = [] fm_steps = [] + + env_vars = {} + for key, val in config_dict.get("SETENV", []): + env_vars[key] = val + for fm_step_description in config_dict.get(ConfigKeys.FORWARD_MODEL, []): if len(fm_step_description) > 1: unsubstituted_step_name, args = fm_step_description @@ -836,7 +841,7 @@ def _create_list_of_forward_model_steps_to_run( skip_pre_experiment_validation=True, ) job_json = substituted_json["jobList"][0] - fm_step.validate_pre_experiment(job_json) + fm_step.validate_pre_experiment(job_json, env_vars) except ForwardModelStepValidationError as err: errors.append( ConfigValidationError.with_context( diff --git a/src/ert/config/forward_model_step.py b/src/ert/config/forward_model_step.py index 04a353e2a7b..e1574e97785 100644 --- a/src/ert/config/forward_model_step.py +++ b/src/ert/config/forward_model_step.py @@ -181,7 +181,9 @@ def convert_to_substitutions(cls, v: Dict[str, str]) -> Substitutions: return v return Substitutions(v) - def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, fm_step_json: ForwardModelStepJSON, env_vars: Dict[str, str] + ) -> None: """ Raise errors pertaining to the environment not being as the forward model step requires it to be. For example diff --git a/src/ert/plugins/hook_implementations/forward_model_steps.py b/src/ert/plugins/hook_implementations/forward_model_steps.py index ffa6ac84bf2..5dc12424351 100644 --- a/src/ert/plugins/hook_implementations/forward_model_steps.py +++ b/src/ert/plugins/hook_implementations/forward_model_steps.py @@ -3,7 +3,7 @@ import subprocess from pathlib import Path from textwrap import dedent -from typing import List, Literal, Optional, Type +from typing import Dict, List, Literal, Optional, Type import yaml @@ -14,7 +14,6 @@ ForwardModelStepValidationError, plugin, ) -from ert.plugins import ErtPluginManager class CarefulCopyFile(ForwardModelStepPlugin): @@ -220,13 +219,17 @@ def __init__(self) -> None: default_mapping={"": 1, "": ""}, ) - def validate_pre_experiment(self, _: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, _: ForwardModelStepJSON, env_vars: Dict[str, str] + ) -> None: if "" not in self.private_args: raise ForwardModelStepValidationError( "Forward model step ECLIPSE100 must be given a VERSION argument" ) version = self.private_args[""] - available_versions = _available_eclrun_versions(simulator="eclipse") + available_versions = _available_eclrun_versions( + simulator="eclipse", env_vars=env_vars + ) if available_versions and version not in available_versions: raise ForwardModelStepValidationError( @@ -278,13 +281,17 @@ def __init__(self) -> None: default_mapping={"": 1, "": "", "": "version"}, ) - def validate_pre_experiment(self, _: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, _: ForwardModelStepJSON, env_vars: Dict[str, str] + ) -> None: if "" not in self.private_args: raise ForwardModelStepValidationError( "Forward model step ECLIPSE300 must be given a VERSION argument" ) version = self.private_args[""] - available_versions = _available_eclrun_versions(simulator="e300") + available_versions = _available_eclrun_versions( + simulator="e300", env_vars=env_vars + ) if available_versions and version not in available_versions: raise ForwardModelStepValidationError( f"Unavailable ECLIPSE300 version {version} current supported " @@ -628,14 +635,15 @@ def installable_forward_model_steps() -> List[Type[ForwardModelStepPlugin]]: return [*_UpperCaseFMSteps, *_LowerCaseFMSteps] -def _available_eclrun_versions(simulator: Literal["eclipse", "e300"]) -> List[str]: +def _available_eclrun_versions( + simulator: Literal["eclipse", "e300"], env_vars: Dict[str, str] +) -> List[str]: if shutil.which("eclrun") is None: return [] - pm = ErtPluginManager() ecl_config_path = ( - pm.get_ecl100_config_path() + env_vars.get("ECL100_SITE_CONFIG") if simulator == "eclipse" - else pm.get_ecl300_config_path() + else env_vars.get("ECL300_SITE_CONFIG") ) if not ecl_config_path: diff --git a/tests/ert/unit_tests/config/test_forward_model.py b/tests/ert/unit_tests/config/test_forward_model.py index b2afc1fca5f..e93c4263d76 100644 --- a/tests/ert/unit_tests/config/test_forward_model.py +++ b/tests/ert/unit_tests/config/test_forward_model.py @@ -5,7 +5,7 @@ import stat from pathlib import Path from textwrap import dedent -from unittest.mock import patch +from typing import Dict import pytest from hypothesis import given, settings @@ -596,9 +596,12 @@ def test_that_eclipse_jobs_check_version(eclipse_v, mock_eclrun): ecl100_config_content = f"eclrun_env:\n PATH: {os.getcwd()}\n" ecl300_config_content = f"eclrun_env:\n PATH: {os.getcwd()}\n" - ert_config_contents = ( - f"NUM_REALIZATIONS 1\nFORWARD_MODEL ECLIPSE{eclipse_v} (=1)\n" - ) + ert_config_contents = f"""NUM_REALIZATIONS 1 + + SETENV ECL100_SITE_CONFIG {ecl100_config_file_name} + SETENV ECL300_SITE_CONFIG {ecl300_config_file_name} + + FORWARD_MODEL ECLIPSE{eclipse_v} (=1)""" # Write config file config_file_name = "test.ert" @@ -607,17 +610,11 @@ def test_that_eclipse_jobs_check_version(eclipse_v, mock_eclrun): Path(ecl100_config_file_name).write_text(ecl100_config_content, encoding="utf-8") # Write ecl300_config file Path(ecl300_config_file_name).write_text(ecl300_config_content, encoding="utf-8") - with patch( - "ert.plugins.hook_implementations.forward_model_steps.ErtPluginManager" - ) as mock: - instance = mock.return_value - instance.get_ecl100_config_path.return_value = ecl100_config_file_name - instance.get_ecl300_config_path.return_value = ecl300_config_file_name - with pytest.raises( - ConfigValidationError, - match=rf".*Unavailable ECLIPSE{eclipse_v} version 1 current supported versions \['4', '2', '8'\].*", - ): - _ = ErtConfig.with_plugins().from_file(config_file_name) + with pytest.raises( + ConfigValidationError, + match=rf".*Unavailable ECLIPSE{eclipse_v} version 1 current supported versions \['4', '2', '8'\].*", + ): + ErtConfig.with_plugins().from_file(config_file_name) @pytest.mark.skipif(shutil.which("eclrun") is not None, reason="eclrun is available") @@ -637,20 +634,14 @@ def test_that_no_error_thrown_when_checking_eclipse_version_and_eclrun_is_not_pr def test_that_no_error_thrown_when_checking_eclipse_version_and_no_ecl_config_defined( eclipse_v, mock_eclrun ): - ert_config_contents = ( - f"NUM_REALIZATIONS 1\nFORWARD_MODEL ECLIPSE{eclipse_v} (=1)\n" + ert_config_contents = dedent( + f"""NUM_REALIZATIONS 1 + FORWARD_MODEL ECLIPSE{eclipse_v} (=1)""" ) - # Write config file config_file_name = "test.ert" Path(config_file_name).write_text(ert_config_contents, encoding="utf-8") - with patch( - "ert.plugins.hook_implementations.forward_model_steps.ErtPluginManager" - ) as mock: - instance = mock.return_value - instance.get_ecl100_config_path.return_value = None - instance.get_ecl300_config_path.return_value = None - _ = ErtConfig.with_plugins().from_file(config_file_name) + ErtConfig.with_plugins().from_file(config_file_name) def test_that_plugin_forward_models_are_installed(tmp_path): @@ -670,7 +661,9 @@ def __init__(self): command=["something", "", "-f", "", ""], ) - def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, fm_step_json: ForwardModelStepJSON, env_vars: Dict[str, str] + ) -> None: if set(self.private_args.keys()) != {"", "", ""}: raise ForwardModelStepValidationError("Bad") @@ -894,6 +887,33 @@ def validate_pre_realization_run( ) +def test_that_plugin_forward_model_validation_sees_setenv(tmp_path): + (tmp_path / "test.ert").write_text( + """ + NUM_REALIZATIONS 1 + SETENV FOO bar + FORWARD_MODEL FM1() + """ + ) + + class ExceptionThatWeWant(ForwardModelStepValidationError): + pass + + class FM1(ForwardModelStepPlugin): + def __init__(self): + super().__init__(name="FM1", command=["dummy.sh"]) + + def validate_pre_experiment( + self, _: ForwardModelStepJSON, env_vars: Dict[str, str] + ) -> None: + raise ExceptionThatWeWant(f'Found FOO={env_vars["FOO"]}') + + with pytest.raises(ConfigValidationError, match=".*Found FOO=bar.*"): + ErtConfig.with_plugins(forward_model_step_classes=[FM1]).from_file( + tmp_path / "test.ert" + ) + + def test_that_plugin_forward_model_raises_pre_experiment_validation_error_early( tmp_path, ): @@ -912,7 +932,9 @@ class FM1(ForwardModelStepPlugin): def __init__(self): super().__init__(name="FM1", command=["the_executable.sh"]) - def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, fm_step_json: ForwardModelStepJSON, _: Dict[str, str] + ) -> None: if self.name != "FM1": raise ForwardModelStepValidationError("Expected name to be FM1") @@ -925,7 +947,9 @@ def __init__(self): command=["the_executable.sh"], ) - def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, fm_step_json: ForwardModelStepJSON, _: Dict[str, str] + ) -> None: if self.name != "FM2": raise ForwardModelStepValidationError("Expected name to be FM2") @@ -1012,7 +1036,9 @@ class FMWithAssertionError(ForwardModelStepPlugin): def __init__(self): super().__init__(name="FMWithAssertionError", command=["the_executable.sh"]) - def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, fm_step_json: ForwardModelStepJSON, _: dict + ) -> None: raise AssertionError("I should be a warning") class FMWithFMStepValidationError(ForwardModelStepPlugin): @@ -1022,7 +1048,9 @@ def __init__(self): command=["the_executable.sh"], ) - def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None: + def validate_pre_experiment( + self, fm_step_json: ForwardModelStepJSON, _: dict + ) -> None: raise ForwardModelStepValidationError("I should not be a warning") with (