diff --git a/src/foamlib/_cases/_run.py b/src/foamlib/_cases/_run.py index fa21c487..c63ea738 100644 --- a/src/foamlib/_cases/_run.py +++ b/src/foamlib/_cases/_run.py @@ -112,7 +112,7 @@ def clone(self, dst: os.PathLike[str] | str | None = None) -> object: @abstractmethod def _prepare( - self, *, check: bool = True, log: bool = True + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True ) -> None | Coroutine[None, None, None]: raise NotImplementedError @@ -124,25 +124,25 @@ def run( parallel: bool | None = None, cpus: int | None = None, check: bool = True, - log: bool = True, + log: bool | str | os.PathLike[str] = True, ) -> None | Coroutine[None, None, None]: raise NotImplementedError @abstractmethod def block_mesh( - self, *, check: bool = True, log: bool = True + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True ) -> None | Coroutine[None, None, None]: raise NotImplementedError @abstractmethod def decompose_par( - self, *, check: bool = True, log: bool = True + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True ) -> None | Coroutine[None, None, None]: raise NotImplementedError @abstractmethod def reconstruct_par( - self, *, check: bool = True, log: bool = True + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True ) -> None | Coroutine[None, None, None]: raise NotImplementedError @@ -262,13 +262,20 @@ def __cmd_name(cmd: Sequence[str | os.PathLike[str]] | str) -> str: @contextmanager def __output( - self, cmd: Sequence[str | os.PathLike[str]] | str, *, log: bool + self, + cmd: Sequence[str | os.PathLike[str]] | str, + *, + log: bool | str | os.PathLike[str], ) -> Generator[tuple[int | TextIOBase, int | TextIOBase], None, None]: - if log: - with (self.path / f"log.{self.__cmd_name(cmd)}").open("a") as stdout: - yield stdout, STDOUT - else: + if log is False: yield DEVNULL, DEVNULL + return + + if log is True: + log = f"log.{self.__cmd_name(cmd)}" + + with (self.path / log).open("a") as stdout: + yield stdout, STDOUT @contextmanager def __process_stdout( @@ -364,22 +371,22 @@ def _restore_0_dir_calls( yield self._copytree(self.path / "0.orig", self.path / "0", symlinks=True) def _block_mesh_calls( - self, *, check: bool, log: bool + self, *, check: bool, log: bool | str | os.PathLike[str] ) -> Generator[object | Coroutine[None, None, object], None, None]: yield self.run(["blockMesh"], cpus=0, check=check, log=log) def _decompose_par_calls( - self, *, check: bool, log: bool + self, *, check: bool, log: bool | str | os.PathLike[str] ) -> Generator[object | Coroutine[None, None, object], None, None]: yield self.run(["decomposePar"], cpus=0, check=check, log=log) def _reconstruct_par_calls( - self, *, check: bool, log: bool + self, *, check: bool, log: bool | str | os.PathLike[str] ) -> Generator[object | Coroutine[None, None, object], None, None]: yield self.run(["reconstructPar"], cpus=0, check=check, log=log) def _prepare_calls( - self, *, check: bool, log: bool + self, *, check: bool, log: bool | str | os.PathLike[str] ) -> Generator[object | Coroutine[None, None, object], None, None]: if (script_path := self.__prepare_script()) is not None: yield self.run([script_path], log=log, check=check) @@ -394,7 +401,7 @@ def _run_calls( cpus: int | None = None, parallel: bool | None, check: bool, - log: bool, + log: bool | str | os.PathLike[str], **kwargs: Any, ) -> Generator[object | Coroutine[None, None, object], None, None]: if cmd is not None: diff --git a/src/foamlib/_cases/async_.py b/src/foamlib/_cases/async_.py index 728920e7..5ddb6ac4 100644 --- a/src/foamlib/_cases/async_.py +++ b/src/foamlib/_cases/async_.py @@ -205,7 +205,9 @@ def __getitem__( return [AsyncFoamCase.TimeDirectory(r) for r in ret] @override - async def _prepare(self, *, check: bool = True, log: bool = True) -> None: + async def _prepare( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: for coro in self._prepare_calls(check=check, log=log): assert isinstance(coro, Awaitable) await coro @@ -218,7 +220,7 @@ async def run( parallel: bool | None = None, cpus: int | None = None, check: bool = True, - log: bool = True, + log: bool | str | os.PathLike[str] = True, ) -> None: """ Run this case, or a specified command in the context of this case. @@ -265,21 +267,27 @@ async def run( await coro @override - async def block_mesh(self, *, check: bool = True, log: bool = True) -> None: + async def block_mesh( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: """Run blockMesh on this case.""" for coro in self._block_mesh_calls(check=check, log=log): assert isinstance(coro, Awaitable) await coro @override - async def decompose_par(self, *, check: bool = True, log: bool = True) -> None: + async def decompose_par( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: """Decompose this case for parallel running.""" for coro in self._decompose_par_calls(check=check, log=log): assert isinstance(coro, Awaitable) await coro @override - async def reconstruct_par(self, *, check: bool = True, log: bool = True) -> None: + async def reconstruct_par( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: """Reconstruct this case after parallel running.""" for coro in self._reconstruct_par_calls(check=check, log=log): assert isinstance(coro, Awaitable) diff --git a/src/foamlib/_cases/slurm.py b/src/foamlib/_cases/slurm.py index 4673a0a9..b1d32b83 100644 --- a/src/foamlib/_cases/slurm.py +++ b/src/foamlib/_cases/slurm.py @@ -78,7 +78,7 @@ async def run( parallel: bool | None = None, cpus: int | None = None, check: bool = True, - log: bool = True, + log: bool | str | os.PathLike[str] = True, fallback: bool = False, ) -> None: """ diff --git a/src/foamlib/_cases/sync.py b/src/foamlib/_cases/sync.py index a23d0b54..a9c780ad 100644 --- a/src/foamlib/_cases/sync.py +++ b/src/foamlib/_cases/sync.py @@ -165,7 +165,9 @@ def clean(self, *, check: bool = False) -> None: pass @override - def _prepare(self, *, check: bool = True, log: bool = True) -> None: + def _prepare( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: for _ in self._prepare_calls(check=check, log=log): pass @@ -177,7 +179,7 @@ def run( parallel: bool | None = None, cpus: int | None = None, check: bool = True, - log: bool = True, + log: bool | str | os.PathLike[str] = True, ) -> None: """ Run this case, or a specified command in the context of this case. @@ -223,19 +225,25 @@ def run( pass @override - def block_mesh(self, *, check: bool = True, log: bool = True) -> None: + def block_mesh( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: """Run blockMesh on this case.""" for _ in self._block_mesh_calls(check=check, log=log): pass @override - def decompose_par(self, *, check: bool = True, log: bool = True) -> None: + def decompose_par( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: """Decompose this case for parallel running.""" for _ in self._decompose_par_calls(check=check, log=log): pass @override - def reconstruct_par(self, *, check: bool = True, log: bool = True) -> None: + def reconstruct_par( + self, *, check: bool = True, log: bool | str | os.PathLike[str] = True + ) -> None: """Reconstruct this case after parallel running.""" for _ in self._reconstruct_par_calls(check=check, log=log): pass diff --git a/tests/test_cases/test_flange.py b/tests/test_cases/test_flange.py index 712ec3ed..405fb2c2 100644 --- a/tests/test_cases/test_flange.py +++ b/tests/test_cases/test_flange.py @@ -71,3 +71,32 @@ def test_run_cmd_shell(flange: FoamCase) -> None: def test_path(flange: FoamCase) -> None: assert Path(flange) == flange.path + + +def test_run_cmd_log_false(flange: FoamCase) -> None: + if not flange: + flange.restore_0_dir() + + ans_path = ( + Path(os.environ["FOAM_TUTORIALS"]) / "resources" / "geometry" / "flange.ans" + ) + if not ans_path.exists(): + ans_path = Path("flange.ans") + + flange.run(["ansysToFoam", ans_path, "-scale", "0.001"], log=False) + assert not (flange.path / "log.ansysToFoam").exists() + + +def test_run_cmd_custom_log(flange: FoamCase) -> None: + if not flange: + flange.restore_0_dir() + + ans_path = ( + Path(os.environ["FOAM_TUTORIALS"]) / "resources" / "geometry" / "flange.ans" + ) + if not ans_path.exists(): + ans_path = Path("flange.ans") + + flange.run(["ansysToFoam", ans_path, "-scale", "0.001"], log="custom_ansys.log") + assert not (flange.path / "log.ansysToFoam").exists() + assert (flange.path / "custom_ansys.log").exists() diff --git a/tests/test_cases/test_flange_async.py b/tests/test_cases/test_flange_async.py index e82f6056..563242d1 100644 --- a/tests/test_cases/test_flange_async.py +++ b/tests/test_cases/test_flange_async.py @@ -94,3 +94,50 @@ async def test_run_cmd_shell(flange: AsyncFoamCase) -> None: def test_path(flange: AsyncFoamCase) -> None: assert Path(flange) == flange.path + + +@pytest.mark.asyncio +async def test_run_cmd_log_false(flange: AsyncFoamCase) -> None: + if not flange: + await flange.restore_0_dir() + + ans_path = ( + Path(os.environ["FOAM_TUTORIALS"]) / "resources" / "geometry" / "flange.ans" + ) + if not ans_path.exists(): + ans_path = Path("flange.ans") + + if isinstance(flange, AsyncSlurmFoamCase): + await flange.run( + ["ansysToFoam", ans_path, "-scale", "0.001"], log=False, fallback=True + ) + else: + await flange.run(["ansysToFoam", ans_path, "-scale", "0.001"], log=False) + + assert not (flange.path / "log.ansysToFoam").exists() + + +@pytest.mark.asyncio +async def test_run_cmd_custom_log(flange: AsyncFoamCase) -> None: + if not flange: + await flange.restore_0_dir() + + ans_path = ( + Path(os.environ["FOAM_TUTORIALS"]) / "resources" / "geometry" / "flange.ans" + ) + if not ans_path.exists(): + ans_path = Path("flange.ans") + + if isinstance(flange, AsyncSlurmFoamCase): + await flange.run( + ["ansysToFoam", ans_path, "-scale", "0.001"], + log="custom_ansys.log", + fallback=True, + ) + else: + await flange.run( + ["ansysToFoam", ans_path, "-scale", "0.001"], log="custom_ansys.log" + ) + + assert not (flange.path / "log.ansysToFoam").exists() + assert (flange.path / "custom_ansys.log").exists()