diff --git a/bionetgen/core/tools/visualize.py b/bionetgen/core/tools/visualize.py index 983a82a2..707ffb2e 100644 --- a/bionetgen/core/tools/visualize.py +++ b/bionetgen/core/tools/visualize.py @@ -1,6 +1,8 @@ import os, bionetgen, glob from tempfile import TemporaryDirectory +from typing import NoReturn, Optional +from bionetgen.core.exc import BNGError, BNGFileError from bionetgen.core.utils.logging import BNGLogger @@ -135,7 +137,7 @@ def run(self) -> VisResult: self.logger.debug("Running", loc=f"{__file__} : BNGVisualize.run()") return self._normal_mode() - def _normal_mode(self): + def _normal_mode(self) -> VisResult: self.logger.debug( f"Running on normal mode, loading model {self.input}", loc=f"{__file__} : BNGVisualize._normal_mode()", @@ -182,42 +184,51 @@ def _normal_mode(self): with TemporaryDirectory() as out: # instantiate a CLI object with the info cli = BNGCLI(model, out, self.bngpath, suppress=self.suppress) - try: - cli.run() - # load vis - vis_res = VisResult( - os.path.abspath(os.getcwd()), - name=model.model_name, - vtype=self.vtype, - ) - # go back - os.chdir(cur_dir) - # dump files - vis_res._dump_files(cur_dir) - return vis_res - except Exception as e: - os.chdir(cur_dir) - print("Couldn't run the simulation, see error.") - raise e - else: - # instantiate a CLI object with the info - cli = BNGCLI(model, self.output, self.bngpath, suppress=self.suppress) - try: - cli.run() - # load vis - vis_res = VisResult( - os.path.abspath(os.getcwd()), - name=model.model_name, - vtype=self.vtype, - ) - # go back - os.chdir(cur_dir) - return vis_res - except Exception as e: - self.logger.error( - "Failed to run file", - loc=f"{__file__} : BNGVisualize._normal_mode()", + return self._run_and_collect_vis( + cli, + model_name=model.model_name, + cur_dir=cur_dir, + dump_dir=cur_dir, ) - os.chdir(cur_dir) - print("Couldn't run the simulation, see error.") - raise e + + # instantiate a CLI object with the info + cli = BNGCLI(model, self.output, self.bngpath, suppress=self.suppress) + return self._run_and_collect_vis( + cli, + model_name=model.model_name, + cur_dir=cur_dir, + ) + + def _run_and_collect_vis( + self, cli, *, model_name: str, cur_dir: str, dump_dir: Optional[str] = None + ) -> VisResult: + try: + cli.run() + vis_res = VisResult( + os.path.abspath(os.getcwd()), + name=model_name, + vtype=self.vtype, + ) + if dump_dir is not None: + vis_res._dump_files(dump_dir) + return vis_res + except BNGError as exc: + self._log_visualization_failure(exc) + raise + except OSError as exc: + self._raise_visualization_file_error(exc) + finally: + os.chdir(cur_dir) + + def _log_visualization_failure(self, exc: BaseException) -> None: + self.logger.error( + f"Failed to generate visualization files: {exc}", + loc=f"{__file__} : BNGVisualize._normal_mode()", + ) + + def _raise_visualization_file_error(self, exc: OSError) -> NoReturn: + self._log_visualization_failure(exc) + raise BNGFileError( + self.input, + message=f"Failed to generate visualization files: {exc}", + ) from exc diff --git a/tests/test_visualize_errors.py b/tests/test_visualize_errors.py new file mode 100644 index 00000000..adfb6417 --- /dev/null +++ b/tests/test_visualize_errors.py @@ -0,0 +1,66 @@ +from unittest import mock + +import pytest + +from bionetgen.core.exc import BNGFileError, BNGRunError + + +@pytest.mark.parametrize("use_output", [False, True]) +def test_normal_mode_logs_and_reraises_cli_failures(tmp_path, capsys, use_output): + from bionetgen.core.tools.visualize import BNGVisualize + + fake_model = mock.MagicMock() + fake_model.model_name = "test_model" + output = str(tmp_path / "viz") if use_output else None + visualize = BNGVisualize("test.bngl", output=output) + visualize.logger = mock.MagicMock() + + with mock.patch( + "bionetgen.core.tools.visualize.bionetgen.modelapi.bngmodel", + return_value=fake_model, + ), mock.patch("bionetgen.core.main.BNGCLI") as mock_cli_cls: + mock_cli_cls.return_value.run.side_effect = BNGRunError( + ["perl", "BNG2.pl", "test.bngl"], + message="boom", + ) + + with pytest.raises(BNGRunError, match="boom"): + visualize._normal_mode() + + captured = capsys.readouterr() + assert captured.out == "" + visualize.logger.error.assert_called_once() + error_args, error_kwargs = visualize.logger.error.call_args + assert error_args[0].startswith("Failed to generate visualization files:") + assert "boom" in error_args[0] + assert "BNGVisualize._normal_mode()" in error_kwargs["loc"] + + +def test_normal_mode_wraps_dump_failures(capsys): + from bionetgen.core.tools.visualize import BNGVisualize + + fake_model = mock.MagicMock() + fake_model.model_name = "test_model" + fake_vis_result = mock.MagicMock() + fake_vis_result._dump_files.side_effect = OSError("disk full") + visualize = BNGVisualize("test.bngl") + visualize.logger = mock.MagicMock() + + with mock.patch( + "bionetgen.core.tools.visualize.bionetgen.modelapi.bngmodel", + return_value=fake_model, + ), mock.patch("bionetgen.core.main.BNGCLI"), mock.patch( + "bionetgen.core.tools.visualize.VisResult", + return_value=fake_vis_result, + ): + with pytest.raises( + BNGFileError, match="Failed to generate visualization files: disk full" + ): + visualize._normal_mode() + + captured = capsys.readouterr() + assert captured.out == "" + visualize.logger.error.assert_called_once() + error_args, error_kwargs = visualize.logger.error.call_args + assert "Failed to generate visualization files: disk full" in error_args[0] + assert "BNGVisualize._normal_mode()" in error_kwargs["loc"]