Skip to content

Commit a9b1394

Browse files
authored
fix: open gui not working (#4270)
* feat(core): add exec_file param to open_gui and improve execable lookup - Add typed exec_file parameter to open_gui and document its behavior. - Defer importing get_mapdl_path and only attempt to resolve the MAPDL executable when ansys.mapdl.core ATP helper is available. - Use start_parm exec_file if provided, otherwise try get_mapdl_path; raise a clear MapdlRuntimeError if no executable path can be determined. - Minor docstring/format tweaks. * fix(core): propagate env_vars into start parameters, avoid circular import, and make MapdlGrpc._launch accept start_parm - Add env_vars to start_parm in launch_mapdl so environment vars are carried in generated start parameters. - Replace import from ansys.mapdl.core.launcher with package-level get_mapdl_path to prevent circular imports. - Update MapdlGrpc._launch signature to accept an optional start_parm, use it when provided, and call _connect() without passing the port explicitly.
1 parent 6427460 commit a9b1394

5 files changed

Lines changed: 286 additions & 8 deletions

File tree

doc/changelog.d/4270.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Open gui not working

src/ansys/mapdl/core/launcher.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,6 +1690,7 @@ def launch_mapdl(
16901690
env_vars.setdefault("HYDRA_BOOTSTRAP", "slurm")
16911691

16921692
start_parm = generate_start_parameters(args)
1693+
start_parm["env_vars"] = env_vars
16931694

16941695
# Early exit for debugging.
16951696
if args["_debug_no_launch"]:

src/ansys/mapdl/core/mapdl_core.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,12 @@ def _generate_iges(self):
17231723
self.igesout(filename, att=1, mute=True)
17241724
return filename
17251725

1726-
def open_gui(self, include_result=None, inplace=None): # pragma: no cover
1726+
def open_gui(
1727+
self,
1728+
include_result: bool | None = None,
1729+
inplace: bool | None = None,
1730+
exec_file: str | None = None,
1731+
): # pragma: no cover
17271732
"""Save the existing database and open it up in the MAPDL GUI.
17281733
17291734
Parameters
@@ -1735,9 +1740,15 @@ def open_gui(self, include_result=None, inplace=None): # pragma: no cover
17351740
inplace : bool, optional
17361741
Open the GUI on the current MAPDL working directory, instead of
17371742
creating a new temporary directory and coping the results files
1738-
over there. If ``True``, ignores ``include_result`` parameter. By
1743+
over there. If ``True``, ignores ``include_result`` parameter. By
17391744
default, this ``False``.
17401745
1746+
exec_file: str, optional
1747+
Path to the MAPDL executable. If not provided, it will try to obtain
1748+
it using the `ansys.tools.path` package. If this package is not
1749+
available, it will use the same executable as the current MAPDL
1750+
instance.
1751+
17411752
Examples
17421753
--------
17431754
>>> from ansys.mapdl.core import launch_mapdl
@@ -1767,7 +1778,7 @@ def open_gui(self, include_result=None, inplace=None): # pragma: no cover
17671778
>>> mapdl.eplot()
17681779
"""
17691780
# lazy load here to avoid circular import
1770-
from ansys.mapdl.core.launcher import get_mapdl_path
1781+
from ansys.mapdl.core import _HAS_ATP
17711782

17721783
if not self._local:
17731784
raise MapdlRuntimeError(
@@ -1844,7 +1855,24 @@ def open_gui(self, include_result=None, inplace=None): # pragma: no cover
18441855
# issue system command to run ansys in GUI mode
18451856
cwd = os.getcwd()
18461857
os.chdir(run_dir)
1847-
exec_file = self._start_parm.get("exec_file", get_mapdl_path(allow_input=False))
1858+
1859+
if not exec_file:
1860+
if _HAS_ATP:
1861+
from ansys.mapdl.core import get_mapdl_path
1862+
1863+
exec_file_ = get_mapdl_path(allow_input=False)
1864+
else:
1865+
exec_file_ = None
1866+
1867+
exec_file = self._start_parm.get("exec_file", exec_file_)
1868+
1869+
if not exec_file:
1870+
raise MapdlRuntimeError(
1871+
"The path to the MAPDL executable was not found. "
1872+
"Please set it using the 'exec_file' parameter when "
1873+
"launching MAPDL."
1874+
)
1875+
18481876
nproc = self._start_parm.get("nproc", 2)
18491877
add_sw = self._start_parm.get("additional_switches", "")
18501878

src/ansys/mapdl/core/mapdl_grpc.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,9 @@ def _get_server_version(self) -> tuple[int, int, int]:
945945
sver = version_string_as_tuple(verstr)
946946
return sver
947947

948-
def _launch(self, start_parm, timeout=10):
948+
def _launch(
949+
self, start_parm: dict[str, Any] | None = None, timeout: int | None = 10
950+
):
949951
"""Launch a local session of MAPDL in gRPC mode.
950952
951953
This should only need to be used for legacy ``open_gui``
@@ -958,7 +960,7 @@ def _launch(self, start_parm, timeout=10):
958960

959961
self._exited = False # reset exit state
960962

961-
args = self._start_parm
963+
args = start_parm or self._start_parm
962964
cmd = generate_mapdl_launch_command(
963965
exec_file=args["exec_file"],
964966
jobname=args["jobname"],
@@ -972,7 +974,7 @@ def _launch(self, start_parm, timeout=10):
972974
cmd=cmd, run_location=args["run_location"], env_vars=self._env_vars or None
973975
)
974976

975-
self._connect(args["port"])
977+
self._connect()
976978

977979
# may need to wait for viable connection in open_gui case
978980
tmax = time.time() + timeout

tests/test_launcher.py

Lines changed: 247 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import subprocess
2727
import tempfile
2828
from time import sleep
29-
from unittest.mock import patch
29+
from unittest.mock import MagicMock, Mock, patch
3030
import warnings
3131

3232
import psutil
@@ -2129,3 +2129,249 @@ def test_handle_launch_exceptions(msg, match, exception_type):
21292129
exception = exception_type(msg)
21302130
with pytest.raises(exception_type, match=match):
21312131
raise handle_launch_exceptions(exception)
2132+
2133+
2134+
def test_env_vars_propagation_in_launch_mapdl():
2135+
"""Test that env_vars are propagated to start_parm in launch_mapdl."""
2136+
env_vars = {"MY_VAR": "test_value", "ANOTHER_VAR": "another_value"}
2137+
2138+
args = launch_mapdl(
2139+
env_vars=env_vars,
2140+
_debug_no_launch=True,
2141+
)
2142+
2143+
# Check that env_vars are in the returned args
2144+
assert "env_vars" in args
2145+
assert args["env_vars"]["MY_VAR"] == "test_value"
2146+
assert args["env_vars"]["ANOTHER_VAR"] == "another_value"
2147+
2148+
2149+
def test_env_vars_with_slurm_bootstrap(monkeypatch):
2150+
"""Test that SLURM env_vars are set correctly when launch_on_hpc is True."""
2151+
# This test verifies that when replace_env_vars is used with launch_on_hpc,
2152+
# SLURM-specific environment variables are added
2153+
monkeypatch.delenv("PYMAPDL_START_INSTANCE", False)
2154+
2155+
env_vars_input = {"CUSTOM_VAR": "custom_value"}
2156+
2157+
# Capture what env_vars are passed to launch_grpc
2158+
captured_env_vars = None
2159+
2160+
def mock_launch_grpc(cmd, run_location, env_vars=None, **kwargs):
2161+
nonlocal captured_env_vars
2162+
captured_env_vars = env_vars
2163+
# Mock process object
2164+
from tests.test_launcher import get_fake_process
2165+
2166+
return get_fake_process("Submitted batch job 1001")
2167+
2168+
with (
2169+
patch("ansys.mapdl.core.launcher.launch_grpc", mock_launch_grpc),
2170+
patch("ansys.mapdl.core.launcher.send_scontrol") as mock_scontrol,
2171+
patch("ansys.mapdl.core.launcher.kill_job"),
2172+
):
2173+
# Mock scontrol to avoid timeout
2174+
mock_scontrol.return_value = get_fake_process(
2175+
"JobState=RUNNING\nBatchHost=testhost\n"
2176+
)
2177+
2178+
try:
2179+
launch_mapdl(
2180+
launch_on_hpc=True,
2181+
replace_env_vars=env_vars_input, # Use replace_env_vars instead of env_vars
2182+
exec_file="/fake/path/to/ansys242",
2183+
nproc=2,
2184+
)
2185+
except Exception: # nosec B703
2186+
# We expect this to fail, we just want to capture env_vars
2187+
pass
2188+
2189+
# Verify the env_vars that were passed to launch_grpc
2190+
assert captured_env_vars is not None
2191+
assert "CUSTOM_VAR" in captured_env_vars
2192+
assert captured_env_vars["CUSTOM_VAR"] == "custom_value"
2193+
assert captured_env_vars["ANS_MULTIPLE_NODES"] == "1"
2194+
assert captured_env_vars["HYDRA_BOOTSTRAP"] == "slurm"
2195+
2196+
2197+
def test_mapdl_grpc_launch_uses_provided_start_parm():
2198+
"""Test that MapdlGrpc._launch uses provided start_parm over instance _start_parm."""
2199+
from ansys.mapdl.core.mapdl_grpc import MapdlGrpc
2200+
2201+
# Create mock instance
2202+
mapdl_grpc = Mock(spec=MapdlGrpc)
2203+
mapdl_grpc._exited = True
2204+
mapdl_grpc._local = True # Add _local attribute
2205+
mapdl_grpc._start_parm = {
2206+
"exec_file": "/original/path/to/ansys242",
2207+
"jobname": "original_job",
2208+
"nproc": 2,
2209+
"ram": 1024,
2210+
"port": 50052,
2211+
"additional_switches": "",
2212+
"mode": "grpc",
2213+
"run_location": "/default/run/location",
2214+
}
2215+
mapdl_grpc._env_vars = None
2216+
mapdl_grpc._connect = MagicMock()
2217+
mapdl_grpc._mapdl_process = None # Add _mapdl_process attribute
2218+
2219+
# Custom start_parm that should be used
2220+
custom_start_parm = {
2221+
"exec_file": "/custom/path/to/ansys242",
2222+
"jobname": "custom_job",
2223+
"nproc": 4,
2224+
"ram": 2048,
2225+
"port": 50053,
2226+
"additional_switches": "-custom",
2227+
"mode": "grpc",
2228+
"env_vars": {"CUSTOM_VAR": "custom_value"},
2229+
"run_location": "/custom/run/location",
2230+
}
2231+
2232+
# Mock the launch_grpc function to capture what parameters are used
2233+
# Note: launch_grpc is in launcher module, not mapdl_grpc module
2234+
with patch("ansys.mapdl.core.launcher.launch_grpc") as mock_launch_grpc:
2235+
# Bind the real _launch method
2236+
mapdl_grpc._launch = MapdlGrpc._launch.__get__(mapdl_grpc, type(mapdl_grpc))
2237+
2238+
# Call _launch with custom start_parm
2239+
mapdl_grpc._launch(start_parm=custom_start_parm, timeout=10)
2240+
2241+
# Verify launch_grpc was called
2242+
mock_launch_grpc.assert_called_once()
2243+
2244+
# Get the cmd argument passed to launch_grpc
2245+
call_args = mock_launch_grpc.call_args
2246+
cmd_used = call_args[1]["cmd"]
2247+
2248+
# Verify the command uses custom_start_parm values
2249+
assert "/custom/path/to/ansys242" in " ".join(cmd_used)
2250+
assert "custom_job" in " ".join(cmd_used)
2251+
2252+
2253+
def test_open_gui_with_mocked_call(mapdl, fake_local_mapdl):
2254+
"""Test that open_gui uses the correct exec_file with mocked subprocess.call."""
2255+
from contextlib import ExitStack
2256+
2257+
custom_exec_file = "/custom/test/path/ansys242"
2258+
captured_call_args = None
2259+
2260+
def mock_call(*args, **kwargs):
2261+
nonlocal captured_call_args
2262+
captured_call_args = args[0] if args else None
2263+
return 0
2264+
2265+
with ExitStack() as stack:
2266+
# Mock _local to True so open_gui doesn't raise "can only be called from local"
2267+
stack.enter_context(patch.object(mapdl, "_local", True))
2268+
2269+
# Mock pathlib.Path to return a mock that has is_file() return True
2270+
mock_path = MagicMock()
2271+
mock_path.is_file.return_value = True
2272+
stack.enter_context(
2273+
patch("ansys.mapdl.core.mapdl_core.pathlib.Path", return_value=mock_path)
2274+
)
2275+
2276+
# Mock the call function imported in mapdl_core
2277+
stack.enter_context(
2278+
patch("ansys.mapdl.core.mapdl_core.call", side_effect=mock_call)
2279+
)
2280+
2281+
# IMPORTANT: Mock exit, finish, save, _launch, resume to prevent killing the MAPDL instance
2282+
stack.enter_context(patch.object(mapdl, "exit"))
2283+
stack.enter_context(patch.object(mapdl, "finish"))
2284+
stack.enter_context(patch.object(mapdl, "save"))
2285+
stack.enter_context(patch.object(mapdl, "_cache_routine"))
2286+
stack.enter_context(patch.object(mapdl, "_launch"))
2287+
stack.enter_context(patch.object(mapdl, "resume"))
2288+
2289+
try:
2290+
# Call open_gui with custom exec_file
2291+
mapdl.open_gui(exec_file=custom_exec_file, inplace=True)
2292+
except Exception: # nosec B703
2293+
# open_gui might fail for various reasons after the call
2294+
# We're only interested in verifying the call arguments
2295+
pass
2296+
2297+
# Verify that subprocess.call was called with the custom exec_file
2298+
assert captured_call_args is not None, "subprocess.call was not called"
2299+
assert (
2300+
custom_exec_file in captured_call_args
2301+
), f"Expected {custom_exec_file} in call args, but got {captured_call_args}"
2302+
assert "-g" in captured_call_args, "Expected -g flag for GUI mode"
2303+
assert mapdl.jobname in " ".join(
2304+
str(arg) for arg in captured_call_args
2305+
), f"Expected jobname {mapdl.jobname} in call args"
2306+
2307+
2308+
def test_open_gui_complete_flow_with_mocked_methods(mapdl, fake_local_mapdl):
2309+
"""Test complete open_gui flow: call, _launch, and reconnection methods are invoked."""
2310+
2311+
custom_exec_file = "/custom/test/path/ansys242"
2312+
2313+
# Track what methods were called
2314+
call_invoked = False
2315+
launch_invoked = False
2316+
2317+
def mock_call(*args, **kwargs):
2318+
nonlocal call_invoked
2319+
call_invoked = True
2320+
return 0
2321+
2322+
def mock_launch(start_parm, timeout=10):
2323+
nonlocal launch_invoked
2324+
launch_invoked = True
2325+
# Verify start_parm is passed
2326+
assert start_parm is not None
2327+
assert "exec_file" in start_parm
2328+
2329+
# Mock the call function imported in mapdl_core
2330+
with patch("ansys.mapdl.core.mapdl_core.call", side_effect=mock_call):
2331+
# Mock _local to True so open_gui doesn't raise "can only be called from local"
2332+
with patch.object(mapdl, "_local", True):
2333+
# Mock pathlib.Path to return a mock that has is_file() return True
2334+
mock_path = MagicMock()
2335+
mock_path.is_file.return_value = True
2336+
with patch(
2337+
"ansys.mapdl.core.mapdl_core.pathlib.Path", return_value=mock_path
2338+
):
2339+
# Store original _launch to restore later
2340+
original_launch = mapdl._launch
2341+
2342+
try:
2343+
# Replace _launch with our mock
2344+
mapdl._launch = mock_launch
2345+
2346+
# Mock methods that open_gui calls before and after
2347+
with (
2348+
patch.object(mapdl, "finish") as mock_finish,
2349+
patch.object(mapdl, "save") as mock_save,
2350+
patch.object(mapdl, "exit") as mock_exit,
2351+
patch.object(mapdl, "resume") as mock_resume,
2352+
patch.object(mapdl, "_cache_routine") as mock_cache,
2353+
):
2354+
try:
2355+
# Call open_gui with custom exec_file
2356+
mapdl.open_gui(exec_file=custom_exec_file, inplace=True)
2357+
except Exception: # nosec B703
2358+
# Some methods might fail, but we verify they were called
2359+
pass
2360+
2361+
# Verify the flow of method calls
2362+
assert (
2363+
mock_finish.called
2364+
), "finish() should be called before GUI"
2365+
assert mock_save.called, "save() should be called before GUI"
2366+
assert mock_exit.called, "exit() should be called before GUI"
2367+
assert call_invoked, "subprocess.call should be invoked for GUI"
2368+
assert launch_invoked, "_launch() should be called to reconnect"
2369+
assert (
2370+
mock_resume.called
2371+
), "resume() should be called after reconnection"
2372+
assert (
2373+
mock_cache.called
2374+
), "_cache_routine() should be called after reconnection"
2375+
finally:
2376+
# Restore original _launch
2377+
mapdl._launch = original_launch

0 commit comments

Comments
 (0)