-
Notifications
You must be signed in to change notification settings - Fork 139
ci: add InProcess testing module #4185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
germa89
wants to merge
23
commits into
main
Choose a base branch
from
ci/adding-inprocess-testing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
35391ca
ci: add MapdlInProcessRunner and related tests for Python execution i…
germa89 5fbe518
chore: adding changelog file 4185.miscellaneous.md [dependabot-skip]
pyansys-ci-bot 52e68da
fix: remove redundant import of ON_LOCAL in test fixture
germa89 9990f13
fix: correct condition for skipping InProcess tests based on environm…
germa89 86d8073
Update tests/test_inprocess.py
germa89 533be47
fix: streamline subprocess execution in MapdlInProcessRunner
germa89 c4b3bef
fix: correct condition for skipping InProcess tests based on environm…
germa89 cc926d5
fix: add support for additional switches in subprocess execution
germa89 cb34418
fix: improve command execution in MapdlInProcessRunner and update tes…
germa89 39148d7
fix: update skip message for InProcess tests to improve clarity
germa89 537a789
Merge remote-tracking branch 'origin/main' into ci/adding-inprocess-t…
germa89 5b22f01
fix: enhance MapdlInProcessRunner with virtual environment detection …
germa89 a0274dc
fix: enhance requirement checking and add virtual environment tests
germa89 4c5e970
fix: keep only tests from mapdl, from pymapdl and check python path w…
valallansys 448e2be
ci: auto fixes from pre-commit.com hooks.
pre-commit-ci[bot] 7aff019
fix: update MapdlInProcessRunner to include version parameter and adj…
germa89 ccf7276
fix: Set venv to the one we're using for pymapdl
valallansys c6445a3
ci: auto fixes from pre-commit.com hooks.
pre-commit-ci[bot] cf68c90
fix: add mapdl_inprocess parameter to test_start_python_from_pymapdl …
germa89 0a914af
chore: adding changelog file 4185.maintenance.md [dependabot-skip]
pyansys-ci-bot 970fb24
fix: Test correction for Windows
valallansys 9b326f0
ci: auto fixes from pre-commit.com hooks.
pre-commit-ci[bot] 47da5e7
Merge branch 'main' into ci/adding-inprocess-testing
germa89 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add InProcess testing module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
# 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 | ||
import sys | ||
from typing import TYPE_CHECKING | ||
|
||
import pytest | ||
|
||
if TYPE_CHECKING: | ||
from ansys.mapdl.core import Mapdl | ||
|
||
from conftest import ON_LOCAL | ||
|
||
|
||
class MapdlInProcessRunner: | ||
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 | ||
|
||
# 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: | ||
|
||
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: | ||
germa89 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""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 | None: | ||
"""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 = [ | ||
exec_path, | ||
"-b", | ||
"-i", | ||
"input.mac", | ||
"-o", | ||
"out.out", | ||
os.getenv("PYMAPDL_ADDITIONAL_SWITCHES", ""), | ||
] | ||
|
||
self.completed_process = subprocess.run( | ||
args=args, | ||
cwd=self.wdir, | ||
check=True, | ||
capture_output=True, | ||
# # 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 | ||
) | ||
|
||
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) | ||
|
||
return self.output() | ||
|
||
|
||
@pytest.fixture() | ||
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: | ||
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", "").lower() == "true"): | ||
pytest.skip("Set TEST_INPROCESS environment variable to run them.") | ||
|
||
mapdl_inprocess = MapdlInProcessRunner(tmp_path, version=mapdl_version) | ||
|
||
return mapdl_inprocess | ||
|
||
|
||
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) | ||
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 "test ends" in output_content | ||
|
||
|
||
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( | ||
""" | ||
*PYTHON | ||
print("Hello from MAPDL") | ||
*ENDPY | ||
""" | ||
) | ||
|
||
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 | ||
|
||
|
||
def test_start_python_from_mapdl(mapdl_inprocess: MapdlInProcessRunner): | ||
"""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 mapdl_inprocess.status_code == 0 | ||
|
||
assert output_content is not None | ||
assert "Starting Python commands" in output_content | ||
germa89 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert "Hello from MAPDL!" in output_content | ||
assert "test ends" in output_content |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this runner does not use the current pymapdl library within *PYTHON, which somewhat defeats the purpose of the test. The purpose of test is to answer the following question:
Does the *PYTHON feature work as expected using the current pymapdl package (which is the python environment from which pymapdl is installed and used by pytest). In this context, only python3.10 should be used for this kind of test and the python environment used by the test should be used also by *PYTHON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*PYTHON
only works with Python3.10?? I dont think it is that restrictive is it? @valallansys ??There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not surprised but to be fair I've never tried earlier versions, we set up the proper python venv for it to work. I think Mohamed wants the creation of a python venv with the *PYT requirements during the test and set up via the environment variable MAPDL_PYTHON_ENV or via /PYC when the mapdl version is recent enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's supposed to be fixed now, I kept the check python path to make sure we're using the right python used for pymapdl, but had to change from sys.executable because it wouldn't work without a virtual env in an embedded python interpreter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@germa89 I confirm it only works with python 3.10