Skip to content

Commit e327fbe

Browse files
feat: add coverage (#909)
Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent c74a9e1 commit e327fbe

File tree

6 files changed

+236
-29
lines changed

6 files changed

+236
-29
lines changed

.github/actions/unit-test/action.yml

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ inputs:
3636
default: ""
3737
type: string
3838

39+
codecov-token:
40+
description: Token for Codecov upload
41+
required: false
42+
default: ""
43+
type: string
44+
45+
coverage-file:
46+
description: Coverage file path
47+
required: false
48+
default: ""
49+
type: string
50+
3951
runs:
4052
using: "composite"
4153
steps:
@@ -72,9 +84,9 @@ runs:
7284
- name: Run pytest
7385
run: |
7486
if [ "${{ inputs.pytest-markers }}" = "" ]; then
75-
pytest
87+
pytest -ra --cov=ansys.dyna.core --cov-report html:.cov/html --cov-report xml:.cov/xml --cov-report term -vv
7688
else
77-
pytest -m "${{ inputs.pytest-markers }}"
89+
pytest -m "${{ inputs.pytest-markers }}" -ra --cov=ansys.dyna.core --cov-report html:.cov/html --cov-report xml:.cov/xml --cov-report term -vv
7890
fi
7991
shell: bash
8092
env:
@@ -96,10 +108,16 @@ runs:
96108
name: server_output_tests.txt
97109
path: server_output.txt
98110

99-
- name: Upload coverage results
100-
if: ${{ inputs.server-logs == 'true' }}
101-
uses: actions/upload-artifact@v4
111+
- name: Upload coverage to Codecov
112+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
113+
env:
114+
CODECOV_TOKEN: ${{ inputs.codecov-token }}
102115
with:
103-
name: coverage-html
104-
path: .cov/html
105-
retention-days: 7
116+
flags: ${{ inputs.coverage-file }}
117+
118+
- name: Stop container
119+
if: ${{ inputs.docker-image != '' && inputs.pytest-markers != 'keywords' }}
120+
run: |
121+
docker stop kw_server
122+
docker rm kw_server
123+
shell: bash

.github/workflows/ci_cd_pr.yml

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ env:
1919
PACKAGE_NAMESPACE: 'ansys.dyna.core'
2020
DOCUMENTATION_CNAME: "dyna.docs.pyansys.com"
2121
PYDYNA_RUN_CONTAINER: ${{ github.event.inputs.PyDynaRunContainer || 'ghcr.io/ansys/pydyna-run:latest'}}
22+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2223

2324
concurrency:
2425
group: ${{ github.workflow }}-${{ github.ref }}
@@ -148,13 +149,32 @@ jobs:
148149
library-name: ${{ env.PACKAGE_NAME }}
149150
operating-system: ${{ matrix.os }}
150151
python-version: ${{ matrix.python-version }}
152+
151153

154+
keyword-testing:
155+
name: "Keyword testing for matrix"
156+
needs: [smoke-tests]
157+
runs-on: ${{ matrix.os }}
158+
strategy:
159+
fail-fast: false
160+
matrix:
161+
os: [ubuntu-latest, windows-latest]
162+
python-version: ['3.10', '3.11', '3.12', '3.13']
163+
steps:
164+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
165+
166+
- name: Run test with "keywords" marker
167+
uses: ./.github/actions/unit-test
168+
with:
169+
python-version: ${{ matrix.python-version }}
170+
github-token: ${{ secrets.GITHUB_TOKEN }}
171+
pytest-markers: keywords
172+
coverage-file: keywords
152173

153174
run-testing:
154175
name: Test the "run" subpackage
155176
runs-on: ubuntu-latest
156177
needs: [smoke-tests]
157-
158178
steps:
159179
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
160180

@@ -166,36 +186,22 @@ jobs:
166186
docker-image: ${{ env.PYDYNA_RUN_CONTAINER }}
167187
pytest-markers: run
168188
license-server: ${{ secrets.LICENSE_SERVER }}
169-
170-
keyword-testing:
171-
name: "Keyword testing"
172-
runs-on: ${{ matrix.os }}
173-
needs: [smoke-tests]
174-
strategy:
175-
fail-fast: false
176-
matrix:
177-
os: [ubuntu-latest, windows-latest]
178-
python-version: ['3.10', '3.11', '3.12', '3.13']
179-
steps:
180-
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
181-
182-
- uses: ./.github/actions/unit-test
183-
with:
184-
python-version: ${{ matrix.python-version }}
185-
github-token: ${{ secrets.GITHUB_TOKEN }}
186-
pytest-markers: keywords
189+
coverage-file: run
187190

188191
unit-tests:
189192
name: "Testing"
190193
runs-on: ubuntu-latest
191-
needs: [run-testing, keyword-testing]
194+
needs: [keyword-testing, run-testing, codegen-testing]
192195
steps:
193196
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
194-
- uses: ./.github/actions/unit-test
197+
- name: Run test without marker
198+
uses: ./.github/actions/unit-test
195199
with:
196200
python-version: ${{ env.MAIN_PYTHON_VERSION }}
197201
github-token: ${{ secrets.GITHUB_TOKEN }}
198202
server-logs: true
203+
codecov-token: ${{ env.CODECOV_TOKEN }}
204+
coverage-file: unittests
199205

200206
build-library:
201207
name: "Build library"

codecov.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
coverage:
2+
range: 75..100
3+
round: down
4+
precision: 2
5+
status:
6+
project:
7+
default:
8+
target: 90%
9+
patch: off
10+
11+
12+
codecov:
13+
notify:
14+
wait_for_ci: yes
15+
16+
flag_management:
17+
default_rules: # the rules that will be followed for any flag added, generally
18+
carryforward: true
19+
statuses:
20+
- type: project
21+
target: auto
22+
threshold: 1%
23+
- type: patch
24+
target: 90%
25+
individual_flags: # exceptions to the default rules above, stated flag by flag
26+
- name: run
27+
paths:
28+
- src/ansys/dyna/core/run
29+
carryforward: true
30+
statuses:
31+
- type: project
32+
target: 20%
33+
- type: patch
34+
target: 100%
35+
- name: keywords
36+
paths:
37+
- src/ansys/dyna/core/keywords #fill in your own path. Note, accepts globs, not regexes
38+
carryforward: true
39+
statuses:
40+
- type: project
41+
target: 20%
42+
- type: patch
43+
target: 100%

doc/changelog/909.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add coverage

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ src_paths = ["doc", "src", "tests"]
119119

120120
[tool.coverage.run]
121121
source = ["ansys.dyna.core"]
122+
omit = [
123+
"src/ansys/dyna/core/keywords/keyword_classes/auto/*"
124+
]
122125

123126
[tool.coverage.report]
124127
show_missing = true

tests/test_windows_runner.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import pytest
2+
from pathlib import Path
3+
from unittest.mock import MagicMock, patch
4+
5+
import ansys.dyna.core.run.windows_runner as windows_runner
6+
from ansys.dyna.core.run.options import MpiOption, Precision
7+
8+
9+
@pytest.fixture
10+
def tmp_workdir(tmp_path):
11+
return tmp_path
12+
13+
14+
def make_runner(tmp_workdir, mpi=MpiOption.SMP, precision=Precision.SINGLE):
15+
# create dummy solver executable
16+
exe = tmp_workdir / "lsdyna.exe"
17+
exe.write_text("dummy")
18+
19+
runner = windows_runner.WindowsRunner(executable=str(exe))
20+
runner.mpi_option = mpi
21+
runner.precision = precision
22+
runner.input_file = "input.k"
23+
runner.working_directory = str(tmp_workdir)
24+
runner.ncpu = 4
25+
runner.get_memory_string = MagicMock(return_value="2000m")
26+
return runner
27+
28+
29+
def test_find_solver_with_explicit_executable(tmp_workdir):
30+
exe = tmp_workdir / "lsdyna.exe"
31+
exe.write_text("dummy")
32+
33+
runner = windows_runner.WindowsRunner(executable=str(exe))
34+
assert runner.solver_location == str(tmp_workdir)
35+
assert runner.solver.endswith("lsdyna.exe\"")
36+
37+
38+
@patch("ansys.dyna.core.run.windows_runner._get_unified_install_base_for_version")
39+
def test_find_solver_with_version(mock_install_base, tmp_workdir):
40+
exe = tmp_workdir / "ansys" / "bin" / "winx64" / "lsdyna_sp.exe"
41+
exe.parent.mkdir(parents=True)
42+
exe.write_text("dummy")
43+
44+
mock_install_base.return_value = (tmp_workdir, None)
45+
runner = windows_runner.WindowsRunner(version=241, precision=Precision.SINGLE)
46+
assert runner.solver.endswith("lsdyna_sp.exe\"")
47+
48+
def test_find_solver_executable_not_found(tmp_workdir):
49+
with pytest.raises(FileNotFoundError):
50+
windows_runner.WindowsRunner(executable=str(tmp_workdir / "missing.exe"))
51+
52+
53+
@pytest.mark.parametrize(
54+
"mpi,prec,expected",
55+
[
56+
(MpiOption.SMP, Precision.SINGLE, "lsdyna_sp.exe"),
57+
(MpiOption.SMP, Precision.DOUBLE, "lsdyna_dp.exe"),
58+
(MpiOption.MPP_INTEL_MPI, Precision.SINGLE, "lsdyna_mpp_sp_impi.exe"),
59+
(MpiOption.MPP_INTEL_MPI, Precision.DOUBLE, "lsdyna_mpp_dp_impi.exe"),
60+
(MpiOption.MPP_MS_MPI, Precision.SINGLE, "lsdyna_mpp_sp_msmpi.exe"),
61+
(MpiOption.MPP_MS_MPI, Precision.DOUBLE, "lsdyna_mpp_dp_msmpi.exe"),
62+
],
63+
)
64+
def test_get_exe_name_variants(tmp_workdir, mpi, prec, expected):
65+
runner = make_runner(tmp_workdir, mpi, prec)
66+
assert runner._get_exe_name() == expected
67+
68+
69+
def test_get_env_script_intel(tmp_workdir):
70+
runner = make_runner(tmp_workdir, MpiOption.MPP_INTEL_MPI)
71+
(tmp_workdir / "lsprepost_foo").mkdir()
72+
script = runner._get_env_script()
73+
assert script.endswith("lsdynaintelvar.bat")
74+
75+
76+
def test_write_runscript(tmp_workdir):
77+
runner = make_runner(tmp_workdir)
78+
runner._get_command_line = MagicMock(return_value="echo hello")
79+
runner._write_runscript()
80+
script_path = tmp_workdir / runner._scriptname
81+
assert "echo hello" in script_path.read_text()
82+
83+
84+
@patch("ansys.dyna.core.run.windows_runner.subprocess.Popen")
85+
def test_run_success(mock_popen, tmp_workdir):
86+
runner = make_runner(tmp_workdir)
87+
runner._get_command_line = MagicMock(return_value="echo hello")
88+
89+
process = MagicMock()
90+
process.poll.side_effect = [None, 0]
91+
process.wait.return_value = 0
92+
process.returncode = 0
93+
mock_popen.return_value = process
94+
95+
# fake log file
96+
log_file = tmp_workdir / "lsrun.out.txt"
97+
log_file.write_text("all good\n")
98+
99+
runner.run()
100+
assert process.wait.called
101+
102+
103+
@patch("ansys.dyna.core.run.windows_runner.subprocess.Popen")
104+
def test_run_with_warning_logs(mock_popen, tmp_workdir, caplog):
105+
runner = make_runner(tmp_workdir)
106+
runner._get_command_line = MagicMock(return_value="echo hello")
107+
108+
process = MagicMock()
109+
process.poll.side_effect = [None, 0]
110+
process.wait.return_value = 0
111+
process.returncode = 0
112+
mock_popen.return_value = process
113+
114+
log_file = tmp_workdir / "lsrun.out.txt"
115+
log_file.write_text("Warning: something\n")
116+
117+
runner.run()
118+
assert "completed with warnings" in caplog.text
119+
120+
121+
@patch("ansys.dyna.core.run.windows_runner.subprocess.Popen")
122+
def test_run_failure(mock_popen, tmp_workdir):
123+
runner = make_runner(tmp_workdir)
124+
runner._get_command_line = MagicMock(return_value="echo hello")
125+
126+
process = MagicMock()
127+
process.poll.side_effect = [0]
128+
process.wait.return_value = 1
129+
process.returncode = 1
130+
mock_popen.return_value = process
131+
132+
log_file = tmp_workdir / "lsrun.out.txt"
133+
log_file.write_text("Error: fail\n")
134+
135+
with pytest.raises(RuntimeError):
136+
runner.run()

0 commit comments

Comments
 (0)