Skip to content

Commit

Permalink
ci: Generate Fluent journals from unittests (#3507)
Browse files Browse the repository at this point in the history
* ci: Add a single pyfluent test to run with Fluent

* ci: Add a single pyfluent test to run with Fluent

* ci: debug logging

* ci: fix

* ci: fix

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: Generate Fluent journals

* ci: stop containers

* ci: fix run

* test: fix

* test: fix

* ci: fix
  • Loading branch information
mkundu1 authored Nov 26, 2024
1 parent 18084aa commit 934dc32
Show file tree
Hide file tree
Showing 16 changed files with 426 additions and 162 deletions.
82 changes: 54 additions & 28 deletions .ci/fluent_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ class FluentRuntimeError(RuntimeError):
pass


def run_fluent_test(journal_file: Path, launcher_args: str = "") -> None:
def run_fluent_test(
src_test_dir: Path, journal_file: Path, launcher_args: str = ""
) -> None:
"""Run Fluent test.
Parameters
----------
src_test_dir : Path
Path to the Fluent test directory in the host.
journal_file : Path
Absolute path to the journal file.
Expand All @@ -41,12 +46,27 @@ def run_fluent_test(journal_file: Path, launcher_args: str = "") -> None:
src_pyfluent_dir = str(Path(pyfluent.__file__).parent)
verion_for_file_name = FluentVersion.current_dev().number
dst_pyfluent_dir = f"/ansys_inc/v{verion_for_file_name}/commonfiles/CPython/3_10/linx64/Release/python/lib/python3.10/site-packages/ansys/fluent/core"
src_test_dir = str(journal_file.parent)
src_gen_dir = (
Path(pyfluent.__file__).parent / "ansys" / "fluent" / "core" / "generated"
)
dst_gen_dir = f"/ansys_inc/v{verion_for_file_name}/fluent/fluent{FluentVersion.current_dev()!r}/cortex/pylib/flapi/generated"
dst_test_dir = "/testing"
working_dir = Path(dst_test_dir)
parent = journal_file.parent
parents = []
while parent != src_test_dir:
parents.append(parent.name)
parent = parent.parent
parents.reverse()
for parent in parents:
working_dir /= parent
working_dir = str(working_dir)
src_test_dir = str(src_test_dir)
logging.debug(f"src_pyfluent_dir: {src_pyfluent_dir}")
logging.debug(f"dst_pyfluent_dir: {dst_pyfluent_dir}")
logging.debug(f"src_test_dir: {src_test_dir}")
logging.debug(f"dst_test_dir: {dst_test_dir}")
logging.debug(f"working_dir: {working_dir}")

docker_client = docker.from_env()
version_for_image_tag = FluentVersion.current_dev().docker_image_tag
Expand All @@ -55,34 +75,39 @@ def run_fluent_test(journal_file: Path, launcher_args: str = "") -> None:
image=image_name,
volumes=[
f"{src_pyfluent_dir}:{dst_pyfluent_dir}",
f"{src_gen_dir}:{dst_gen_dir}", # Try removing this after pyfluent is updated in commonfiles
f"{src_test_dir}:{dst_test_dir}",
],
working_dir=dst_test_dir,
working_dir=working_dir,
environment={"ANSYSLMD_LICENSE_FILE": os.environ["ANSYSLMD_LICENSE_FILE"]},
command=f"3ddp {launcher_args} -gu -py -i {journal_file.name}",
command=f"{launcher_args} -gu -py -i {journal_file.name}",
detach=True,
stdout=True,
stderr=True,
auto_remove=True,
)
while True:
container.reload()
if container.status == "exited":
break
stderr = container.logs(stdout=False, stderr=True)
if stderr:
stderr = stderr.decode()
for line in stderr.split("\n"):
if line.strip().startswith("Error:"):
if "Expected exception" in line: # for check_assert.py
container.stop()
else:
raise FluentRuntimeError(line)
sleep(1)
logging.debug(container.logs(stderr=True).decode())
container.remove()


MAX_TEST_PATH_LENGTH = 40
try:
while True:
container.reload()
if container.status == "exited":
break
stderr = container.logs(stdout=False, stderr=True)
if stderr:
stderr = stderr.decode()
for line in stderr.split("\n"):
if line.strip().startswith("Error:"):
if "Expected exception" in line: # for check_assert.py
container.stop()
else:
raise FluentRuntimeError(line)
sleep(1)
logging.debug(container.logs(stderr=True).decode())
container.remove()
except docker.errors.NotFound:
pass


MAX_TEST_PATH_LENGTH = 100


if __name__ == "__main__":
Expand All @@ -93,19 +118,20 @@ def run_fluent_test(journal_file: Path, launcher_args: str = "") -> None:
)
args = parser.parse_args()
test_dir = Path.cwd() / args.test_dir
with TemporaryDirectory(ignore_cleanup_errors=True) as tmpdir:
copytree(test_dir, tmpdir, dirs_exist_ok=True)
with TemporaryDirectory(ignore_cleanup_errors=True) as src_test_dir:
copytree(test_dir, src_test_dir, dirs_exist_ok=True)
exception_occurred = False
for test_file in Path(tmpdir).rglob("*.py"):
src_test_dir = Path(src_test_dir)
for test_file in (src_test_dir / "fluent").rglob("*.py"):
config_file = test_file.with_suffix(".yaml")
launcher_args = ""
if config_file.exists():
configs = yaml.safe_load(config_file.read_text())
launcher_args = configs.get("launcher_args", "")
test_file_relpath = str(test_file.relative_to(tmpdir))
test_file_relpath = str(test_file.relative_to(src_test_dir))
print(f"Running {test_file_relpath}", end="", flush=True)
try:
run_fluent_test(test_file, launcher_args)
run_fluent_test(src_test_dir, test_file, launcher_args)
print(
f"{(MAX_TEST_PATH_LENGTH + 10 - len(test_file_relpath)) * '·'}PASSED"
)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-fluent-journals.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ jobs:
- name: Run Fluent tests
run: |
python .ci/fluent_test_runner.py tests/fluent
make write-and-run-fluent-tests
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ docker-pull:
test-import:
@python -c "import ansys.fluent.core as pyfluent"

PYTESTEXTRA = --cache-clear --cov=ansys.fluent --cov-report=xml:cov_xml.xml --cov-report=html
PYTESTRERUN = --last-failed --last-failed-no-failures none
PYTESTEXTRA = --cache-clear --cov=ansys.fluent --cov-report=xml:cov_xml.xml --cov-report=html -n 4
PYTESTRERUN = --last-failed --last-failed-no-failures none -n 4

unittest: unittest-dev-242

Expand Down Expand Up @@ -179,3 +179,9 @@ cleanup-previous-docker-containers:
docker stop $(docker ps -a -q); \
fi
@if [ -n "$(docker ps -a -q)" ]; then docker rm -vf $(docker ps -a -q); fi

write-and-run-fluent-tests:
@pip install -r requirements/requirements_build.txt
@poetry install --with test -E reader
@poetry run python -m pytest --write-fluent-journals
@python .ci/fluent_test_runner.py tests
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ addopts = """
-v
--durations=0
--show-capture=all
-n 4
"""
markers = [
"settings_only: Read and modify the case settings only, without loading the mesh, initializing, or solving the case",
Expand Down
79 changes: 79 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from contextlib import nullcontext
import functools
import inspect
import operator
import os
from pathlib import Path
import shutil
import sys

from packaging.specifiers import SpecifierSet
from packaging.version import Version
Expand All @@ -26,6 +30,12 @@ def pytest_addoption(parser):
parser.addoption(
"--solvermode", action="store_true", default=False, help="run solvermode tests"
)
parser.addoption(
"--write-fluent-journals",
action="store_true",
default=False,
help="Write Fluent journals for unittests",
)


def pytest_runtest_setup(item):
Expand Down Expand Up @@ -65,6 +75,75 @@ def pytest_runtest_setup(item):
pytest.skip()


def pytest_collection_finish(session):
if session.config.getoption("--write-fluent-journals"):
import_path = Path(__file__).parent
sys.path.append(str(import_path))
import fluent_fixtures

launcher_args_by_fixture = {}
for k, v in fluent_fixtures.__dict__.items():
if hasattr(v, "fluent_launcher_args"):
launcher_args_by_fixture[k] = v.fluent_launcher_args
fluent_test_root = import_path / "fluent"
shutil.rmtree(fluent_test_root, ignore_errors=True)
for item in session.items:
skip = False
for mark in item.iter_markers(name="skip"):
skip = True
for mark in item.iter_markers(name="fluent_version"):
spec = mark.args[0]
# TODO: Support older versions
if not (
spec == "latest"
or Version(FluentVersion.current_dev().value) in SpecifierSet(spec)
):
skip = True
if skip:
continue
fluent_test_dir = fluent_test_root / item.module.__name__ / item.name
fluent_test_config = fluent_test_dir / "test.yaml"
fluent_test_file = fluent_test_dir / "test.py"
launcher_args = ""
parameters = inspect.signature(item.function).parameters
parameter_set = {p for p in parameters}
if not (parameter_set & set(launcher_args_by_fixture.keys())):
# Skipping as unittest doesn't use fluent fixture
continue
for param in parameters:
if param not in dir(fluent_fixtures):
print(f"Skipping {item.nodeid} because of missing fixture {param}")
skip = True
break
if skip:
continue
for param in parameters:
if param in launcher_args_by_fixture:
launcher_args = launcher_args_by_fixture[param]
break
fluent_test_dir.mkdir(parents=True, exist_ok=True)
with open(fluent_test_config, "w") as f:
f.write(f"launcher_args: {launcher_args}\n")
with open(fluent_test_file, "w") as f:
f.write("import sys\n")
f.write('sys.path.append("/testing")\n')
f.write(
f"from {item.module.__name__} import {item.name} # noqa: E402\n"
)
f.write("from fluent_fixtures import ( # noqa: E402\n")
for param in parameters:
f.write(f" {param},\n")
f.write(")\n")
f.write("\n")
f.write(f"{item.name}(")
f.write(", ".join([f"{p}(globals())" for p in parameters]))
f.write(")\n")
f.write("exit()\n")
print(f"Written {fluent_test_file}")
session.items = []
session.testscollected = 0


@pytest.fixture(autouse=True)
def run_before_each_test(
monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest
Expand Down
2 changes: 0 additions & 2 deletions tests/fluent/test_assert/test.py

This file was deleted.

21 changes: 0 additions & 21 deletions tests/fluent/test_meshing_workflow/test.py

This file was deleted.

1 change: 0 additions & 1 deletion tests/fluent/test_meshing_workflow/test.yaml

This file was deleted.

12 changes: 0 additions & 12 deletions tests/fluent/test_settings_api/test.py

This file was deleted.

2 changes: 0 additions & 2 deletions tests/fluent/test_version/test.py

This file was deleted.

Loading

0 comments on commit 934dc32

Please sign in to comment.