Skip to content

Commit

Permalink
fix(HeadUFile): fix #1503 (#1510)
Browse files Browse the repository at this point in the history
* add flopy.utils.grid.py and get_lni(ncpl, *nodes) function ('layer node index')
* add get_lni(*nodes) method to grids
* add tests for new functions & methods
* use get_lni in HeadUFile.get_ts(idx) (fix #1503)
* test HeadUFile.get_ts(idx) for every grid node
* run linter on examples and tests
* add addopts = -ra to pytest.ini (show info for skipped/xfailed tests)
* add pytest-cases to setup.cfg and environment.yml test dependencies
* use pytest-cases to define grids & parametrize grid tests
* mark EPSG test cases flaky
  • Loading branch information
wpbonelli committed Dec 14, 2022
1 parent 085bfd9 commit f212707
Show file tree
Hide file tree
Showing 64 changed files with 1,573 additions and 1,057 deletions.
72 changes: 44 additions & 28 deletions autotest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ def backtrack_or_raise():
if is_in_ci():
tries.append(2)
for t in tries:
parts = cwd.parts[0: cwd.parts.index("flopy") + t]
parts = cwd.parts[0 : cwd.parts.index("flopy") + t]
pth = Path(*parts)
if next(iter([p for p in pth.glob("setup.cfg")]), None) is not None:
if (
next(iter([p for p in pth.glob("setup.cfg")]), None)
is not None
):
return pth
raise Exception(
f"Can't infer location of project root from {cwd} "
Expand All @@ -61,13 +64,17 @@ def backtrack_or_raise():
if cwd.name == "autotest":
# we're in top-level autotest folder
return cwd.parent
elif "autotest" in cwd.parts and cwd.parts.index("autotest") > cwd.parts.index("flopy"):
elif "autotest" in cwd.parts and cwd.parts.index(
"autotest"
) > cwd.parts.index("flopy"):
# we're somewhere inside autotests
parts = cwd.parts[0: cwd.parts.index("autotest")]
parts = cwd.parts[0 : cwd.parts.index("autotest")]
return Path(*parts)
elif "examples" in cwd.parts and cwd.parts.index("examples") > cwd.parts.index("flopy"):
elif "examples" in cwd.parts and cwd.parts.index(
"examples"
) > cwd.parts.index("flopy"):
# we're somewhere inside examples folder
parts = cwd.parts[0: cwd.parts.index("examples")]
parts = cwd.parts[0 : cwd.parts.index("examples")]
return Path(*parts)
elif "flopy" in cwd.parts:
if cwd.parts.count("flopy") >= 2:
Expand Down Expand Up @@ -154,7 +161,7 @@ def is_github_rate_limited() -> Optional[bool]:
"""
try:
with request.urlopen(
"https://api.github.com/users/octocat"
"https://api.github.com/users/octocat"
) as response:
remaining = int(response.headers["x-ratelimit-remaining"])
if remaining < 10:
Expand Down Expand Up @@ -197,30 +204,32 @@ def requires_exe(*exes):
missing = {exe for exe in exes if not has_exe(exe)}
return pytest.mark.skipif(
missing,
reason=f"missing executable{'s' if len(missing) != 1 else ''}: " +
", ".join(missing),
reason=f"missing executable{'s' if len(missing) != 1 else ''}: "
+ ", ".join(missing),
)


def requires_pkg(*pkgs):
missing = {pkg for pkg in pkgs if not has_pkg(pkg)}
return pytest.mark.skipif(
missing,
reason=f"missing package{'s' if len(missing) != 1 else ''}: " +
", ".join(missing),
reason=f"missing package{'s' if len(missing) != 1 else ''}: "
+ ", ".join(missing),
)


def requires_platform(platform, ci_only=False):
return pytest.mark.skipif(
system().lower() != platform.lower() and (is_in_ci() if ci_only else True),
system().lower() != platform.lower()
and (is_in_ci() if ci_only else True),
reason=f"only compatible with platform: {platform.lower()}",
)


def excludes_platform(platform, ci_only=False):
return pytest.mark.skipif(
system().lower() == platform.lower() and (is_in_ci() if ci_only else True),
system().lower() == platform.lower()
and (is_in_ci() if ci_only else True),
reason=f"not compatible with platform: {platform.lower()}",
)

Expand All @@ -240,18 +249,19 @@ def excludes_branch(branch):


requires_github = pytest.mark.skipif(
not is_connected("github.com"),
reason="github.com is required.")
not is_connected("github.com"), reason="github.com is required."
)


requires_spatial_reference = pytest.mark.skipif(
not is_connected("spatialreference.org"),
reason="spatialreference.org is required."
reason="spatialreference.org is required.",
)


# example data fixtures


@pytest.fixture(scope="session")
def project_root_path(request) -> Path:
return get_project_root_path(request.session.path)
Expand All @@ -274,12 +284,14 @@ def example_shapefiles(example_data_path) -> List[Path]:

# keepable temporary directory fixtures for various scopes


@pytest.fixture(scope="function")
def tmpdir(tmpdir_factory, request) -> Path:
node = request.node.name \
.replace("/", "_") \
.replace("\\", "_") \
node = (
request.node.name.replace("/", "_")
.replace("\\", "_")
.replace(":", "_")
)
temp = Path(tmpdir_factory.mktemp(node))
yield Path(temp)

Expand All @@ -295,7 +307,7 @@ def tmpdir(tmpdir_factory, request) -> Path:
@pytest.fixture(scope="class")
def class_tmpdir(tmpdir_factory, request) -> Path:
assert (
request.cls is not None
request.cls is not None
), "Class-scoped temp dir fixture must be used on class"
temp = Path(tmpdir_factory.mktemp(request.cls.__name__))
yield temp
Expand Down Expand Up @@ -327,6 +339,7 @@ def session_tmpdir(tmpdir_factory, request) -> Path:

# pytest configuration hooks


@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
# this is necessary so temp dir fixtures can
Expand All @@ -348,19 +361,19 @@ def pytest_addoption(parser):
action="store",
default=None,
help="Move the contents of temporary test directories to correspondingly named subdirectories at the given "
"location after tests complete. This option can be used to exclude test results from automatic cleanup, "
"e.g. for manual inspection. The provided path is created if it does not already exist. An error is "
"thrown if any matching files already exist.",
"location after tests complete. This option can be used to exclude test results from automatic cleanup, "
"e.g. for manual inspection. The provided path is created if it does not already exist. An error is "
"thrown if any matching files already exist.",
)

parser.addoption(
"--keep-failed",
action="store",
default=None,
help="Move the contents of temporary test directories to correspondingly named subdirectories at the given "
"location if the test case fails. This option automatically saves the outputs of failed tests in the "
"given location. The path is created if it doesn't already exist. An error is thrown if files with the "
"same names already exist in the given location.",
"location if the test case fails. This option automatically saves the outputs of failed tests in the "
"given location. The path is created if it doesn't already exist. An error is thrown if files with the "
"same names already exist in the given location.",
)

parser.addoption(
Expand All @@ -376,7 +389,7 @@ def pytest_addoption(parser):
"--smoke",
action="store_true",
default=False,
help="Run only smoke tests (should complete in <1 minute)."
help="Run only smoke tests (should complete in <1 minute).",
)


Expand Down Expand Up @@ -444,6 +457,7 @@ def pytest_report_header(config):

# functions to run commands and scripts


def run_cmd(*args, verbose=False, **kwargs):
"""Run any command, return tuple (stdout, stderr, returncode)."""
args = [str(g) for g in args]
Expand All @@ -464,7 +478,8 @@ def run_cmd(*args, verbose=False, **kwargs):
def run_py_script(script, *args, verbose=False):
"""Run a Python script, return tuple (stdout, stderr, returncode)."""
return run_cmd(
sys.executable, script, *args, verbose=verbose, cwd=Path(script).parent)
sys.executable, script, *args, verbose=verbose, cwd=Path(script).parent
)


# use noninteractive matplotlib backend if in Mac OS CI to avoid pytest-xdist node failure
Expand All @@ -473,4 +488,5 @@ def run_py_script(script, *args, verbose=False):
def patch_macos_ci_matplotlib():
if is_in_ci() and system().lower() == "darwin":
import matplotlib

matplotlib.use("agg")
1 change: 1 addition & 0 deletions autotest/pytest.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[pytest]
addopts = -ra
python_files =
test_*.py
profile_*.py
Expand Down
33 changes: 24 additions & 9 deletions autotest/regression/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def get_mf6_examples_path() -> Path:

def is_nested(namfile) -> bool:
p = Path(namfile)
if not p.is_file() or not p.name.endswith('.nam'):
if not p.is_file() or not p.name.endswith(".nam"):
raise ValueError(f"Expected a namfile path, got {p}")

return p.parent.parent.name != __mf6_examples
Expand All @@ -43,7 +43,11 @@ def pytest_generate_tests(metafunc):
# examples to skip:
# - ex-gwtgwt-mt3dms-p10: https://github.com/MODFLOW-USGS/modflow6/pull/1008
exclude = ["ex-gwt-gwtgwt-mt3dms-p10"]
namfiles = [str(p) for p in get_mf6_examples_path().rglob("mfsim.nam") if not any(e in str(p) for e in exclude)]
namfiles = [
str(p)
for p in get_mf6_examples_path().rglob("mfsim.nam")
if not any(e in str(p) for e in exclude)
]

# parametrization by model
# - single namfile per test case
Expand All @@ -63,16 +67,27 @@ def simulation_name_from_model_path(p):
p = Path(p)
return p.parent.parent.name if is_nested(p) else p.parent.name

for model_name, model_namfiles in groupby(namfiles, key=simulation_name_from_model_path):
models = sorted(list(model_namfiles)) # sort in alphabetical order (gwf < gwt)
for model_name, model_namfiles in groupby(
namfiles, key=simulation_name_from_model_path
):
models = sorted(
list(model_namfiles)
) # sort in alphabetical order (gwf < gwt)
simulations.append(models)
print(f"Simulation {model_name} has {len(models)} model(s):\n"
f"{linesep.join(model_namfiles)}")
print(
f"Simulation {model_name} has {len(models)} model(s):\n"
f"{linesep.join(model_namfiles)}"
)

def simulation_name_from_model_namfiles(mnams):
namfile = next(iter(mnams), None)
if namfile is None: pytest.skip("No namfiles (expected ordered collection)")
if namfile is None:
pytest.skip("No namfiles (expected ordered collection)")
namfile = Path(namfile)
return (namfile.parent.parent if is_nested(namfile) else namfile.parent).name
return (
namfile.parent.parent if is_nested(namfile) else namfile.parent
).name

metafunc.parametrize(key, simulations, ids=simulation_name_from_model_namfiles)
metafunc.parametrize(
key, simulations, ids=simulation_name_from_model_namfiles
)
4 changes: 2 additions & 2 deletions autotest/regression/test_lgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from pathlib import Path

import pytest
from autotest.conftest import requires_exe, requires_pkg

import flopy
from autotest.conftest import requires_exe, requires_pkg


@requires_exe("mflgr")
Expand Down Expand Up @@ -47,7 +47,7 @@ def test_simplelgr(tmpdir, example_data_path):
# get the namefiles of the parent and child
namefiles = lgr.get_namefiles()
assert (
len(namefiles) == 2
len(namefiles) == 2
), f"get_namefiles returned {len(namefiles)} items instead of 2"

tpth = dirname(namefiles[0])
Expand Down
Loading

0 comments on commit f212707

Please sign in to comment.