Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
validate eclrun-version with no eclconfig, untested
Browse files Browse the repository at this point in the history
wip: simplified EclRun

wip: can run eclipse on flow_example

wip: eclipse maybe works, flowrun missing

Renamed files, merging eclrun and flowrun now

Merged runner script for flowrun and eclrun

more opm flowrun tests in place

Allow usage of direct flow installation if no flowrun

No MPI run without flowrun

fixup, test_await needs to u se tmp_dir fixture

Can't test eclipse version when no elcrun. UNTESTED

fixup d3ee82b127410fea173d7e914dd80dfb5132831e2

wip: dedicated test_run_reservoirsimultor
berland committed Oct 31, 2024
1 parent ee0c26f commit d50f250
Showing 16 changed files with 705 additions and 1,667 deletions.
45 changes: 14 additions & 31 deletions src/ert/plugins/hook_implementations/forward_model_steps.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import os
import shutil
import subprocess
from pathlib import Path
from textwrap import dedent
from typing import List, Literal, Optional, Type

import yaml

from ert import (
ForwardModelStepDocumentation,
ForwardModelStepJSON,
@@ -207,12 +204,12 @@ def __init__(self) -> None:
str(
(
Path(__file__)
/ "../../../resources/forward_models/res/script/ecl100.py"
/ "../../../resources/forward_models/run_reservoirsimulator.py"
).resolve()
),
"<ECLBASE>",
"-v",
"eclipse",
"<VERSION>",
"<ECLBASE>",
"-n",
"<NUM_CPU>",
"<OPTS>",
@@ -265,12 +262,12 @@ def __init__(self) -> None:
str(
(
Path(__file__)
/ "../../../resources/forward_models/res/script/ecl300.py"
/ "../../../resources/forward_models/run_reservoirsimulator.py"
).resolve()
),
"<ECLBASE>",
"-v",
"e300",
"<VERSION>",
"<ECLBASE>",
"-n",
"<NUM_CPU>",
"<OPTS>",
@@ -317,11 +314,11 @@ def __init__(self) -> None:
str(
(
Path(__file__)
/ "../../../resources/forward_models/res/script/flow.py"
/ "../../../resources/forward_models/run_reservoirsimulator.py"
).resolve()
),
"flow",
"<ECLBASE>",
"-v",
"<VERSION>",
"-n",
"<NUM_CPU>",
@@ -629,31 +626,17 @@ def installable_forward_model_steps() -> List[Type[ForwardModelStepPlugin]]:


def _available_eclrun_versions(simulator: Literal["eclipse", "e300"]) -> List[str]:
if shutil.which("eclrun") is None:
return []
pm = ErtPluginManager()
ecl_config_path = (
pm.get_ecl100_config_path()
if simulator == "eclipse"
else pm.get_ecl300_config_path()
eclrun_env = os.environ.copy()
eclrun_env["PATH"] = os.pathsep.join(
pm.get_forward_model_paths() + os.getenv("PATH", "").split(os.pathsep)
)

if not ecl_config_path:
return []
eclrun_env = {"PATH": os.getenv("PATH", "")}

with open(ecl_config_path, encoding="utf-8") as f:
try:
config = yaml.safe_load(f)
except yaml.YAMLError as e:
raise ValueError(f"Failed parse: {ecl_config_path} as yaml") from e
ecl_install_path = config.get("eclrun_env", {}).get("PATH", "")
eclrun_env["PATH"] = eclrun_env["PATH"] + os.pathsep + ecl_install_path

try:
return (
subprocess.check_output(
["eclrun", "--report-versions", simulator],
# It is not sufficient to just give the correct path to eclrun, its PATH must include its location
# for versions to be displayed. Ensure this is covered in tests.
["eclrun", simulator, "--report-versions"],
env=eclrun_env,
)
.decode("utf-8")
9 changes: 0 additions & 9 deletions src/ert/resources/forward_models/res/script/ecl100.py

This file was deleted.

14 changes: 0 additions & 14 deletions src/ert/resources/forward_models/res/script/ecl100_config.yml

This file was deleted.

9 changes: 0 additions & 9 deletions src/ert/resources/forward_models/res/script/ecl300.py

This file was deleted.

8 changes: 0 additions & 8 deletions src/ert/resources/forward_models/res/script/ecl300_config.yml

This file was deleted.

280 changes: 0 additions & 280 deletions src/ert/resources/forward_models/res/script/ecl_config.py

This file was deleted.

588 changes: 0 additions & 588 deletions src/ert/resources/forward_models/res/script/ecl_run.py

This file was deleted.

9 changes: 0 additions & 9 deletions src/ert/resources/forward_models/res/script/flow.py

This file was deleted.

417 changes: 417 additions & 0 deletions src/ert/resources/forward_models/run_reservoirsimulator.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test-data/ert/flow_example/flow.ert
Original file line number Diff line number Diff line change
@@ -16,4 +16,4 @@ GEN_KW FIELD_PROPERTIES field_properties_priors

FORWARD_MODEL TEMPLATE_RENDER(<INPUT_FILES>=parameters.json, <TEMPLATE_FILE>=<CONFIG_PATH>/resources/SPE1.DATA.jinja2, <OUTPUT_FILE>=SPE1.DATA)

FORWARD_MODEL FLOW
FORWARD_MODEL ECLIPSE300(<VERSION>=2019.3)
26 changes: 6 additions & 20 deletions tests/ert/unit_tests/config/test_forward_model.py
Original file line number Diff line number Diff line change
@@ -575,36 +575,22 @@ def test_that_eclipse_jobs_require_version_field(eclipse_v):
)


@pytest.mark.skipif(shutil.which("eclrun") is not None, reason="eclrun is available")
@pytest.mark.parametrize("eclipse_v", ["100", "300"])
@pytest.mark.usefixtures("use_tmpdir")
def test_that_eclipse_jobs_check_version(eclipse_v, mock_eclrun):
ecl100_config_file_name = "ecl100_config.yml"
ecl300_config_file_name = "ecl300_config.yml"

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} (<VERSION>=1)\n"
)

# Write config file
config_file_name = "test.ert"
Path(config_file_name).write_text(ert_config_contents, encoding="utf-8")
# Write ecl100_config file
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")
203 changes: 0 additions & 203 deletions tests/ert/unit_tests/resources/test_ecl_versioning_config.py

This file was deleted.

333 changes: 0 additions & 333 deletions tests/ert/unit_tests/resources/test_opm_flow.py

This file was deleted.

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions tests/ert/unit_tests/resources/test_run_flow_simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import shutil
from pathlib import Path
from subprocess import CalledProcessError

import pytest

from tests.ert.utils import SOURCE_DIR

from ._import_from_location import import_from_location

# import ecl_config.py and ecl_run from ert/forward-models package-data path
# which. These are kept out of the ert package to avoid the overhead of
# importing ert. This is necessary as these may be invoked as a subprocess on
# each realization.

run_reservoirsimulator = import_from_location(
"run_reservoirsimulator",
SOURCE_DIR / "src/ert/resources/forward_models/run_reservoirsimulator.py",
)

FLOW_VERSION = "daily"


@pytest.mark.integration_test
def test_flow_can_produce_output(source_root):
shutil.copy(source_root / "test-data/ert/eclipse/SPE1.DATA", "SPE1.DATA")
run_reservoirsimulator.RunReservoirSimulator(
"flow", FLOW_VERSION, "SPE1.DATA"
).runFlow()
assert Path("SPE1.UNSMRY").exists()


@pytest.mark.integration_test
def test_flowrunner_will_raise_when_flow_fails(source_root):
shutil.copy(
source_root / "test-data/ert/eclipse/SPE1_ERROR.DATA", "SPE1_ERROR.DATA"
)
with pytest.raises(CalledProcessError, match="returned non-zero exit status 1"):
run_reservoirsimulator.RunReservoirSimulator(
"flow", FLOW_VERSION, "SPE1_ERROR.DATA"
).runFlow()


@pytest.mark.integration_test
def test_flowrunner_will_can_ignore_flow_errors(source_root):
shutil.copy(
source_root / "test-data/ert/eclipse/SPE1_ERROR.DATA", "SPE1_ERROR.DATA"
)
run_reservoirsimulator.RunReservoirSimulator(
"flow", FLOW_VERSION, "SPE1_ERROR.DATA", check_status=False
).runFlow()


@pytest.mark.integration_test
def test_flowrunner_will_raise_on_unknown_version():
with pytest.raises(CalledProcessError):
run_reservoirsimulator.RunReservoirSimulator(
"flow", "garbled_version", ""
).runFlow()


@pytest.mark.integration_test
def test_flow_with_parallel_keyword(source_root):
"""This only tests that ERT will be able to start flow on a data deck with
the PARALLEL keyword present. It does not assert anything regarding whether
MPI-parallelization will get into play."""
shutil.copy(
source_root / "test-data/ert/eclipse/SPE1_PARALLEL.DATA", "SPE1_PARALLEL.DATA"
)
run_reservoirsimulator.RunReservoirSimulator(
"flow", FLOW_VERSION, "SPE1_PARALLEL.DATA"
).runFlow()
102 changes: 102 additions & 0 deletions tests/ert/unit_tests/resources/test_run_reservoirsimulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import threading
import time

import numpy as np
import pytest
import resfo

from tests.ert.utils import SOURCE_DIR

from ._import_from_location import import_from_location

# import ecl_config.py and ecl_run from ert/resources/forward_models
# package-data path which. These are kept out of the ert package to avoid the
# overhead of importing ert. This is necessary as these may be invoked as a
# subprocess on each realization.


run_reservoirsimulator = import_from_location(
"run_reservoirsimulator",
SOURCE_DIR / "src/ert/resources/forward_models/run_reservoirsimulator.py",
)
ecl_case_to_data_file = import_from_location(
"ecl_case_to_data_file",
SOURCE_DIR / "src/ert/resources/forward_models/run_reservoirsimulator.py",
)


def test_runners_are_found_from_path():
# assert different runners are looked for given wanted
# simulator
# assert runtimeoerror if no runner found
pass


def test_flowrun_can_be_bypassed():
# if flow is in path, then we can bypass
# assert an error if num_cpu is more than 1., not suppported yet.
pass


def test_runner_fails_on_missing_data_file():
pass


def test_ecl_case_from_data_file():
pass


@pytest.mark.usefixtures("use_tmpdir")
def test_await_completed_summary_file_will_timeout_on_missing_smry():
assert (
# Expected wait time is 0.3
run_reservoirsimulator.await_completed_unsmry_file(
"SPE1.UNSMRY", max_wait=0.3, poll_interval=0.1
)
> 0.3
)


@pytest.mark.usefixtures("use_tmpdir")
def test_await_completed_summary_file_will_return_asap():
resfo.write("FOO.UNSMRY", [("INTEHEAD", np.array([1], dtype=np.int32))])
assert (
0.01
# Expected wait time is the poll_interval
< run_reservoirsimulator.await_completed_unsmry_file(
"FOO.UNSMRY", max_wait=0.5, poll_interval=0.1
)
< 0.4
)


@pytest.mark.flaky(reruns=5)
@pytest.mark.integration_test
@pytest.mark.usefixtures("use_tmpdir")
def test_await_completed_summary_file_will_wait_for_slow_smry():
# This is a timing test, and has inherent flakiness:
# * Reading and writing to the same smry file at the same time
# can make the reading throw an exception every time, and can
# result in max_wait triggering.
# * If the writer thread is starved, two consecutive polls may
# yield the same summary length, resulting in premature exit.
# * Heavily loaded hardware can make everything go too slow.
def slow_smry_writer():
for size in range(10):
resfo.write(
"FOO.UNSMRY", (size + 1) * [("INTEHEAD", np.array([1], dtype=np.int32))]
)
time.sleep(0.05)

thread = threading.Thread(target=slow_smry_writer)
thread.start()
time.sleep(0.1) # Let the thread start writing
assert (
0.5
# Minimal wait time is around 0.55
< run_reservoirsimulator.await_completed_unsmry_file(
"FOO.UNSMRY", max_wait=4, poll_interval=0.21
)
< 2
)
thread.join()

0 comments on commit d50f250

Please sign in to comment.