Skip to content

Commit 5e6b95f

Browse files
committed
test: replace conftest parametrization with devtools
1 parent 0d3fd88 commit 5e6b95f

File tree

5 files changed

+21
-204
lines changed

5 files changed

+21
-204
lines changed

.github/workflows/ci.yml

+5-32
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ on:
44
schedule:
55
- cron: '0 8 * * *' # run at 8 AM UTC (12 am PST)
66
push:
7-
branches:
8-
- main
9-
- develop
10-
- 'release*'
117
pull_request:
128
branches: [main, develop]
13-
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
1412
jobs:
1513

1614
std_setup:
@@ -24,7 +22,7 @@ jobs:
2422
steps:
2523

2624
- name: Checkout repo
27-
uses: actions/checkout@v3
25+
uses: actions/checkout@v4
2826

2927
- name: Setup uv
3028
uses: astral-sh/setup-uv@v5
@@ -158,13 +156,7 @@ jobs:
158156
shell: bash
159157
steps:
160158
- name: Checkout repo
161-
uses: actions/checkout@v3
162-
163-
- name: Checkout mf6-examples
164159
uses: actions/checkout@v4
165-
with:
166-
repository: MODFLOW-ORG/modflow6-examples
167-
path: modflow6-examples
168160

169161
- name: Setup uv
170162
uses: astral-sh/setup-uv@v5
@@ -175,25 +167,6 @@ jobs:
175167
- name: Install
176168
run: uv sync --all-extras
177169

178-
- name: Install Python dependencies
179-
run: |
180-
uv pip install git+https://git@github.com/Deltares/xmipy@develop
181-
uv pip install git+https://git@github.com/MODFLOW-ORG/modflow-devtools@develop
182-
uv pip install git+https://git@github.com/modflowpy/flopy@develop
183-
184-
- name: update flopy for mf6-examples
185-
run: uv run python -m flopy.mf6.utils.generate_classes --ref develop --no-backup
186-
187-
- name: Install modflow6 for modflow6-examples
188-
uses: modflowpy/install-modflow-action@v1
189-
with:
190-
path: ${{ github.workspace }}/modflow6-examples/autotest
191-
192-
- name: Build mf6-examples
193-
working-directory: modflow6-examples/autotest
194-
run: |
195-
uv run pytest -v -n=auto --init test_scripts.py -k "not synthetic-valley"
196-
197170
- name: Install modflow6 nightly build
198171
uses: modflowpy/install-modflow-action@v1
199172
with:
@@ -203,4 +176,4 @@ jobs:
203176
- name: Run autotests
204177
working-directory: ./autotest
205178
shell: bash -l {0}
206-
run: uv run pytest -v -n=auto test_mf6_examples.py --mf6-examples-path=../modflow6-examples/examples
179+
run: uv run pytest -v test_mf6_examples.py

.github/workflows/release.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
steps:
2222

2323
- name: Checkout release branch
24-
uses: actions/checkout@v3
24+
uses: actions/checkout@v4
2525
with:
2626
fetch-depth: 0
2727

@@ -131,7 +131,7 @@ jobs:
131131
steps:
132132

133133
- name: Checkout repo
134-
uses: actions/checkout@v3
134+
uses: actions/checkout@v4
135135
with:
136136
ref: main
137137

@@ -179,7 +179,7 @@ jobs:
179179
steps:
180180

181181
- name: Checkout main branch
182-
uses: actions/checkout@v3
182+
uses: actions/checkout@v4
183183
with:
184184
ref: main
185185

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,7 @@ dmypy.json
137137
**.dylib
138138

139139
# uv lockfile
140-
uv.lock
140+
uv.lock
141+
142+
# temporary test folders
143+
autotest/temp/**

autotest/conftest.py

-121
Original file line numberDiff line numberDiff line change
@@ -1,122 +1 @@
1-
from itertools import groupby
2-
from os import linesep
3-
from pathlib import Path
4-
from tempfile import gettempdir
5-
6-
import pytest
7-
from filelock import FileLock
8-
from modflow_devtools.download import download_and_unzip
9-
10-
# import modflow-devtools fixtures
111
pytest_plugins = ["modflow_devtools.fixtures"]
12-
13-
14-
__mf6_examples = "mf6_examples"
15-
__mf6_examples_path = Path(gettempdir()) / __mf6_examples
16-
__mf6_examples_lock = FileLock(Path(gettempdir()) / f"{__mf6_examples}.lock")
17-
18-
19-
def get_mf6_examples_path() -> Path:
20-
# use file lock so mf6 distribution is downloaded once,
21-
# even when tests are run in parallel with pytest-xdist
22-
__mf6_examples_lock.acquire()
23-
try:
24-
if not __mf6_examples_path.is_dir():
25-
__mf6_examples_path.mkdir(exist_ok=True)
26-
download_and_unzip(
27-
url="https://github.com/MODFLOW-ORG/modflow6-examples/releases/download/current/mf6examples.zip",
28-
path=__mf6_examples_path,
29-
verbose=True,
30-
)
31-
return __mf6_examples_path
32-
finally:
33-
__mf6_examples_lock.release()
34-
35-
36-
def pytest_addoption(parser):
37-
parser.addoption("--mf6-examples-path", action="store", default=None)
38-
39-
40-
def is_nested(namfile) -> bool:
41-
p = Path(namfile)
42-
if not p.is_file() or not p.name.endswith(".nam"):
43-
raise ValueError(f"Expected a namfile path, got {p}")
44-
return p.parent.parent.name != __mf6_examples
45-
46-
47-
def pytest_generate_tests(metafunc):
48-
# examples to skip:
49-
# - ex-gwtgwt-mt3dms-p10: https://github.com/MODFLOW-ORG/modflow6/pull/1008
50-
option_value = metafunc.config.option.mf6_examples_path
51-
t = metafunc.fixturenames
52-
if "mf6_example_namfiles" in metafunc.fixturenames and option_value is not None:
53-
mf6_examples_path = Path(option_value)
54-
global __mf6_examples
55-
__mf6_examples = str(mf6_examples_path.name)
56-
else:
57-
mf6_examples_path = get_mf6_examples_path()
58-
59-
# grouping...
60-
exclude = [
61-
"ex-gwt-gwtgwt-mt3dms-p10",
62-
"mp7-p02",
63-
"mp7-p04",
64-
"synthetic-valley",
65-
]
66-
namfiles = [
67-
str(p)
68-
for p in mf6_examples_path.rglob("mfsim.nam")
69-
if not any(e in str(p) for e in exclude)
70-
]
71-
72-
# parametrization by model
73-
# - single namfile per test case
74-
# - no coupling (only first model in each simulation subdir is used)
75-
key = "mf6_example_namfile"
76-
if key in metafunc.fixturenames:
77-
metafunc.parametrize(key, sorted(namfiles))
78-
79-
# parametrization by simulation
80-
# - each test case gets an ordered list of 1+ namfiles
81-
# - models can be coupled (run in order provided, sharing workspace)
82-
key = "mf6_example_namfiles"
83-
if key in metafunc.fixturenames:
84-
simulations = []
85-
86-
def simulation_name_from_model_path(p):
87-
p = Path(p)
88-
return p.parent.parent.name if is_nested(p) else p.parent.name
89-
90-
for model_name, model_namfiles in groupby(
91-
namfiles, key=simulation_name_from_model_path
92-
):
93-
models = []
94-
model_namfiles = list(model_namfiles)
95-
if len(model_namfiles) > 1:
96-
# trap gwf models as first set of models
97-
idxs = [ix for ix, _ in enumerate(model_namfiles) if "gwf" in _]
98-
if len(idxs) > 0:
99-
for ix in idxs[::-1]:
100-
models.append(model_namfiles.pop(ix))
101-
102-
# sort remaining models in alphabetical order (gwe < gwt < prt)
103-
models += list(sorted(model_namfiles))
104-
simulations.append(models)
105-
print(
106-
f"Simulation {model_name} has {len(models)} model(s):\n"
107-
f"{linesep.join(model_namfiles)}"
108-
)
109-
110-
def simulation_name_from_model_namfiles(mnams):
111-
try:
112-
namfile = next(iter(mnams), None)
113-
except TypeError:
114-
namfile = None
115-
if namfile is None:
116-
pytest.skip("No namfiles (expected ordered collection)")
117-
namfile = Path(namfile)
118-
return (
119-
namfile.parent.parent if is_nested(namfile) else namfile.parent
120-
).name
121-
122-
metafunc.parametrize(key, simulations, ids=simulation_name_from_model_namfiles)

autotest/test_mf6_examples.py

+9-47
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,17 @@
1-
from pathlib import Path
2-
from shutil import copytree
3-
1+
import modflow_devtools.models as models
42
import pytest
53

6-
from autotest.conftest import is_nested
74
from modflowapi import run_simulation
85

6+
examples = models.get_examples()
97
pytestmark = pytest.mark.mf6
108
dll = "libmf6"
119

1210

13-
def test_mf6_example_simulations(function_tmpdir, mf6_example_namfiles):
14-
"""
15-
MF6 examples parametrized by simulation. `mf6_example_namfiles` is a list
16-
of models to run in order provided. Coupled models share the same tempdir
17-
18-
Parameters
19-
----------
20-
function_tmpdir: function-scoped temporary directory fixture
21-
mf6_example_namfiles: ordered list of namfiles for 1+ coupled models
22-
"""
23-
if len(mf6_example_namfiles) == 0:
24-
pytest.skip("No namfiles (expected ordered collection)")
25-
namfile = Path(mf6_example_namfiles[0])
26-
27-
nested = is_nested(namfile)
28-
function_tmpdir = Path(function_tmpdir / "workspace")
29-
30-
copytree(
31-
src=namfile.parent.parent if nested else namfile.parent, dst=function_tmpdir
32-
)
33-
34-
def callback(sim, step):
35-
pass
36-
37-
def run_models():
38-
# run models in order received (should be alphabetical,
39-
# so gwf precedes gwt)
40-
for namfile in mf6_example_namfiles:
41-
namfile_path = Path(namfile).resolve()
42-
model_path = namfile_path.parent
43-
44-
# working directory must be named according to namefile's parent
45-
# (e.g. 'mf6gwf') because coupled models refer to each other with
46-
# relative paths
47-
wrkdir = (
48-
Path(function_tmpdir / model_path.name) if nested else function_tmpdir
49-
)
50-
try:
51-
run_simulation(dll, wrkdir, callback, verbose=True)
52-
except Exception as e:
53-
raise Exception(e)
54-
55-
run_models()
11+
@pytest.mark.parametrize("example_name", examples.keys())
12+
def test_example(function_tmpdir, example_name):
13+
for model_name in examples[example_name]:
14+
model_relpath = model_name.rpartition(example_name + "/")[-1]
15+
model_workspace = function_tmpdir / model_relpath
16+
models.copy_to(model_workspace, model_name, verbose=True)
17+
run_simulation(dll, model_workspace, lambda sim, step: None, verbose=True)

0 commit comments

Comments
 (0)