From 35391cacfe60c0cae432abd5ddc078113b48a5f8 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:59:10 +0200 Subject: [PATCH 01/21] ci: add MapdlInProcessRunner and related tests for Python execution in MAPDL --- tests/test_inprocess.py | 144 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 tests/test_inprocess.py diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py new file mode 100644 index 00000000000..c9aff1823a1 --- /dev/null +++ b/tests/test_inprocess.py @@ -0,0 +1,144 @@ +# 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. + +import os +import subprocess +from typing import TYPE_CHECKING + +import pytest + +if TYPE_CHECKING: + from ansys.mapdl.core import Mapdl + + +class MapdlInProcessRunner: + def __init__(self, wdir: str, exec_path: str | None = None) -> None: + self.wdir = str(wdir) + self.completed_process: subprocess.CompletedProcess[bytes] | None = None + self._exec_path: str | None = exec_path + + @property + def exec_path(self) -> str: + if self._exec_path is None: + + self._exec_path = os.getenv("PYMAPDL_MAPDL_EXEC") + if self._exec_path is None: + from ansys.tools.path import get_mapdl_path + + version: float | None = ( + float(os.getenv("PYMAPDL_MAPDL_VERSION", 0)) or None + ) + self._exec_path = get_mapdl_path(version=version) + + if self._exec_path is None: + raise ValueError( + "MAPDL executable path is not set. Set the PYMAPDL_MAPDL_EXEC " + "environment variable or use get_mapdl_path() to find it." + ) + + return self._exec_path + + @property + def stdout(self) -> str | None: + if self.completed_process: + return self.completed_process.stdout.decode() + + @property + def stderr(self) -> str | None: + if self.completed_process: + return self.completed_process.stderr.decode() + + def run(self, cmds: str) -> str: + """Simulate running commands in MAPDL.""" + # This is a placeholder for the actual implementation + + with open(os.path.join(self.wdir, "input.mac"), "w") as f: + f.write(cmds) + + self.completed_process = subprocess.run( + args=[ + self.exec_path, + "-b", + "-i", + "input.mac", + "-o", + "out.out", + "-dir", + self.wdir, + ], + check=True, + capture_output=True, + ) + + with open(os.path.join(self.wdir, "out.out"), "r") as f: + self.output = f.read() + + return self.output + + +@pytest.fixture() +def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: + from conftest import ON_LOCAL + + # check if MAPDL has *PYTHON + if mapdl.version < 25.2: + pytest.skip("To test InProcess interface MAPDL 25.2 or higher is required") + + if not ON_LOCAL: + pytest.skip("InProcess interface can only be tested on local machines") + + if not os.getenv("TEST_INPROCESS"): + pytest.skip( + "Skipping InProcess tests, set TEST_INPROCESS environment variable to run them" + ) + + mapdl_inprocess = MapdlInProcessRunner(tmp_path) + + return mapdl_inprocess + + +def test_start_python_from_pymapdl(mapdl, mapdl_inprocess): + # calling mapdl_inprocess just to make sure we do not + # run it in PyMAPDL versions below 25.2 + mapdl.input_strings( + """ + *PYTHON + print("Hello from MAPDL") + *ENDPY + """ + ) + + +def test_start_python(mapdl_inprocess): + """Test that MAPDL starts Python correctly.""" + cmds = """ + /com, Starting Python commands + *PYTHON + print('Hello from MAPDL!') + *ENDPY + /com, test ends + """ + output_content = mapdl_inprocess.run(cmds) + + assert ( + "Hello from MAPDL!" in output_content + ), "MAPDL did not start Python correctly." From 5fbe5181e80db9badad754a083d2a2a929906830 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:02:02 +0000 Subject: [PATCH 02/21] chore: adding changelog file 4185.miscellaneous.md [dependabot-skip] --- doc/changelog.d/4185.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4185.miscellaneous.md diff --git a/doc/changelog.d/4185.miscellaneous.md b/doc/changelog.d/4185.miscellaneous.md new file mode 100644 index 00000000000..ae6d236cfe0 --- /dev/null +++ b/doc/changelog.d/4185.miscellaneous.md @@ -0,0 +1 @@ +Ci: add InProcess testing module From 52e68da16e179a580dbef4ba41087e29d840637a Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:20:06 +0200 Subject: [PATCH 03/21] fix: remove redundant import of ON_LOCAL in test fixture --- tests/test_inprocess.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index c9aff1823a1..9edd2f103cd 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -29,6 +29,8 @@ if TYPE_CHECKING: from ansys.mapdl.core import Mapdl +from conftest import ON_LOCAL + class MapdlInProcessRunner: def __init__(self, wdir: str, exec_path: str | None = None) -> None: @@ -97,7 +99,6 @@ def run(self, cmds: str) -> str: @pytest.fixture() def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: - from conftest import ON_LOCAL # check if MAPDL has *PYTHON if mapdl.version < 25.2: From 9990f1317e20b9607a6189f611638aaae24faa4c Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:23:14 +0200 Subject: [PATCH 04/21] fix: correct condition for skipping InProcess tests based on environment variable --- tests/test_inprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 9edd2f103cd..25d1b77e8f6 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -107,7 +107,7 @@ def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: if not ON_LOCAL: pytest.skip("InProcess interface can only be tested on local machines") - if not os.getenv("TEST_INPROCESS"): + if not os.getenv("TEST_INPROCESS", "").lower() != "true": pytest.skip( "Skipping InProcess tests, set TEST_INPROCESS environment variable to run them" ) From 86d80737abafeffd485e4cdc291ebfd6729154e7 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:23:44 +0200 Subject: [PATCH 05/21] Update tests/test_inprocess.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_inprocess.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 25d1b77e8f6..f57a739e91c 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -89,7 +89,29 @@ def run(self, cmds: str) -> str: ], check=True, capture_output=True, - ) + try: + self.completed_process = subprocess.run( + args=[ + self.exec_path, + "-b", + "-i", + "input.mac", + "-o", + "out.out", + "-dir", + self.wdir, + ], + check=True, + capture_output=True, + ) + except subprocess.CalledProcessError as e: + error_msg = ( + f"MAPDL execution failed with return code {e.returncode}.\n" + f"Command: {' '.join(e.cmd)}\n" + f"Stdout:\n{e.stdout.decode() if e.stdout else ''}\n" + f"Stderr:\n{e.stderr.decode() if e.stderr else ''}\n" + ) + pytest.fail(error_msg) with open(os.path.join(self.wdir, "out.out"), "r") as f: self.output = f.read() From 533be47d4906b081f865b795019cd0a95f228d15 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:25:44 +0200 Subject: [PATCH 06/21] fix: streamline subprocess execution in MapdlInProcessRunner --- tests/test_inprocess.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index f57a739e91c..05a9f5474d4 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -76,19 +76,6 @@ def run(self, cmds: str) -> str: with open(os.path.join(self.wdir, "input.mac"), "w") as f: f.write(cmds) - self.completed_process = subprocess.run( - args=[ - self.exec_path, - "-b", - "-i", - "input.mac", - "-o", - "out.out", - "-dir", - self.wdir, - ], - check=True, - capture_output=True, try: self.completed_process = subprocess.run( args=[ @@ -104,6 +91,7 @@ def run(self, cmds: str) -> str: check=True, capture_output=True, ) + except subprocess.CalledProcessError as e: error_msg = ( f"MAPDL execution failed with return code {e.returncode}.\n" From c4b3befd51b71501b9667b398ab2ba02428c33f8 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:56:19 +0200 Subject: [PATCH 07/21] fix: correct condition for skipping InProcess tests based on environment variable --- tests/test_inprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 05a9f5474d4..5d7a4335a7d 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -117,7 +117,7 @@ def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: if not ON_LOCAL: pytest.skip("InProcess interface can only be tested on local machines") - if not os.getenv("TEST_INPROCESS", "").lower() != "true": + if not (os.getenv("TEST_INPROCESS", "").lower() != "true"): pytest.skip( "Skipping InProcess tests, set TEST_INPROCESS environment variable to run them" ) From cc926d575f7cb359483ba79bb9bc80d4cd39a930 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:27:06 +0200 Subject: [PATCH 08/21] fix: add support for additional switches in subprocess execution --- tests/test_inprocess.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 5d7a4335a7d..392412617f6 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -85,11 +85,13 @@ def run(self, cmds: str) -> str: "input.mac", "-o", "out.out", + os.getenv("PYMAPDL_ADDITIONAL_SWITCHES", ""), "-dir", self.wdir, ], check=True, capture_output=True, + shell=True, # nosec B603 ) except subprocess.CalledProcessError as e: From cb34418202d25f0825c45744fc31fdfd09306fc4 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:14:51 +0200 Subject: [PATCH 09/21] fix: improve command execution in MapdlInProcessRunner and update test assertions --- tests/test_inprocess.py | 65 ++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 392412617f6..de24a89b110 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -70,28 +70,29 @@ def stderr(self) -> str | None: return self.completed_process.stderr.decode() def run(self, cmds: str) -> str: - """Simulate running commands in MAPDL.""" - # This is a placeholder for the actual implementation - + """Run commands in MAPDL on batch mode.""" with open(os.path.join(self.wdir, "input.mac"), "w") as f: f.write(cmds) try: + args = [ + self.exec_path, + "-b", + "-i", + "input.mac", + "-o", + "out.out", + os.getenv("PYMAPDL_ADDITIONAL_SWITCHES", ""), + ] + self.completed_process = subprocess.run( - args=[ - self.exec_path, - "-b", - "-i", - "input.mac", - "-o", - "out.out", - os.getenv("PYMAPDL_ADDITIONAL_SWITCHES", ""), - "-dir", - self.wdir, - ], + args=args, + cwd=self.wdir, check=True, capture_output=True, - shell=True, # nosec B603 + # it does not support shell=True, because it does not + # generate the out.out file + shell=False, ) except subprocess.CalledProcessError as e: @@ -119,7 +120,7 @@ def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: if not ON_LOCAL: pytest.skip("InProcess interface can only be tested on local machines") - if not (os.getenv("TEST_INPROCESS", "").lower() != "true"): + if not (os.getenv("TEST_INPROCESS", "").lower() == "true"): pytest.skip( "Skipping InProcess tests, set TEST_INPROCESS environment variable to run them" ) @@ -132,26 +133,30 @@ def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: def test_start_python_from_pymapdl(mapdl, mapdl_inprocess): # calling mapdl_inprocess just to make sure we do not # run it in PyMAPDL versions below 25.2 - mapdl.input_strings( + output = mapdl.input_strings( """ - *PYTHON - print("Hello from MAPDL") - *ENDPY - """ +*PYTHON +print("Hello from MAPDL") +*ENDPY +""" ) + assert "START PYTHON COMMAND BLOCK" in output + assert "Hello from MAPDL" in output + assert "END PYTHON COMMAND BLOCK" in output + def test_start_python(mapdl_inprocess): """Test that MAPDL starts Python correctly.""" cmds = """ - /com, Starting Python commands - *PYTHON - print('Hello from MAPDL!') - *ENDPY - /com, test ends - """ +/com, Starting Python commands +*PYTHON +print('Hello from MAPDL!') +*ENDPY +/com, test ends +""" output_content = mapdl_inprocess.run(cmds) - assert ( - "Hello from MAPDL!" in output_content - ), "MAPDL did not start Python correctly." + assert "Starting Python commands" in output_content + assert "Hello from MAPDL!" in output_content + assert "test ends" in output_content From 39148d7325cca046b925dd069c296878da92a0bc Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:19:38 +0200 Subject: [PATCH 10/21] fix: update skip message for InProcess tests to improve clarity --- tests/test_inprocess.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index de24a89b110..a544c36d2c8 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -121,9 +121,7 @@ def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: pytest.skip("InProcess interface can only be tested on local machines") if not (os.getenv("TEST_INPROCESS", "").lower() == "true"): - pytest.skip( - "Skipping InProcess tests, set TEST_INPROCESS environment variable to run them" - ) + pytest.skip("Set TEST_INPROCESS environment variable to run them.") mapdl_inprocess = MapdlInProcessRunner(tmp_path) From 5b22f01d7e6087fbadd3fa96a4656c90d5313d3d Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:29:53 +0200 Subject: [PATCH 11/21] fix: enhance MapdlInProcessRunner with virtual environment detection and output handling --- tests/test_inprocess.py | 64 +++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index a544c36d2c8..548b2051180 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -22,6 +22,7 @@ import os import subprocess +import sys from typing import TYPE_CHECKING import pytest @@ -37,6 +38,18 @@ def __init__(self, wdir: str, exec_path: str | None = None) -> None: self.wdir = str(wdir) self.completed_process: subprocess.CompletedProcess[bytes] | None = None self._exec_path: str | None = exec_path + self._output: str | None = None + + venv = os.getenv("MAPDL_PYTHON_ENV", None) + if not venv: + # Detecting the venv + if sys.prefix == sys.base_prefix: + pytest.warns( + "Running from global python interpreter is not recommended." + ) + venv = sys.prefix + + self.venv = venv @property def exec_path(self) -> str: @@ -61,22 +74,48 @@ def exec_path(self) -> str: @property def stdout(self) -> str | None: + """Return the standard output of the completed process. If the process fail, this returns None.""" if self.completed_process: return self.completed_process.stdout.decode() @property def stderr(self) -> str | None: + """Return the standard error output of the completed process. If the process fail, this returns None.""" if self.completed_process: return self.completed_process.stderr.decode() + @property + def status_code(self) -> float | None: + """Return the status code of the completed process. If the process fail, this returns None.""" + if self.completed_process: + return float(self.completed_process.returncode) + + @property + def exist_output(self) -> bool: + """Return True if the output file exists, False otherwise.""" + return os.path.exists(os.path.join(self.wdir, "out.out")) + + def output(self) -> str | None: + if not self._output and self.exist_output: + with open(os.path.join(self.wdir, "out.out"), "r") as f: + self._output = f.read() + return self._output + def run(self, cmds: str) -> str: """Run commands in MAPDL on batch mode.""" with open(os.path.join(self.wdir, "input.mac"), "w") as f: f.write(cmds) + exec_path = self.exec_path + # Security: Validate the executable path + if not os.path.isabs(exec_path) or not os.path.isfile(exec_path): + raise ValueError( + "MAPDL executable path must be an absolute path to an existing file." + ) + try: args = [ - self.exec_path, + exec_path, "-b", "-i", "input.mac", @@ -90,9 +129,11 @@ def run(self, cmds: str) -> str: cwd=self.wdir, check=True, capture_output=True, + env={"MAPDL_PYTHON_ENV": self.venv}, # it does not support shell=True, because it does not # generate the out.out file - shell=False, + # TODO: Why shell should be false in order to generate the output file? + shell=False, # nosec B603 ) except subprocess.CalledProcessError as e: @@ -104,17 +145,19 @@ def run(self, cmds: str) -> str: ) pytest.fail(error_msg) - with open(os.path.join(self.wdir, "out.out"), "r") as f: - self.output = f.read() - return self.output @pytest.fixture() -def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: +def mapdl_version(mapdl: "Mapdl"): + return mapdl.version + + +@pytest.fixture() +def mapdl_inprocess(mapdl_version: float, tmp_path: str) -> MapdlInProcessRunner: # check if MAPDL has *PYTHON - if mapdl.version < 25.2: + if mapdl_version < 25.2: pytest.skip("To test InProcess interface MAPDL 25.2 or higher is required") if not ON_LOCAL: @@ -128,7 +171,9 @@ def mapdl_inprocess(mapdl: "Mapdl", tmp_path) -> MapdlInProcessRunner: return mapdl_inprocess -def test_start_python_from_pymapdl(mapdl, mapdl_inprocess): +def test_start_python_from_pymapdl( + mapdl: "Mapdl", mapdl_inprocess: MapdlInProcessRunner +): # calling mapdl_inprocess just to make sure we do not # run it in PyMAPDL versions below 25.2 output = mapdl.input_strings( @@ -144,7 +189,7 @@ def test_start_python_from_pymapdl(mapdl, mapdl_inprocess): assert "END PYTHON COMMAND BLOCK" in output -def test_start_python(mapdl_inprocess): +def test_start_python_from_mapdl(mapdl_inprocess: MapdlInProcessRunner): """Test that MAPDL starts Python correctly.""" cmds = """ /com, Starting Python commands @@ -154,6 +199,7 @@ def test_start_python(mapdl_inprocess): /com, test ends """ output_content = mapdl_inprocess.run(cmds) + assert mapdl_inprocess.status_code == 0 assert "Starting Python commands" in output_content assert "Hello from MAPDL!" in output_content From a0274dc3afa5fd7c89343cad826b069ca38950d5 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:23:04 +0200 Subject: [PATCH 12/21] fix: enhance requirement checking and add virtual environment tests --- tests/test_inprocess.py | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 548b2051180..f3574530140 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -20,11 +20,15 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import importlib.metadata import os +import pathlib import subprocess import sys from typing import TYPE_CHECKING +from packaging.requirements import Requirement +from packaging.version import Version import pytest if TYPE_CHECKING: @@ -32,6 +36,58 @@ from conftest import ON_LOCAL +# This should be updated +REQUIREMENTS_STAR_PYTHON = """ +ansys-api-mapdl==0.5.2 +ansys-mapdl-core==0.70.1 +ansys-tools-path==0.7.1 +pyansys-tools-versioning==0.6.0 +platformdirs==4.3.8 #already in CommonFiles on windows +click==8.1.8 +""" + + +def check_requirements( + req_file: str | None = None, +) -> dict[str, tuple[bool, Version | None, Version | None]]: + if req_file is None: + req_text = REQUIREMENTS_STAR_PYTHON + else: + req_text = pathlib.Path(req_file).read_text() + + reqs = req_text.splitlines() + results: dict[str, tuple[bool, Version | None, Version | None]] = {} + + for req_line in reqs: + if not req_line.strip() or req_line.strip().startswith("#"): + # skip blanks/comments + continue + + # Removing trailing comments + req_line = req_line.split("#", 1)[0].strip() + + requirement = Requirement(req_line) + try: + installed_version = Version(importlib.metadata.version(requirement.name)) + except importlib.metadata.PackageNotFoundError: + results[requirement.name] = (False, None, None) + continue + + # Extract the *minimum required version* (if any) + min_required = None + for spec in requirement.specifier: + if spec.operator in (">=", "=="): + candidate = Version(spec.version) + if min_required is None or candidate > min_required: + min_required = candidate + + if min_required is None or installed_version >= min_required: + results[requirement.name] = (True, installed_version, min_required) + else: + results[requirement.name] = (False, installed_version, min_required) + + return results + class MapdlInProcessRunner: def __init__(self, wdir: str, exec_path: str | None = None) -> None: @@ -204,3 +260,35 @@ def test_start_python_from_mapdl(mapdl_inprocess: MapdlInProcessRunner): assert "Starting Python commands" in output_content assert "Hello from MAPDL!" in output_content assert "test ends" in output_content + + +def test_python_path(mapdl_inprocess: MapdlInProcessRunner): + """This test makes sure we are using the correct Python venv""" + cmds = """ +/com, Testing Python path +*PYTHON +import sys +print(sys.executable) +*ENDPY +/com, test ends +""" + output_content = mapdl_inprocess.run(cmds) + assert mapdl_inprocess.status_code == 0 + + assert "Testing Python path" in output_content + assert sys.executable in output_content + assert "test ends" in output_content + + +@pytest.mark.parametrize("pkg, data", list(check_requirements().items())) +def test_venv_requirements( + mapdl_inprocess: MapdlInProcessRunner, + pkg: str, + data: tuple[bool, Version | None, Version | None], +): + """Test that the virtual environment has the required packages.""" + ok, installed, required = data + # Example usage + for pkg, (ok, installed, required) in check_requirements().items(): + if not ok: + pytest.fail(f"{pkg} {installed} (needs >= {required})") From 4c5e970f85676ae701c871ee5cfc547efa505c98 Mon Sep 17 00:00:00 2001 From: valallansys Date: Fri, 22 Aug 2025 05:09:32 -0400 Subject: [PATCH 13/21] fix: keep only tests from mapdl, from pymapdl and check python path with fix without python env --- tests/test_inprocess.py | 126 +++++++--------------------------------- 1 file changed, 21 insertions(+), 105 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index f3574530140..deb9d4e4ed9 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -20,15 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import importlib.metadata import os -import pathlib import subprocess import sys from typing import TYPE_CHECKING -from packaging.requirements import Requirement -from packaging.version import Version import pytest if TYPE_CHECKING: @@ -36,59 +32,6 @@ from conftest import ON_LOCAL -# This should be updated -REQUIREMENTS_STAR_PYTHON = """ -ansys-api-mapdl==0.5.2 -ansys-mapdl-core==0.70.1 -ansys-tools-path==0.7.1 -pyansys-tools-versioning==0.6.0 -platformdirs==4.3.8 #already in CommonFiles on windows -click==8.1.8 -""" - - -def check_requirements( - req_file: str | None = None, -) -> dict[str, tuple[bool, Version | None, Version | None]]: - if req_file is None: - req_text = REQUIREMENTS_STAR_PYTHON - else: - req_text = pathlib.Path(req_file).read_text() - - reqs = req_text.splitlines() - results: dict[str, tuple[bool, Version | None, Version | None]] = {} - - for req_line in reqs: - if not req_line.strip() or req_line.strip().startswith("#"): - # skip blanks/comments - continue - - # Removing trailing comments - req_line = req_line.split("#", 1)[0].strip() - - requirement = Requirement(req_line) - try: - installed_version = Version(importlib.metadata.version(requirement.name)) - except importlib.metadata.PackageNotFoundError: - results[requirement.name] = (False, None, None) - continue - - # Extract the *minimum required version* (if any) - min_required = None - for spec in requirement.specifier: - if spec.operator in (">=", "=="): - candidate = Version(spec.version) - if min_required is None or candidate > min_required: - min_required = candidate - - if min_required is None or installed_version >= min_required: - results[requirement.name] = (True, installed_version, min_required) - else: - results[requirement.name] = (False, installed_version, min_required) - - return results - - class MapdlInProcessRunner: def __init__(self, wdir: str, exec_path: str | None = None) -> None: self.wdir = str(wdir) @@ -96,17 +39,6 @@ def __init__(self, wdir: str, exec_path: str | None = None) -> None: self._exec_path: str | None = exec_path self._output: str | None = None - venv = os.getenv("MAPDL_PYTHON_ENV", None) - if not venv: - # Detecting the venv - if sys.prefix == sys.base_prefix: - pytest.warns( - "Running from global python interpreter is not recommended." - ) - venv = sys.prefix - - self.venv = venv - @property def exec_path(self) -> str: if self._exec_path is None: @@ -185,7 +117,6 @@ def run(self, cmds: str) -> str: cwd=self.wdir, check=True, capture_output=True, - env={"MAPDL_PYTHON_ENV": self.venv}, # it does not support shell=True, because it does not # generate the out.out file # TODO: Why shell should be false in order to generate the output file? @@ -201,7 +132,7 @@ def run(self, cmds: str) -> str: ) pytest.fail(error_msg) - return self.output + return self.output() @pytest.fixture() @@ -227,9 +158,26 @@ def mapdl_inprocess(mapdl_version: float, tmp_path: str) -> MapdlInProcessRunner return mapdl_inprocess -def test_start_python_from_pymapdl( - mapdl: "Mapdl", mapdl_inprocess: MapdlInProcessRunner -): +def test_python_path(mapdl_inprocess: MapdlInProcessRunner): + """This test makes sure we are using the correct Python venv""" + cmds = """ +/com, Testing Python path +*PYTHON +import shutil +print(shutil.which("python")) +*ENDPY +/com, test ends +""" + output_content = mapdl_inprocess.run(cmds) + print(output_content) + assert mapdl_inprocess.status_code == 0 + + assert "Testing Python path" in output_content + assert sys.executable in output_content + assert "test ends" in output_content + + +def test_start_python_from_pymapdl(mapdl: "Mapdl"): # calling mapdl_inprocess just to make sure we do not # run it in PyMAPDL versions below 25.2 output = mapdl.input_strings( @@ -260,35 +208,3 @@ def test_start_python_from_mapdl(mapdl_inprocess: MapdlInProcessRunner): assert "Starting Python commands" in output_content assert "Hello from MAPDL!" in output_content assert "test ends" in output_content - - -def test_python_path(mapdl_inprocess: MapdlInProcessRunner): - """This test makes sure we are using the correct Python venv""" - cmds = """ -/com, Testing Python path -*PYTHON -import sys -print(sys.executable) -*ENDPY -/com, test ends -""" - output_content = mapdl_inprocess.run(cmds) - assert mapdl_inprocess.status_code == 0 - - assert "Testing Python path" in output_content - assert sys.executable in output_content - assert "test ends" in output_content - - -@pytest.mark.parametrize("pkg, data", list(check_requirements().items())) -def test_venv_requirements( - mapdl_inprocess: MapdlInProcessRunner, - pkg: str, - data: tuple[bool, Version | None, Version | None], -): - """Test that the virtual environment has the required packages.""" - ok, installed, required = data - # Example usage - for pkg, (ok, installed, required) in check_requirements().items(): - if not ok: - pytest.fail(f"{pkg} {installed} (needs >= {required})") From 448e2be8cfb76f19464e03de09949a69502eae9d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:14:30 +0000 Subject: [PATCH 14/21] ci: auto fixes from pre-commit.com hooks. for more information, see https://pre-commit.ci --- tests/test_inprocess.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index deb9d4e4ed9..fa373c50dce 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -32,6 +32,7 @@ from conftest import ON_LOCAL + class MapdlInProcessRunner: def __init__(self, wdir: str, exec_path: str | None = None) -> None: self.wdir = str(wdir) From 7aff019ff3141e6929ad2dd5dad88b793019ef31 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:56:31 +0200 Subject: [PATCH 15/21] fix: update MapdlInProcessRunner to include version parameter and adjust environment handling --- tests/test_inprocess.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index fa373c50dce..73f1c7b0e58 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -34,8 +34,9 @@ class MapdlInProcessRunner: - def __init__(self, wdir: str, exec_path: str | None = None) -> None: + def __init__(self, wdir: str, version: float, exec_path: str | None = None) -> None: self.wdir = str(wdir) + self.version = version self.completed_process: subprocess.CompletedProcess[bytes] | None = None self._exec_path: str | None = exec_path self._output: str | None = None @@ -90,7 +91,7 @@ def output(self) -> str | None: self._output = f.read() return self._output - def run(self, cmds: str) -> str: + def run(self, cmds: str) -> str | None: """Run commands in MAPDL on batch mode.""" with open(os.path.join(self.wdir, "input.mac"), "w") as f: f.write(cmds) @@ -102,6 +103,11 @@ def run(self, cmds: str) -> str: "MAPDL executable path must be an absolute path to an existing file." ) + if self.version == 25.2: + env = {"MAPDL_PYTHON_ENV": os.getenv("MAPDL_PYTHON_ENV", sys.prefix)} + else: + env = {} + try: args = [ exec_path, @@ -118,6 +124,7 @@ def run(self, cmds: str) -> str: cwd=self.wdir, check=True, capture_output=True, + env=env, # it does not support shell=True, because it does not # generate the out.out file # TODO: Why shell should be false in order to generate the output file? @@ -154,7 +161,7 @@ def mapdl_inprocess(mapdl_version: float, tmp_path: str) -> MapdlInProcessRunner if not (os.getenv("TEST_INPROCESS", "").lower() == "true"): pytest.skip("Set TEST_INPROCESS environment variable to run them.") - mapdl_inprocess = MapdlInProcessRunner(tmp_path) + mapdl_inprocess = MapdlInProcessRunner(tmp_path, version=mapdl_version) return mapdl_inprocess @@ -170,9 +177,9 @@ def test_python_path(mapdl_inprocess: MapdlInProcessRunner): /com, test ends """ output_content = mapdl_inprocess.run(cmds) - print(output_content) assert mapdl_inprocess.status_code == 0 + assert output_content is not None assert "Testing Python path" in output_content assert sys.executable in output_content assert "test ends" in output_content @@ -189,6 +196,7 @@ def test_start_python_from_pymapdl(mapdl: "Mapdl"): """ ) + assert output is not None assert "START PYTHON COMMAND BLOCK" in output assert "Hello from MAPDL" in output assert "END PYTHON COMMAND BLOCK" in output @@ -206,6 +214,7 @@ def test_start_python_from_mapdl(mapdl_inprocess: MapdlInProcessRunner): output_content = mapdl_inprocess.run(cmds) assert mapdl_inprocess.status_code == 0 + assert output_content is not None assert "Starting Python commands" in output_content assert "Hello from MAPDL!" in output_content assert "test ends" in output_content From ccf72765d31d70fbc9f1139a09e0d977b92136aa Mon Sep 17 00:00:00 2001 From: valallansys Date: Fri, 22 Aug 2025 10:49:58 -0400 Subject: [PATCH 16/21] fix: Set venv to the one we're using for pymapdl --- tests/test_inprocess.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 73f1c7b0e58..ea5433474de 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -41,6 +41,13 @@ def __init__(self, wdir: str, version: float, exec_path: str | None = None) -> N self._exec_path: str | None = exec_path self._output: str | None = None + # Detecting the venv + if sys.prefix == sys.base_prefix: + pytest.warns( + "Running from global python interpreter is not recommended." + ) + os.environ["MAPDL_PYTHON_ENV"] = sys.prefix + @property def exec_path(self) -> str: if self._exec_path is None: From c6445a33609640a962c30c15dad03c415be454eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:51:59 +0000 Subject: [PATCH 17/21] ci: auto fixes from pre-commit.com hooks. for more information, see https://pre-commit.ci --- tests/test_inprocess.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index ea5433474de..cc7adcb638d 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -43,9 +43,7 @@ def __init__(self, wdir: str, version: float, exec_path: str | None = None) -> N # Detecting the venv if sys.prefix == sys.base_prefix: - pytest.warns( - "Running from global python interpreter is not recommended." - ) + pytest.warns("Running from global python interpreter is not recommended.") os.environ["MAPDL_PYTHON_ENV"] = sys.prefix @property From cf68c90de73f978b975242e4d1c4c63a27bdcc4c Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:29:28 +0200 Subject: [PATCH 18/21] fix: add mapdl_inprocess parameter to test_start_python_from_pymapdl function to avoid running the function in MAPDL < 25.2 --- tests/test_inprocess.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index cc7adcb638d..62a6d91ad4a 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -190,7 +190,9 @@ def test_python_path(mapdl_inprocess: MapdlInProcessRunner): assert "test ends" in output_content -def test_start_python_from_pymapdl(mapdl: "Mapdl"): +def test_start_python_from_pymapdl( + mapdl: "Mapdl", mapdl_inprocess: MapdlInProcessRunner +): # calling mapdl_inprocess just to make sure we do not # run it in PyMAPDL versions below 25.2 output = mapdl.input_strings( From 0a914aff01537633fc0e06bb1ce11a4f27abb293 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:31:33 +0000 Subject: [PATCH 19/21] chore: adding changelog file 4185.maintenance.md [dependabot-skip] --- doc/changelog.d/4185.maintenance.md | 1 + doc/changelog.d/4185.miscellaneous.md | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 doc/changelog.d/4185.maintenance.md delete mode 100644 doc/changelog.d/4185.miscellaneous.md diff --git a/doc/changelog.d/4185.maintenance.md b/doc/changelog.d/4185.maintenance.md new file mode 100644 index 00000000000..535c9e73f5c --- /dev/null +++ b/doc/changelog.d/4185.maintenance.md @@ -0,0 +1 @@ +Add InProcess testing module diff --git a/doc/changelog.d/4185.miscellaneous.md b/doc/changelog.d/4185.miscellaneous.md deleted file mode 100644 index ae6d236cfe0..00000000000 --- a/doc/changelog.d/4185.miscellaneous.md +++ /dev/null @@ -1 +0,0 @@ -Ci: add InProcess testing module From 970fb24c5a7e7c0930f785301c0206e53daa1c39 Mon Sep 17 00:00:00 2001 From: Valentin Allard Date: Tue, 2 Sep 2025 11:58:42 +0200 Subject: [PATCH 20/21] fix: Test correction for Windows --- tests/test_inprocess.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index 62a6d91ad4a..d0ed00646aa 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -108,11 +108,6 @@ def run(self, cmds: str) -> str | None: "MAPDL executable path must be an absolute path to an existing file." ) - if self.version == 25.2: - env = {"MAPDL_PYTHON_ENV": os.getenv("MAPDL_PYTHON_ENV", sys.prefix)} - else: - env = {} - try: args = [ exec_path, @@ -129,10 +124,9 @@ def run(self, cmds: str) -> str | None: cwd=self.wdir, check=True, capture_output=True, - env=env, - # it does not support shell=True, because it does not - # generate the out.out file - # TODO: Why shell should be false in order to generate the output file? + # # it does not support shell=True, because it does not + # # generate the out.out file + # # TODO: Why shell should be false in order to generate the output file? shell=False, # nosec B603 ) @@ -183,10 +177,9 @@ def test_python_path(mapdl_inprocess: MapdlInProcessRunner): """ output_content = mapdl_inprocess.run(cmds) assert mapdl_inprocess.status_code == 0 - assert output_content is not None assert "Testing Python path" in output_content - assert sys.executable in output_content + assert sys.executable.replace('\\\\', '\\').lower() in output_content.lower() assert "test ends" in output_content From 9b326f0bf4807cf4452147ffbeece0d6a1da7844 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:03:02 +0000 Subject: [PATCH 21/21] ci: auto fixes from pre-commit.com hooks. for more information, see https://pre-commit.ci --- tests/test_inprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_inprocess.py b/tests/test_inprocess.py index d0ed00646aa..35b5d69023a 100644 --- a/tests/test_inprocess.py +++ b/tests/test_inprocess.py @@ -179,7 +179,7 @@ def test_python_path(mapdl_inprocess: MapdlInProcessRunner): assert mapdl_inprocess.status_code == 0 assert output_content is not None assert "Testing Python path" in output_content - assert sys.executable.replace('\\\\', '\\').lower() in output_content.lower() + assert sys.executable.replace("\\\\", "\\").lower() in output_content.lower() assert "test ends" in output_content