diff --git a/HISTORY.md b/HISTORY.md index 4f87fb5..e9c7399 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,13 @@ +### Version 1.6.0 + +#### New features + +* [feat(snapshots)](https://github.com/MODFLOW-USGS/modflow-devtools/commit/4e289ee4a13d13724c5cbcb7a1ee5328fc588c13): Add --snapshot-disable cli option (#157). Committed by wpbonelli on 2024-05-21. + +#### Bug fixes + +* [fix(get_model_paths)](https://github.com/MODFLOW-USGS/modflow-devtools/commit/0e3120a9d1cf53ecc98b861aeb05e3a8fa7afb71): Fix model order within scenario (#156). Committed by wpbonelli on 2024-05-21. + ### Version 1.5.0 #### New features diff --git a/autotest/conftest.py b/autotest/conftest.py index d952441..6d69b98 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -1,4 +1,4 @@ from pathlib import Path -pytest_plugins = ["modflow_devtools.fixtures"] +pytest_plugins = ["modflow_devtools.fixtures", "modflow_devtools.snapshots"] project_root_path = Path(__file__).parent diff --git a/autotest/test_misc.py b/autotest/test_misc.py index c3034a3..5997bbc 100644 --- a/autotest/test_misc.py +++ b/autotest/test_misc.py @@ -151,12 +151,12 @@ def get_expected_namefiles(path, pattern="mfsim.nam") -> List[Path]: def test_get_model_paths_examples(): expected_paths = get_expected_model_dirs(_examples_path) paths = get_model_paths(_examples_path) - assert paths == sorted(list(set(paths))) # no duplicates + assert sorted(paths) == sorted(list(set(paths))) # no duplicates assert set(expected_paths) == set(paths) expected_paths = get_expected_model_dirs(_examples_path, "*.nam") paths = get_model_paths(_examples_path, namefile="*.nam") - assert paths == sorted(list(set(paths))) + assert sorted(paths) == sorted(list(set(paths))) assert set(expected_paths) == set(paths) @@ -166,12 +166,12 @@ def test_get_model_paths_examples(): def test_get_model_paths_largetestmodels(): expected_paths = get_expected_model_dirs(_examples_path) paths = get_model_paths(_examples_path) - assert paths == sorted(list(set(paths))) + assert sorted(paths) == sorted(list(set(paths))) assert set(expected_paths) == set(paths) expected_paths = get_expected_model_dirs(_examples_path) paths = get_model_paths(_examples_path) - assert paths == sorted(list(set(paths))) + assert sorted(paths) == sorted(list(set(paths))) assert set(expected_paths) == set(paths) diff --git a/autotest/test_snapshots.py b/autotest/test_snapshots.py index 04cabc9..01d5f3e 100644 --- a/autotest/test_snapshots.py +++ b/autotest/test_snapshots.py @@ -2,10 +2,11 @@ from pathlib import Path import numpy as np +import pytest +from _pytest.config import ExitCode proj_root = Path(__file__).parents[1] module_path = Path(inspect.getmodulename(__file__)) -pytest_plugins = [ "modflow_devtools.snapshots" ] # activate snapshot fixtures snapshot_array = np.array([1.1, 2.2, 3.3]) snapshots_path = proj_root / "autotest" / "__snapshots__" @@ -61,3 +62,47 @@ def test_readable_text_array_snapshot(readable_array_snapshot): ), snapshot_array, ) + + +@pytest.mark.meta("test_snapshot_disable") +def test_snapshot_disable_inner(snapshot): + assert snapshot == "match this!" + + +@pytest.mark.parametrize("disable", [True, False]) +def test_snapshot_disable(disable): + inner_fn = test_snapshot_disable_inner.__name__ + args = [ + __file__, + "-v", + "-s", + "-k", + inner_fn, + "-M", + "test_snapshot_disable", + ] + if disable: + args.append("--snapshot-disable") + assert pytest.main(args) == (ExitCode.OK if disable else ExitCode.TESTS_FAILED) + + +@pytest.mark.meta("test_array_snapshot_disable") +def test_array_snapshot_disable_inner(array_snapshot): + assert array_snapshot == "can you match that?" + + +@pytest.mark.parametrize("disable", [True, False]) +def test_array_snapshot_disable(disable): + inner_fn = test_array_snapshot_disable_inner.__name__ + args = [ + __file__, + "-v", + "-s", + "-k", + inner_fn, + "-M", + "test_array_snapshot_disable", + ] + if disable: + args.append("--snapshot-disable") + assert pytest.main(args) == (ExitCode.OK if disable else ExitCode.TESTS_FAILED) diff --git a/docs/conf.py b/docs/conf.py index 63390be..81f36ec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ project = "modflow-devtools" author = "MODFLOW Team" -release = '1.5.0' +release = '1.6.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/md/snapshots.md b/docs/md/snapshots.md index 0254617..4b8a41b 100644 --- a/docs/md/snapshots.md +++ b/docs/md/snapshots.md @@ -14,4 +14,8 @@ To use snapshot fixtures, add the following line to a test file or `conftest.py` ```python pytest_plugins = [ "modflow_devtools.snapshots" ] -``` \ No newline at end of file +``` + +## Disable snapshots + +Snapshot comparisons can be disabled by invoked `pytest` with the `--snapshot-disable` flag. \ No newline at end of file diff --git a/modflow_devtools/__init__.py b/modflow_devtools/__init__.py index 7614347..84768a3 100644 --- a/modflow_devtools/__init__.py +++ b/modflow_devtools/__init__.py @@ -1,6 +1,6 @@ __author__ = "Joseph D. Hughes" -__date__ = "May 15, 2024" -__version__ = "1.5.0" +__date__ = "May 30, 2024" +__version__ = "1.6.0" __maintainer__ = "Joseph D. Hughes" __email__ = "jdhughes@usgs.gov" __status__ = "Production" diff --git a/modflow_devtools/misc.py b/modflow_devtools/misc.py index 19931ee..08a03f4 100644 --- a/modflow_devtools/misc.py +++ b/modflow_devtools/misc.py @@ -284,13 +284,41 @@ def get_model_paths( Find model directories recursively in the given location. A model directory is any directory containing one or more namefiles. Model directories can be filtered or excluded, - by prefix, pattern, namefile name, or packages used. - """ - - namefile_paths = get_namefile_paths( - path, prefix, namefile, excluded, selected, packages - ) - model_paths = sorted(list(set([p.parent for p in namefile_paths if p.parent.name]))) + by prefix, pattern, namefile name, or packages used. The + directories are returned in order within scenario folders + such that groundwater flow model workspaces precede other + model types. This allows models which depend on the flow + model's outputs to consume its head or budget, and models + should successfully run in the sequence returned provided + input files (e.g. FMI) refer to output via relative paths. + """ + + def keyfunc(v): + v = str(v) + if "gwf" in v: + return 0 + else: + return 1 + + model_paths = [] + globbed = path.rglob(f"{prefix if prefix else ''}*") + example_paths = [p for p in globbed if p.is_dir()] + for p in example_paths: + for mp in sorted( + list( + set( + [ + p.parent + for p in get_namefile_paths( + p, prefix, namefile, excluded, selected, packages + ) + ] + ) + ), + key=keyfunc, + ): + if mp not in model_paths: + model_paths.append(mp) return model_paths diff --git a/modflow_devtools/snapshots.py b/modflow_devtools/snapshots.py index eed8776..9049861 100644 --- a/modflow_devtools/snapshots.py +++ b/modflow_devtools/snapshots.py @@ -8,10 +8,13 @@ syrupy = import_optional_dependency("syrupy") # ruff: noqa: E402 +from syrupy import __import_extension +from syrupy.assertion import SnapshotAssertion from syrupy.extensions.single_file import ( SingleFileSnapshotExtension, WriteMode, ) +from syrupy.location import PyTestLocation from syrupy.types import ( PropertyFilter, PropertyMatcher, @@ -90,19 +93,67 @@ def serialize( return np.array2string(data, threshold=np.inf) +class MatchAnything: + def __eq__(self, _): + return True + + # fixtures +@pytest.fixture(scope="session") +def snapshot_disable(pytestconfig) -> bool: + return pytestconfig.getoption("--snapshot-disable") + + @pytest.fixture -def array_snapshot(snapshot): - return snapshot.use_extension(BinaryArrayExtension) +def snapshot(request, snapshot_disable) -> "SnapshotAssertion": + return ( + MatchAnything() + if snapshot_disable + else SnapshotAssertion( + update_snapshots=request.config.option.update_snapshots, + extension_class=__import_extension(request.config.option.default_extension), + test_location=PyTestLocation(request.node), + session=request.session.config._syrupy, + ) + ) @pytest.fixture -def text_array_snapshot(snapshot): - return snapshot.use_extension(TextArrayExtension) +def array_snapshot(snapshot, snapshot_disable): + return ( + MatchAnything() + if snapshot_disable + else snapshot.use_extension(BinaryArrayExtension) + ) @pytest.fixture -def readable_array_snapshot(snapshot): - return snapshot.use_extension(ReadableArrayExtension) +def text_array_snapshot(snapshot, snapshot_disable): + return ( + MatchAnything() + if snapshot_disable + else snapshot.use_extension(TextArrayExtension) + ) + + +@pytest.fixture +def readable_array_snapshot(snapshot, snapshot_disable): + return ( + MatchAnything() + if snapshot_disable + else snapshot.use_extension(ReadableArrayExtension) + ) + + +# pytest config hooks + + +def pytest_addoption(parser): + parser.addoption( + "--snapshot-disable", + action="store_true", + default=False, + help="Disable snapshot comparisons.", + ) diff --git a/version.txt b/version.txt index 3e1ad72..ce6a70b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.0 \ No newline at end of file +1.6.0 \ No newline at end of file