From 0d8ac3201efba15fcab189aec16c4d1a15cd7132 Mon Sep 17 00:00:00 2001 From: germa89 <28149841+germa89@users.noreply.github.com> Date: Fri, 28 Nov 2025 09:01:54 +0100 Subject: [PATCH 1/7] feat(cli): refactor list_instances for improved process validation and instance retrieval --- src/ansys/mapdl/core/cli/list_instances.py | 66 +++++++++++++++------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/src/ansys/mapdl/core/cli/list_instances.py b/src/ansys/mapdl/core/cli/list_instances.py index 7ad94d1bf19..3f4e14bfc1e 100644 --- a/src/ansys/mapdl/core/cli/list_instances.py +++ b/src/ansys/mapdl/core/cli/list_instances.py @@ -66,32 +66,45 @@ help="Print running location info.", ) def list_instances(instances, long, cmd, location) -> None: + return _list_instances(instances, long, cmd, location) + + +def is_grpc_based(proc): + cmdline = proc.cmdline() + return "-grpc" in cmdline + + +def get_port(proc): + cmdline = proc.cmdline() + ind_grpc = cmdline.index("-port") + return cmdline[ind_grpc + 1] + + +def is_valid_ansys_process(proc): + return ("ansys" in proc.name().lower()) or ("mapdl" in proc.name().lower()) + + +def is_alive(proc): + import psutil + + return proc.status() in [ + psutil.STATUS_RUNNING, + psutil.STATUS_IDLE, + psutil.STATUS_SLEEPING, + ] + + +def is_valid_process(proc): + return is_alive(proc) and is_valid_ansys_process(proc) and is_grpc_based(proc) + + +def _list_instances(instances, long, cmd, location): import psutil from tabulate import tabulate # Assuming all ansys processes have -grpc flag mapdl_instances = [] - def is_grpc_based(proc): - cmdline = proc.cmdline() - return "-grpc" in cmdline - - def get_port(proc): - cmdline = proc.cmdline() - ind_grpc = cmdline.index("-port") - return cmdline[ind_grpc + 1] - - def is_valid_process(proc): - valid_status = proc.status() in [ - psutil.STATUS_RUNNING, - psutil.STATUS_IDLE, - psutil.STATUS_SLEEPING, - ] - valid_ansys_process = ("ansys" in proc.name().lower()) or ( - "mapdl" in proc.name().lower() - ) - return valid_status and valid_ansys_process and is_grpc_based(proc) - for proc in psutil.process_iter(): # Check if the process is running and not suspended try: @@ -145,3 +158,16 @@ def is_valid_process(proc): table.append(proc_line) print(tabulate(table, headers)) + + +def get_ansys_process_from_port(port: int): + import psutil + + for proc in psutil.process_iter(): + # Check if the process is running and not suspended + try: + if is_valid_process(proc) and get_port(proc) == str(port): + return proc + + except (psutil.NoSuchProcess, psutil.ZombieProcess) as e: + continue From 296c2e8b481f7d8f170d9ece777f015467955c4b Mon Sep 17 00:00:00 2001 From: germa89 <28149841+germa89@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:14:28 +0100 Subject: [PATCH 2/7] feat(cli): refactor list_instances to utilize core functionality for instance retrieval --- src/ansys/mapdl/core/cli/__init__.py | 10 ++- src/ansys/mapdl/core/cli/core.py | 92 +++++++++++++++++++++ src/ansys/mapdl/core/cli/list_instances.py | 93 +++++++++------------- 3 files changed, 135 insertions(+), 60 deletions(-) create mode 100644 src/ansys/mapdl/core/cli/core.py diff --git a/src/ansys/mapdl/core/cli/__init__.py b/src/ansys/mapdl/core/cli/__init__.py index e2eedcb92df..31cfdb3ea21 100644 --- a/src/ansys/mapdl/core/cli/__init__.py +++ b/src/ansys/mapdl/core/cli/__init__.py @@ -20,12 +20,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from ansys.mapdl.core import _HAS_CLICK +try: + import click # noqa: F401 + + _HAS_CLICK = True +except ImportError: + _HAS_CLICK = False if _HAS_CLICK: ################################### # PyMAPDL CLI - import click @click.group(invoke_without_command=True) @click.pass_context @@ -38,9 +42,9 @@ def main(ctx: click.Context): from ansys.mapdl.core.cli.stop import stop as stop_cmd main.add_command(convert_cmd, name="convert") + main.add_command(list_instances, name="list") main.add_command(start_cmd, name="start") main.add_command(stop_cmd, name="stop") - main.add_command(list_instances, name="list") else: diff --git a/src/ansys/mapdl/core/cli/core.py b/src/ansys/mapdl/core/cli/core.py new file mode 100644 index 00000000000..555c553de8c --- /dev/null +++ b/src/ansys/mapdl/core/cli/core.py @@ -0,0 +1,92 @@ +# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Minimal core functionality for CLI operations. +This module avoids importing heavy dependencies like pandas, numpy, etc. +""" + +from typing import Any, Dict, List + +import psutil + + +def is_valid_ansys_process_name(name: str) -> bool: + """Check if process name indicates ANSYS/MAPDL""" + return ("ansys" in name.lower()) or ("mapdl" in name.lower()) + + +def is_alive_status(status) -> bool: + """Check if process status indicates alive""" + return status in [ + psutil.STATUS_RUNNING, + psutil.STATUS_IDLE, + psutil.STATUS_SLEEPING, + ] + + +def get_mapdl_instances() -> List[Dict[str, Any]]: + """Get list of MAPDL instances with minimal data""" + instances = [] + + for proc in psutil.process_iter(attrs=["name"]): + name = proc.info["name"] + if not is_valid_ansys_process_name(name): + continue + + try: + status = proc.status() + if not is_alive_status(status): + continue + + cmdline = proc.cmdline() + if "-grpc" not in cmdline: + continue + + # Get port from cmdline + port = None + try: + ind_grpc = cmdline.index("-port") + port = int(cmdline[ind_grpc + 1]) + except (ValueError, IndexError): + continue + + children = proc.children(recursive=True) + is_instance = len(children) >= 2 + + cwd = proc.cwd() + instances.append( + { + "name": name, + "status": status, + "port": port, + "pid": proc.pid, + "cmdline": cmdline, + "is_instance": is_instance, + "cwd": cwd, + } + ) + + except (psutil.NoSuchProcess, psutil.ZombieProcess, psutil.AccessDenied): + continue + + return instances diff --git a/src/ansys/mapdl/core/cli/list_instances.py b/src/ansys/mapdl/core/cli/list_instances.py index 3f4e14bfc1e..38c983f4a0a 100644 --- a/src/ansys/mapdl/core/cli/list_instances.py +++ b/src/ansys/mapdl/core/cli/list_instances.py @@ -69,56 +69,13 @@ def list_instances(instances, long, cmd, location) -> None: return _list_instances(instances, long, cmd, location) -def is_grpc_based(proc): - cmdline = proc.cmdline() - return "-grpc" in cmdline - - -def get_port(proc): - cmdline = proc.cmdline() - ind_grpc = cmdline.index("-port") - return cmdline[ind_grpc + 1] - - -def is_valid_ansys_process(proc): - return ("ansys" in proc.name().lower()) or ("mapdl" in proc.name().lower()) - - -def is_alive(proc): - import psutil - - return proc.status() in [ - psutil.STATUS_RUNNING, - psutil.STATUS_IDLE, - psutil.STATUS_SLEEPING, - ] - - -def is_valid_process(proc): - return is_alive(proc) and is_valid_ansys_process(proc) and is_grpc_based(proc) - - def _list_instances(instances, long, cmd, location): - import psutil from tabulate import tabulate - # Assuming all ansys processes have -grpc flag - mapdl_instances = [] - - for proc in psutil.process_iter(): - # Check if the process is running and not suspended - try: - if is_valid_process(proc): - # Checking the number of children we infer if the process is the main process, - # or one of the main process thread. - if len(proc.children(recursive=True)) < 2: - proc.ansys_instance = False - else: - proc.ansys_instance = True - mapdl_instances.append(proc) + from ansys.mapdl.core.cli.core import get_mapdl_instances - except (psutil.NoSuchProcess, psutil.ZombieProcess) as e: - continue + # Assuming all ansys processes have -grpc flag + mapdl_instances = get_mapdl_instances() # printing if long: @@ -137,23 +94,23 @@ def _list_instances(instances, long, cmd, location): table = [] for each_p in mapdl_instances: - if instances and not each_p.ansys_instance: + if instances and not each_p.get("is_instance", False): # Skip child processes if only printing instances continue proc_line = [] - proc_line.append(each_p.name()) + proc_line.append(each_p["name"]) if not instances: - proc_line.append(each_p.ansys_instance) + proc_line.append(each_p.get("is_instance", False)) - proc_line.extend([each_p.status(), get_port(each_p), each_p.pid]) + proc_line.extend([each_p["status"], each_p["port"], each_p["pid"]]) if cmd: - proc_line.append(" ".join(each_p.cmdline())) + proc_line.append(" ".join(each_p["cmdline"])) if location: - proc_line.append(each_p.cwd()) + proc_line.append(each_p["cwd"]) table.append(proc_line) @@ -161,13 +118,35 @@ def _list_instances(instances, long, cmd, location): def get_ansys_process_from_port(port: int): + import socket + import psutil - for proc in psutil.process_iter(): - # Check if the process is running and not suspended - try: - if is_valid_process(proc) and get_port(proc) == str(port): - return proc + from ansys.mapdl.core.cli.core import is_alive_status, is_valid_ansys_process_name + # Filter by name first + potential_procs = [] + for proc in psutil.process_iter(attrs=["name"]): + name = proc.info["name"] + if is_valid_ansys_process_name(name): + potential_procs.append(proc) + + for proc in potential_procs: + try: + status = proc.status() + if not is_alive_status(status): + continue + cmdline = proc.cmdline() + if "-grpc" not in cmdline: + continue + # Check if listening on the port + connections = proc.connections() + for conn in connections: + if ( + conn.status == "LISTEN" + and conn.family == socket.AF_INET + and conn.laddr[1] == port + ): + return proc except (psutil.NoSuchProcess, psutil.ZombieProcess) as e: continue From 5f0d7c768bb4a6ec6edead9ed7a361731356eab5 Mon Sep 17 00:00:00 2001 From: germa89 <28149841+germa89@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:38:11 +0100 Subject: [PATCH 3/7] fix(cli): optimize process iteration by adding info attribute for better performance --- tests/test_cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index ed7780d9351..cc4aed98592 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -46,6 +46,7 @@ def make_fake_process(pid, name, port=PORT1, ansys_process=False, n_children=0): mock_process = MagicMock(spec=psutil.Process) mock_process.pid = pid mock_process.name.return_value = name + mock_process.info = {"name": name} # For attrs=['name'] optimization mock_process.status.return_value = psutil.STATUS_RUNNING mock_process.children.side_effect = lambda *arg, **kwargs: [ i for i in range(n_children) @@ -210,6 +211,7 @@ def make_other_user_process(pid, name, ansys_process=True): mock_process = MagicMock(spec=psutil.Process) mock_process.pid = pid mock_process.name.return_value = name + mock_process.info = {"name": name} # For attrs=['name'] optimization mock_process.status.return_value = psutil.STATUS_RUNNING if ansys_process: @@ -226,6 +228,7 @@ def make_inaccessible_process(pid: int, name: str): mock_process = MagicMock(spec=psutil.Process) mock_process.pid = pid mock_process.name.return_value = name + mock_process.info = {"name": name} # For attrs=['name'] optimization # Simulate the original issue: AccessDenied when accessing process info mock_process.cmdline.side_effect = psutil.AccessDenied(pid, name) @@ -301,6 +304,7 @@ def test_pymapdl_stop_with_username_containing_domain(run_cli): mock_process = MagicMock(spec=psutil.Process) mock_process.pid = 12 mock_process.name.return_value = "ansys252" + mock_process.info = {"name": "ansys252"} # For attrs=['name'] optimization mock_process.status.return_value = psutil.STATUS_RUNNING mock_process.cmdline.return_value = ["ansys251", "-grpc", "-port", "50052"] mock_process.username.return_value = f"DOMAIN\\{current_user}" From e51dea8db5c0270da5c3e9cdc263f145270d235b Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:17:09 +0000 Subject: [PATCH 4/7] chore: adding changelog file 4329.miscellaneous.md [dependabot-skip] --- doc/changelog.d/4329.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4329.miscellaneous.md diff --git a/doc/changelog.d/4329.miscellaneous.md b/doc/changelog.d/4329.miscellaneous.md new file mode 100644 index 00000000000..66816e8df79 --- /dev/null +++ b/doc/changelog.d/4329.miscellaneous.md @@ -0,0 +1 @@ +List_instances From 7a20e16e392c1ab72f3d08e63ad967e742179efc Mon Sep 17 00:00:00 2001 From: germa89 <28149841+germa89@users.noreply.github.com> Date: Fri, 28 Nov 2025 15:59:35 +0100 Subject: [PATCH 5/7] fix(cli): handle AccessDenied exception in get_ansys_process_from_port function --- src/ansys/mapdl/core/cli/list_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/mapdl/core/cli/list_instances.py b/src/ansys/mapdl/core/cli/list_instances.py index 38c983f4a0a..8d422134e59 100644 --- a/src/ansys/mapdl/core/cli/list_instances.py +++ b/src/ansys/mapdl/core/cli/list_instances.py @@ -148,5 +148,5 @@ def get_ansys_process_from_port(port: int): and conn.laddr[1] == port ): return proc - except (psutil.NoSuchProcess, psutil.ZombieProcess) as e: + except (psutil.NoSuchProcess, psutil.ZombieProcess, psutil.AccessDenied): continue From c3bd7733a56943effdfd3bbeae2a5e488e5ecd37 Mon Sep 17 00:00:00 2001 From: germa89 <28149841+germa89@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:00:48 +0100 Subject: [PATCH 6/7] test(cli): add comprehensive tests for get_ansys_process_from_port function --- src/ansys/mapdl/core/cli/__init__.py | 2 +- tests/test_cli.py | 257 +++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 1 deletion(-) diff --git a/src/ansys/mapdl/core/cli/__init__.py b/src/ansys/mapdl/core/cli/__init__.py index 31cfdb3ea21..0f62e648588 100644 --- a/src/ansys/mapdl/core/cli/__init__.py +++ b/src/ansys/mapdl/core/cli/__init__.py @@ -24,7 +24,7 @@ import click # noqa: F401 _HAS_CLICK = True -except ImportError: +except ImportError: # pragma: no cover _HAS_CLICK = False if _HAS_CLICK: diff --git a/tests/test_cli.py b/tests/test_cli.py index cc4aed98592..46ffd4c604c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -31,6 +31,8 @@ import psutil import pytest +import ansys.mapdl.core.cli.core as core_module +from ansys.mapdl.core.cli.list_instances import get_ansys_process_from_port from ansys.mapdl.core.plotting import GraphicsBackend from conftest import VALID_PORTS, requires @@ -596,3 +598,258 @@ def test_convert_passing(mock_conv, run_cli, tmpdir, arg, value): assert kwargs[key] == GraphicsBackend[value.upper()] else: assert kwargs[key] == default_[key] + + +def make_mock_process_for_port_test( + pid, + name, + status=psutil.STATUS_RUNNING, + cmdline=None, + connections=None, + raise_exception=None, +): + """Helper to create mock process for get_ansys_process_from_port tests.""" + mock_proc = MagicMock(spec=psutil.Process) + mock_proc.pid = pid + mock_proc.info = {"name": name} + + class NoSuchProcess(psutil.NoSuchProcess): + def __init__(self): + super().__init__(pid) + + class ZombieProcess(psutil.ZombieProcess): + def __init__(self): + super().__init__(pid) + + if raise_exception == "status": + mock_proc.status.side_effect = NoSuchProcess + else: + mock_proc.status.return_value = status + if raise_exception == "cmdline": + mock_proc.cmdline.side_effect = psutil.AccessDenied + else: + mock_proc.cmdline.return_value = cmdline or [] + if raise_exception == "connections": + mock_proc.connections.side_effect = ZombieProcess + else: + mock_proc.connections.return_value = connections or [] + return mock_proc + + +def test_get_ansys_process_from_port_no_processes(): + """Test get_ansys_process_from_port with no processes.""" + with patch("psutil.process_iter", return_value=[]): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_not_ansys(): + """Test get_ansys_process_from_port with non-ANSYS process.""" + mock_proc = make_mock_process_for_port_test(1, "python") + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=False), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_not_alive(): + """Test get_ansys_process_from_port with ANSYS process not alive.""" + mock_proc = make_mock_process_for_port_test(1, "ansys", status=psutil.STATUS_DEAD) + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=False), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_no_grpc(): + """Test get_ansys_process_from_port with ANSYS process without -grpc.""" + mock_proc = make_mock_process_for_port_test( + 1, "ansys", cmdline=["ansys", "-port", "50052"] + ) + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_not_listening(): + """Test get_ansys_process_from_port with ANSYS process not listening on port.""" + import socket + + mock_conn = MagicMock() + mock_conn.status = "LISTEN" + mock_conn.family = socket.AF_INET + mock_conn.laddr = ("127.0.0.1", 50053) # wrong port + mock_proc = make_mock_process_for_port_test( + 1, + "ansys", + cmdline=["ansys", "-grpc", "-port", "50052"], + connections=[mock_conn], + ) + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_not_listen_status(): + """Test get_ansys_process_from_port with connection not LISTEN.""" + import socket + + mock_conn = MagicMock() + mock_conn.status = "ESTABLISHED" + mock_conn.family = socket.AF_INET + mock_conn.laddr = ("127.0.0.1", 50052) + mock_proc = make_mock_process_for_port_test( + 1, + "ansys", + cmdline=["ansys", "-grpc", "-port", "50052"], + connections=[mock_conn], + ) + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_wrong_family(): + """Test get_ansys_process_from_port with wrong family.""" + import socket + + mock_conn = MagicMock() + mock_conn.status = "LISTEN" + mock_conn.family = socket.AF_INET6 # wrong family + mock_conn.laddr = ("127.0.0.1", 50052) + mock_proc = make_mock_process_for_port_test( + 1, + "ansys", + cmdline=["ansys", "-grpc", "-port", "50052"], + connections=[mock_conn], + ) + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_success(): + """Test get_ansys_process_from_port finds the process.""" + import socket + + mock_conn = MagicMock() + mock_conn.status = "LISTEN" + mock_conn.family = socket.AF_INET + mock_conn.laddr = ("127.0.0.1", 50052) + mock_proc = make_mock_process_for_port_test( + 1, + "ansys", + cmdline=["ansys", "-grpc", "-port", "50052"], + connections=[mock_conn], + ) + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result == mock_proc + + +def test_get_ansys_process_from_port_exception_status(): + """Test get_ansys_process_from_port handles exception in status.""" + mock_proc = make_mock_process_for_port_test(1, "ansys") + + class NoSuchProcess(psutil.NoSuchProcess): + def __init__(self): + super().__init__(1) + + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", side_effect=NoSuchProcess), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_exception_cmdline(): + """Test get_ansys_process_from_port handles exception in cmdline.""" + mock_proc = make_mock_process_for_port_test(1, "ansys", raise_exception="cmdline") + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + # with pytest.raises(psutil.AccessDenied): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_exception_connections(): + """Test get_ansys_process_from_port handles exception in connections.""" + mock_proc = make_mock_process_for_port_test( + 1, + "ansys", + cmdline=["ansys", "-grpc", "-port", "50052"], + raise_exception="connections", + ) + with ( + patch("psutil.process_iter", return_value=[mock_proc]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result is None + + +def test_get_ansys_process_from_port_multiple_processes(): + """Test get_ansys_process_from_port with multiple processes, returns first match.""" + import socket + + mock_conn1 = MagicMock() + mock_conn1.status = "LISTEN" + mock_conn1.family = socket.AF_INET + mock_conn1.laddr = ("127.0.0.1", 50052) + mock_proc1 = make_mock_process_for_port_test( + 1, + "ansys", + cmdline=["ansys", "-grpc", "-port", "50052"], + connections=[mock_conn1], + ) + + mock_conn2 = MagicMock() + mock_conn2.status = "LISTEN" + mock_conn2.family = socket.AF_INET + mock_conn2.laddr = ("127.0.0.1", 50053) + mock_proc2 = make_mock_process_for_port_test( + 2, + "ansys", + cmdline=["ansys", "-grpc", "-port", "50053"], + connections=[mock_conn2], + ) + + with ( + patch("psutil.process_iter", return_value=[mock_proc1, mock_proc2]), + patch.object(core_module, "is_valid_ansys_process_name", return_value=True), + patch.object(core_module, "is_alive_status", return_value=True), + ): + result = get_ansys_process_from_port(50052) + assert result == mock_proc1 From b72f419106e224a566acfb3c85cfa388da5c96e6 Mon Sep 17 00:00:00 2001 From: germa89 <28149841+germa89@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:46:09 +0100 Subject: [PATCH 7/7] refactor: move get_ansys_process_from_port function to core module --- src/ansys/mapdl/core/cli/core.py | 35 ++++++++++++++++++++++ src/ansys/mapdl/core/cli/list_instances.py | 35 ---------------------- tests/test_cli.py | 2 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/ansys/mapdl/core/cli/core.py b/src/ansys/mapdl/core/cli/core.py index 555c553de8c..40d1d296689 100644 --- a/src/ansys/mapdl/core/cli/core.py +++ b/src/ansys/mapdl/core/cli/core.py @@ -90,3 +90,38 @@ def get_mapdl_instances() -> List[Dict[str, Any]]: continue return instances + + +def get_ansys_process_from_port(port: int): + import socket + + import psutil + + from ansys.mapdl.core.cli.core import is_alive_status, is_valid_ansys_process_name + + # Filter by name first + potential_procs = [] + for proc in psutil.process_iter(attrs=["name"]): + name = proc.info["name"] + if is_valid_ansys_process_name(name): + potential_procs.append(proc) + + for proc in potential_procs: + try: + status = proc.status() + if not is_alive_status(status): + continue + cmdline = proc.cmdline() + if "-grpc" not in cmdline: + continue + # Check if listening on the port + connections = proc.connections() + for conn in connections: + if ( + conn.status == "LISTEN" + and conn.family == socket.AF_INET + and conn.laddr[1] == port + ): + return proc + except (psutil.NoSuchProcess, psutil.ZombieProcess, psutil.AccessDenied): + continue diff --git a/src/ansys/mapdl/core/cli/list_instances.py b/src/ansys/mapdl/core/cli/list_instances.py index 8d422134e59..515d00ce458 100644 --- a/src/ansys/mapdl/core/cli/list_instances.py +++ b/src/ansys/mapdl/core/cli/list_instances.py @@ -115,38 +115,3 @@ def _list_instances(instances, long, cmd, location): table.append(proc_line) print(tabulate(table, headers)) - - -def get_ansys_process_from_port(port: int): - import socket - - import psutil - - from ansys.mapdl.core.cli.core import is_alive_status, is_valid_ansys_process_name - - # Filter by name first - potential_procs = [] - for proc in psutil.process_iter(attrs=["name"]): - name = proc.info["name"] - if is_valid_ansys_process_name(name): - potential_procs.append(proc) - - for proc in potential_procs: - try: - status = proc.status() - if not is_alive_status(status): - continue - cmdline = proc.cmdline() - if "-grpc" not in cmdline: - continue - # Check if listening on the port - connections = proc.connections() - for conn in connections: - if ( - conn.status == "LISTEN" - and conn.family == socket.AF_INET - and conn.laddr[1] == port - ): - return proc - except (psutil.NoSuchProcess, psutil.ZombieProcess, psutil.AccessDenied): - continue diff --git a/tests/test_cli.py b/tests/test_cli.py index 46ffd4c604c..2cc13997b32 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -32,7 +32,7 @@ import pytest import ansys.mapdl.core.cli.core as core_module -from ansys.mapdl.core.cli.list_instances import get_ansys_process_from_port +from ansys.mapdl.core.cli.core import get_ansys_process_from_port from ansys.mapdl.core.plotting import GraphicsBackend from conftest import VALID_PORTS, requires