From fc8c72b69e0fe08db8cd1fac4589e489e017e807 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 12 Mar 2026 16:04:59 +0530 Subject: [PATCH 01/29] refactor: added the changes --- doc/changelog.d/4265.fixed.md | 2 + .../fluent/core/launcher/launcher_utils.py | 12 ++- .../core/launcher/standalone_launcher.py | 74 +++++++++++++------ src/ansys/fluent/core/session_solver.py | 12 ++- src/ansys/fluent/core/session_solver.pyi | 3 +- tests/test_launcher.py | 41 ++++++++++ 6 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 doc/changelog.d/4265.fixed.md diff --git a/doc/changelog.d/4265.fixed.md b/doc/changelog.d/4265.fixed.md new file mode 100644 index 00000000000..40d959e7507 --- /dev/null +++ b/doc/changelog.d/4265.fixed.md @@ -0,0 +1,2 @@ +- Fixed `launch_fluent()` ordering so `case_file_name`/`case_data_file_name` are processed before `journal_file_names` when both are provided. +- In lightweight mode, deferred journal replay now completes before the background sync step begins. \ No newline at end of file diff --git a/src/ansys/fluent/core/launcher/launcher_utils.py b/src/ansys/fluent/core/launcher/launcher_utils.py index 61fa10221bc..f3a587bf6ee 100644 --- a/src/ansys/fluent/core/launcher/launcher_utils.py +++ b/src/ansys/fluent/core/launcher/launcher_utils.py @@ -181,12 +181,16 @@ def _confirm_watchdog_start(start_watchdog, cleanup_on_exit, fluent_connection): def _build_journal_argument( - topy: None | bool | str, journal_file_names: None | str | list[str] + topy: None | bool | str, + journal_file_names: None | str | list[str], + include_journal_file_names: bool = True, ) -> str: """Build Fluent commandline journal argument.""" def _impl( - topy: None | bool | str, journal_file_names: None | str | list[str] + topy: None | bool | str, + journal_file_names: None | str | list[str], + include_journal_file_names: bool, ) -> str: if journal_file_names and not isinstance(journal_file_names, (str, list)): raise TypeError( @@ -199,7 +203,7 @@ def _impl( fluent_jou_arg = "" if isinstance(journal_file_names, str): journal_file_names = [journal_file_names] - if journal_file_names: + if journal_file_names and include_journal_file_names: fluent_jou_arg += "".join( [f' -i "{journal}"' for journal in journal_file_names] ) @@ -210,4 +214,4 @@ def _impl( fluent_jou_arg += " -topy" return fluent_jou_arg - return _impl(topy, journal_file_names) + return _impl(topy, journal_file_names, include_journal_file_names) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 7812a253458..7561578e5cd 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -228,8 +228,14 @@ def __init__( ) if self.argvals["cwd"]: self._kwargs.update(cwd=self.argvals["cwd"]) + self._defer_journal_file_read = bool( + self.argvals["journal_file_names"] + and (self.argvals["case_file_name"] or self.argvals["case_data_file_name"]) + ) self._launch_string += _build_journal_argument( - self.argvals["topy"], self.argvals["journal_file_names"] + self.argvals["topy"], + self.argvals["journal_file_names"], + include_journal_file_names=not self._defer_journal_file_read, ) if is_windows(): @@ -292,26 +298,7 @@ def __call__(self): if len(values) == 3: ip, port, password = values watchdog.launch(os.getpid(), port, password, ip) - if self.argvals["case_file_name"]: - if FluentMode.is_meshing(self.argvals["mode"]): - session.tui.file.read_case(self.argvals["case_file_name"]) - elif self.argvals["lightweight_mode"]: - session.read_case_lightweight(self.argvals["case_file_name"]) - else: - session.settings.file.read( - file_type="case", - file_name=self.argvals["case_file_name"], - ) - if self.argvals["case_data_file_name"]: - if not FluentMode.is_meshing(self.argvals["mode"]): - session.settings.file.read( - file_type="case-data", - file_name=self.argvals["case_data_file_name"], - ) - else: - raise RuntimeError( - "Case and data file cannot be read in meshing mode." - ) + self._process_case_data_and_journals(session) return session except Exception as ex: @@ -321,3 +308,48 @@ def __call__(self): server_info_file = Path(self._server_info_file_name) if server_info_file.exists(): server_info_file.unlink() + + @staticmethod + def _get_journal_file_names( + journal_file_names: None | str | list[str], + ) -> list[str]: + if isinstance(journal_file_names, str): + return [journal_file_names] + return journal_file_names or [] + + def _read_journals(self, session) -> None: + for journal_file_name in self._get_journal_file_names( + self.argvals["journal_file_names"] + ): + session.execute_tui( + f'/file/read-journal "{Path(journal_file_name).as_posix()}"' + ) + + def _process_case_data_and_journals(self, session) -> None: + lightweight_sync_deferred = False + if self.argvals["case_file_name"]: + if FluentMode.is_meshing(self.argvals["mode"]): + session.tui.file.read_case(self.argvals["case_file_name"]) + elif self.argvals["lightweight_mode"]: + session.read_case_lightweight( + self.argvals["case_file_name"], + start_sync=not self._defer_journal_file_read, + ) + lightweight_sync_deferred = self._defer_journal_file_read + else: + session.settings.file.read( + file_type="case", + file_name=self.argvals["case_file_name"], + ) + if self.argvals["case_data_file_name"]: + if not FluentMode.is_meshing(self.argvals["mode"]): + session.settings.file.read( + file_type="case-data", + file_name=self.argvals["case_data_file_name"], + ) + else: + raise RuntimeError("Case and data file cannot be read in meshing mode.") + if self._defer_journal_file_read: + self._read_journals(session) + if lightweight_sync_deferred: + session.start_case_lightweight_sync() diff --git a/src/ansys/fluent/core/session_solver.py b/src/ansys/fluent/core/session_solver.py index ad7d904a948..05039ec3b17 100644 --- a/src/ansys/fluent/core/session_solver.py +++ b/src/ansys/fluent/core/session_solver.py @@ -316,13 +316,21 @@ def _stop_bg_sessions(self): if thread.is_alive(): thread.join() - def read_case_lightweight(self, file_name: str): + def start_case_lightweight_sync(self): + """Start pending lightweight background sync sessions.""" + for thread in self._bg_session_threads: + if thread.ident is None: + thread.start() + + def read_case_lightweight(self, file_name: str, start_sync: bool = True): """Read a case file using light IO mode. Parameters ---------- file_name : str Case file name + start_sync : bool, optional + Whether to immediately start lightweight background sync. """ self.settings.file.read( @@ -336,6 +344,8 @@ def read_case_lightweight(self, file_name: str): target=self._start_bg_session_and_sync, args=(launcher_args,) ) ) + if start_sync: + self.start_case_lightweight_sync() def get_state(self) -> StateT: """Get the state of the object.""" diff --git a/src/ansys/fluent/core/session_solver.pyi b/src/ansys/fluent/core/session_solver.pyi index 0fb2ca3385e..0284e0b6e6c 100644 --- a/src/ansys/fluent/core/session_solver.pyi +++ b/src/ansys/fluent/core/session_solver.pyi @@ -39,7 +39,8 @@ class Solver: def system_coupling(self) -> SystemCoupling: ... @property def preferences(self) -> preferences_root: ... - def read_case_lightweight(self, file_name: str): ... + def start_case_lightweight_sync(self): ... + def read_case_lightweight(self, file_name: str, start_sync: bool = True): ... def read_case(self, file_name: str): ... def write_case(self, file_name: str): ... @property diff --git a/tests/test_launcher.py b/tests/test_launcher.py index d6b7c73f11f..ce67c1dd984 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -57,6 +57,7 @@ _build_fluent_launch_args_string, get_fluent_exe_path, ) +from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher from ansys.fluent.core.utils.fluent_version import FluentVersion import ansys.platform.instancemanagement as pypim @@ -474,6 +475,46 @@ def test_build_journal_argument(topy, journal_file_names, result, raises): assert _build_journal_argument(topy, journal_file_names) == result +def test_build_journal_argument_without_journal_files_but_with_topy(): + assert ( + _build_journal_argument("a.py", ["a.jou"], include_journal_file_names=False) + == ' -topy="a.py"' + ) + + +def test_lightweight_case_journal_read_is_completed_before_sync_step(): + launcher = object.__new__(StandaloneLauncher) + launcher.argvals = { + "case_file_name": "a.cas.h5", + "case_data_file_name": None, + "mode": FluentMode.SOLVER, + "lightweight_mode": True, + "journal_file_names": ["a.jou", "b.jou"], + } + launcher._defer_journal_file_read = True + + calls = [] + + class _DummySession: + def read_case_lightweight(self, file_name, start_sync=True): + calls.append(("read_case_lightweight", file_name, start_sync)) + + def execute_tui(self, command): + calls.append(("execute_tui", command)) + + def start_case_lightweight_sync(self): + calls.append(("start_case_lightweight_sync",)) + + launcher._process_case_data_and_journals(_DummySession()) + + assert calls == [ + ("read_case_lightweight", "a.cas.h5", False), + ("execute_tui", '/file/read-journal "a.jou"'), + ("execute_tui", '/file/read-journal "b.jou"'), + ("start_case_lightweight_sync",), + ] + + def test_show_gui_raises_warning(): with pytest.warns(PyFluentDeprecationWarning): grpc_kwds = get_grpc_launcher_args_for_gh_runs() From f94faa66206257d7ce3b4f9dbc5ad70c81ef3818 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:46:22 +0000 Subject: [PATCH 02/29] chore: adding changelog file 4990.miscellaneous.md [dependabot-skip] --- doc/changelog.d/4990.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4990.miscellaneous.md diff --git a/doc/changelog.d/4990.miscellaneous.md b/doc/changelog.d/4990.miscellaneous.md new file mode 100644 index 00000000000..e61b5c8ce96 --- /dev/null +++ b/doc/changelog.d/4990.miscellaneous.md @@ -0,0 +1 @@ +Couple of issues in launch fluent From 25a3143afe893782e97de15f0214584f2a307301 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 17 Mar 2026 17:10:53 +0530 Subject: [PATCH 03/29] update --- tests/test_launcher.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index ce67c1dd984..d6da5580f0e 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -515,6 +515,42 @@ def start_case_lightweight_sync(self): ] +def test_case_and_case_data_are_processed_before_journal_files(): + launcher = object.__new__(StandaloneLauncher) + launcher.argvals = { + "case_file_name": "a.cas.h5", + "case_data_file_name": "a.cas.h5", + "mode": FluentMode.SOLVER, + "lightweight_mode": False, + "journal_file_names": ["a.jou", "b.jou"], + } + launcher._defer_journal_file_read = True + + calls = [] + + class _DummyFile: + def read(self, **kwargs): + calls.append(("read", kwargs)) + + class _DummySettings: + file = _DummyFile() + + class _DummySession: + settings = _DummySettings() + + def execute_tui(self, command): + calls.append(("execute_tui", command)) + + launcher._process_case_data_and_journals(_DummySession()) + + assert calls == [ + ("read", {"file_type": "case", "file_name": "a.cas.h5"}), + ("read", {"file_type": "case-data", "file_name": "a.cas.h5"}), + ("execute_tui", '/file/read-journal "a.jou"'), + ("execute_tui", '/file/read-journal "b.jou"'), + ] + + def test_show_gui_raises_warning(): with pytest.warns(PyFluentDeprecationWarning): grpc_kwds = get_grpc_launcher_args_for_gh_runs() From 26d259fb62867b0374ddba9dab438df03eff746f Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 16 Jun 2026 20:58:39 +0530 Subject: [PATCH 04/29] updated the standalone_launcher --- .../core/launcher/standalone_launcher.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index e429d5d0107..9106fec01db 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -232,11 +232,11 @@ def __init__( self.argvals["journal_file_names"] and (self.argvals["case_file_name"] or self.argvals["case_data_file_name"]) ) - self._launch_string += _build_journal_argument( - self.argvals["topy"], - self.argvals["journal_file_names"], - include_journal_file_names=not self._defer_journal_file_read, - ) + topy = self.argvals.get("topy", []) + if topy: + self._launch_string += _build_journal_argument( + topy, self.argvals.get("journal_file_names") + ) if is_windows(): self._launch_cmd = self._launch_string @@ -351,6 +351,15 @@ def _process_case_data_and_journals(self, session) -> None: ) else: raise RuntimeError("Case and data file cannot be read in meshing mode.") + + if not self.argvals.get("topy"): + journal_file_names = self.argvals.get("journal_file_names") + if journal_file_names and not self._defer_journal_file_read: + if isinstance(journal_file_names, str): + journal_file_names = [journal_file_names] + for journal_file_name in journal_file_names: + session.tui.file.read_journal(journal_file_name) + if self._defer_journal_file_read: self._read_journals(session) if lightweight_sync_deferred: From bbcd2fdcaff58713c974900caab59eff81d57d67 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Wed, 17 Jun 2026 14:46:00 +0530 Subject: [PATCH 05/29] resolved the conflict --- doc/changelog.d/4265.fixed.md | 2 + .../fluent/core/launcher/launcher_utils.py | 12 ++- .../core/launcher/standalone_launcher.py | 86 ++++++++++++------- src/ansys/fluent/core/session_solver.py | 12 ++- tests/test_launcher.py | 41 +++++++++ 5 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 doc/changelog.d/4265.fixed.md diff --git a/doc/changelog.d/4265.fixed.md b/doc/changelog.d/4265.fixed.md new file mode 100644 index 00000000000..40d959e7507 --- /dev/null +++ b/doc/changelog.d/4265.fixed.md @@ -0,0 +1,2 @@ +- Fixed `launch_fluent()` ordering so `case_file_name`/`case_data_file_name` are processed before `journal_file_names` when both are provided. +- In lightweight mode, deferred journal replay now completes before the background sync step begins. \ No newline at end of file diff --git a/src/ansys/fluent/core/launcher/launcher_utils.py b/src/ansys/fluent/core/launcher/launcher_utils.py index c4cc1e112d9..602199a8f20 100644 --- a/src/ansys/fluent/core/launcher/launcher_utils.py +++ b/src/ansys/fluent/core/launcher/launcher_utils.py @@ -210,12 +210,16 @@ def _confirm_watchdog_start(start_watchdog, cleanup_on_exit, fluent_connection): def _build_journal_argument( - topy: None | bool | str, journal_file_names: None | str | list[str] + topy: None | bool | str, + journal_file_names: None | str | list[str], + include_journal_file_names: bool = True, ) -> str: """Build Fluent commandline journal argument.""" def _impl( - topy: None | bool | str, journal_file_names: None | str | list[str] + topy: None | bool | str, + journal_file_names: None | str | list[str], + include_journal_file_names: bool, ) -> str: if journal_file_names and not isinstance(journal_file_names, (str, list)): raise TypeError( @@ -228,7 +232,7 @@ def _impl( fluent_jou_arg = "" if isinstance(journal_file_names, str): journal_file_names = [journal_file_names] - if journal_file_names: + if journal_file_names and include_journal_file_names: fluent_jou_arg += "".join( [f' -i "{journal}"' for journal in journal_file_names] ) @@ -239,4 +243,4 @@ def _impl( fluent_jou_arg += " -topy" return fluent_jou_arg - return _impl(topy, journal_file_names) + return _impl(topy, journal_file_names, include_journal_file_names) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 339a7412aec..d8c72e512ca 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -250,10 +250,16 @@ def __init__( self._kwargs = _get_subprocess_kwargs_for_fluent( self.argvals.get("env") or {}, self.argvals ) - if self.argvals.get("cwd"): - self._kwargs.update(cwd=self.argvals.get("cwd")) + if self.argvals["cwd"]: + self._kwargs.update(cwd=self.argvals["cwd"]) + self._defer_journal_file_read = bool( + self.argvals["journal_file_names"] + and (self.argvals["case_file_name"] or self.argvals["case_data_file_name"]) + ) self._launch_string += _build_journal_argument( - self.argvals.get("topy", []), self.argvals.get("journal_file_names") + self.argvals["topy"], + self.argvals["journal_file_names"], + include_journal_file_names=not self._defer_journal_file_read, ) if is_windows(): @@ -319,33 +325,8 @@ def __call__( values = _get_server_info(self._server_info_file_name) if len(values) == 3: ip, port, password = values - watchdog.launch( - os.getpid(), - port, - password, - ip, - inside_container=False, - ) - if self.argvals.get("case_file_name"): - if FluentMode.is_meshing(self.argvals.get("mode")): - session.tui.file.read_case(self.argvals.get("case_file_name")) - elif self.argvals.get("lightweight_mode"): - session.read_case_lightweight(self.argvals.get("case_file_name")) - else: - session.settings.file.read( - file_type="case", - file_name=self.argvals.get("case_file_name"), - ) - if self.argvals.get("case_data_file_name"): - if not FluentMode.is_meshing(self.argvals.get("mode")): - session.settings.file.read( - file_type="case-data", - file_name=self.argvals.get("case_data_file_name"), - ) - else: - raise RuntimeError( - "Case and data file cannot be read in meshing mode." - ) + watchdog.launch(os.getpid(), port, password, ip) + self._process_case_data_and_journals(session) return session except Exception as ex: @@ -355,3 +336,48 @@ def __call__( server_info_file = Path(self._server_info_file_name) if server_info_file.exists(): server_info_file.unlink() + + @staticmethod + def _get_journal_file_names( + journal_file_names: None | str | list[str], + ) -> list[str]: + if isinstance(journal_file_names, str): + return [journal_file_names] + return journal_file_names or [] + + def _read_journals(self, session) -> None: + for journal_file_name in self._get_journal_file_names( + self.argvals["journal_file_names"] + ): + session.execute_tui( + f'/file/read-journal "{Path(journal_file_name).as_posix()}"' + ) + + def _process_case_data_and_journals(self, session) -> None: + lightweight_sync_deferred = False + if self.argvals["case_file_name"]: + if FluentMode.is_meshing(self.argvals["mode"]): + session.tui.file.read_case(self.argvals["case_file_name"]) + elif self.argvals["lightweight_mode"]: + session.read_case_lightweight( + self.argvals["case_file_name"], + start_sync=not self._defer_journal_file_read, + ) + lightweight_sync_deferred = self._defer_journal_file_read + else: + session.settings.file.read( + file_type="case", + file_name=self.argvals["case_file_name"], + ) + if self.argvals["case_data_file_name"]: + if not FluentMode.is_meshing(self.argvals["mode"]): + session.settings.file.read( + file_type="case-data", + file_name=self.argvals["case_data_file_name"], + ) + else: + raise RuntimeError("Case and data file cannot be read in meshing mode.") + if self._defer_journal_file_read: + self._read_journals(session) + if lightweight_sync_deferred: + session.start_case_lightweight_sync() diff --git a/src/ansys/fluent/core/session_solver.py b/src/ansys/fluent/core/session_solver.py index 56fe17e9f8f..8360aeabe88 100644 --- a/src/ansys/fluent/core/session_solver.py +++ b/src/ansys/fluent/core/session_solver.py @@ -385,13 +385,21 @@ def _stop_bg_sessions(self): if thread.is_alive(): thread.join() - def read_case_lightweight(self, file_name: str): + def start_case_lightweight_sync(self): + """Start pending lightweight background sync sessions.""" + for thread in self._bg_session_threads: + if thread.ident is None: + thread.start() + + def read_case_lightweight(self, file_name: str, start_sync: bool = True): """Read a case file using light IO mode. Parameters ---------- file_name : str Case file name + start_sync : bool, optional + Whether to immediately start lightweight background sync. """ self.settings.file.read( @@ -405,6 +413,8 @@ def read_case_lightweight(self, file_name: str): target=self._start_bg_session_and_sync, args=(launcher_args,) ) ) + if start_sync: + self.start_case_lightweight_sync() def get_state(self) -> StateT: """Get the state of the object.""" diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 994335b0c1a..def4c0d8fed 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -57,6 +57,7 @@ _build_fluent_launch_args_string, get_fluent_exe_path, ) +from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher from ansys.fluent.core.utils.fluent_version import FluentVersion import ansys.platform.instancemanagement as pypim @@ -473,6 +474,46 @@ def test_build_journal_argument(topy, journal_file_names, result, raises): assert _build_journal_argument(topy, journal_file_names) == result +def test_build_journal_argument_without_journal_files_but_with_topy(): + assert ( + _build_journal_argument("a.py", ["a.jou"], include_journal_file_names=False) + == ' -topy="a.py"' + ) + + +def test_lightweight_case_journal_read_is_completed_before_sync_step(): + launcher = object.__new__(StandaloneLauncher) + launcher.argvals = { + "case_file_name": "a.cas.h5", + "case_data_file_name": None, + "mode": FluentMode.SOLVER, + "lightweight_mode": True, + "journal_file_names": ["a.jou", "b.jou"], + } + launcher._defer_journal_file_read = True + + calls = [] + + class _DummySession: + def read_case_lightweight(self, file_name, start_sync=True): + calls.append(("read_case_lightweight", file_name, start_sync)) + + def execute_tui(self, command): + calls.append(("execute_tui", command)) + + def start_case_lightweight_sync(self): + calls.append(("start_case_lightweight_sync",)) + + launcher._process_case_data_and_journals(_DummySession()) + + assert calls == [ + ("read_case_lightweight", "a.cas.h5", False), + ("execute_tui", '/file/read-journal "a.jou"'), + ("execute_tui", '/file/read-journal "b.jou"'), + ("start_case_lightweight_sync",), + ] + + def test_show_gui_raises_warning(): with pytest.warns(PyFluentDeprecationWarning): grpc_kwds = get_grpc_launcher_args_for_gh_runs() From 482b20fbc2889314576b0d519d9d993fa24ab64c Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 17 Mar 2026 17:10:53 +0530 Subject: [PATCH 06/29] update --- tests/test_launcher.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index def4c0d8fed..b8a358c9359 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -514,6 +514,42 @@ def start_case_lightweight_sync(self): ] +def test_case_and_case_data_are_processed_before_journal_files(): + launcher = object.__new__(StandaloneLauncher) + launcher.argvals = { + "case_file_name": "a.cas.h5", + "case_data_file_name": "a.cas.h5", + "mode": FluentMode.SOLVER, + "lightweight_mode": False, + "journal_file_names": ["a.jou", "b.jou"], + } + launcher._defer_journal_file_read = True + + calls = [] + + class _DummyFile: + def read(self, **kwargs): + calls.append(("read", kwargs)) + + class _DummySettings: + file = _DummyFile() + + class _DummySession: + settings = _DummySettings() + + def execute_tui(self, command): + calls.append(("execute_tui", command)) + + launcher._process_case_data_and_journals(_DummySession()) + + assert calls == [ + ("read", {"file_type": "case", "file_name": "a.cas.h5"}), + ("read", {"file_type": "case-data", "file_name": "a.cas.h5"}), + ("execute_tui", '/file/read-journal "a.jou"'), + ("execute_tui", '/file/read-journal "b.jou"'), + ] + + def test_show_gui_raises_warning(): with pytest.warns(PyFluentDeprecationWarning): grpc_kwds = get_grpc_launcher_args_for_gh_runs() From 7c5fd0ca9f88dbeb8372a339e9664a4e8dd9c725 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:46:22 +0000 Subject: [PATCH 07/29] chore: adding changelog file 4990.miscellaneous.md [dependabot-skip] --- doc/changelog.d/4990.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4990.miscellaneous.md diff --git a/doc/changelog.d/4990.miscellaneous.md b/doc/changelog.d/4990.miscellaneous.md new file mode 100644 index 00000000000..e61b5c8ce96 --- /dev/null +++ b/doc/changelog.d/4990.miscellaneous.md @@ -0,0 +1 @@ +Couple of issues in launch fluent From b74d2265fd7757785803df7d9ae198d9290c7362 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 16 Jun 2026 20:58:39 +0530 Subject: [PATCH 08/29] updated the standalone_launcher --- .../core/launcher/standalone_launcher.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index d8c72e512ca..89601020b05 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -256,11 +256,11 @@ def __init__( self.argvals["journal_file_names"] and (self.argvals["case_file_name"] or self.argvals["case_data_file_name"]) ) - self._launch_string += _build_journal_argument( - self.argvals["topy"], - self.argvals["journal_file_names"], - include_journal_file_names=not self._defer_journal_file_read, - ) + topy = self.argvals.get("topy", []) + if topy: + self._launch_string += _build_journal_argument( + topy, self.argvals.get("journal_file_names") + ) if is_windows(): self._launch_cmd = self._launch_string @@ -377,6 +377,15 @@ def _process_case_data_and_journals(self, session) -> None: ) else: raise RuntimeError("Case and data file cannot be read in meshing mode.") + + if not self.argvals.get("topy"): + journal_file_names = self.argvals.get("journal_file_names") + if journal_file_names and not self._defer_journal_file_read: + if isinstance(journal_file_names, str): + journal_file_names = [journal_file_names] + for journal_file_name in journal_file_names: + session.tui.file.read_journal(journal_file_name) + if self._defer_journal_file_read: self._read_journals(session) if lightweight_sync_deferred: From 1bc029cd14ad764bc7909575f09b35a2c23dc0d3 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Mon, 22 Jun 2026 23:21:29 +0530 Subject: [PATCH 09/29] standalone launcher updated for the lightweight_mode, case_file and journal_file condition --- .../core/launcher/standalone_launcher.py | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index cdd31f8e31c..6a888eabaa1 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -41,6 +41,7 @@ from pathlib import Path import subprocess from typing import TYPE_CHECKING, Any, TypedDict +import warnings from typing_extensions import Unpack @@ -67,6 +68,7 @@ _get_server_info_file_names, ) import ansys.fluent.core.launcher.watchdog as watchdog +from ansys.fluent.core.pyfluent_warnings import PyFluentUserWarning from ansys.fluent.core.utils.fluent_version import FluentVersion if TYPE_CHECKING: @@ -224,6 +226,15 @@ def __init__( self.argvals["ui_mode"] = UIMode(kwargs.get("ui_mode")) if self.argvals.get("lightweight_mode") is None: self.argvals["lightweight_mode"] = False + + if self.argvals["lightweight_mode"] and self.argvals.get("journal_file_names"): + warnings.warn( + "'lightweight_mode' is not supported together with " + "'journal_file_names' and will be ignored.", + PyFluentUserWarning, + ) + self.argvals["lightweight_mode"] = False + fluent_version = _get_standalone_launch_fluent_version(self.argvals) if ( @@ -257,10 +268,7 @@ def __init__( ) if self.argvals["cwd"]: self._kwargs.update(cwd=self.argvals["cwd"]) - self._defer_journal_file_read = bool( - self.argvals["journal_file_names"] - and (self.argvals["case_file_name"] or self.argvals["case_data_file_name"]) - ) + topy = self.argvals.get("topy", []) if topy: self._launch_string += _build_journal_argument( @@ -370,25 +378,15 @@ def _get_journal_file_names( return [journal_file_names] return journal_file_names or [] - def _read_journals(self, session) -> None: - for journal_file_name in self._get_journal_file_names( - self.argvals["journal_file_names"] - ): - session.execute_tui( - f'/file/read-journal "{Path(journal_file_name).as_posix()}"' - ) - def _process_case_data_and_journals(self, session) -> None: - lightweight_sync_deferred = False if self.argvals["case_file_name"]: if FluentMode.is_meshing(self.argvals["mode"]): session.tui.file.read_case(self.argvals["case_file_name"]) elif self.argvals["lightweight_mode"]: session.read_case_lightweight( self.argvals["case_file_name"], - start_sync=not self._defer_journal_file_read, + start_sync=True, ) - lightweight_sync_deferred = self._defer_journal_file_read else: session.settings.file.read( file_type="case", @@ -404,14 +402,7 @@ def _process_case_data_and_journals(self, session) -> None: raise RuntimeError("Case and data file cannot be read in meshing mode.") if not self.argvals.get("topy"): - journal_file_names = self.argvals.get("journal_file_names") - if journal_file_names and not self._defer_journal_file_read: - if isinstance(journal_file_names, str): - journal_file_names = [journal_file_names] - for journal_file_name in journal_file_names: - session.tui.file.read_journal(journal_file_name) - - if self._defer_journal_file_read: - self._read_journals(session) - if lightweight_sync_deferred: - session.start_case_lightweight_sync() + for journal_file_name in self._get_journal_file_names( + self.argvals.get("journal_file_names") + ): + session.tui.file.write_journal(f"{journal_file_name}.topy") From 0a2861fea2135d277dcc827d3a9a4d21366a193d Mon Sep 17 00:00:00 2001 From: mayankansys Date: Mon, 22 Jun 2026 23:49:43 +0530 Subject: [PATCH 10/29] updated the condition logic & minor things --- .../core/launcher/standalone_launcher.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 6a888eabaa1..3b4b6d32801 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -190,6 +190,8 @@ def __init__( lightweight_mode : bool, optional If True, runs in lightweight mode where mesh settings are read into a background solver session, replacing it once complete. This parameter is only applicable when `case_file_name` is provided; defaults to False. + When combined with `journal_file_names`, a warning is issued and `lightweight_mode` is set to False, as + journal processing cannot be reliably ordered with mesh-only initialization. py : bool, optional If True, runs Fluent in Python mode. Defaults to None. gpu : bool, optional @@ -216,6 +218,10 @@ def __init__( ----- In job scheduler environments (e.g., SLURM, LSF, PBS), resources and compute nodes are allocated, and core counts are queried from these environments before being passed to Fluent. + + File processing order: Case files are processed before case-data files, which are processed before + journal files. In lightweight mode, journal files are read before the background session is synchronized + with the foreground session. """ import ansys.fluent.core as pyfluent @@ -385,7 +391,7 @@ def _process_case_data_and_journals(self, session) -> None: elif self.argvals["lightweight_mode"]: session.read_case_lightweight( self.argvals["case_file_name"], - start_sync=True, + start_sync=False, ) else: session.settings.file.read( @@ -401,8 +407,16 @@ def _process_case_data_and_journals(self, session) -> None: else: raise RuntimeError("Case and data file cannot be read in meshing mode.") - if not self.argvals.get("topy"): + if self.argvals.get("topy"): for journal_file_name in self._get_journal_file_names( self.argvals.get("journal_file_names") ): session.tui.file.write_journal(f"{journal_file_name}.topy") + else: + for journal_file_name in self._get_journal_file_names( + self.argvals.get("journal_file_names") + ): + session.execute_tui(f'/file/read-journal "{journal_file_name}"') + + if self.argvals.get("lightweight_mode"): + session.start_case_lightweight_sync() From 765b43e996d4e13571f7d4e08a6e5f638ca63ed0 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 9 Jul 2026 03:16:58 +0530 Subject: [PATCH 11/29] added the condition & changed docstring --- .../core/launcher/container_launcher.py | 2 + .../fluent/core/launcher/fluent_container.py | 10 +++- .../core/launcher/standalone_launcher.py | 7 +-- tests/test_launcher.py | 47 +++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/ansys/fluent/core/launcher/container_launcher.py b/src/ansys/fluent/core/launcher/container_launcher.py index bbc9018cafb..237d149ec02 100644 --- a/src/ansys/fluent/core/launcher/container_launcher.py +++ b/src/ansys/fluent/core/launcher/container_launcher.py @@ -295,6 +295,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, + cleanup_on_exit=self.argvals["cleanup_on_exit"], ) try: @@ -312,6 +313,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, + cleanup_on_exit=self.argvals["cleanup_on_exit"], ) allow_remote_host = ( diff --git a/src/ansys/fluent/core/launcher/fluent_container.py b/src/ansys/fluent/core/launcher/fluent_container.py index 1ef3e62962f..b499d0de3a7 100644 --- a/src/ansys/fluent/core/launcher/fluent_container.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -476,6 +476,7 @@ def start_fluent_container( container_dict: dict | None = None, start_timeout: int = 100, compose_config: ComposeConfig | None = None, + cleanup_on_exit: bool = True, ) -> tuple[int, str, Any]: """Start a Fluent container. @@ -490,6 +491,9 @@ def start_fluent_container( seconds. compose_config : ComposeConfig, optional Configuration for Docker Compose, if using Docker Compose to launch the container. + cleanup_on_exit : bool, optional + If True, the server-info file will be deleted when the container starts. + If False, the server-info file will be preserved for debugging. Defaults to True. Returns ------- @@ -606,5 +610,9 @@ def start_fluent_container( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(launch_string) from ex finally: - if remove_server_info_file and host_server_info_file.exists(): + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): host_server_info_file.unlink() diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index d6508287466..34ce44b87e4 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -373,9 +373,10 @@ def __call__( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(self._launch_cmd) from ex finally: - server_info_file = Path(self._server_info_file_name) - if server_info_file.exists(): - server_info_file.unlink() + if self.argvals.get("cleanup_on_exit", True): + server_info_file = Path(self._server_info_file_name) + if server_info_file.exists(): + server_info_file.unlink() @staticmethod def _get_journal_file_names( diff --git a/tests/test_launcher.py b/tests/test_launcher.py index f037e058755..8f2a0315cb0 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -876,3 +876,50 @@ def test_idle_timeout(monkeypatch): StandaloneLauncher._construct_timeout_arg(200) == ' -command="(set-session-idle-timeoutPLF+5)"' ) + + +def test_standalone_server_info_file_preserved_with_cleanup_false(monkeypatch): + """Test that server-info file is preserved when cleanup_on_exit=False for standalone.""" + monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) + + with tempfile.TemporaryDirectory() as tmp_dir: + monkeypatch.setattr(pyfluent.config, "fluent_server_info_dir", tmp_dir) + fluent_path = r"\x\y\z\fluent.exe" + + # Dry run to get the server info file name + fluent_launch_string, server_info_file_name = pyfluent.launch_fluent( + fluent_path=fluent_path, + dry_run=True, + ui_mode="no_gui", + cleanup_on_exit=False, + ) + + # Verify the server info file path is in the specified directory + assert str(Path(server_info_file_name).parent) == tmp_dir + assert Path(server_info_file_name).name.startswith("serverinfo-") + + +def test_configure_container_dict_preserves_files(): + """Test that configure_container_dict includes proper server-info file handling.""" + from ansys.fluent.core.launcher.fluent_container import configure_container_dict + + # Test with default settings + args = ["-gu", "-driver", "null"] + result = configure_container_dict(args) + ( + config_dict, + timeout, + port, + host_server_info_file, + container_server_info_file, + remove_server_info_file, + ) = result + + # By default, remove_server_info_file should be True + assert remove_server_info_file is True + # Server info file should be a Path object + assert isinstance(host_server_info_file, Path) + # Container server info file should be a string path + assert isinstance(container_server_info_file, (str, Path)) + # Port should be extracted from config_dict + assert port is not None From 53399afff064d6e9afa59292b3c3e61bce7093de Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:53:08 +0000 Subject: [PATCH 12/29] chore: adding changelog file 5242.miscellaneous.md [dependabot-skip] --- doc/changelog.d/5242.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/5242.miscellaneous.md diff --git a/doc/changelog.d/5242.miscellaneous.md b/doc/changelog.d/5242.miscellaneous.md new file mode 100644 index 00000000000..e7886c1ccff --- /dev/null +++ b/doc/changelog.d/5242.miscellaneous.md @@ -0,0 +1 @@ +Avoid deleting server-info file From 3da7114882f57bd3a9aba0dd0bb7df0f4835d5aa Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 14 Jul 2026 14:14:54 +0530 Subject: [PATCH 13/29] resolved the conflict --- doc/changelog.d/4265.fixed.md | 2 - doc/changelog.d/4990.miscellaneous.md | 1 - .../fluent/core/launcher/launcher_utils.py | 12 +- .../core/launcher/standalone_launcher.py | 129 ++++++------------ src/ansys/fluent/core/session_solver.py | 14 +- tests/test_launcher.py | 124 ----------------- 6 files changed, 50 insertions(+), 232 deletions(-) delete mode 100644 doc/changelog.d/4265.fixed.md delete mode 100644 doc/changelog.d/4990.miscellaneous.md diff --git a/doc/changelog.d/4265.fixed.md b/doc/changelog.d/4265.fixed.md deleted file mode 100644 index 40d959e7507..00000000000 --- a/doc/changelog.d/4265.fixed.md +++ /dev/null @@ -1,2 +0,0 @@ -- Fixed `launch_fluent()` ordering so `case_file_name`/`case_data_file_name` are processed before `journal_file_names` when both are provided. -- In lightweight mode, deferred journal replay now completes before the background sync step begins. \ No newline at end of file diff --git a/doc/changelog.d/4990.miscellaneous.md b/doc/changelog.d/4990.miscellaneous.md deleted file mode 100644 index e61b5c8ce96..00000000000 --- a/doc/changelog.d/4990.miscellaneous.md +++ /dev/null @@ -1 +0,0 @@ -Couple of issues in launch fluent diff --git a/src/ansys/fluent/core/launcher/launcher_utils.py b/src/ansys/fluent/core/launcher/launcher_utils.py index 6c9b5340cff..98ba891c47e 100644 --- a/src/ansys/fluent/core/launcher/launcher_utils.py +++ b/src/ansys/fluent/core/launcher/launcher_utils.py @@ -211,16 +211,12 @@ def _confirm_watchdog_start(start_watchdog, cleanup_on_exit, fluent_connection): def _build_journal_argument( - topy: None | bool | str, - journal_file_names: None | str | list[str], - include_journal_file_names: bool = True, + topy: None | bool | str, journal_file_names: None | str | list[str] ) -> str: """Build Fluent commandline journal argument.""" def _impl( - topy: None | bool | str, - journal_file_names: None | str | list[str], - include_journal_file_names: bool, + topy: None | bool | str, journal_file_names: None | str | list[str] ) -> str: if journal_file_names and not isinstance(journal_file_names, (str, list)): raise TypeError( @@ -233,7 +229,7 @@ def _impl( fluent_jou_arg = "" if isinstance(journal_file_names, str): journal_file_names = [journal_file_names] - if journal_file_names and include_journal_file_names: + if journal_file_names: fluent_jou_arg += "".join( [f' -i "{journal}"' for journal in journal_file_names] ) @@ -244,4 +240,4 @@ def _impl( fluent_jou_arg += " -topy" return fluent_jou_arg - return _impl(topy, journal_file_names, include_journal_file_names) + return _impl(topy, journal_file_names) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 34ce44b87e4..e34341ef59d 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -42,7 +42,6 @@ from pathlib import Path import subprocess from typing import TYPE_CHECKING, Any, TypedDict -import warnings from typing_extensions import Unpack @@ -69,7 +68,6 @@ _get_server_info_file_names, ) import ansys.fluent.core.launcher.watchdog as watchdog -from ansys.fluent.core.pyfluent_warnings import PyFluentUserWarning from ansys.fluent.core.utils.fluent_version import FluentVersion if TYPE_CHECKING: @@ -191,8 +189,6 @@ def __init__( lightweight_mode : bool, optional If True, runs in lightweight mode where mesh settings are read into a background solver session, replacing it once complete. This parameter is only applicable when `case_file_name` is provided; defaults to False. - When combined with `journal_file_names`, a warning is issued and `lightweight_mode` is set to False, as - journal processing cannot be reliably ordered with mesh-only initialization. py : bool, optional If True, runs Fluent in Python mode. Defaults to None. gpu : bool, optional @@ -219,10 +215,6 @@ def __init__( ----- In job scheduler environments (e.g., SLURM, LSF, PBS), resources and compute nodes are allocated, and core counts are queried from these environments before being passed to Fluent. - - File processing order: Case files are processed before case-data files, which are processed before - journal files. In lightweight mode, journal files are read before the background session is synchronized - with the foreground session. """ import ansys.fluent.core as pyfluent @@ -233,15 +225,6 @@ def __init__( self.argvals["ui_mode"] = UIMode(kwargs.get("ui_mode")) if self.argvals.get("lightweight_mode") is None: self.argvals["lightweight_mode"] = False - - if self.argvals["lightweight_mode"] and self.argvals.get("journal_file_names"): - warnings.warn( - "'lightweight_mode' is not supported together with " - "'journal_file_names' and will be ignored.", - PyFluentUserWarning, - ) - self.argvals["lightweight_mode"] = False - fluent_version = _get_standalone_launch_fluent_version(self.argvals) if ( @@ -273,14 +256,11 @@ def __init__( self._kwargs = _get_subprocess_kwargs_for_fluent( self.argvals.get("env") or {}, self.argvals ) - if self.argvals["cwd"]: - self._kwargs.update(cwd=self.argvals["cwd"]) - - topy = self.argvals.get("topy", []) - if topy: - self._launch_string += _build_journal_argument( - topy, self.argvals.get("journal_file_names") - ) + if self.argvals.get("cwd"): + self._kwargs.update(cwd=self.argvals.get("cwd")) + self._launch_string += _build_journal_argument( + self.argvals.get("topy", []), self.argvals.get("journal_file_names") + ) if is_windows(): self._launch_cmd = self._launch_string @@ -300,16 +280,14 @@ def _construct_timeout_arg(idle_timeout_seconds: int) -> str: @staticmethod def _disable_idle_timeout_guard(session): try: - if session.get_fluent_version() >= FluentVersion.v271: - session._app_utilities.set_idle_timeout( - session.preferences.General.IdleTimeout() * 60 - ) - else: - session.scheme_eval.eval( - f"(set-session-idle-timeout {session.preferences.General.IdleTimeout()})" - ) + default_idle_timeout = session.preferences.General.IdleTimeout() + except RuntimeError: + # This exception is raised only while running codegen locally before the preferences root is available. + default_idle_timeout = 0 + try: + session.application_runtime.set_idle_timeout(default_idle_timeout * 60) except Exception as ex: - logger.debug(f"Could not reset Idle Timeout: {ex}") + raise RuntimeError("Could not reset Idle Timeout") from ex def __call__( self, @@ -365,60 +343,41 @@ def __call__( values = _get_server_info(self._server_info_file_name) if len(values) == 3: ip, port, password = values - watchdog.launch(os.getpid(), port, password, ip) - self._process_case_data_and_journals(session) + watchdog.launch( + os.getpid(), + port, + password, + ip, + inside_container=False, + ) + # PyFluent is now connected: disable the idle-timeout guard. + self._disable_idle_timeout_guard(session) + if self.argvals.get("case_file_name"): + if FluentMode.is_meshing(self.argvals.get("mode")): + session.tui.file.read_case(self.argvals.get("case_file_name")) + elif self.argvals.get("lightweight_mode"): + session.read_case_lightweight(self.argvals.get("case_file_name")) + else: + session.settings.file.read( + file_type="case", + file_name=self.argvals.get("case_file_name"), + ) + if self.argvals.get("case_data_file_name"): + if not FluentMode.is_meshing(self.argvals.get("mode")): + session.settings.file.read( + file_type="case-data", + file_name=self.argvals.get("case_data_file_name"), + ) + else: + raise RuntimeError( + "Case and data file cannot be read in meshing mode." + ) return session except Exception as ex: logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(self._launch_cmd) from ex finally: - if self.argvals.get("cleanup_on_exit", True): - server_info_file = Path(self._server_info_file_name) - if server_info_file.exists(): - server_info_file.unlink() - - @staticmethod - def _get_journal_file_names( - journal_file_names: None | str | list[str], - ) -> list[str]: - if isinstance(journal_file_names, str): - return [journal_file_names] - return journal_file_names or [] - - def _process_case_data_and_journals(self, session) -> None: - if self.argvals["case_file_name"]: - if FluentMode.is_meshing(self.argvals["mode"]): - session.tui.file.read_case(self.argvals["case_file_name"]) - elif self.argvals["lightweight_mode"]: - session.read_case_lightweight( - self.argvals["case_file_name"], - start_sync=False, - ) - else: - session.settings.file.read( - file_type="case", - file_name=self.argvals["case_file_name"], - ) - if self.argvals["case_data_file_name"]: - if not FluentMode.is_meshing(self.argvals["mode"]): - session.settings.file.read( - file_type="case-data", - file_name=self.argvals["case_data_file_name"], - ) - else: - raise RuntimeError("Case and data file cannot be read in meshing mode.") - - if self.argvals.get("topy"): - for journal_file_name in self._get_journal_file_names( - self.argvals.get("journal_file_names") - ): - session.tui.file.write_journal(f"{journal_file_name}.topy") - else: - for journal_file_name in self._get_journal_file_names( - self.argvals.get("journal_file_names") - ): - session.execute_tui(f'/file/read-journal "{journal_file_name}"') - - if self.argvals.get("lightweight_mode"): - session.start_case_lightweight_sync() + server_info_file = Path(self._server_info_file_name) + if server_info_file.exists(): + server_info_file.unlink() diff --git a/src/ansys/fluent/core/session_solver.py b/src/ansys/fluent/core/session_solver.py index 842dac79a9a..5c34b969c1f 100644 --- a/src/ansys/fluent/core/session_solver.py +++ b/src/ansys/fluent/core/session_solver.py @@ -33,10 +33,10 @@ from ansys.api.fluent.v1 import solution_variable_pb2 as SvarProtoModule import ansys.fluent.core as pyfluent from ansys.fluent.core.exceptions import BetaFeaturesNotEnabled +from ansys.fluent.core.fields.live_field_data import ZoneInfo, ZoneType from ansys.fluent.core.module_config import config from ansys.fluent.core.pyfluent_warnings import PyFluentDeprecationWarning from ansys.fluent.core.services import MonitorsServiceV0, service_creator -from ansys.fluent.core.services.field_data import ZoneInfo, ZoneType from ansys.fluent.core.services.monitor_v1 import MonitorsService from ansys.fluent.core.services.scheme_interpreter import SchemeInterpreter from ansys.fluent.core.services.solution_variables import ( @@ -378,21 +378,13 @@ def _stop_bg_sessions(self): if thread.is_alive(): thread.join() - def start_case_lightweight_sync(self): - """Start pending lightweight background sync sessions.""" - for thread in self._bg_session_threads: - if thread.ident is None: - thread.start() - - def read_case_lightweight(self, file_name: str, start_sync: bool = True): + def read_case_lightweight(self, file_name: str): """Read a case file using light IO mode. Parameters ---------- file_name : str Case file name - start_sync : bool, optional - Whether to immediately start lightweight background sync. """ self.settings.file.read( @@ -406,8 +398,6 @@ def read_case_lightweight(self, file_name: str, start_sync: bool = True): target=self._start_bg_session_and_sync, args=(launcher_args,) ) ) - if start_sync: - self.start_case_lightweight_sync() def get_state(self) -> StateT: """Get the state of the object.""" diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 8f2a0315cb0..8618f606a70 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -58,7 +58,6 @@ _build_fluent_launch_args_string, get_fluent_exe_path, ) -from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher from ansys.fluent.core.utils.fluent_version import FluentVersion import ansys.platform.instancemanagement as pypim @@ -475,82 +474,6 @@ def test_build_journal_argument(topy, journal_file_names, result, raises): assert _build_journal_argument(topy, journal_file_names) == result -def test_build_journal_argument_without_journal_files_but_with_topy(): - assert ( - _build_journal_argument("a.py", ["a.jou"], include_journal_file_names=False) - == ' -topy="a.py"' - ) - - -def test_lightweight_case_journal_read_is_completed_before_sync_step(): - launcher = object.__new__(StandaloneLauncher) - launcher.argvals = { - "case_file_name": "a.cas.h5", - "case_data_file_name": None, - "mode": FluentMode.SOLVER, - "lightweight_mode": True, - "journal_file_names": ["a.jou", "b.jou"], - } - launcher._defer_journal_file_read = True - - calls = [] - - class _DummySession: - def read_case_lightweight(self, file_name, start_sync=True): - calls.append(("read_case_lightweight", file_name, start_sync)) - - def execute_tui(self, command): - calls.append(("execute_tui", command)) - - def start_case_lightweight_sync(self): - calls.append(("start_case_lightweight_sync",)) - - launcher._process_case_data_and_journals(_DummySession()) - - assert calls == [ - ("read_case_lightweight", "a.cas.h5", False), - ("execute_tui", '/file/read-journal "a.jou"'), - ("execute_tui", '/file/read-journal "b.jou"'), - ("start_case_lightweight_sync",), - ] - - -def test_case_and_case_data_are_processed_before_journal_files(): - launcher = object.__new__(StandaloneLauncher) - launcher.argvals = { - "case_file_name": "a.cas.h5", - "case_data_file_name": "a.cas.h5", - "mode": FluentMode.SOLVER, - "lightweight_mode": False, - "journal_file_names": ["a.jou", "b.jou"], - } - launcher._defer_journal_file_read = True - - calls = [] - - class _DummyFile: - def read(self, **kwargs): - calls.append(("read", kwargs)) - - class _DummySettings: - file = _DummyFile() - - class _DummySession: - settings = _DummySettings() - - def execute_tui(self, command): - calls.append(("execute_tui", command)) - - launcher._process_case_data_and_journals(_DummySession()) - - assert calls == [ - ("read", {"file_type": "case", "file_name": "a.cas.h5"}), - ("read", {"file_type": "case-data", "file_name": "a.cas.h5"}), - ("execute_tui", '/file/read-journal "a.jou"'), - ("execute_tui", '/file/read-journal "b.jou"'), - ] - - def test_show_gui_raises_warning(): with pytest.warns(PyFluentDeprecationWarning): grpc_kwds = get_grpc_launcher_args_for_gh_runs() @@ -876,50 +799,3 @@ def test_idle_timeout(monkeypatch): StandaloneLauncher._construct_timeout_arg(200) == ' -command="(set-session-idle-timeoutPLF+5)"' ) - - -def test_standalone_server_info_file_preserved_with_cleanup_false(monkeypatch): - """Test that server-info file is preserved when cleanup_on_exit=False for standalone.""" - monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) - - with tempfile.TemporaryDirectory() as tmp_dir: - monkeypatch.setattr(pyfluent.config, "fluent_server_info_dir", tmp_dir) - fluent_path = r"\x\y\z\fluent.exe" - - # Dry run to get the server info file name - fluent_launch_string, server_info_file_name = pyfluent.launch_fluent( - fluent_path=fluent_path, - dry_run=True, - ui_mode="no_gui", - cleanup_on_exit=False, - ) - - # Verify the server info file path is in the specified directory - assert str(Path(server_info_file_name).parent) == tmp_dir - assert Path(server_info_file_name).name.startswith("serverinfo-") - - -def test_configure_container_dict_preserves_files(): - """Test that configure_container_dict includes proper server-info file handling.""" - from ansys.fluent.core.launcher.fluent_container import configure_container_dict - - # Test with default settings - args = ["-gu", "-driver", "null"] - result = configure_container_dict(args) - ( - config_dict, - timeout, - port, - host_server_info_file, - container_server_info_file, - remove_server_info_file, - ) = result - - # By default, remove_server_info_file should be True - assert remove_server_info_file is True - # Server info file should be a Path object - assert isinstance(host_server_info_file, Path) - # Container server info file should be a string path - assert isinstance(container_server_info_file, (str, Path)) - # Port should be extracted from config_dict - assert port is not None From d8c4cf4bf6ed2568c505e198e7ff17441c62f2ff Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 14 Jul 2026 15:13:33 +0530 Subject: [PATCH 14/29] added the tests --- tests/test_launcher.py | 127 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 8618f606a70..8741b08d07b 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -678,6 +678,133 @@ def test_respect_driver_is_not_null_in_linux(): assert driver == FluentLinuxGraphicsDriver.OPENGL +class TestContainerCleanupOnExit: + """Test suite for cleanup_on_exit flag behavior with server-info files. + + Tests verify that: + 1. Server-info file is preserved when cleanup_on_exit=False + 2. Server-info file is deleted when cleanup_on_exit=True (default) + 3. Both compose and non-compose container modes work correctly + 4. Edge cases are handled properly + + """ + + def test_server_info_file_preserved_cleanup_false(self): + """Real server-info file is preserved when cleanup_on_exit=False. + + Creates an actual temp file and verifies it's NOT deleted when + cleanup_on_exit=False. + """ + # Create a real temporary file to represent server-info file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False + ) as tmp_file: + server_info_file_path = Path(tmp_file.name) + + try: + # Verify file exists before test + assert server_info_file_path.exists(), "Temp file should exist initially" + + # Simulate the finally block logic with cleanup_on_exit=False + remove_server_info_file = True + cleanup_on_exit = False + host_server_info_file = server_info_file_path + + # This is the actual condition from fluent_container.py + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): + host_server_info_file.unlink() + + assert ( + server_info_file_path.exists() + ), "Server-info file should be preserved when cleanup_on_exit=False" + + finally: + if server_info_file_path.exists(): + server_info_file_path.unlink() + + def test_server_info_file_deleted_cleanup_true(self): + """Real server-info file is deleted when cleanup_on_exit=True. + + Creates an actual temp file and verifies it IS deleted when + cleanup_on_exit=True. + """ + # Create a real temporary file to represent server-info file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False + ) as tmp_file: + server_info_file_path = Path(tmp_file.name) + + try: + # Verify file exists before test + assert server_info_file_path.exists(), "Temp file should exist initially" + + # Simulate the finally block logic with cleanup_on_exit=True + remove_server_info_file = True + cleanup_on_exit = True + host_server_info_file = server_info_file_path + + # This is the actual condition from fluent_container.py + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): + host_server_info_file.unlink() + + # Assert: file should be deleted because cleanup_on_exit=True + assert ( + not server_info_file_path.exists() + ), "Server-info file should be deleted when cleanup_on_exit=True" + + finally: + # Cleanup: delete temp file if it still exists (shouldn't) + if server_info_file_path.exists(): + server_info_file_path.unlink() + + def test_remove_server_info_file_parameter_override(self): + """Remove_server_info_file parameter works independently of cleanup_on_exit. + + Verifies that remove_server_info_file=False prevents file deletion + even when cleanup_on_exit=True. + """ + # Create a real temporary file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False + ) as tmp_file: + server_info_file_path = Path(tmp_file.name) + + try: + # Verify file exists before test + assert server_info_file_path.exists(), "Temp file should exist initially" + + # Test scenario: remove_server_info_file=False but cleanup_on_exit=True + remove_server_info_file = False # This should prevent deletion + cleanup_on_exit = True + host_server_info_file = server_info_file_path + + # This is the actual condition from fluent_container.py + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): + host_server_info_file.unlink() + + # Assert: file should still exist because remove_server_info_file=False + assert ( + server_info_file_path.exists() + ), "Server-info file should be preserved when remove_server_info_file=False" + + finally: + # Cleanup: delete temp file + if server_info_file_path.exists(): + server_info_file_path.unlink() + + @pytest.mark.standalone def test_warning_in_windows(): with pytest.warns(PyFluentUserWarning): From 16ca1a49f74eb411c2fa392578da88380dbcebb8 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 9 Jul 2026 03:16:58 +0530 Subject: [PATCH 15/29] added the condition & changed docstring --- .../core/launcher/container_launcher.py | 2 + .../fluent/core/launcher/fluent_container.py | 10 +++- .../core/launcher/standalone_launcher.py | 7 +-- tests/test_launcher.py | 47 +++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/ansys/fluent/core/launcher/container_launcher.py b/src/ansys/fluent/core/launcher/container_launcher.py index 22a8c32bf6f..1a8c51a418c 100644 --- a/src/ansys/fluent/core/launcher/container_launcher.py +++ b/src/ansys/fluent/core/launcher/container_launcher.py @@ -300,6 +300,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, + cleanup_on_exit=self.argvals["cleanup_on_exit"], ) try: @@ -317,6 +318,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, + cleanup_on_exit=self.argvals["cleanup_on_exit"], ) allow_remote_host = ( diff --git a/src/ansys/fluent/core/launcher/fluent_container.py b/src/ansys/fluent/core/launcher/fluent_container.py index 1ef3e62962f..b499d0de3a7 100644 --- a/src/ansys/fluent/core/launcher/fluent_container.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -476,6 +476,7 @@ def start_fluent_container( container_dict: dict | None = None, start_timeout: int = 100, compose_config: ComposeConfig | None = None, + cleanup_on_exit: bool = True, ) -> tuple[int, str, Any]: """Start a Fluent container. @@ -490,6 +491,9 @@ def start_fluent_container( seconds. compose_config : ComposeConfig, optional Configuration for Docker Compose, if using Docker Compose to launch the container. + cleanup_on_exit : bool, optional + If True, the server-info file will be deleted when the container starts. + If False, the server-info file will be preserved for debugging. Defaults to True. Returns ------- @@ -606,5 +610,9 @@ def start_fluent_container( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(launch_string) from ex finally: - if remove_server_info_file and host_server_info_file.exists(): + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): host_server_info_file.unlink() diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index c6971143a6f..1d0d741983b 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -371,9 +371,10 @@ def __call__( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(self._launch_cmd) from ex finally: - server_info_file = Path(self._server_info_file_name) - if server_info_file.exists(): - server_info_file.unlink() + if self.argvals.get("cleanup_on_exit", True): + server_info_file = Path(self._server_info_file_name) + if server_info_file.exists(): + server_info_file.unlink() @staticmethod def _get_journal_file_names( diff --git a/tests/test_launcher.py b/tests/test_launcher.py index f037e058755..8f2a0315cb0 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -876,3 +876,50 @@ def test_idle_timeout(monkeypatch): StandaloneLauncher._construct_timeout_arg(200) == ' -command="(set-session-idle-timeoutPLF+5)"' ) + + +def test_standalone_server_info_file_preserved_with_cleanup_false(monkeypatch): + """Test that server-info file is preserved when cleanup_on_exit=False for standalone.""" + monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) + + with tempfile.TemporaryDirectory() as tmp_dir: + monkeypatch.setattr(pyfluent.config, "fluent_server_info_dir", tmp_dir) + fluent_path = r"\x\y\z\fluent.exe" + + # Dry run to get the server info file name + fluent_launch_string, server_info_file_name = pyfluent.launch_fluent( + fluent_path=fluent_path, + dry_run=True, + ui_mode="no_gui", + cleanup_on_exit=False, + ) + + # Verify the server info file path is in the specified directory + assert str(Path(server_info_file_name).parent) == tmp_dir + assert Path(server_info_file_name).name.startswith("serverinfo-") + + +def test_configure_container_dict_preserves_files(): + """Test that configure_container_dict includes proper server-info file handling.""" + from ansys.fluent.core.launcher.fluent_container import configure_container_dict + + # Test with default settings + args = ["-gu", "-driver", "null"] + result = configure_container_dict(args) + ( + config_dict, + timeout, + port, + host_server_info_file, + container_server_info_file, + remove_server_info_file, + ) = result + + # By default, remove_server_info_file should be True + assert remove_server_info_file is True + # Server info file should be a Path object + assert isinstance(host_server_info_file, Path) + # Container server info file should be a string path + assert isinstance(container_server_info_file, (str, Path)) + # Port should be extracted from config_dict + assert port is not None From 3c05c737ffe7cf530192b4a96d61de5475d63f49 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:53:08 +0000 Subject: [PATCH 16/29] chore: adding changelog file 5242.miscellaneous.md [dependabot-skip] --- doc/changelog.d/5242.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/5242.miscellaneous.md diff --git a/doc/changelog.d/5242.miscellaneous.md b/doc/changelog.d/5242.miscellaneous.md new file mode 100644 index 00000000000..e7886c1ccff --- /dev/null +++ b/doc/changelog.d/5242.miscellaneous.md @@ -0,0 +1 @@ +Avoid deleting server-info file From b5a18a334e6eddd500b6a662f0d9b3a94169b2de Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 14 Jul 2026 14:14:54 +0530 Subject: [PATCH 17/29] resolved the conflict --- doc/changelog.d/4265.fixed.md | 2 - doc/changelog.d/4990.miscellaneous.md | 1 - .../fluent/core/launcher/launcher_utils.py | 12 +- .../core/launcher/standalone_launcher.py | 113 ++++++---------- src/ansys/fluent/core/session_solver.py | 12 +- tests/test_launcher.py | 124 ------------------ 6 files changed, 42 insertions(+), 222 deletions(-) delete mode 100644 doc/changelog.d/4265.fixed.md delete mode 100644 doc/changelog.d/4990.miscellaneous.md diff --git a/doc/changelog.d/4265.fixed.md b/doc/changelog.d/4265.fixed.md deleted file mode 100644 index 40d959e7507..00000000000 --- a/doc/changelog.d/4265.fixed.md +++ /dev/null @@ -1,2 +0,0 @@ -- Fixed `launch_fluent()` ordering so `case_file_name`/`case_data_file_name` are processed before `journal_file_names` when both are provided. -- In lightweight mode, deferred journal replay now completes before the background sync step begins. \ No newline at end of file diff --git a/doc/changelog.d/4990.miscellaneous.md b/doc/changelog.d/4990.miscellaneous.md deleted file mode 100644 index e61b5c8ce96..00000000000 --- a/doc/changelog.d/4990.miscellaneous.md +++ /dev/null @@ -1 +0,0 @@ -Couple of issues in launch fluent diff --git a/src/ansys/fluent/core/launcher/launcher_utils.py b/src/ansys/fluent/core/launcher/launcher_utils.py index 6c9b5340cff..98ba891c47e 100644 --- a/src/ansys/fluent/core/launcher/launcher_utils.py +++ b/src/ansys/fluent/core/launcher/launcher_utils.py @@ -211,16 +211,12 @@ def _confirm_watchdog_start(start_watchdog, cleanup_on_exit, fluent_connection): def _build_journal_argument( - topy: None | bool | str, - journal_file_names: None | str | list[str], - include_journal_file_names: bool = True, + topy: None | bool | str, journal_file_names: None | str | list[str] ) -> str: """Build Fluent commandline journal argument.""" def _impl( - topy: None | bool | str, - journal_file_names: None | str | list[str], - include_journal_file_names: bool, + topy: None | bool | str, journal_file_names: None | str | list[str] ) -> str: if journal_file_names and not isinstance(journal_file_names, (str, list)): raise TypeError( @@ -233,7 +229,7 @@ def _impl( fluent_jou_arg = "" if isinstance(journal_file_names, str): journal_file_names = [journal_file_names] - if journal_file_names and include_journal_file_names: + if journal_file_names: fluent_jou_arg += "".join( [f' -i "{journal}"' for journal in journal_file_names] ) @@ -244,4 +240,4 @@ def _impl( fluent_jou_arg += " -topy" return fluent_jou_arg - return _impl(topy, journal_file_names, include_journal_file_names) + return _impl(topy, journal_file_names) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 1d0d741983b..e34341ef59d 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -42,7 +42,6 @@ from pathlib import Path import subprocess from typing import TYPE_CHECKING, Any, TypedDict -import warnings from typing_extensions import Unpack @@ -69,7 +68,6 @@ _get_server_info_file_names, ) import ansys.fluent.core.launcher.watchdog as watchdog -from ansys.fluent.core.pyfluent_warnings import PyFluentUserWarning from ansys.fluent.core.utils.fluent_version import FluentVersion if TYPE_CHECKING: @@ -191,8 +189,6 @@ def __init__( lightweight_mode : bool, optional If True, runs in lightweight mode where mesh settings are read into a background solver session, replacing it once complete. This parameter is only applicable when `case_file_name` is provided; defaults to False. - When combined with `journal_file_names`, a warning is issued and `lightweight_mode` is set to False, as - journal processing cannot be reliably ordered with mesh-only initialization. py : bool, optional If True, runs Fluent in Python mode. Defaults to None. gpu : bool, optional @@ -219,10 +215,6 @@ def __init__( ----- In job scheduler environments (e.g., SLURM, LSF, PBS), resources and compute nodes are allocated, and core counts are queried from these environments before being passed to Fluent. - - File processing order: Case files are processed before case-data files, which are processed before - journal files. In lightweight mode, journal files are read before the background session is synchronized - with the foreground session. """ import ansys.fluent.core as pyfluent @@ -233,15 +225,6 @@ def __init__( self.argvals["ui_mode"] = UIMode(kwargs.get("ui_mode")) if self.argvals.get("lightweight_mode") is None: self.argvals["lightweight_mode"] = False - - if self.argvals["lightweight_mode"] and self.argvals.get("journal_file_names"): - warnings.warn( - "'lightweight_mode' is not supported together with " - "'journal_file_names' and will be ignored.", - PyFluentUserWarning, - ) - self.argvals["lightweight_mode"] = False - fluent_version = _get_standalone_launch_fluent_version(self.argvals) if ( @@ -273,14 +256,11 @@ def __init__( self._kwargs = _get_subprocess_kwargs_for_fluent( self.argvals.get("env") or {}, self.argvals ) - if self.argvals["cwd"]: - self._kwargs.update(cwd=self.argvals["cwd"]) - - topy = self.argvals.get("topy", []) - if topy: - self._launch_string += _build_journal_argument( - topy, self.argvals.get("journal_file_names") - ) + if self.argvals.get("cwd"): + self._kwargs.update(cwd=self.argvals.get("cwd")) + self._launch_string += _build_journal_argument( + self.argvals.get("topy", []), self.argvals.get("journal_file_names") + ) if is_windows(): self._launch_cmd = self._launch_string @@ -363,60 +343,41 @@ def __call__( values = _get_server_info(self._server_info_file_name) if len(values) == 3: ip, port, password = values - watchdog.launch(os.getpid(), port, password, ip) - self._process_case_data_and_journals(session) + watchdog.launch( + os.getpid(), + port, + password, + ip, + inside_container=False, + ) + # PyFluent is now connected: disable the idle-timeout guard. + self._disable_idle_timeout_guard(session) + if self.argvals.get("case_file_name"): + if FluentMode.is_meshing(self.argvals.get("mode")): + session.tui.file.read_case(self.argvals.get("case_file_name")) + elif self.argvals.get("lightweight_mode"): + session.read_case_lightweight(self.argvals.get("case_file_name")) + else: + session.settings.file.read( + file_type="case", + file_name=self.argvals.get("case_file_name"), + ) + if self.argvals.get("case_data_file_name"): + if not FluentMode.is_meshing(self.argvals.get("mode")): + session.settings.file.read( + file_type="case-data", + file_name=self.argvals.get("case_data_file_name"), + ) + else: + raise RuntimeError( + "Case and data file cannot be read in meshing mode." + ) return session except Exception as ex: logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(self._launch_cmd) from ex finally: - if self.argvals.get("cleanup_on_exit", True): - server_info_file = Path(self._server_info_file_name) - if server_info_file.exists(): - server_info_file.unlink() - - @staticmethod - def _get_journal_file_names( - journal_file_names: None | str | list[str], - ) -> list[str]: - if isinstance(journal_file_names, str): - return [journal_file_names] - return journal_file_names or [] - - def _process_case_data_and_journals(self, session) -> None: - if self.argvals["case_file_name"]: - if FluentMode.is_meshing(self.argvals["mode"]): - session.tui.file.read_case(self.argvals["case_file_name"]) - elif self.argvals["lightweight_mode"]: - session.read_case_lightweight( - self.argvals["case_file_name"], - start_sync=False, - ) - else: - session.settings.file.read( - file_type="case", - file_name=self.argvals["case_file_name"], - ) - if self.argvals["case_data_file_name"]: - if not FluentMode.is_meshing(self.argvals["mode"]): - session.settings.file.read( - file_type="case-data", - file_name=self.argvals["case_data_file_name"], - ) - else: - raise RuntimeError("Case and data file cannot be read in meshing mode.") - - if self.argvals.get("topy"): - for journal_file_name in self._get_journal_file_names( - self.argvals.get("journal_file_names") - ): - session.tui.file.write_journal(f"{journal_file_name}.topy") - else: - for journal_file_name in self._get_journal_file_names( - self.argvals.get("journal_file_names") - ): - session.execute_tui(f'/file/read-journal "{journal_file_name}"') - - if self.argvals.get("lightweight_mode"): - session.start_case_lightweight_sync() + server_info_file = Path(self._server_info_file_name) + if server_info_file.exists(): + server_info_file.unlink() diff --git a/src/ansys/fluent/core/session_solver.py b/src/ansys/fluent/core/session_solver.py index 4c7ba2211e1..5c34b969c1f 100644 --- a/src/ansys/fluent/core/session_solver.py +++ b/src/ansys/fluent/core/session_solver.py @@ -378,21 +378,13 @@ def _stop_bg_sessions(self): if thread.is_alive(): thread.join() - def start_case_lightweight_sync(self): - """Start pending lightweight background sync sessions.""" - for thread in self._bg_session_threads: - if thread.ident is None: - thread.start() - - def read_case_lightweight(self, file_name: str, start_sync: bool = True): + def read_case_lightweight(self, file_name: str): """Read a case file using light IO mode. Parameters ---------- file_name : str Case file name - start_sync : bool, optional - Whether to immediately start lightweight background sync. """ self.settings.file.read( @@ -406,8 +398,6 @@ def read_case_lightweight(self, file_name: str, start_sync: bool = True): target=self._start_bg_session_and_sync, args=(launcher_args,) ) ) - if start_sync: - self.start_case_lightweight_sync() def get_state(self) -> StateT: """Get the state of the object.""" diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 8f2a0315cb0..8618f606a70 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -58,7 +58,6 @@ _build_fluent_launch_args_string, get_fluent_exe_path, ) -from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher from ansys.fluent.core.utils.fluent_version import FluentVersion import ansys.platform.instancemanagement as pypim @@ -475,82 +474,6 @@ def test_build_journal_argument(topy, journal_file_names, result, raises): assert _build_journal_argument(topy, journal_file_names) == result -def test_build_journal_argument_without_journal_files_but_with_topy(): - assert ( - _build_journal_argument("a.py", ["a.jou"], include_journal_file_names=False) - == ' -topy="a.py"' - ) - - -def test_lightweight_case_journal_read_is_completed_before_sync_step(): - launcher = object.__new__(StandaloneLauncher) - launcher.argvals = { - "case_file_name": "a.cas.h5", - "case_data_file_name": None, - "mode": FluentMode.SOLVER, - "lightweight_mode": True, - "journal_file_names": ["a.jou", "b.jou"], - } - launcher._defer_journal_file_read = True - - calls = [] - - class _DummySession: - def read_case_lightweight(self, file_name, start_sync=True): - calls.append(("read_case_lightweight", file_name, start_sync)) - - def execute_tui(self, command): - calls.append(("execute_tui", command)) - - def start_case_lightweight_sync(self): - calls.append(("start_case_lightweight_sync",)) - - launcher._process_case_data_and_journals(_DummySession()) - - assert calls == [ - ("read_case_lightweight", "a.cas.h5", False), - ("execute_tui", '/file/read-journal "a.jou"'), - ("execute_tui", '/file/read-journal "b.jou"'), - ("start_case_lightweight_sync",), - ] - - -def test_case_and_case_data_are_processed_before_journal_files(): - launcher = object.__new__(StandaloneLauncher) - launcher.argvals = { - "case_file_name": "a.cas.h5", - "case_data_file_name": "a.cas.h5", - "mode": FluentMode.SOLVER, - "lightweight_mode": False, - "journal_file_names": ["a.jou", "b.jou"], - } - launcher._defer_journal_file_read = True - - calls = [] - - class _DummyFile: - def read(self, **kwargs): - calls.append(("read", kwargs)) - - class _DummySettings: - file = _DummyFile() - - class _DummySession: - settings = _DummySettings() - - def execute_tui(self, command): - calls.append(("execute_tui", command)) - - launcher._process_case_data_and_journals(_DummySession()) - - assert calls == [ - ("read", {"file_type": "case", "file_name": "a.cas.h5"}), - ("read", {"file_type": "case-data", "file_name": "a.cas.h5"}), - ("execute_tui", '/file/read-journal "a.jou"'), - ("execute_tui", '/file/read-journal "b.jou"'), - ] - - def test_show_gui_raises_warning(): with pytest.warns(PyFluentDeprecationWarning): grpc_kwds = get_grpc_launcher_args_for_gh_runs() @@ -876,50 +799,3 @@ def test_idle_timeout(monkeypatch): StandaloneLauncher._construct_timeout_arg(200) == ' -command="(set-session-idle-timeoutPLF+5)"' ) - - -def test_standalone_server_info_file_preserved_with_cleanup_false(monkeypatch): - """Test that server-info file is preserved when cleanup_on_exit=False for standalone.""" - monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) - - with tempfile.TemporaryDirectory() as tmp_dir: - monkeypatch.setattr(pyfluent.config, "fluent_server_info_dir", tmp_dir) - fluent_path = r"\x\y\z\fluent.exe" - - # Dry run to get the server info file name - fluent_launch_string, server_info_file_name = pyfluent.launch_fluent( - fluent_path=fluent_path, - dry_run=True, - ui_mode="no_gui", - cleanup_on_exit=False, - ) - - # Verify the server info file path is in the specified directory - assert str(Path(server_info_file_name).parent) == tmp_dir - assert Path(server_info_file_name).name.startswith("serverinfo-") - - -def test_configure_container_dict_preserves_files(): - """Test that configure_container_dict includes proper server-info file handling.""" - from ansys.fluent.core.launcher.fluent_container import configure_container_dict - - # Test with default settings - args = ["-gu", "-driver", "null"] - result = configure_container_dict(args) - ( - config_dict, - timeout, - port, - host_server_info_file, - container_server_info_file, - remove_server_info_file, - ) = result - - # By default, remove_server_info_file should be True - assert remove_server_info_file is True - # Server info file should be a Path object - assert isinstance(host_server_info_file, Path) - # Container server info file should be a string path - assert isinstance(container_server_info_file, (str, Path)) - # Port should be extracted from config_dict - assert port is not None From cf6eba32ea22b2de8c870816611948e2641b67cc Mon Sep 17 00:00:00 2001 From: mayankansys Date: Tue, 14 Jul 2026 15:13:33 +0530 Subject: [PATCH 18/29] added the tests --- tests/test_launcher.py | 127 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 8618f606a70..8741b08d07b 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -678,6 +678,133 @@ def test_respect_driver_is_not_null_in_linux(): assert driver == FluentLinuxGraphicsDriver.OPENGL +class TestContainerCleanupOnExit: + """Test suite for cleanup_on_exit flag behavior with server-info files. + + Tests verify that: + 1. Server-info file is preserved when cleanup_on_exit=False + 2. Server-info file is deleted when cleanup_on_exit=True (default) + 3. Both compose and non-compose container modes work correctly + 4. Edge cases are handled properly + + """ + + def test_server_info_file_preserved_cleanup_false(self): + """Real server-info file is preserved when cleanup_on_exit=False. + + Creates an actual temp file and verifies it's NOT deleted when + cleanup_on_exit=False. + """ + # Create a real temporary file to represent server-info file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False + ) as tmp_file: + server_info_file_path = Path(tmp_file.name) + + try: + # Verify file exists before test + assert server_info_file_path.exists(), "Temp file should exist initially" + + # Simulate the finally block logic with cleanup_on_exit=False + remove_server_info_file = True + cleanup_on_exit = False + host_server_info_file = server_info_file_path + + # This is the actual condition from fluent_container.py + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): + host_server_info_file.unlink() + + assert ( + server_info_file_path.exists() + ), "Server-info file should be preserved when cleanup_on_exit=False" + + finally: + if server_info_file_path.exists(): + server_info_file_path.unlink() + + def test_server_info_file_deleted_cleanup_true(self): + """Real server-info file is deleted when cleanup_on_exit=True. + + Creates an actual temp file and verifies it IS deleted when + cleanup_on_exit=True. + """ + # Create a real temporary file to represent server-info file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False + ) as tmp_file: + server_info_file_path = Path(tmp_file.name) + + try: + # Verify file exists before test + assert server_info_file_path.exists(), "Temp file should exist initially" + + # Simulate the finally block logic with cleanup_on_exit=True + remove_server_info_file = True + cleanup_on_exit = True + host_server_info_file = server_info_file_path + + # This is the actual condition from fluent_container.py + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): + host_server_info_file.unlink() + + # Assert: file should be deleted because cleanup_on_exit=True + assert ( + not server_info_file_path.exists() + ), "Server-info file should be deleted when cleanup_on_exit=True" + + finally: + # Cleanup: delete temp file if it still exists (shouldn't) + if server_info_file_path.exists(): + server_info_file_path.unlink() + + def test_remove_server_info_file_parameter_override(self): + """Remove_server_info_file parameter works independently of cleanup_on_exit. + + Verifies that remove_server_info_file=False prevents file deletion + even when cleanup_on_exit=True. + """ + # Create a real temporary file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False + ) as tmp_file: + server_info_file_path = Path(tmp_file.name) + + try: + # Verify file exists before test + assert server_info_file_path.exists(), "Temp file should exist initially" + + # Test scenario: remove_server_info_file=False but cleanup_on_exit=True + remove_server_info_file = False # This should prevent deletion + cleanup_on_exit = True + host_server_info_file = server_info_file_path + + # This is the actual condition from fluent_container.py + if ( + remove_server_info_file + and cleanup_on_exit + and host_server_info_file.exists() + ): + host_server_info_file.unlink() + + # Assert: file should still exist because remove_server_info_file=False + assert ( + server_info_file_path.exists() + ), "Server-info file should be preserved when remove_server_info_file=False" + + finally: + # Cleanup: delete temp file + if server_info_file_path.exists(): + server_info_file_path.unlink() + + @pytest.mark.standalone def test_warning_in_windows(): with pytest.warns(PyFluentUserWarning): From 1a754952d6ca4c45f667728bc2e7bccb4ad494ab Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 16 Jul 2026 01:45:30 +0530 Subject: [PATCH 19/29] added the standalone_launcher as well. --- src/ansys/fluent/core/launcher/standalone_launcher.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index e34341ef59d..893fa5c9bed 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -378,6 +378,7 @@ def __call__( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(self._launch_cmd) from ex finally: - server_info_file = Path(self._server_info_file_name) - if server_info_file.exists(): - server_info_file.unlink() + if self.argvals.get("cleanup_on_exit", True): + server_info_file = Path(self._server_info_file_name) + if server_info_file.exists(): + server_info_file.unlink() From 53d63ba236e5eae37977e6ef1156b2265f5b8b2e Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 16 Jul 2026 02:32:37 +0530 Subject: [PATCH 20/29] updated the tests --- tests/test_launcher.py | 226 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 8741b08d07b..3673235eab0 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -926,3 +926,229 @@ def test_idle_timeout(monkeypatch): StandaloneLauncher._construct_timeout_arg(200) == ' -command="(set-session-idle-timeoutPLF+5)"' ) + + +# ============================================================================ +# Integration Tests for cleanup_on_exit Behavior +# ============================================================================ +class TestCleanupOnExitIntegration: + """Integration tests for cleanup_on_exit parameter across launchers.""" + + @pytest.mark.standalone + def test_standalone_launcher_cleanup_on_exit_default_deletes_file( + self, monkeypatch + ): + """Verify standalone launcher deletes server-info file by default (cleanup_on_exit=True).""" + from pathlib import Path + import tempfile + + monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) + + # Create a temporary server-info file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False, dir=None + ) as tmp_file: + server_info_path = Path(tmp_file.name) + tmp_file.write(b"127.0.0.1:1234:password") + + assert server_info_path.exists(), "Temp file should exist initially" + + try: + # Simulate the finally block behavior from StandaloneLauncher + # with default cleanup_on_exit=True + cleanup_on_exit = True + if cleanup_on_exit: + if server_info_path.exists(): + server_info_path.unlink() + + assert ( + not server_info_path.exists() + ), "Server-info file should be deleted when cleanup_on_exit=True (default)" + finally: + if server_info_path.exists(): + server_info_path.unlink() + + @pytest.mark.standalone + def test_standalone_launcher_cleanup_on_exit_false_preserves_file( + self, monkeypatch + ): + """Verify standalone launcher preserves server-info file when cleanup_on_exit=False.""" + from pathlib import Path + import tempfile + + monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) + + # Create a temporary server-info file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False, dir=None + ) as tmp_file: + server_info_path = Path(tmp_file.name) + tmp_file.write(b"127.0.0.1:1234:password") + + assert server_info_path.exists(), "Temp file should exist initially" + + try: + # Simulate the finally block behavior from StandaloneLauncher + # with cleanup_on_exit=False + cleanup_on_exit = False + if cleanup_on_exit: + if server_info_path.exists(): + server_info_path.unlink() + + assert ( + server_info_path.exists() + ), "Server-info file should be preserved when cleanup_on_exit=False" + finally: + if server_info_path.exists(): + server_info_path.unlink() + + def test_cleanup_on_exit_parameter_threading(self): + """Verify cleanup_on_exit parameter is properly threaded through DockerLauncher.""" + from unittest.mock import MagicMock, patch + + from ansys.fluent.core.launcher.container_launcher import DockerLauncher + from ansys.fluent.core.launcher.launch_options import ( + FluentLinuxGraphicsDriver, + LaunchMode, + UIMode, + ) + + # Create DockerLauncher with cleanup_on_exit=False + with patch( + "ansys.fluent.core.launcher.container_launcher.start_fluent_container" + ) as mock_start: + mock_start.return_value = (5678, {}, MagicMock()) + + launcher = DockerLauncher( + fluent_mode=LaunchMode.STANDALONE, + ui_mode=UIMode.NO_GUI, + graphics_driver=FluentLinuxGraphicsDriver.NULL, + cleanup_on_exit=False, + insecure_mode=True, # Required for test mode + ) + + # Verify that cleanup_on_exit is stored in argvals + assert launcher.argvals["cleanup_on_exit"] is False + + # When _call_docker is invoked (which calls start_fluent_container), + # cleanup_on_exit should be passed along + # This would be verified by checking mock_start was called with cleanup_on_exit=False + + def test_cleanup_on_exit_none_defaults_to_true(self): + """Verify cleanup_on_exit=None defaults to True.""" + from pathlib import Path + import tempfile + + # Create a temporary server-info file + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False, dir=None + ) as tmp_file: + server_info_path = Path(tmp_file.name) + tmp_file.write(b"127.0.0.1:1234:password") + + assert server_info_path.exists(), "Temp file should exist initially" + + try: + # Simulate the finally block behavior with cleanup_on_exit=None + # Should default to True + cleanup_on_exit = None + if cleanup_on_exit is None: + cleanup_on_exit = True + if cleanup_on_exit: + if server_info_path.exists(): + server_info_path.unlink() + + assert ( + not server_info_path.exists() + ), "Server-info file should be deleted when cleanup_on_exit defaults to True" + finally: + if server_info_path.exists(): + server_info_path.unlink() + + def test_preserved_server_info_file_readability(self): + """Verify preserved server-info file can be read for debugging.""" + from pathlib import Path + import tempfile + + from ansys.fluent.core.launcher.fluent_container import ( + _parse_server_info_file, + ) + + # Create a temporary server-info file with realistic content + # Format: Line 1 = "ip:port", Line 2 = "password" + connection_data = { + "ip": "127.0.0.1", + "port": "5678", + "password": "test_password_123", + } + + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False, dir=None, mode="w" + ) as tmp_file: + server_info_path = Path(tmp_file.name) + # Write in correct format: ip:port on first line, password on second + tmp_file.write(f"{connection_data['ip']}:{connection_data['port']}\n") + tmp_file.write(f"{connection_data['password']}\n") + + try: + # Simulate preservation and read the file + cleanup_on_exit = False + if cleanup_on_exit: + if server_info_path.exists(): + server_info_path.unlink() + + assert server_info_path.exists(), "Server-info file should be preserved" + + # Verify file can be read and parsed + ip, port, password = _parse_server_info_file(str(server_info_path)) + assert ip == connection_data["ip"] + assert port == int(connection_data["port"]) + assert password == connection_data["password"] + finally: + if server_info_path.exists(): + server_info_path.unlink() + + def test_cleanup_on_exit_true_vs_false_comparison(self): + """Verify file deletion behavior with cleanup_on_exit=True vs False.""" + from pathlib import Path + import tempfile + + def simulate_cleanup(cleanup_on_exit, server_info_path): + """Simulate the cleanup logic.""" + if cleanup_on_exit: + if server_info_path.exists(): + server_info_path.unlink() + + # Test with cleanup_on_exit=True + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False, dir=None + ) as tmp_file: + true_path = Path(tmp_file.name) + tmp_file.write(b"cleanup_true_test") + + try: + assert true_path.exists() + simulate_cleanup(True, true_path) + assert ( + not true_path.exists() + ), "File should be deleted with cleanup_on_exit=True" + finally: + if true_path.exists(): + true_path.unlink() + + # Test with cleanup_on_exit=False + with tempfile.NamedTemporaryFile( + suffix=".txt", prefix="serverinfo-", delete=False, dir=None + ) as tmp_file: + false_path = Path(tmp_file.name) + tmp_file.write(b"cleanup_false_test") + + try: + assert false_path.exists() + simulate_cleanup(False, false_path) + assert ( + false_path.exists() + ), "File should be preserved with cleanup_on_exit=False" + finally: + if false_path.exists(): + false_path.unlink() From fec936a3d528f1fc5c876bc00866d15fbc9528d2 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 16 Jul 2026 02:46:07 +0530 Subject: [PATCH 21/29] test update --- tests/test_launcher.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 3673235eab0..a26ce3660a6 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -931,6 +931,8 @@ def test_idle_timeout(monkeypatch): # ============================================================================ # Integration Tests for cleanup_on_exit Behavior # ============================================================================ + + class TestCleanupOnExitIntegration: """Integration tests for cleanup_on_exit parameter across launchers.""" From ca79203f76c343016fed48afb2c05e8f69967689 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Sat, 18 Jul 2026 21:02:59 +0530 Subject: [PATCH 22/29] updated the .exits & tmp_file.write() --- .../fluent/core/launcher/fluent_container.py | 8 +- .../core/launcher/standalone_launcher.py | 4 +- tests/test_launcher.py | 410 ++++++------------ 3 files changed, 133 insertions(+), 289 deletions(-) diff --git a/src/ansys/fluent/core/launcher/fluent_container.py b/src/ansys/fluent/core/launcher/fluent_container.py index b499d0de3a7..29b0abbb394 100644 --- a/src/ansys/fluent/core/launcher/fluent_container.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -610,9 +610,5 @@ def start_fluent_container( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(launch_string) from ex finally: - if ( - remove_server_info_file - and cleanup_on_exit - and host_server_info_file.exists() - ): - host_server_info_file.unlink() + if remove_server_info_file and cleanup_on_exit: + host_server_info_file.unlink(missing_ok=True) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 893fa5c9bed..abc1e099421 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -379,6 +379,4 @@ def __call__( raise LaunchFluentError(self._launch_cmd) from ex finally: if self.argvals.get("cleanup_on_exit", True): - server_info_file = Path(self._server_info_file_name) - if server_info_file.exists(): - server_info_file.unlink() + Path(self._server_info_file_name).unlink(missing_ok=True) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index a26ce3660a6..b7d1e15b75c 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -678,133 +678,6 @@ def test_respect_driver_is_not_null_in_linux(): assert driver == FluentLinuxGraphicsDriver.OPENGL -class TestContainerCleanupOnExit: - """Test suite for cleanup_on_exit flag behavior with server-info files. - - Tests verify that: - 1. Server-info file is preserved when cleanup_on_exit=False - 2. Server-info file is deleted when cleanup_on_exit=True (default) - 3. Both compose and non-compose container modes work correctly - 4. Edge cases are handled properly - - """ - - def test_server_info_file_preserved_cleanup_false(self): - """Real server-info file is preserved when cleanup_on_exit=False. - - Creates an actual temp file and verifies it's NOT deleted when - cleanup_on_exit=False. - """ - # Create a real temporary file to represent server-info file - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False - ) as tmp_file: - server_info_file_path = Path(tmp_file.name) - - try: - # Verify file exists before test - assert server_info_file_path.exists(), "Temp file should exist initially" - - # Simulate the finally block logic with cleanup_on_exit=False - remove_server_info_file = True - cleanup_on_exit = False - host_server_info_file = server_info_file_path - - # This is the actual condition from fluent_container.py - if ( - remove_server_info_file - and cleanup_on_exit - and host_server_info_file.exists() - ): - host_server_info_file.unlink() - - assert ( - server_info_file_path.exists() - ), "Server-info file should be preserved when cleanup_on_exit=False" - - finally: - if server_info_file_path.exists(): - server_info_file_path.unlink() - - def test_server_info_file_deleted_cleanup_true(self): - """Real server-info file is deleted when cleanup_on_exit=True. - - Creates an actual temp file and verifies it IS deleted when - cleanup_on_exit=True. - """ - # Create a real temporary file to represent server-info file - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False - ) as tmp_file: - server_info_file_path = Path(tmp_file.name) - - try: - # Verify file exists before test - assert server_info_file_path.exists(), "Temp file should exist initially" - - # Simulate the finally block logic with cleanup_on_exit=True - remove_server_info_file = True - cleanup_on_exit = True - host_server_info_file = server_info_file_path - - # This is the actual condition from fluent_container.py - if ( - remove_server_info_file - and cleanup_on_exit - and host_server_info_file.exists() - ): - host_server_info_file.unlink() - - # Assert: file should be deleted because cleanup_on_exit=True - assert ( - not server_info_file_path.exists() - ), "Server-info file should be deleted when cleanup_on_exit=True" - - finally: - # Cleanup: delete temp file if it still exists (shouldn't) - if server_info_file_path.exists(): - server_info_file_path.unlink() - - def test_remove_server_info_file_parameter_override(self): - """Remove_server_info_file parameter works independently of cleanup_on_exit. - - Verifies that remove_server_info_file=False prevents file deletion - even when cleanup_on_exit=True. - """ - # Create a real temporary file - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False - ) as tmp_file: - server_info_file_path = Path(tmp_file.name) - - try: - # Verify file exists before test - assert server_info_file_path.exists(), "Temp file should exist initially" - - # Test scenario: remove_server_info_file=False but cleanup_on_exit=True - remove_server_info_file = False # This should prevent deletion - cleanup_on_exit = True - host_server_info_file = server_info_file_path - - # This is the actual condition from fluent_container.py - if ( - remove_server_info_file - and cleanup_on_exit - and host_server_info_file.exists() - ): - host_server_info_file.unlink() - - # Assert: file should still exist because remove_server_info_file=False - assert ( - server_info_file_path.exists() - ), "Server-info file should be preserved when remove_server_info_file=False" - - finally: - # Cleanup: delete temp file - if server_info_file_path.exists(): - server_info_file_path.unlink() - - @pytest.mark.standalone def test_warning_in_windows(): with pytest.warns(PyFluentUserWarning): @@ -928,205 +801,183 @@ def test_idle_timeout(monkeypatch): ) -# ============================================================================ -# Integration Tests for cleanup_on_exit Behavior -# ============================================================================ +class TestContainerCleanupOnExit: + """Test cleanup_on_exit parameter for container launcher.""" + + def test_server_info_file_preserved_cleanup_false(self): + """Verify server-info file is preserved when cleanup_on_exit=False.""" + from pathlib import Path + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test content") -class TestCleanupOnExitIntegration: - """Integration tests for cleanup_on_exit parameter across launchers.""" + # Simulate cleanup logic with cleanup_on_exit=False + remove_server_info_file = True + cleanup_on_exit = False + if remove_server_info_file and cleanup_on_exit: + server_info_file.unlink(missing_ok=True) - @pytest.mark.standalone - def test_standalone_launcher_cleanup_on_exit_default_deletes_file( - self, monkeypatch - ): - """Verify standalone launcher deletes server-info file by default (cleanup_on_exit=True).""" + # File should still exist after cleanup + assert ( + server_info_file.exists() + ), "Server-info file should be preserved when cleanup_on_exit=False" + + def test_server_info_file_deleted_cleanup_true(self): + """Verify server-info file is deleted when cleanup_on_exit=True.""" from pathlib import Path - import tempfile - monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test content") - # Create a temporary server-info file - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False, dir=None - ) as tmp_file: - server_info_path = Path(tmp_file.name) - tmp_file.write(b"127.0.0.1:1234:password") + # Simulate cleanup logic with cleanup_on_exit=True + remove_server_info_file = True + cleanup_on_exit = True + if remove_server_info_file and cleanup_on_exit: + server_info_file.unlink(missing_ok=True) + + # File should be deleted after cleanup + assert ( + not server_info_file.exists() + ), "Server-info file should be deleted when cleanup_on_exit=True" + + def test_remove_server_info_file_parameter_override(self): + """Verify remove_server_info_file=False prevents deletion regardless of cleanup_on_exit.""" + from pathlib import Path - assert server_info_path.exists(), "Temp file should exist initially" + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test content") - try: - # Simulate the finally block behavior from StandaloneLauncher - # with default cleanup_on_exit=True + # Simulate cleanup logic with remove_server_info_file=False + remove_server_info_file = False cleanup_on_exit = True - if cleanup_on_exit: - if server_info_path.exists(): - server_info_path.unlink() + if remove_server_info_file and cleanup_on_exit: + server_info_file.unlink(missing_ok=True) + # File should still exist (remove_server_info_file takes precedence) assert ( - not server_info_path.exists() - ), "Server-info file should be deleted when cleanup_on_exit=True (default)" - finally: - if server_info_path.exists(): - server_info_path.unlink() + server_info_file.exists() + ), "Server-info file should be preserved when remove_server_info_file=False" + + +class TestCleanupOnExitIntegration: + """Integration tests for cleanup_on_exit behavior across launchers.""" + + def test_standalone_launcher_cleanup_on_exit_default_deletes_file(self): + """Verify standalone launcher deletes server-info file by default.""" + from pathlib import Path + + from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher + + with tempfile.TemporaryDirectory() as tmp_dir: + # Create a mock server info file + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test") - @pytest.mark.standalone - def test_standalone_launcher_cleanup_on_exit_false_preserves_file( - self, monkeypatch - ): + launcher = StandaloneLauncher() + launcher._server_info_file_name = str(server_info_file) + launcher.argvals = {"cleanup_on_exit": True} + + # Simulate cleanup + if launcher.argvals.get("cleanup_on_exit", True): + Path(launcher._server_info_file_name).unlink(missing_ok=True) + + assert ( + not server_info_file.exists() + ), "File should be deleted with cleanup_on_exit=True" + + def test_standalone_launcher_cleanup_on_exit_false_preserves_file(self): """Verify standalone launcher preserves server-info file when cleanup_on_exit=False.""" from pathlib import Path - import tempfile - monkeypatch.setattr(pyfluent.config, "launch_fluent_container", False) + from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher - # Create a temporary server-info file - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False, dir=None - ) as tmp_file: - server_info_path = Path(tmp_file.name) - tmp_file.write(b"127.0.0.1:1234:password") + with tempfile.TemporaryDirectory() as tmp_dir: + # Create a mock server info file + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test") - assert server_info_path.exists(), "Temp file should exist initially" + launcher = StandaloneLauncher() + launcher._server_info_file_name = str(server_info_file) + launcher.argvals = {"cleanup_on_exit": False} - try: - # Simulate the finally block behavior from StandaloneLauncher - # with cleanup_on_exit=False - cleanup_on_exit = False - if cleanup_on_exit: - if server_info_path.exists(): - server_info_path.unlink() + # Simulate cleanup + if launcher.argvals.get("cleanup_on_exit", True): + Path(launcher._server_info_file_name).unlink(missing_ok=True) assert ( - server_info_path.exists() - ), "Server-info file should be preserved when cleanup_on_exit=False" - finally: - if server_info_path.exists(): - server_info_path.unlink() + server_info_file.exists() + ), "File should be preserved with cleanup_on_exit=False" def test_cleanup_on_exit_parameter_threading(self): - """Verify cleanup_on_exit parameter is properly threaded through DockerLauncher.""" - from unittest.mock import MagicMock, patch - - from ansys.fluent.core.launcher.container_launcher import DockerLauncher - from ansys.fluent.core.launcher.launch_options import ( - FluentLinuxGraphicsDriver, - LaunchMode, - UIMode, + """Verify cleanup_on_exit is threaded through launcher chain.""" + from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher + + kwargs = dict( + ui_mode=UIMode.NO_GUI, + graphics_driver=( + FluentWindowsGraphicsDriver.AUTO + if is_windows() + else FluentLinuxGraphicsDriver.AUTO + ), + cleanup_on_exit=False, ) - # Create DockerLauncher with cleanup_on_exit=False - with patch( - "ansys.fluent.core.launcher.container_launcher.start_fluent_container" - ) as mock_start: - mock_start.return_value = (5678, {}, MagicMock()) - - launcher = DockerLauncher( - fluent_mode=LaunchMode.STANDALONE, - ui_mode=UIMode.NO_GUI, - graphics_driver=FluentLinuxGraphicsDriver.NULL, - cleanup_on_exit=False, - insecure_mode=True, # Required for test mode - ) - - # Verify that cleanup_on_exit is stored in argvals - assert launcher.argvals["cleanup_on_exit"] is False - - # When _call_docker is invoked (which calls start_fluent_container), - # cleanup_on_exit should be passed along - # This would be verified by checking mock_start was called with cleanup_on_exit=False + launcher = StandaloneLauncher(**kwargs) + assert ( + launcher.argvals.get("cleanup_on_exit") is False + ), "cleanup_on_exit should be threaded through launcher" def test_cleanup_on_exit_none_defaults_to_true(self): - """Verify cleanup_on_exit=None defaults to True.""" + """Verify cleanup_on_exit defaults to True behavior when None.""" from pathlib import Path - import tempfile - - # Create a temporary server-info file - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False, dir=None - ) as tmp_file: - server_info_path = Path(tmp_file.name) - tmp_file.write(b"127.0.0.1:1234:password") - assert server_info_path.exists(), "Temp file should exist initially" + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test content") - try: - # Simulate the finally block behavior with cleanup_on_exit=None - # Should default to True + # Simulate cleanup logic when cleanup_on_exit is None cleanup_on_exit = None - if cleanup_on_exit is None: - cleanup_on_exit = True - if cleanup_on_exit: - if server_info_path.exists(): - server_info_path.unlink() + # When cleanup_on_exit is None, treat as True (delete file) + if cleanup_on_exit is None or cleanup_on_exit: + server_info_file.unlink(missing_ok=True) assert ( - not server_info_path.exists() - ), "Server-info file should be deleted when cleanup_on_exit defaults to True" - finally: - if server_info_path.exists(): - server_info_path.unlink() + not server_info_file.exists() + ), "File should be deleted when cleanup_on_exit is None (defaults to True)" def test_preserved_server_info_file_readability(self): - """Verify preserved server-info file can be read for debugging.""" + """Verify preserved server-info file is readable after launch.""" from pathlib import Path - import tempfile - - from ansys.fluent.core.launcher.fluent_container import ( - _parse_server_info_file, - ) - - # Create a temporary server-info file with realistic content - # Format: Line 1 = "ip:port", Line 2 = "password" - connection_data = { - "ip": "127.0.0.1", - "port": "5678", - "password": "test_password_123", - } - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False, dir=None, mode="w" - ) as tmp_file: - server_info_path = Path(tmp_file.name) - # Write in correct format: ip:port on first line, password on second - tmp_file.write(f"{connection_data['ip']}:{connection_data['port']}\n") - tmp_file.write(f"{connection_data['password']}\n") + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + test_content = "port=12345\nhost=localhost" + server_info_file.write_text(test_content) - try: - # Simulate preservation and read the file - cleanup_on_exit = False - if cleanup_on_exit: - if server_info_path.exists(): - server_info_path.unlink() - - assert server_info_path.exists(), "Server-info file should be preserved" - - # Verify file can be read and parsed - ip, port, password = _parse_server_info_file(str(server_info_path)) - assert ip == connection_data["ip"] - assert port == int(connection_data["port"]) - assert password == connection_data["password"] - finally: - if server_info_path.exists(): - server_info_path.unlink() + # File should be readable + assert ( + server_info_file.read_text() == test_content + ), "Preserved file should be readable" def test_cleanup_on_exit_true_vs_false_comparison(self): """Verify file deletion behavior with cleanup_on_exit=True vs False.""" from pathlib import Path - import tempfile def simulate_cleanup(cleanup_on_exit, server_info_path): """Simulate the cleanup logic.""" if cleanup_on_exit: - if server_info_path.exists(): - server_info_path.unlink() + server_info_path.unlink(missing_ok=True) # Test with cleanup_on_exit=True with tempfile.NamedTemporaryFile( suffix=".txt", prefix="serverinfo-", delete=False, dir=None ) as tmp_file: true_path = Path(tmp_file.name) - tmp_file.write(b"cleanup_true_test") + + true_path.write_bytes(b"cleanup_true_test") try: assert true_path.exists() @@ -1135,15 +986,15 @@ def simulate_cleanup(cleanup_on_exit, server_info_path): not true_path.exists() ), "File should be deleted with cleanup_on_exit=True" finally: - if true_path.exists(): - true_path.unlink() + true_path.unlink(missing_ok=True) # Test with cleanup_on_exit=False with tempfile.NamedTemporaryFile( suffix=".txt", prefix="serverinfo-", delete=False, dir=None ) as tmp_file: false_path = Path(tmp_file.name) - tmp_file.write(b"cleanup_false_test") + + false_path.write_bytes(b"cleanup_false_test") try: assert false_path.exists() @@ -1152,5 +1003,4 @@ def simulate_cleanup(cleanup_on_exit, server_info_path): false_path.exists() ), "File should be preserved with cleanup_on_exit=False" finally: - if false_path.exists(): - false_path.unlink() + false_path.unlink(missing_ok=True) From 9c5c9910ab82d120c359253282ff9c9824587045 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Mon, 20 Jul 2026 11:18:00 +0530 Subject: [PATCH 23/29] updated the test case --- tests/test_launcher.py | 120 +++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index b7d1e15b75c..39f223cb7ce 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -26,6 +26,7 @@ import platform import tempfile from tempfile import TemporaryDirectory +from unittest.mock import MagicMock, Mock, patch import pytest @@ -863,72 +864,113 @@ def test_remove_server_info_file_parameter_override(self): class TestCleanupOnExitIntegration: - """Integration tests for cleanup_on_exit behavior across launchers.""" + """Unit tests for cleanup_on_exit behavior using mocks (no Fluent required).""" def test_standalone_launcher_cleanup_on_exit_default_deletes_file(self): - """Verify standalone launcher deletes server-info file by default.""" + """Verify cleanup logic deletes server-info file by default (cleanup_on_exit=True).""" from pathlib import Path - from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher - with tempfile.TemporaryDirectory() as tmp_dir: # Create a mock server info file server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test") - - launcher = StandaloneLauncher() - launcher._server_info_file_name = str(server_info_file) - launcher.argvals = {"cleanup_on_exit": True} + server_info_file.write_text("test content") - # Simulate cleanup - if launcher.argvals.get("cleanup_on_exit", True): - Path(launcher._server_info_file_name).unlink(missing_ok=True) + # Simulate the cleanup logic from standalone_launcher.py finally block + cleanup_on_exit = True + if cleanup_on_exit: + server_info_file.unlink(missing_ok=True) assert ( not server_info_file.exists() ), "File should be deleted with cleanup_on_exit=True" def test_standalone_launcher_cleanup_on_exit_false_preserves_file(self): - """Verify standalone launcher preserves server-info file when cleanup_on_exit=False.""" + """Verify cleanup logic preserves server-info file when cleanup_on_exit=False.""" from pathlib import Path - from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher - with tempfile.TemporaryDirectory() as tmp_dir: # Create a mock server info file server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test") - - launcher = StandaloneLauncher() - launcher._server_info_file_name = str(server_info_file) - launcher.argvals = {"cleanup_on_exit": False} + server_info_file.write_text("test content") - # Simulate cleanup - if launcher.argvals.get("cleanup_on_exit", True): - Path(launcher._server_info_file_name).unlink(missing_ok=True) + # Simulate the cleanup logic from standalone_launcher.py finally block + cleanup_on_exit = False + if cleanup_on_exit: + server_info_file.unlink(missing_ok=True) assert ( server_info_file.exists() ), "File should be preserved with cleanup_on_exit=False" - def test_cleanup_on_exit_parameter_threading(self): - """Verify cleanup_on_exit is threaded through launcher chain.""" - from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher - - kwargs = dict( - ui_mode=UIMode.NO_GUI, - graphics_driver=( - FluentWindowsGraphicsDriver.AUTO - if is_windows() - else FluentLinuxGraphicsDriver.AUTO - ), - cleanup_on_exit=False, - ) + def test_container_launcher_removes_server_info_with_cleanup_true(self): + """Verify container launcher deletes server-info file when cleanup_on_exit=True.""" + from pathlib import Path + + with tempfile.TemporaryDirectory() as tmp_dir: + host_server_info_file = Path(tmp_dir) / "serverinfo-container.txt" + host_server_info_file.write_text("mock server info") + + # Simulate fluent_container.py finally block cleanup logic + remove_server_info_file = True + cleanup_on_exit = True + if remove_server_info_file and cleanup_on_exit: + host_server_info_file.unlink(missing_ok=True) + + assert ( + not host_server_info_file.exists() + ), "File should be deleted when both remove_server_info_file and cleanup_on_exit are True" + + def test_container_launcher_preserves_server_info_with_cleanup_false(self): + """Verify container launcher preserves server-info file when cleanup_on_exit=False.""" + from pathlib import Path + + with tempfile.TemporaryDirectory() as tmp_dir: + host_server_info_file = Path(tmp_dir) / "serverinfo-container.txt" + host_server_info_file.write_text("mock server info") + + # Simulate fluent_container.py finally block cleanup logic + remove_server_info_file = True # default + cleanup_on_exit = False # user specified + if remove_server_info_file and cleanup_on_exit: + host_server_info_file.unlink(missing_ok=True) + + assert ( + host_server_info_file.exists() + ), "File should be preserved when cleanup_on_exit=False" + + def test_cleanup_on_exit_parameter_propagation_to_container_dict(self): + """Verify cleanup_on_exit is propagated to container_dict via remove_server_info_file.""" + container_dict = {} + + # Simulate what DockerLauncher.__call__ should do + cleanup_on_exit = False + container_dict.setdefault("remove_server_info_file", cleanup_on_exit) + + assert ( + container_dict.get("remove_server_info_file") is False + ), "remove_server_info_file should default to cleanup_on_exit value" + + # Test with cleanup_on_exit=True + container_dict2 = {} + cleanup_on_exit2 = True + container_dict2.setdefault("remove_server_info_file", cleanup_on_exit2) + + assert ( + container_dict2.get("remove_server_info_file") is True + ), "remove_server_info_file should default to cleanup_on_exit=True" + + def test_explicit_remove_server_info_file_not_overridden(self): + """Verify explicit remove_server_info_file in container_dict is respected.""" + # When user explicitly provides remove_server_info_file, cleanup_on_exit should not override it + container_dict = {"remove_server_info_file": False} + cleanup_on_exit = True + + # Simulate setdefault behavior (only sets if key not present) + container_dict.setdefault("remove_server_info_file", cleanup_on_exit) - launcher = StandaloneLauncher(**kwargs) assert ( - launcher.argvals.get("cleanup_on_exit") is False - ), "cleanup_on_exit should be threaded through launcher" + container_dict.get("remove_server_info_file") is False + ), "Explicit remove_server_info_file should not be overridden by cleanup_on_exit" def test_cleanup_on_exit_none_defaults_to_true(self): """Verify cleanup_on_exit defaults to True behavior when None.""" From e0925c999cede2534a6a5ec0ac4cf9a5525c229b Mon Sep 17 00:00:00 2001 From: mayankansys Date: Mon, 20 Jul 2026 19:58:14 +0530 Subject: [PATCH 24/29] updated changes --- .../core/launcher/container_launcher.py | 26 ++- .../fluent/core/launcher/fluent_container.py | 53 ++++- .../core/launcher/standalone_launcher.py | 25 ++- tests/test_launcher.py | 212 +++++++++++++----- 4 files changed, 234 insertions(+), 82 deletions(-) diff --git a/src/ansys/fluent/core/launcher/container_launcher.py b/src/ansys/fluent/core/launcher/container_launcher.py index 1a8c51a418c..05ce3b0fa84 100644 --- a/src/ansys/fluent/core/launcher/container_launcher.py +++ b/src/ansys/fluent/core/launcher/container_launcher.py @@ -49,6 +49,7 @@ CERTIFICATES_FOLDER_NOT_PROVIDED_AT_LAUNCH, ) from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, configure_container_dict, dict_to_str, start_fluent_container, @@ -64,6 +65,7 @@ ) import ansys.fluent.core.launcher.watchdog as watchdog from ansys.fluent.core.session import _parse_server_info_file +from ansys.fluent.core.utils.deprecate import deprecate_arguments from ansys.fluent.core.utils.fluent_version import FluentVersion if TYPE_CHECKING: @@ -139,6 +141,12 @@ def _get_server_info_from_container(config_dict): class DockerLauncher: """Instantiates Fluent session in container mode.""" + @deprecate_arguments( + old_args="cleanup_on_exit", + new_args="preserve_info_file", + version="0.42.0", + converter=_cleanup_on_exit_to_preserve_info_file_converter, + ) def __init__( self, **kwargs: Unpack[ContainerArgs], @@ -181,9 +189,9 @@ def __init__( dry_run : bool, optional If True, does not launch Fluent but prints configuration information instead. If dry running a container start, this method will return the configured ``container_dict``. Defaults to False. - cleanup_on_exit : bool - Determines whether to shut down the connected Fluent session upon exit or when calling - the session's `exit()` method. Defaults to True. + preserve_info_file : bool, optional + If True, the server-info file will be preserved for debugging. If False, the server-info file + will be deleted when the session exits. Defaults to False. start_transcript : bool Indicates whether to start streaming the Fluent transcript in the client. Defaults to True; streaming can be controlled via `transcript.start()` and `transcript.stop()` methods on the session object. @@ -237,8 +245,8 @@ def __init__( raise ValueError(CERTIFICATES_FOLDER_NOT_PROVIDED_AT_LAUNCH) self.argvals, self.new_session = _get_argvals_and_session(kwargs) - if self.argvals.get("cleanup_on_exit") is None: - self.argvals["cleanup_on_exit"] = True + if self.argvals.get("preserve_info_file") is None: + self.argvals["preserve_info_file"] = False if self.argvals.get("start_transcript") is None: self.argvals["start_transcript"] = True if "start_watchdog" not in self.argvals: @@ -300,7 +308,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, - cleanup_on_exit=self.argvals["cleanup_on_exit"], + preserve_info_file=self.argvals["preserve_info_file"], ) try: @@ -318,7 +326,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, - cleanup_on_exit=self.argvals["cleanup_on_exit"], + preserve_info_file=self.argvals["preserve_info_file"], ) allow_remote_host = ( @@ -332,7 +340,7 @@ def __call__( certificates_folder=self.argvals["certificates_folder"], insecure_mode=self.argvals["insecure_mode"], file_transfer_service=self.file_transfer_service, - cleanup_on_exit=self.argvals["cleanup_on_exit"], + cleanup_on_exit=not self.argvals["preserve_info_file"], slurm_job_id=self.argvals and self.argvals.get("slurm_job_id"), inside_container=True, container=container, @@ -354,7 +362,7 @@ def __call__( if not self._compose_config.is_compose: if ( self.argvals["start_watchdog"] is None - and self.argvals["cleanup_on_exit"] + and not self.argvals["preserve_info_file"] ): self.argvals["start_watchdog"] = True if self.argvals["start_watchdog"]: diff --git a/src/ansys/fluent/core/launcher/fluent_container.py b/src/ansys/fluent/core/launcher/fluent_container.py index 29b0abbb394..d9725029983 100644 --- a/src/ansys/fluent/core/launcher/fluent_container.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -96,6 +96,32 @@ logger = logging.getLogger("pyfluent.launcher") +def _cleanup_on_exit_to_preserve_info_file_converter( + kwargs: dict[str, Any], + old_args: list[str], + new_args: list[str], +) -> dict[str, Any]: + """ + Convert deprecated cleanup_on_exit to preserve_info_file with inverted logic. + + cleanup_on_exit=True (clean up the file) → preserve_info_file=False (don't preserve) + cleanup_on_exit=False (keep the file) → preserve_info_file=True (preserve) + """ + if "cleanup_on_exit" in kwargs: + cleanup_on_exit = kwargs.pop("cleanup_on_exit") + if "preserve_info_file" in kwargs: + warnings.warn( + "Both deprecated argument 'cleanup_on_exit' and new argument 'preserve_info_file' were provided. " + "Ignoring cleanup_on_exit.", + PyFluentDeprecationWarning, + stacklevel=3, + ) + else: + # Invert the logic: cleanup_on_exit → preserve_info_file + kwargs["preserve_info_file"] = not cleanup_on_exit + return kwargs + + class FluentImageNameTagNotSpecified(ValueError): """Raised when Fluent image name or image tag is not specified.""" @@ -471,12 +497,18 @@ def configure_container_dict( ) +@deprecate_arguments( + old_args="cleanup_on_exit", + new_args="preserve_info_file", + version="0.42.0", + converter=_cleanup_on_exit_to_preserve_info_file_converter, +) def start_fluent_container( args: list[str], container_dict: dict | None = None, start_timeout: int = 100, compose_config: ComposeConfig | None = None, - cleanup_on_exit: bool = True, + preserve_info_file: bool = False, ) -> tuple[int, str, Any]: """Start a Fluent container. @@ -491,9 +523,9 @@ def start_fluent_container( seconds. compose_config : ComposeConfig, optional Configuration for Docker Compose, if using Docker Compose to launch the container. - cleanup_on_exit : bool, optional - If True, the server-info file will be deleted when the container starts. - If False, the server-info file will be preserved for debugging. Defaults to True. + preserve_info_file : bool, optional + If True, the server-info file will be preserved for debugging. + If False, the server-info file will be deleted when the container exits. Defaults to False. Returns ------- @@ -506,6 +538,8 @@ def start_fluent_container( ------ TimeoutError If Fluent container launch reaches timeout. + ValueError + If ``remove_server_info_file`` and ``preserve_info_file`` have contradictory values. Notes ----- @@ -610,5 +644,14 @@ def start_fluent_container( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(launch_string) from ex finally: - if remove_server_info_file and cleanup_on_exit: + # Validate that remove_server_info_file and cleanup_on_exit don't contradict. + # should_preserve is True if we want to keep the file + should_preserve = preserve_info_file or not remove_server_info_file + if remove_server_info_file and preserve_info_file: + raise ValueError( + "Contradictory file cleanup parameters: cannot both set remove_server_info_file=True " + "and preserve_info_file=True. Either remove the file or preserve it, not both." + ) + # Delete file only if both flags agree not to preserve it. + if not should_preserve: host_server_info_file.unlink(missing_ok=True) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index abc1e099421..d80c944304c 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -49,6 +49,9 @@ from ansys.fluent.core.launcher.error_handler import ( LaunchFluentError, ) +from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, +) from ansys.fluent.core.launcher.launch_options import ( FluentMode, UIMode, @@ -68,6 +71,7 @@ _get_server_info_file_names, ) import ansys.fluent.core.launcher.watchdog as watchdog +from ansys.fluent.core.utils.deprecate import deprecate_arguments from ansys.fluent.core.utils.fluent_version import FluentVersion if TYPE_CHECKING: @@ -133,6 +137,12 @@ class StandaloneArgs( class StandaloneLauncher: """Instantiates Fluent session in standalone mode.""" + @deprecate_arguments( + old_args="cleanup_on_exit", + new_args="preserve_info_file", + version="0.42.0", + converter=_cleanup_on_exit_to_preserve_info_file_converter, + ) def __init__( self, **kwargs: Unpack[StandaloneArgs], @@ -173,9 +183,9 @@ def __init__( Additional command-line arguments for Fluent, formatted as they would be on the command line. env : dict[str, str], optional A mapping for modifying environment variables in Fluent. Defaults to ``None``. - cleanup_on_exit : bool, optional - Determines whether to shut down the connected Fluent session when exiting PyFluent or calling - the session's `exit()` method. Defaults to True. + preserve_info_file : bool, optional + If True, the server-info file will be preserved for debugging. + If False, the server-info file will be deleted when the session exits. Defaults to False. dry_run : bool, optional If True, does not launch Fluent but prints configuration information instead. The `call()` method returns a tuple containing the launch string and server info file name. Defaults to False. @@ -201,7 +211,7 @@ def __init__( A flag indicating whether to write equivalent Python journals from provided journal files; can also specify a filename for the new Python journal. start_watchdog : bool, optional - When `cleanup_on_exit` is True, defaults to True; an independent watchdog process ensures that any local + When `preserve_info_file` is False (default cleanup enabled), defaults to True; an independent watchdog process ensures that any local GUI-less Fluent sessions started by PyFluent are properly closed when the current Python process ends. file_transfer_service : Any Service for uploading/downloading files to/from the server. @@ -327,7 +337,7 @@ def __call__( session = self.new_session._create_from_server_info_file( server_info_file_name=self._server_info_file_name, file_transfer_service=self.file_transfer_service, - cleanup_on_exit=self.argvals.get("cleanup_on_exit"), + preserve_info_file=self.argvals.get("preserve_info_file", False), start_transcript=self.argvals.get("start_transcript"), launcher_args=self.argvals, inside_container=False, @@ -335,7 +345,7 @@ def __call__( session._process = process start_watchdog = _confirm_watchdog_start( self.argvals.get("start_watchdog"), - self.argvals.get("cleanup_on_exit"), + not self.argvals.get("preserve_info_file", False), session._fluent_connection, ) if start_watchdog: @@ -378,5 +388,6 @@ def __call__( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(self._launch_cmd) from ex finally: - if self.argvals.get("cleanup_on_exit", True): + # preserve_info_file defaults to False, meaning cleanup happens by default + if not self.argvals.get("preserve_info_file", False): Path(self._server_info_file_name).unlink(missing_ok=True) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 39f223cb7ce..ebbe616bfe9 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -27,6 +27,7 @@ import tempfile from tempfile import TemporaryDirectory from unittest.mock import MagicMock, Mock, patch +import warnings import pytest @@ -802,67 +803,6 @@ def test_idle_timeout(monkeypatch): ) -class TestContainerCleanupOnExit: - """Test cleanup_on_exit parameter for container launcher.""" - - def test_server_info_file_preserved_cleanup_false(self): - """Verify server-info file is preserved when cleanup_on_exit=False.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate cleanup logic with cleanup_on_exit=False - remove_server_info_file = True - cleanup_on_exit = False - if remove_server_info_file and cleanup_on_exit: - server_info_file.unlink(missing_ok=True) - - # File should still exist after cleanup - assert ( - server_info_file.exists() - ), "Server-info file should be preserved when cleanup_on_exit=False" - - def test_server_info_file_deleted_cleanup_true(self): - """Verify server-info file is deleted when cleanup_on_exit=True.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate cleanup logic with cleanup_on_exit=True - remove_server_info_file = True - cleanup_on_exit = True - if remove_server_info_file and cleanup_on_exit: - server_info_file.unlink(missing_ok=True) - - # File should be deleted after cleanup - assert ( - not server_info_file.exists() - ), "Server-info file should be deleted when cleanup_on_exit=True" - - def test_remove_server_info_file_parameter_override(self): - """Verify remove_server_info_file=False prevents deletion regardless of cleanup_on_exit.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate cleanup logic with remove_server_info_file=False - remove_server_info_file = False - cleanup_on_exit = True - if remove_server_info_file and cleanup_on_exit: - server_info_file.unlink(missing_ok=True) - - # File should still exist (remove_server_info_file takes precedence) - assert ( - server_info_file.exists() - ), "Server-info file should be preserved when remove_server_info_file=False" - - class TestCleanupOnExitIntegration: """Unit tests for cleanup_on_exit behavior using mocks (no Fluent required).""" @@ -1046,3 +986,153 @@ def simulate_cleanup(cleanup_on_exit, server_info_path): ), "File should be preserved with cleanup_on_exit=False" finally: false_path.unlink(missing_ok=True) + + def test_deprecation_cleanup_on_exit_true_converts_to_preserve_false(self): + """Verify cleanup_on_exit=True converts to preserve_info_file=False (inverted logic).""" + from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, + ) + + # The converter function doesn't issue warnings; the decorator does + kwargs = {"cleanup_on_exit": True} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) + + # Should be converted to preserve_info_file=False + assert ( + result.get("preserve_info_file") is False + ), "cleanup_on_exit=True should convert to preserve_info_file=False" + assert ( + "cleanup_on_exit" not in result + ), "cleanup_on_exit should be removed from kwargs" + + def test_deprecation_cleanup_on_exit_false_converts_to_preserve_true(self): + """Verify cleanup_on_exit=False converts to preserve_info_file=True (inverted logic).""" + from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, + ) + + # The converter function doesn't issue warnings; the decorator does + kwargs = {"cleanup_on_exit": False} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) + + # Should be converted to preserve_info_file=True + assert ( + result.get("preserve_info_file") is True + ), "cleanup_on_exit=False should convert to preserve_info_file=True" + assert ( + "cleanup_on_exit" not in result + ), "cleanup_on_exit should be removed from kwargs" + + def test_new_preserve_info_file_true_no_deprecation_warning(self): + """Verify new preserve_info_file=True parameter works without warnings.""" + import pytest + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, + ) + + kwargs = {"preserve_info_file": True} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) + + # Filter out non-deprecation warnings + deprecation_warnings = [ + warning + for warning in w + if issubclass(warning.category, PyFluentDeprecationWarning) + ] + assert ( + len(deprecation_warnings) == 0 + ), "Should not warn when using new parameter" + assert ( + result.get("preserve_info_file") is True + ), "preserve_info_file should remain True" + + def test_new_preserve_info_file_false_no_deprecation_warning(self): + """Verify new preserve_info_file=False parameter works without warnings.""" + import pytest + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, + ) + + kwargs = {"preserve_info_file": False} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) + + # Filter out non-deprecation warnings + deprecation_warnings = [ + warning + for warning in w + if issubclass(warning.category, PyFluentDeprecationWarning) + ] + assert ( + len(deprecation_warnings) == 0 + ), "Should not warn when using new parameter" + assert ( + result.get("preserve_info_file") is False + ), "preserve_info_file should remain False" + + def test_both_cleanup_on_exit_and_preserve_info_file_provided(self): + """Verify providing both parameters warns and uses new one.""" + import pytest + + with pytest.warns(PyFluentDeprecationWarning, match="Both deprecated"): + from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, + ) + + kwargs = {"cleanup_on_exit": True, "preserve_info_file": False} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) + + # Should keep the new parameter value + assert ( + result.get("preserve_info_file") is False + ), "New preserve_info_file parameter should take precedence" + assert "cleanup_on_exit" not in result, "cleanup_on_exit should be removed" + + def test_preserve_info_file_defaults_to_false(self): + """Verify preserve_info_file defaults to False (cleanup by default).""" + from pathlib import Path + + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test content") + + # Simulate cleanup logic with preserve_info_file default + preserve_info_file = False # Default + if not preserve_info_file: + server_info_file.unlink(missing_ok=True) + + assert ( + not server_info_file.exists() + ), "File should be deleted by default (preserve_info_file=False)" + + def test_preserve_info_file_true_preserves_file(self): + """Verify preserve_info_file=True preserves the server-info file.""" + from pathlib import Path + + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test content") + + # Simulate cleanup logic with preserve_info_file=True + preserve_info_file = True + if not preserve_info_file: + server_info_file.unlink(missing_ok=True) + + assert ( + server_info_file.exists() + ), "File should be preserved when preserve_info_file=True" From 6d876b05b16e396d6ff0155631c87b22ace2c9ee Mon Sep 17 00:00:00 2001 From: mayankansys Date: Mon, 20 Jul 2026 20:14:58 +0530 Subject: [PATCH 25/29] updated the test file --- tests/test_launcher.py | 395 +++++++++-------------------------------- 1 file changed, 88 insertions(+), 307 deletions(-) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index ebbe616bfe9..d679ab7b5e0 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -803,336 +803,117 @@ def test_idle_timeout(monkeypatch): ) -class TestCleanupOnExitIntegration: - """Unit tests for cleanup_on_exit behavior using mocks (no Fluent required).""" - - def test_standalone_launcher_cleanup_on_exit_default_deletes_file(self): - """Verify cleanup logic deletes server-info file by default (cleanup_on_exit=True).""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - # Create a mock server info file - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate the cleanup logic from standalone_launcher.py finally block - cleanup_on_exit = True - if cleanup_on_exit: - server_info_file.unlink(missing_ok=True) - - assert ( - not server_info_file.exists() - ), "File should be deleted with cleanup_on_exit=True" - - def test_standalone_launcher_cleanup_on_exit_false_preserves_file(self): - """Verify cleanup logic preserves server-info file when cleanup_on_exit=False.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - # Create a mock server info file - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate the cleanup logic from standalone_launcher.py finally block - cleanup_on_exit = False - if cleanup_on_exit: - server_info_file.unlink(missing_ok=True) - - assert ( - server_info_file.exists() - ), "File should be preserved with cleanup_on_exit=False" - - def test_container_launcher_removes_server_info_with_cleanup_true(self): - """Verify container launcher deletes server-info file when cleanup_on_exit=True.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - host_server_info_file = Path(tmp_dir) / "serverinfo-container.txt" - host_server_info_file.write_text("mock server info") - - # Simulate fluent_container.py finally block cleanup logic - remove_server_info_file = True - cleanup_on_exit = True - if remove_server_info_file and cleanup_on_exit: - host_server_info_file.unlink(missing_ok=True) - - assert ( - not host_server_info_file.exists() - ), "File should be deleted when both remove_server_info_file and cleanup_on_exit are True" - - def test_container_launcher_preserves_server_info_with_cleanup_false(self): - """Verify container launcher preserves server-info file when cleanup_on_exit=False.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - host_server_info_file = Path(tmp_dir) / "serverinfo-container.txt" - host_server_info_file.write_text("mock server info") - - # Simulate fluent_container.py finally block cleanup logic - remove_server_info_file = True # default - cleanup_on_exit = False # user specified - if remove_server_info_file and cleanup_on_exit: - host_server_info_file.unlink(missing_ok=True) - - assert ( - host_server_info_file.exists() - ), "File should be preserved when cleanup_on_exit=False" - - def test_cleanup_on_exit_parameter_propagation_to_container_dict(self): - """Verify cleanup_on_exit is propagated to container_dict via remove_server_info_file.""" - container_dict = {} - - # Simulate what DockerLauncher.__call__ should do +def test_standalone_launcher_cleanup_on_exit_false_preserves_file(): + """Verify old cleanup_on_exit=False parameter still works (backward compatibility).""" + from pathlib import Path + + with tempfile.TemporaryDirectory() as tmp_dir: + server_info_file = Path(tmp_dir) / "serverinfo-test.txt" + server_info_file.write_text("test content") + cleanup_on_exit = False - container_dict.setdefault("remove_server_info_file", cleanup_on_exit) + if cleanup_on_exit: + server_info_file.unlink(missing_ok=True) assert ( - container_dict.get("remove_server_info_file") is False - ), "remove_server_info_file should default to cleanup_on_exit value" + server_info_file.exists() + ), "File should be preserved with cleanup_on_exit=False" - # Test with cleanup_on_exit=True - container_dict2 = {} - cleanup_on_exit2 = True - container_dict2.setdefault("remove_server_info_file", cleanup_on_exit2) - assert ( - container_dict2.get("remove_server_info_file") is True - ), "remove_server_info_file should default to cleanup_on_exit=True" +def test_deprecation_cleanup_on_exit_true_converts_to_preserve_false(): + """Verify cleanup_on_exit=True converts to preserve_info_file=False (inverted logic).""" + from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, + ) - def test_explicit_remove_server_info_file_not_overridden(self): - """Verify explicit remove_server_info_file in container_dict is respected.""" - # When user explicitly provides remove_server_info_file, cleanup_on_exit should not override it - container_dict = {"remove_server_info_file": False} - cleanup_on_exit = True + # The converter function doesn't issue warnings; the decorator does + kwargs = {"cleanup_on_exit": True} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) - # Simulate setdefault behavior (only sets if key not present) - container_dict.setdefault("remove_server_info_file", cleanup_on_exit) + # Should be converted to preserve_info_file=False + assert ( + result.get("preserve_info_file") is False + ), "cleanup_on_exit=True should convert to preserve_info_file=False" + assert ( + "cleanup_on_exit" not in result + ), "cleanup_on_exit should be removed from kwargs" - assert ( - container_dict.get("remove_server_info_file") is False - ), "Explicit remove_server_info_file should not be overridden by cleanup_on_exit" - - def test_cleanup_on_exit_none_defaults_to_true(self): - """Verify cleanup_on_exit defaults to True behavior when None.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate cleanup logic when cleanup_on_exit is None - cleanup_on_exit = None - # When cleanup_on_exit is None, treat as True (delete file) - if cleanup_on_exit is None or cleanup_on_exit: - server_info_file.unlink(missing_ok=True) - - assert ( - not server_info_file.exists() - ), "File should be deleted when cleanup_on_exit is None (defaults to True)" - - def test_preserved_server_info_file_readability(self): - """Verify preserved server-info file is readable after launch.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - test_content = "port=12345\nhost=localhost" - server_info_file.write_text(test_content) - - # File should be readable - assert ( - server_info_file.read_text() == test_content - ), "Preserved file should be readable" - - def test_cleanup_on_exit_true_vs_false_comparison(self): - """Verify file deletion behavior with cleanup_on_exit=True vs False.""" - from pathlib import Path - - def simulate_cleanup(cleanup_on_exit, server_info_path): - """Simulate the cleanup logic.""" - if cleanup_on_exit: - server_info_path.unlink(missing_ok=True) - - # Test with cleanup_on_exit=True - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False, dir=None - ) as tmp_file: - true_path = Path(tmp_file.name) - - true_path.write_bytes(b"cleanup_true_test") - - try: - assert true_path.exists() - simulate_cleanup(True, true_path) - assert ( - not true_path.exists() - ), "File should be deleted with cleanup_on_exit=True" - finally: - true_path.unlink(missing_ok=True) - - # Test with cleanup_on_exit=False - with tempfile.NamedTemporaryFile( - suffix=".txt", prefix="serverinfo-", delete=False, dir=None - ) as tmp_file: - false_path = Path(tmp_file.name) - - false_path.write_bytes(b"cleanup_false_test") - - try: - assert false_path.exists() - simulate_cleanup(False, false_path) - assert ( - false_path.exists() - ), "File should be preserved with cleanup_on_exit=False" - finally: - false_path.unlink(missing_ok=True) - - def test_deprecation_cleanup_on_exit_true_converts_to_preserve_false(self): - """Verify cleanup_on_exit=True converts to preserve_info_file=False (inverted logic).""" + +def test_deprecation_cleanup_on_exit_false_converts_to_preserve_true(): + """Verify cleanup_on_exit=False converts to preserve_info_file=True (inverted logic).""" + from ansys.fluent.core.launcher.fluent_container import ( + _cleanup_on_exit_to_preserve_info_file_converter, + ) + + # The converter function doesn't issue warnings; the decorator does + kwargs = {"cleanup_on_exit": False} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) + + # Should be converted to preserve_info_file=True + assert ( + result.get("preserve_info_file") is True + ), "cleanup_on_exit=False should convert to preserve_info_file=True" + assert ( + "cleanup_on_exit" not in result + ), "cleanup_on_exit should be removed from kwargs" + + +def test_new_preserve_info_file_no_deprecation_warning(): + """Verify new preserve_info_file parameter works without deprecation warnings.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") from ansys.fluent.core.launcher.fluent_container import ( _cleanup_on_exit_to_preserve_info_file_converter, ) - # The converter function doesn't issue warnings; the decorator does - kwargs = {"cleanup_on_exit": True} + # Test with preserve_info_file=True + kwargs = {"preserve_info_file": True} + result = _cleanup_on_exit_to_preserve_info_file_converter( + kwargs, ["cleanup_on_exit"], ["preserve_info_file"] + ) + deprecation_warnings = [ + warning + for warning in w + if issubclass(warning.category, PyFluentDeprecationWarning) + ] + assert len(deprecation_warnings) == 0, "Should not warn with new parameter" + assert result.get("preserve_info_file") is True + + # Test with preserve_info_file=False + w.clear() + kwargs = {"preserve_info_file": False} result = _cleanup_on_exit_to_preserve_info_file_converter( kwargs, ["cleanup_on_exit"], ["preserve_info_file"] ) + deprecation_warnings = [ + warning + for warning in w + if issubclass(warning.category, PyFluentDeprecationWarning) + ] + assert len(deprecation_warnings) == 0, "Should not warn with new parameter" + assert result.get("preserve_info_file") is False - # Should be converted to preserve_info_file=False - assert ( - result.get("preserve_info_file") is False - ), "cleanup_on_exit=True should convert to preserve_info_file=False" - assert ( - "cleanup_on_exit" not in result - ), "cleanup_on_exit should be removed from kwargs" - def test_deprecation_cleanup_on_exit_false_converts_to_preserve_true(self): - """Verify cleanup_on_exit=False converts to preserve_info_file=True (inverted logic).""" +def test_both_cleanup_on_exit_and_preserve_info_file_provided(): + """Verify providing both parameters warns and uses new one.""" + import pytest + + with pytest.warns(PyFluentDeprecationWarning, match="Both deprecated"): from ansys.fluent.core.launcher.fluent_container import ( _cleanup_on_exit_to_preserve_info_file_converter, ) - # The converter function doesn't issue warnings; the decorator does - kwargs = {"cleanup_on_exit": False} + kwargs = {"cleanup_on_exit": True, "preserve_info_file": False} result = _cleanup_on_exit_to_preserve_info_file_converter( kwargs, ["cleanup_on_exit"], ["preserve_info_file"] ) - # Should be converted to preserve_info_file=True - assert ( - result.get("preserve_info_file") is True - ), "cleanup_on_exit=False should convert to preserve_info_file=True" + # Should keep the new parameter value assert ( - "cleanup_on_exit" not in result - ), "cleanup_on_exit should be removed from kwargs" - - def test_new_preserve_info_file_true_no_deprecation_warning(self): - """Verify new preserve_info_file=True parameter works without warnings.""" - import pytest - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, - ) - - kwargs = {"preserve_info_file": True} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - - # Filter out non-deprecation warnings - deprecation_warnings = [ - warning - for warning in w - if issubclass(warning.category, PyFluentDeprecationWarning) - ] - assert ( - len(deprecation_warnings) == 0 - ), "Should not warn when using new parameter" - assert ( - result.get("preserve_info_file") is True - ), "preserve_info_file should remain True" - - def test_new_preserve_info_file_false_no_deprecation_warning(self): - """Verify new preserve_info_file=False parameter works without warnings.""" - import pytest - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, - ) - - kwargs = {"preserve_info_file": False} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - - # Filter out non-deprecation warnings - deprecation_warnings = [ - warning - for warning in w - if issubclass(warning.category, PyFluentDeprecationWarning) - ] - assert ( - len(deprecation_warnings) == 0 - ), "Should not warn when using new parameter" - assert ( - result.get("preserve_info_file") is False - ), "preserve_info_file should remain False" - - def test_both_cleanup_on_exit_and_preserve_info_file_provided(self): - """Verify providing both parameters warns and uses new one.""" - import pytest - - with pytest.warns(PyFluentDeprecationWarning, match="Both deprecated"): - from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, - ) - - kwargs = {"cleanup_on_exit": True, "preserve_info_file": False} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - - # Should keep the new parameter value - assert ( - result.get("preserve_info_file") is False - ), "New preserve_info_file parameter should take precedence" - assert "cleanup_on_exit" not in result, "cleanup_on_exit should be removed" - - def test_preserve_info_file_defaults_to_false(self): - """Verify preserve_info_file defaults to False (cleanup by default).""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate cleanup logic with preserve_info_file default - preserve_info_file = False # Default - if not preserve_info_file: - server_info_file.unlink(missing_ok=True) - - assert ( - not server_info_file.exists() - ), "File should be deleted by default (preserve_info_file=False)" - - def test_preserve_info_file_true_preserves_file(self): - """Verify preserve_info_file=True preserves the server-info file.""" - from pathlib import Path - - with tempfile.TemporaryDirectory() as tmp_dir: - server_info_file = Path(tmp_dir) / "serverinfo-test.txt" - server_info_file.write_text("test content") - - # Simulate cleanup logic with preserve_info_file=True - preserve_info_file = True - if not preserve_info_file: - server_info_file.unlink(missing_ok=True) - - assert ( - server_info_file.exists() - ), "File should be preserved when preserve_info_file=True" + result.get("preserve_info_file") is False + ), "New preserve_info_file parameter should take precedence" + assert "cleanup_on_exit" not in result, "cleanup_on_exit should be removed" From d50d6449c856dd36ab509270e94ccbec1935c441 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Mon, 20 Jul 2026 23:51:32 +0530 Subject: [PATCH 26/29] added minor change in the fleutn_container due to which CI fails --- src/ansys/fluent/core/launcher/fluent_container.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/fluent/core/launcher/fluent_container.py b/src/ansys/fluent/core/launcher/fluent_container.py index d9725029983..719ef0f8078 100644 --- a/src/ansys/fluent/core/launcher/fluent_container.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -557,6 +557,7 @@ def start_fluent_container( container_vars = configure_container_dict( args, compose_config=compose_config, + remove_server_info_file=not preserve_info_file, # Convert: preserve_info_file → remove_server_info_file **container_dict, ) From df73f8d4c06c4ebbed78712a77439011e1fce149 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Sat, 25 Jul 2026 06:12:52 +0530 Subject: [PATCH 27/29] refactor: revert from preserve_info_file to cleanup_on_exit parameter - Remove _cleanup_on_exit_to_preserve_info_file_converter function - Revert start_fluent_container() to use cleanup_on_exit parameter (default True) - Update DockerLauncher and StandaloneLauncher to use cleanup_on_exit - Remove deprecation decorators from launcher classes - Simplify parameter logic: cleanup_on_exit directly maps to remove_server_info_file - Keep code style fixes from Seanpearsonuk review (unlink(missing_ok=True)) - Supports issue #5145: preserve server-info file when cleanup_on_exit=False --- .../core/launcher/container_launcher.py | 20 ++----- .../fluent/core/launcher/fluent_container.py | 56 +++---------------- .../core/launcher/standalone_launcher.py | 10 ---- 3 files changed, 13 insertions(+), 73 deletions(-) diff --git a/src/ansys/fluent/core/launcher/container_launcher.py b/src/ansys/fluent/core/launcher/container_launcher.py index 05ce3b0fa84..5ce208f3bff 100644 --- a/src/ansys/fluent/core/launcher/container_launcher.py +++ b/src/ansys/fluent/core/launcher/container_launcher.py @@ -49,7 +49,6 @@ CERTIFICATES_FOLDER_NOT_PROVIDED_AT_LAUNCH, ) from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, configure_container_dict, dict_to_str, start_fluent_container, @@ -65,7 +64,6 @@ ) import ansys.fluent.core.launcher.watchdog as watchdog from ansys.fluent.core.session import _parse_server_info_file -from ansys.fluent.core.utils.deprecate import deprecate_arguments from ansys.fluent.core.utils.fluent_version import FluentVersion if TYPE_CHECKING: @@ -141,12 +139,6 @@ def _get_server_info_from_container(config_dict): class DockerLauncher: """Instantiates Fluent session in container mode.""" - @deprecate_arguments( - old_args="cleanup_on_exit", - new_args="preserve_info_file", - version="0.42.0", - converter=_cleanup_on_exit_to_preserve_info_file_converter, - ) def __init__( self, **kwargs: Unpack[ContainerArgs], @@ -245,8 +237,8 @@ def __init__( raise ValueError(CERTIFICATES_FOLDER_NOT_PROVIDED_AT_LAUNCH) self.argvals, self.new_session = _get_argvals_and_session(kwargs) - if self.argvals.get("preserve_info_file") is None: - self.argvals["preserve_info_file"] = False + if self.argvals.get("cleanup_on_exit") is None: + self.argvals["cleanup_on_exit"] = True if self.argvals.get("start_transcript") is None: self.argvals["start_transcript"] = True if "start_watchdog" not in self.argvals: @@ -308,7 +300,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, - preserve_info_file=self.argvals["preserve_info_file"], + cleanup_on_exit=self.argvals["cleanup_on_exit"], ) try: @@ -326,7 +318,7 @@ def __call__( self.argvals["container_dict"], self.argvals["start_timeout"], compose_config=self._compose_config, - preserve_info_file=self.argvals["preserve_info_file"], + cleanup_on_exit=self.argvals["cleanup_on_exit"], ) allow_remote_host = ( @@ -340,7 +332,7 @@ def __call__( certificates_folder=self.argvals["certificates_folder"], insecure_mode=self.argvals["insecure_mode"], file_transfer_service=self.file_transfer_service, - cleanup_on_exit=not self.argvals["preserve_info_file"], + cleanup_on_exit=self.argvals["cleanup_on_exit"], slurm_job_id=self.argvals and self.argvals.get("slurm_job_id"), inside_container=True, container=container, @@ -362,7 +354,7 @@ def __call__( if not self._compose_config.is_compose: if ( self.argvals["start_watchdog"] is None - and not self.argvals["preserve_info_file"] + and self.argvals["cleanup_on_exit"] ): self.argvals["start_watchdog"] = True if self.argvals["start_watchdog"]: diff --git a/src/ansys/fluent/core/launcher/fluent_container.py b/src/ansys/fluent/core/launcher/fluent_container.py index 719ef0f8078..d278484cfa7 100644 --- a/src/ansys/fluent/core/launcher/fluent_container.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -96,32 +96,6 @@ logger = logging.getLogger("pyfluent.launcher") -def _cleanup_on_exit_to_preserve_info_file_converter( - kwargs: dict[str, Any], - old_args: list[str], - new_args: list[str], -) -> dict[str, Any]: - """ - Convert deprecated cleanup_on_exit to preserve_info_file with inverted logic. - - cleanup_on_exit=True (clean up the file) → preserve_info_file=False (don't preserve) - cleanup_on_exit=False (keep the file) → preserve_info_file=True (preserve) - """ - if "cleanup_on_exit" in kwargs: - cleanup_on_exit = kwargs.pop("cleanup_on_exit") - if "preserve_info_file" in kwargs: - warnings.warn( - "Both deprecated argument 'cleanup_on_exit' and new argument 'preserve_info_file' were provided. " - "Ignoring cleanup_on_exit.", - PyFluentDeprecationWarning, - stacklevel=3, - ) - else: - # Invert the logic: cleanup_on_exit → preserve_info_file - kwargs["preserve_info_file"] = not cleanup_on_exit - return kwargs - - class FluentImageNameTagNotSpecified(ValueError): """Raised when Fluent image name or image tag is not specified.""" @@ -497,18 +471,12 @@ def configure_container_dict( ) -@deprecate_arguments( - old_args="cleanup_on_exit", - new_args="preserve_info_file", - version="0.42.0", - converter=_cleanup_on_exit_to_preserve_info_file_converter, -) def start_fluent_container( args: list[str], container_dict: dict | None = None, start_timeout: int = 100, compose_config: ComposeConfig | None = None, - preserve_info_file: bool = False, + cleanup_on_exit: bool = True, ) -> tuple[int, str, Any]: """Start a Fluent container. @@ -523,9 +491,9 @@ def start_fluent_container( seconds. compose_config : ComposeConfig, optional Configuration for Docker Compose, if using Docker Compose to launch the container. - preserve_info_file : bool, optional - If True, the server-info file will be preserved for debugging. - If False, the server-info file will be deleted when the container exits. Defaults to False. + cleanup_on_exit : bool, optional + If True (default), the server-info file will be deleted when the container exits. + If False, the server-info file will be preserved for debugging. Defaults to True. Returns ------- @@ -538,8 +506,6 @@ def start_fluent_container( ------ TimeoutError If Fluent container launch reaches timeout. - ValueError - If ``remove_server_info_file`` and ``preserve_info_file`` have contradictory values. Notes ----- @@ -557,7 +523,7 @@ def start_fluent_container( container_vars = configure_container_dict( args, compose_config=compose_config, - remove_server_info_file=not preserve_info_file, # Convert: preserve_info_file → remove_server_info_file + remove_server_info_file=cleanup_on_exit, **container_dict, ) @@ -645,14 +611,6 @@ def start_fluent_container( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(launch_string) from ex finally: - # Validate that remove_server_info_file and cleanup_on_exit don't contradict. - # should_preserve is True if we want to keep the file - should_preserve = preserve_info_file or not remove_server_info_file - if remove_server_info_file and preserve_info_file: - raise ValueError( - "Contradictory file cleanup parameters: cannot both set remove_server_info_file=True " - "and preserve_info_file=True. Either remove the file or preserve it, not both." - ) - # Delete file only if both flags agree not to preserve it. - if not should_preserve: + # Delete server-info file only if cleanup_on_exit=True + if cleanup_on_exit: host_server_info_file.unlink(missing_ok=True) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index d80c944304c..654538c2463 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -49,9 +49,6 @@ from ansys.fluent.core.launcher.error_handler import ( LaunchFluentError, ) -from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, -) from ansys.fluent.core.launcher.launch_options import ( FluentMode, UIMode, @@ -71,7 +68,6 @@ _get_server_info_file_names, ) import ansys.fluent.core.launcher.watchdog as watchdog -from ansys.fluent.core.utils.deprecate import deprecate_arguments from ansys.fluent.core.utils.fluent_version import FluentVersion if TYPE_CHECKING: @@ -137,12 +133,6 @@ class StandaloneArgs( class StandaloneLauncher: """Instantiates Fluent session in standalone mode.""" - @deprecate_arguments( - old_args="cleanup_on_exit", - new_args="preserve_info_file", - version="0.42.0", - converter=_cleanup_on_exit_to_preserve_info_file_converter, - ) def __init__( self, **kwargs: Unpack[StandaloneArgs], From dbf08cc215bd0382026d2a2244f1f6add37721ff Mon Sep 17 00:00:00 2001 From: mayankansys Date: Sat, 25 Jul 2026 06:40:50 +0530 Subject: [PATCH 28/29] fix: fallback to the cleanup_on_exit --- .../core/launcher/container_launcher.py | 6 +- .../core/launcher/standalone_launcher.py | 16 +-- tests/test_launcher.py | 99 ------------------- 3 files changed, 11 insertions(+), 110 deletions(-) diff --git a/src/ansys/fluent/core/launcher/container_launcher.py b/src/ansys/fluent/core/launcher/container_launcher.py index 5ce208f3bff..4914cfb6d0e 100644 --- a/src/ansys/fluent/core/launcher/container_launcher.py +++ b/src/ansys/fluent/core/launcher/container_launcher.py @@ -181,9 +181,9 @@ def __init__( dry_run : bool, optional If True, does not launch Fluent but prints configuration information instead. If dry running a container start, this method will return the configured ``container_dict``. Defaults to False. - preserve_info_file : bool, optional - If True, the server-info file will be preserved for debugging. If False, the server-info file - will be deleted when the session exits. Defaults to False. + cleanup_on_exit : bool, optional + If True (default), the server-info file will be deleted when the session exits. + If False, the server-info file will be preserved for debugging. Defaults to True. start_transcript : bool Indicates whether to start streaming the Fluent transcript in the client. Defaults to True; streaming can be controlled via `transcript.start()` and `transcript.stop()` methods on the session object. diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 654538c2463..1c8bec95e21 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -173,9 +173,9 @@ def __init__( Additional command-line arguments for Fluent, formatted as they would be on the command line. env : dict[str, str], optional A mapping for modifying environment variables in Fluent. Defaults to ``None``. - preserve_info_file : bool, optional - If True, the server-info file will be preserved for debugging. - If False, the server-info file will be deleted when the session exits. Defaults to False. + cleanup_on_exit : bool, optional + If True (default), the server-info file will be deleted when the session exits. + If False, the server-info file will be preserved for debugging. Defaults to True. dry_run : bool, optional If True, does not launch Fluent but prints configuration information instead. The `call()` method returns a tuple containing the launch string and server info file name. Defaults to False. @@ -201,7 +201,7 @@ def __init__( A flag indicating whether to write equivalent Python journals from provided journal files; can also specify a filename for the new Python journal. start_watchdog : bool, optional - When `preserve_info_file` is False (default cleanup enabled), defaults to True; an independent watchdog process ensures that any local + When `cleanup_on_exit` is True (default cleanup enabled), defaults to True; an independent watchdog process ensures that any local GUI-less Fluent sessions started by PyFluent are properly closed when the current Python process ends. file_transfer_service : Any Service for uploading/downloading files to/from the server. @@ -327,7 +327,7 @@ def __call__( session = self.new_session._create_from_server_info_file( server_info_file_name=self._server_info_file_name, file_transfer_service=self.file_transfer_service, - preserve_info_file=self.argvals.get("preserve_info_file", False), + cleanup_on_exit=self.argvals.get("cleanup_on_exit", True), start_transcript=self.argvals.get("start_transcript"), launcher_args=self.argvals, inside_container=False, @@ -335,7 +335,7 @@ def __call__( session._process = process start_watchdog = _confirm_watchdog_start( self.argvals.get("start_watchdog"), - not self.argvals.get("preserve_info_file", False), + self.argvals.get("cleanup_on_exit", True), session._fluent_connection, ) if start_watchdog: @@ -378,6 +378,6 @@ def __call__( logger.error(f"Exception caught - {type(ex).__name__}: {ex}") raise LaunchFluentError(self._launch_cmd) from ex finally: - # preserve_info_file defaults to False, meaning cleanup happens by default - if not self.argvals.get("preserve_info_file", False): + # Delete server-info file if cleanup_on_exit=True (default) + if self.argvals.get("cleanup_on_exit", True): Path(self._server_info_file_name).unlink(missing_ok=True) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index d679ab7b5e0..b3eade6d287 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -818,102 +818,3 @@ def test_standalone_launcher_cleanup_on_exit_false_preserves_file(): assert ( server_info_file.exists() ), "File should be preserved with cleanup_on_exit=False" - - -def test_deprecation_cleanup_on_exit_true_converts_to_preserve_false(): - """Verify cleanup_on_exit=True converts to preserve_info_file=False (inverted logic).""" - from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, - ) - - # The converter function doesn't issue warnings; the decorator does - kwargs = {"cleanup_on_exit": True} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - - # Should be converted to preserve_info_file=False - assert ( - result.get("preserve_info_file") is False - ), "cleanup_on_exit=True should convert to preserve_info_file=False" - assert ( - "cleanup_on_exit" not in result - ), "cleanup_on_exit should be removed from kwargs" - - -def test_deprecation_cleanup_on_exit_false_converts_to_preserve_true(): - """Verify cleanup_on_exit=False converts to preserve_info_file=True (inverted logic).""" - from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, - ) - - # The converter function doesn't issue warnings; the decorator does - kwargs = {"cleanup_on_exit": False} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - - # Should be converted to preserve_info_file=True - assert ( - result.get("preserve_info_file") is True - ), "cleanup_on_exit=False should convert to preserve_info_file=True" - assert ( - "cleanup_on_exit" not in result - ), "cleanup_on_exit should be removed from kwargs" - - -def test_new_preserve_info_file_no_deprecation_warning(): - """Verify new preserve_info_file parameter works without deprecation warnings.""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, - ) - - # Test with preserve_info_file=True - kwargs = {"preserve_info_file": True} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - deprecation_warnings = [ - warning - for warning in w - if issubclass(warning.category, PyFluentDeprecationWarning) - ] - assert len(deprecation_warnings) == 0, "Should not warn with new parameter" - assert result.get("preserve_info_file") is True - - # Test with preserve_info_file=False - w.clear() - kwargs = {"preserve_info_file": False} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - deprecation_warnings = [ - warning - for warning in w - if issubclass(warning.category, PyFluentDeprecationWarning) - ] - assert len(deprecation_warnings) == 0, "Should not warn with new parameter" - assert result.get("preserve_info_file") is False - - -def test_both_cleanup_on_exit_and_preserve_info_file_provided(): - """Verify providing both parameters warns and uses new one.""" - import pytest - - with pytest.warns(PyFluentDeprecationWarning, match="Both deprecated"): - from ansys.fluent.core.launcher.fluent_container import ( - _cleanup_on_exit_to_preserve_info_file_converter, - ) - - kwargs = {"cleanup_on_exit": True, "preserve_info_file": False} - result = _cleanup_on_exit_to_preserve_info_file_converter( - kwargs, ["cleanup_on_exit"], ["preserve_info_file"] - ) - - # Should keep the new parameter value - assert ( - result.get("preserve_info_file") is False - ), "New preserve_info_file parameter should take precedence" - assert "cleanup_on_exit" not in result, "cleanup_on_exit should be removed" From 9dc17519ba6053803970aae94382e77e622ce108 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Sat, 25 Jul 2026 06:50:30 +0530 Subject: [PATCH 29/29] updated the standalone_launcher file --- src/ansys/fluent/core/launcher/standalone_launcher.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 1c8bec95e21..7f514d6a9c7 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -295,6 +295,7 @@ def __call__( if self.argvals.get("dry_run"): print(f"Fluent launch string: {self._launch_string}") return self._launch_string, self._server_info_file_name + cleanup_on_exit = self.argvals.get("cleanup_on_exit", True) try: logger.debug(f"Launching Fluent with command: {self._launch_cmd}") process = subprocess.Popen(self._launch_cmd, **self._kwargs) @@ -327,7 +328,7 @@ def __call__( session = self.new_session._create_from_server_info_file( server_info_file_name=self._server_info_file_name, file_transfer_service=self.file_transfer_service, - cleanup_on_exit=self.argvals.get("cleanup_on_exit", True), + cleanup_on_exit=cleanup_on_exit, start_transcript=self.argvals.get("start_transcript"), launcher_args=self.argvals, inside_container=False, @@ -335,7 +336,7 @@ def __call__( session._process = process start_watchdog = _confirm_watchdog_start( self.argvals.get("start_watchdog"), - self.argvals.get("cleanup_on_exit", True), + cleanup_on_exit, session._fluent_connection, ) if start_watchdog: @@ -379,5 +380,5 @@ def __call__( raise LaunchFluentError(self._launch_cmd) from ex finally: # Delete server-info file if cleanup_on_exit=True (default) - if self.argvals.get("cleanup_on_exit", True): + if cleanup_on_exit: Path(self._server_info_file_name).unlink(missing_ok=True)