Skip to content

Commit

Permalink
Merge pull request #50 from MODFLOW-USGS/v0.1.3
Browse files Browse the repository at this point in the history
* ci(release): update to development version 0.1.3
* docs: update/expand docs (#47)
* refactor(fixtures): update defaults for model-finding fixtures (#48)
* fix(fixtures): fix test_model_mf6 fixture node id (#49)
* ci(release): set version to 0.1.3, update changelog

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: w-bonelli <[email protected]>
  • Loading branch information
wpbonelli and github-actions[bot] authored Jan 7, 2023
2 parents d5a94a4 + d96756c commit 614b6ee
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 78 deletions.
2 changes: 2 additions & 0 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ This document provides guidance to set up a development environment and discusse
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Requirements](#requirements)
- [Installation](#installation)
- [Testing](#testing)
- [Environment variables](#environment-variables)
- [Running the tests](#running-the-tests)
- [Writing new tests](#writing-new-tests)
- [Temporary directories](#temporary-directories)
- [Releasing](#releasing)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down
10 changes: 10 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
### Version 0.1.3

#### Bug fixes

* [fix(fixtures)](https://github.com/MODFLOW-USGS/modflow-devtools/commit/32e227bd2a6db39d3dada29ceb4ea6279f215f94): Fix test_model_mf6 fixture node id (#49). Committed by w-bonelli on 2023-01-07.

#### Refactoring

* [refactor(fixtures)](https://github.com/MODFLOW-USGS/modflow-devtools/commit/9987209620bf6b0422079d605c996c868116d725): Update defaults for model-finding fixtures (#48). Committed by w-bonelli on 2023-01-07.

### Version 0.1.2

#### Bug fixes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MODFLOW developer tools

### Version 0.1.2 &mdash; release candidate
### Version 0.1.3 &mdash; release candidate
[![GitHub tag](https://img.shields.io/github/tag/MODFLOW-USGS/modflow-devtools.svg)](https://github.com/MODFLOW-USGS/modflow-devtools/tags/latest)
[![PyPI Version](https://img.shields.io/pypi/v/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools)
[![PyPI Versions](https://img.shields.io/pypi/pyversions/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools)
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

project = "modflow-devtools"
author = "MODFLOW Team"
release = "0.1.2"
release = "0.1.3"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
8 changes: 4 additions & 4 deletions docs/md/act.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Local CI testing
# Testing CI workflows locally

The [`act`](https://github.com/nektos/act) tool uses Docker to run containerized CI workflows in a simulated GitHub Actions environment. [Docker Desktop](https://www.docker.com/products/docker-desktop/) is required for Mac or Windows and [Docker Engine](https://docs.docker.com/engine/) on Linux.
The [`act`](https://github.com/nektos/act) tool uses Docker to run CI workflows in a simulated GitHub Actions environment. [Docker Desktop](https://www.docker.com/products/docker-desktop/) is required for Mac or Windows and [Docker Engine](https://docs.docker.com/engine/) on Linux.

**Note:** `act` can only run Linux-based container definitions. Mac or Windows workflows or matrix OS entries will be skipped.

With Docker installed and running, run `act -l` from the project root to see available CI workflows. To run all workflows and jobs, just run `act`. To run a particular workflow use `-W`:

Expand All @@ -17,5 +19,3 @@ act -W .github/workflows/commit.yml -j build
**Note:** GitHub API rate limits are easy to exceed, especially with job matrices. Authenticated GitHub users have a much higher rate limit: use `-s GITHUB_TOKEN=<your token>` when invoking `act` to provide a personal access token. Note that this will log your token in shell history &mdash; leave the value blank for a prompt to enter it more securely.

The `-n` flag can be used to execute a dry run, which doesn't run anything, just evaluates workflow, job and step definitions. See the [docs](https://github.com/nektos/act#example-commands) for more.

**Note:** `act` can only run Linux-based container definitions, so Mac or Windows workflows or matrix OS entries will be skipped.
4 changes: 2 additions & 2 deletions docs/md/cases.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Cases

An alternative approach to testing, rather than loading pre-existing models from a repository, is to construct test models in code. This typically involves defining variables or `pytest` fixtures in the same test script as the test function. While this pattern is effective for manually defined scenarios, it tightly couples test functions to test cases, prevents easy reuse of the test case by other tests, and tends to lead to duplication, as each test script may reproduce similar test functions and data-generation procedures.
Constructing test models in code typically involves defining variables or `pytest` fixtures in the same test script as the test function. While this is quick and effective for manually defining new scenarios, it tightly couples test functions to test cases, makes reuse of the test case by other tests more difficult, and tends to lead to duplication, as test scripts may reproduce similar testing and data-generating procedures.

This package provides a minimal framework for self-describing test cases which can be defined once and plugged into arbitrary test functions. At its core is the `Case` class, which is just a `SimpleNamespace` with a few defaults and a `copy_update()` method for easy modification. This pairs nicely with [`pytest-cases`](https://smarie.github.io/python-pytest-cases/), which is recommended but not required.
A minimal framework is provided for self-describing test cases which can be plugged into arbitrary test functions. At its core is the `Case` class, which is just a `SimpleNamespace` with a few defaults and a `copy_update()` method. This pairs nicely with [`pytest-cases`](https://smarie.github.io/python-pytest-cases/), which is recommended but not required.

## Overview

Expand Down
71 changes: 52 additions & 19 deletions docs/md/executables.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
# Executables

The `Executables` class is just a mapping between executable names and paths on the filesystem. This can be useful to test multiple versions of the same program, and is easily injected into test functions as a fixture:
The `Executables` class maps executable names to paths on the filesystem. This is useful mainly for testing multiple versions of the same program.

## Usage

For example, assuming you have some development binaries in `bin` relative to your current working directory, and an official installation of the same programs in `~/.local.bin`:

```python
from os import environ
from pathlib import Path
import subprocess
import sys
from modflow_devtools.executables import Executables

import pytest
bindir_path = Path("~/.local/bin").expanduser()
executables = Executables(mf6=bindir_path / "mf6", mf6_dev=Path("mf6"))

from modflow_devtools.misc import get_suffixes
from modflow_devtools.executables import Executables
# constructor also supports kwargs
executables = Executables(**{"mf6": bindir_path / "mf6", "mf6_dev": Path("mf6")})

_bin_path = Path("~/.local/bin/modflow").expanduser()
_dev_path = Path(environ.get("BIN_PATH")).absolute()
_ext, _ = get_suffixes(sys.platform)
def test_executables():
assert executables.mf6.is_file()
assert executables.mf6_dev.is_file()

assert executables.mf6 == bindir_path / "mf6"
assert executables.mf6_dev == Path("mf6")
```

The class is easily injected into test functions as a fixture:

```python
import pytest

@pytest.fixture
@pytest.mark.skipif(not (_bin_path.is_dir() and _dev_path.is_dir()))
@pytest.mark.skipif(not bindir_path.is_dir())
def exes():
return Executables(
mf6_rel=_bin_path / f"mf6{_ext}",
mf6_dev=_dev_path / f"mf6{_ext}"
)

def test_exes(exes):
print(subprocess.check_output([f"{exes.mf6_rel}", "-v"]).decode('utf-8'))
print(subprocess.check_output([f"{exes.mf6_dev}", "-v"]).decode('utf-8'))
return executables
```

Dictionary-style access is also supported:

```python
def test_executables_access(executables):
assert executables["mf6"] == executables.mf6 == Path("mf6")
```

There is a convenience function for getting a program's version string. The function will automatically strip the program name from the output (assumed delimited with `:`).

```python
import subprocess

def test_executables_version(exes):
# e.g. '6.4.1 Release 12/09/2022'
assert exes.get_version(exes.mf6) == \
subprocess.check_output([f"{exes.mf6}", "-v"]).decode('utf-8').strip().split(":")[1].strip()
```

## Default mapping

A utility function is provided to create the default executable mapping used by MODFLOW 6 autotests:

```python
from modflow_devtools.executables import build_default_exe_dict, Executables

exes = Executables(**build_default_exe_dict())
```
60 changes: 46 additions & 14 deletions docs/md/fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,43 +42,73 @@ There is also a `--keep-failed <path>` option which preserves outputs only from

## Loading example models

Fixtures are provided to find and enumerate models from the MODFLOW 6 example and test model repositories and feed them to test functions. Models can be loaded from:
Fixtures are provided to find models from the MODFLOW 6 example and test model repositories and feed them to test functions. Models can be loaded from:

- [`MODFLOW-USGS/modflow6-examples`](https://github.com/MODFLOW-USGS/modflow6-examples)
- [`MODFLOW-USGS/modflow6-testmodels`](https://github.com/MODFLOW-USGS/modflow6-testmodels)
- [`MODFLOW-USGS/modflow6-largetestmodels`](https://github.com/MODFLOW-USGS/modflow6-largetestmodels)

These models can be requested like any other `pytest` fixture, by adding one of the following parameters to test functions:

- `test_model_mf5to6`
- `test_model_mf6`
- `large_test_model`
- `example_scenario`
- `test_model_mf5to6`: a `Path` to a MODFLOW 2005 model namefile, loaded from the `mf5to6` subdirectory of the `modflow6-testmodels` repository
- `test_model_mf6`: a `Path` to a MODFLOW 6 model namefile, loaded from the `mf6` subdirectory of the `modflow6-testmodels` repository
- `large_test_model`: a `Path` to a large MODFLOW 6 model namefile, loaded from the `modflow6-largetestmodels` repository
- `example_scenario`: a `Tuple[str, List[Path]]` containing the name of a MODFLOW 6 example scenario and a list of paths to its model namefiles, loaded from the `modflow6-examples` repository

To use these fixtures, the environment variable `REPOS_PATH` must point to the location of model repositories on the filesystem. Model repositories must live side-by-side in this location, and repository directories are expected to be named identically to GitHub repositories. If `REPOS_PATH` is not configured, test functions requesting these fixtures will be skipped.
### Configuration

**Note**: example models must be built by running the `ci_build_files.py` script in `modflow6-examples/etc` before running tests using the `example_scenario` fixture.
Model repositories must first be cloned

### Test models
It is recommended to set the environment variable `REPOS_PATH` to the location of the model repositories on the filesystem. Model repositories must live side-by-side in this location, and repository directories are expected to be named identically to GitHub repositories. If `REPOS_PATH` is not configured, `modflow-devtools` assumes tests are being run from an `autotest` subdirectory of the consuming project's root, and model repos live side-by-side with the consuming project. If this guess is incorrect and repositories cannot be found, tests requesting these fixtures will be skipped.

The `test_model_mf5to6`, `test_model_mf6` and `large_test_model` fixtures are each a `Path` to the model's namefile. For example:, to load `mf5to6` models from the repository:
**Note:** by default, all models found in the respective external repository will be returned by these fixtures. It is up to the consuming project to exclude models if needed.

### MODFLOW 2005 test models

The `test_model_mf5to6` fixture are each a `Path` to the model's namefile. For example, to load `mf5to6` models from the `MODFLOW-USGS/modflow6-testmodels` repo:

```python
def test_mf5to6_model(test_model_mf5to6):
assert isinstance(test_model_mf5to6, Path)
assert test_model_mf5to6.is_file()
assert test_model_mf5to6.suffix == ".nam"
```

This test function will be parametrized with all `mf5to6` models found in the [`MODFLOW-USGS/modflow6-testmodels`](https://github.com/MODFLOW-USGS/modflow6-testmodels).
This test function will be parametrized with all models found in the `mf5to6` subdirectory of the [`MODFLOW-USGS/modflow6-testmodels`](https://github.com/MODFLOW-USGS/modflow6-testmodels) repository. Note that MODFLOW-2005 namefiles need not be named `mfsim.nam`.

### MODFLOW 6 test models

The `test_model_mf6` fixture loads all MODFLOW 6 models found in the `mf6` subdirectory of the `MODFLOW-USGS/modflow6-testmodels` repository.

```python
def test_test_model_mf6(test_model_mf6):
assert isinstance(test_model_mf6, Path)
assert test_model_mf6.is_file()
assert test_model_mf6.name == "mfsim.nam"
```

Because these are MODFLOW 6 models, each namefile will be named `mfsim.nam`. The model name can be inferred from the namefile's parent directory.

### Large test models

The `large_test_model` fixture loads all MODFLOW 6 models found in the `MODFLOW-USGS/modflow6-largetestmodels` repository.

```python
def test_large_test_model(large_test_model):
print(large_test_model)
assert isinstance(large_test_model, Path)
assert large_test_model.is_file()
assert large_test_model.name == "mfsim.nam"
```

### Example scenarios

The [`MODFLOW-USGS/modflow6-examples`](https://github.com/MODFLOW-USGS/modflow6-examples) repository contains a collection of scenarios, each consisting of 1 or more models. The `example_scenario` fixture is a `Tuple[str, List[Path]]`. The first item is the name of the scenario. The second item is a list of namefile `Path`s, ordered alphabetically by name. Model naming conventions are as follows:
The [`MODFLOW-USGS/modflow6-examples`](https://github.com/MODFLOW-USGS/modflow6-examples) repository contains a collection of example scenarios, each with 1 or more models. The `example_scenario` fixture is a `Tuple[str, List[Path]]`. The first item is the name of the scenario. The second item is a list of MODFLOW 6 namefile `Path`s, ordered alphabetically by name, with models generally named as follows:

- groundwater flow models begin with prefix `gwf*`
- groundwater flow models begin with `gwf*`
- transport models begin with `gwt*`

Ordering as above permits models to be run directly in the order provided, with transport models potentially consuming the outputs of flow models. A straightforward pattern is to loop over models and run each in a subdirectory of the same top-level working directory.
This naming permits models to be run in the order provided, with transport models potentially consuming the outputs of flow models. One possible pattern is to loop over models and run each in a subdirectory of the same top-level working directory.

```python
def test_example_scenario(tmp_path, example_scenario):
Expand All @@ -90,11 +120,13 @@ def test_example_scenario(tmp_path, example_scenario):
# ...
```

**Note**: example models must first be built by running the `ci_build_files.py` script in `modflow6-examples/etc` before running tests using the `example_scenario` fixture. See the [install docs](https://modflow-devtools.readthedocs.io/en/latest/md/install.html) for more info.

### Utility functions

Model-loading fixtures use a set of utility functions to find and enumerate models. These functions can be imported from `modflow_devtools.misc` for use in other contexts:

- `get_model_paths()`
- `get_namefile_paths()`

See this project's test suite for usage examples.
These functions are used internally in a `pytest_generate_tests` hook to implement the above model-parametrization fixtures. See `fixtures.py` and/or this project's test suite for usage examples.
54 changes: 49 additions & 5 deletions docs/md/install.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Installing `modflow-devtools`
# Installation

## Official package
## Installing `modflow-devtools` from PyPI

The `modflow-devtools` package is [available on PyPi](https://pypi.org/project/modflow-devtools/) and can be installed with `pip`:

```shell
pip install modflow-devtools
```

## Development version
## Installing `modflow-devtools` from source

To set up a `modflow-devtools` development environment, first clone the repository:

Expand All @@ -28,5 +28,49 @@ pip install ".[lint, test, docs]"
Fixtures provided by `modflow-devtools` can be imported into a `pytest` test suite by adding the following to the consuming project's top-level `conftest.py` file:

```python
pytest_plugins = ["modflow_devtools"]
```
pytest_plugins = ["modflow_devtools.fixtures"]
```

## Installing external model repositories

`modflow-devtools` provides fixtures to load models from external repositories:

- [`MODFLOW-USGS/modflow6-examples`](https://github.com/MODFLOW-USGS/modflow6-examples)
- [`MODFLOW-USGS/modflow6-testmodels`](https://github.com/MODFLOW-USGS/modflow6-testmodels)
- [`MODFLOW-USGS/modflow6-largetestmodels`](https://github.com/MODFLOW-USGS/modflow6-largetestmodels)

By default, these fixtures expect model repositories to live next to (i.e. in the same parent directory as) the consuming project repository. If the repos are somewhere else, you can set the `REPOS_PATH` environment variable to point to their parent directory.

**Note:** a convenient way to persist environment variables needed for tests is to store them in a `.env` file in the `autotest` folder. Each variable should be defined on a separate line, with format `KEY=VALUE`. The `pytest-dotenv` plugin will then automatically load any variables found in this file into the test process' environment.

### Installing test models

The test model repos can simply be cloned &mdash; ideally, into the parent directory of the `modflow6` repository, so that repositories live side-by-side:

```shell
git clone MODFLOW-USGS/modflow6-testmodels
git clone MODFLOW-USGS/modflow6-largetestmodels
```

### Installing example models

First clone the example models repo:

```shell
git clone MODFLOW-USGS/modflow6-examples
```

The example models require some setup after cloning. Some extra Python dependencies are required to build the examples:

```shell
cd modflow6-examples/etc
pip install -r requirements.pip.txt
```

Then, still from the `etc` folder, run:

```shell
python ci_build_files.py
```

This will build the examples for subsequent use by the tests.
4 changes: 2 additions & 2 deletions modflow_devtools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__author__ = "Joseph D. Hughes"
__date__ = "Jan 04, 2023"
__version__ = "0.1.2"
__date__ = "Jan 07, 2023"
__version__ = "0.1.3"
__maintainer__ = "Joseph D. Hughes"
__email__ = "[email protected]"
__status__ = "Production"
Expand Down
Loading

0 comments on commit 614b6ee

Please sign in to comment.