diff --git a/PyStemmusScope/bmi/docker_process.py b/PyStemmusScope/bmi/docker_process.py index 11ea0ecc..0bcc97fe 100644 --- a/PyStemmusScope/bmi/docker_process.py +++ b/PyStemmusScope/bmi/docker_process.py @@ -14,11 +14,19 @@ docker = None -def wait_for_model(phrase: bytes, socket: Any) -> None: +def _wait_for_model(phrase: bytes, socket: Any, client: Any, container_id: Any) -> None: """Wait for the model to be ready to receive (more) commands, or is finalized.""" output = b"" - + error_msg = b"Error in " while phrase not in output: + if error_msg in output: + client.stop(container_id) + logs = client.logs(container_id).decode("utf-8") + raise ValueError( + f"Error in container '{container_id['Id']}'. Please inspect logs." + "\nDOCKER LOGS:\n" + logs + ) + data = socket.read(1) if data is None: msg = "Could not read data from socket. Docker container might be dead." @@ -62,7 +70,9 @@ def __init__(self, cfg_file: str): def wait_for_model(self) -> None: """Wait for the model to be ready to receive (more) commands.""" - wait_for_model(self._process_ready_phrase, self.socket) + _wait_for_model( + self._process_ready_phrase, self.socket, self.client, self.container_id + ) def is_alive(self) -> bool: """Return if the process is alive.""" @@ -99,7 +109,12 @@ def finalize(self) -> None: """Finalize the model.""" if self.is_alive(): os.write(self.socket.fileno(), b"finalize\n") - wait_for_model(self._process_finalized_phrase, self.socket) + _wait_for_model( + self._process_finalized_phrase, + self.socket, + self.client, + self.container_id, + ) sleep(0.5) # Ensure the container can stop cleanly. self.client.stop(self.container_id) self.client.remove_container(self.container_id, v=True) diff --git a/pyproject.toml b/pyproject.toml index 3827f3dd..fc3b6d8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ docs = [ ] [tool.hatch.envs.default] -features = ["dev"] +features = ["dev", "docker"] [tool.hatch.envs.default.scripts] lint = [ diff --git a/tests/test_data/config_file_docker.txt b/tests/test_data/config_file_docker.txt new file mode 100644 index 00000000..cc401a3e --- /dev/null +++ b/tests/test_data/config_file_docker.txt @@ -0,0 +1,16 @@ +WorkDir=tests/test_data/directories/ +SoilPropertyPath=tests/test_data/directories/model_parameters/soil_property/ +ForcingPath=tests/test_data/directories/forcing/plumber2_data/ +Location=XX-Xxx +directional=tests/test_data/directories/model_parameters/vegetation_property/directional/ +fluspect_parameters=tests/test_data/directories/model_parameters/vegetation_property/fluspect_parameters/ +leafangles=tests/test_data/directories/model_parameters/vegetation_property/leafangles/ +radiationdata=tests/test_data/directories/model_parameters/vegetation_property/radiationdata/ +soil_spectrum=tests/test_data/directories/model_parameters/vegetation_property/soil_spectrum/ +input_data=tests/test_data/directories/model_parameters/vegetation_property/dummy_data.xlsx +InitialConditionPath=tests/test_data/directories/model_parameters/soil_initialcondition/ +StartTime=1996-01-01T00:00 +EndTime=1996-01-01T02:00 +InputPath= +OutputPath= +DockerImage=ghcr.io/ecoextreml/stemmus_scope:1.5.0 \ No newline at end of file diff --git a/tests/test_data/directories/model_parameters/vegetation_property/.gitignore b/tests/test_data/directories/model_parameters/vegetation_property/.gitignore new file mode 100644 index 00000000..aa410e12 --- /dev/null +++ b/tests/test_data/directories/model_parameters/vegetation_property/.gitignore @@ -0,0 +1,3 @@ +fluspect_parameters/Optipar2017_ProspectD.mat +radiationdata/FLEX-S3_std.atm +soil_spectrum/soilnew.txt diff --git a/tests/test_data/directories/model_parameters/vegetation_property/input_data.xlsx b/tests/test_data/directories/model_parameters/vegetation_property/input_data.xlsx new file mode 100755 index 00000000..70380768 Binary files /dev/null and b/tests/test_data/directories/model_parameters/vegetation_property/input_data.xlsx differ diff --git a/tests/test_docker_model.py b/tests/test_docker_model.py new file mode 100644 index 00000000..5e08f465 --- /dev/null +++ b/tests/test_docker_model.py @@ -0,0 +1,79 @@ +from pathlib import Path +from PyStemmusScope.bmi.implementation import StemmusScopeBmi +from PyStemmusScope import config_io, forcing_io, soil_io +from distutils.dir_util import copy_tree +import requests + +from . import data_folder +import pytest + + +SCOPE_INPUTDATA_v2_1 = "https://github.com/Christiaanvandertol/SCOPE/raw/2.1/input/" +SCOPE_INPUTDATA_v1_7 = "https://github.com/Christiaanvandertol/SCOPE/raw/1.73/data/input/" + + +cfg_file = data_folder / "config_file_docker.txt" +vegetation_property_dir = data_folder / "directories" / "model_parameters" / "vegetation_property" + + +def write_config_file(cfg: dict, file: Path) -> None: + with file.open("w") as f: + for key, val in cfg.items(): + f.write(f"{key}={val}\n") + + +@pytest.fixture(scope="session") +def prep_input_data(): + optipar_path = vegetation_property_dir / "fluspect_parameters" / "Optipar2017_ProspectD.mat" + if not optipar_path.exists(): + r = requests.get( # Older version due to v2 compatibility issues + SCOPE_INPUTDATA_v1_7 + "fluspect_parameters/Optipar2017_ProspectD.mat" + ) + assert r.status_code == 200 + optipar_path.open("wb").write(r.content) + + flex_s3_path = vegetation_property_dir / "radiationdata" / "FLEX-S3_std.atm" + if not flex_s3_path.exists(): + r = requests.get( + SCOPE_INPUTDATA_v2_1 + "radiationdata/FLEX-S3_std.atm" + ) + assert r.status_code == 200 + flex_s3_path.open("wb").write(r.content) + + soil_path = vegetation_property_dir / "soil_spectrum" / "soilnew.txt" + if not soil_path.exists(): + r = requests.get( + SCOPE_INPUTDATA_v2_1 + "soil_spectra/soilnew.txt" + ) + assert r.status_code == 200 + soil_path.open("wb").write(r.content) + + +@pytest.fixture(scope="session") +def prepare_data_config(tmpdir_factory, prep_input_data) -> Path: + tempdir = Path(tmpdir_factory.mktemp("tempdir")) + output_dir = tempdir / "output_dir" + input_dir = tempdir / "input_dir" + output_dir.mkdir() + input_dir.mkdir() + config = config_io.read_config(cfg_file) + config["OutputPath"] = str(output_dir) + "/" + config["InputPath"] = str(input_dir) + "/" + + forcing_io.prepare_forcing(config) + soil_io.prepare_soil_data(config) + soil_io.prepare_soil_init(config) + + copy_tree(src=str(vegetation_property_dir), dst=str(input_dir)) + + config_dir = tempdir / "config.txt" + write_config_file(config, config_dir) + + return config_dir + + +def test_initialize(prepare_data_config): + model = StemmusScopeBmi() + model.initialize(str(prepare_data_config)) + model.update() + model.finalize()