Skip to content

Commit

Permalink
ci: test without optional dependencies (modflowpy#1918)
Browse files Browse the repository at this point in the history
* add nightly matrix to test with some and no optional dependencies
* randomly select 3 optional dependencies to test without per day
* check crs-related deprecation warnings only if pyproj available
* fix markers in test_grid.py and test_export.py
* use strict=True with has_pkg()
  • Loading branch information
wpbonelli authored Aug 16, 2023
1 parent ed3a0cd commit 0418f85
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 30 deletions.
35 changes: 20 additions & 15 deletions .github/workflows/commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ jobs:
run:
shell: bash
timeout-minutes: 10
env:
PYTHON_VERSION: 3.8

steps:
- name: Checkout repo
Expand All @@ -114,33 +116,37 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: pyproject.toml

- name: Install Python dependencies
run: |
pip install --upgrade pip
pip install .
pip install ".[test, optional]"
pip install ".[test,optional]"
- name: Install Modflow executables
uses: modflowpy/install-modflow-action@v1

- name: Run smoke tests
working-directory: ./autotest
run: |
pytest -v -n=auto --smoke --durations=0 --keep-failed=.failed

- name: Smoke test
working-directory: autotest
run: pytest -v -n=auto --smoke --cov=flopy --cov-report=xml --durations=0 --keep-failed=.failed
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload failed test outputs
uses: actions/upload-artifact@v3
if: failure()
with:
name: failed-smoke-${{ matrix.os }}-${{ matrix.python-version }}
path: |
./autotest/.failed/**
name: failed-smoke-${{ runner.os }}-${{ env.PYTHON_VERSION }}
path: ./autotest/.failed/**

- name: Upload coverage
if: github.repository_owner == 'modflowpy' && (github.event_name == 'push' || github.event_name == 'pull_request')
uses: codecov/codecov-action@v3
with:
files: ./autotest/coverage.xml

test:
name: Test
Expand Down Expand Up @@ -220,7 +226,7 @@ jobs:
if: runner.os != 'Windows'
working-directory: ./autotest
run: |
pytest -v -m="not example and not regression" -n=auto --cov=flopy --cov-report=xml --durations=0 --keep-failed=.failed --dist loadfile
pytest -v -m="not example and not regression" -n=auto --cov=flopy --cov-append --cov-report=xml --durations=0 --keep-failed=.failed --dist loadfile
coverage report
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -230,7 +236,7 @@ jobs:
shell: bash -l {0}
working-directory: ./autotest
run: |
pytest -v -m="not example and not regression" -n=auto --cov=flopy --cov-report=xml --durations=0 --keep-failed=.failed --dist loadfile
pytest -v -m="not example and not regression" -n=auto --cov=flopy --cov-append --cov-report=xml --durations=0 --keep-failed=.failed --dist loadfile
coverage report
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -244,8 +250,7 @@ jobs:
./autotest/.failed/**
- name: Upload coverage
if:
github.repository_owner == 'modflowpy' && (github.event_name == 'push' || github.event_name == 'pull_request')
if: github.repository_owner == 'modflowpy' && (github.event_name == 'push' || github.event_name == 'pull_request')
uses: codecov/codecov-action@v3
with:
files: ./autotest/coverage.xml
75 changes: 75 additions & 0 deletions .github/workflows/optional.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: FloPy optional dependency testing
on:
schedule:
- cron: '0 8 * * *' # run at 8 AM UTC (12 am PST)
jobs:
test:
name: Test
runs-on: ubuntu-latest
defaults:
run:
shell: bash
timeout-minutes: 10
env:
PYTHON_VERSION: 3.8
strategy:
fail-fast: false
matrix:
optdeps:
# - "all optional dependencies"
- "no optional dependencies"
- "some optional dependencies"

steps:
- name: Checkout repo
uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: pyproject.toml

- name: Install Python dependencies
run: |
pip install --upgrade pip
pip install .
pip install ".[test]"
# Install optional dependencies according to matrix.optdeps.
# If matrix.optdeps is "some" remove 3 optional dependencies
# selected randomly using the current date as the seed.
if [[ ! "${{ matrix.optdeps }}" == *"no"* ]]; then
pip install ".[optional]"
fi
if [[ "${{ matrix.optdeps }}" == *"some"* ]]; then
deps=$(sed '/optional =/,/]/!d' pyproject.toml | sed -e '1d;$d' -e 's/\"//g' -e 's/,//g' | tr -d ' ' | cut -f 1 -d ';')
rmvd=$(echo $deps | tr ' ' '\n' | shuf --random-source <(yes date +%d.%m.%y) | head -n 3)
echo "Removing optional dependencies: $rmvd" >> removed_dependencies.txt
cat removed_dependencies.txt
pip uninstall --yes $rmvd
fi
- name: Upload removed dependencies log
uses: actions/upload-artifact@v3
with:
name: smoke-test-removed-dependencies
path: ./removed_dependencies.txt

- name: Install Modflow executables
uses: modflowpy/install-modflow-action@v1

- name: Smoke test (${{ matrix.optdeps }})
working-directory: autotest
run: pytest -v -n=auto --smoke --cov=flopy --cov-report=xml --durations=0 --keep-failed=.failed
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload failed test outputs
uses: actions/upload-artifact@v3
if: failure()
with:
name: failed-smoke-${{ runner.os }}-${{ env.PYTHON_VERSION }}
path: ./autotest/.failed/**

12 changes: 7 additions & 5 deletions autotest/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
from flopy.utils.geometry import Polygon


HAS_PYPROJ = has_pkg("pyproj", strict=True)
if HAS_PYPROJ:
import pyproj


def namfiles() -> List[Path]:
mf2005_path = get_example_data_path() / "mf2005_test"
return list(mf2005_path.rglob("*.nam"))
Expand Down Expand Up @@ -330,10 +335,7 @@ def test_write_gridlines_shapefile(function_tmpdir):

for suffix in [".dbf", ".shp", ".shx"]:
assert outshp.with_suffix(suffix).exists()
if has_pkg("pyproj"):
assert outshp.with_suffix(".prj").exists()
else:
assert not outshp.with_suffix(".prj").exists()
assert outshp.with_suffix(".prj").exists() == HAS_PYPROJ

with shapefile.Reader(str(outshp)) as sf:
assert sf.shapeType == shapefile.POLYLINE
Expand Down Expand Up @@ -1378,7 +1380,7 @@ def test_vtk_unstructured(function_tmpdir, example_data_path):
assert np.allclose(np.ravel(top), top2), "Field data not properly written"


@requires_pkg("pyvista")
@requires_pkg("vtk", "pyvista")
def test_vtk_to_pyvista(function_tmpdir, example_data_path):
from autotest.test_mp7_cases import Mp7Cases

Expand Down
26 changes: 19 additions & 7 deletions autotest/test_grid.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import re
import warnings
from contextlib import nullcontext
from warnings import warn

import matplotlib
Expand All @@ -22,7 +23,7 @@
from flopy.utils.triangle import Triangle
from flopy.utils.voronoi import VoronoiGrid

HAS_PYPROJ = has_pkg("pyproj")
HAS_PYPROJ = has_pkg("pyproj", strict=True)
if HAS_PYPROJ:
import pyproj

Expand Down Expand Up @@ -594,9 +595,14 @@ def do_checks(g):
do_checks(UnstructuredGrid(**d, crs=crs))
do_checks(VertexGrid(vertices=d["vertices"], crs=crs))

# only check deprecations if pyproj is available
pyproj_avail_context = (
pytest.deprecated_call() if HAS_PYPROJ else nullcontext()
)

# test deprecated 'epsg' parameter
if isinstance(crs, int):
with pytest.deprecated_call():
with pyproj_avail_context:
do_checks(StructuredGrid(delr=delr, delc=delc, epsg=crs))

if HAS_PYPROJ and crs == 26916:
Expand All @@ -612,14 +618,14 @@ def do_checks(g):
do_checks(StructuredGrid(delr=delr, delc=delc, prjfile=prjfile))

# test deprecated 'prj' parameter
with pytest.deprecated_call():
with pyproj_avail_context:
do_checks(StructuredGrid(delr=delr, delc=delc, prj=prjfile))

# test deprecated 'proj4' parameter
with warnings.catch_warnings():
warnings.simplefilter("ignore") # pyproj warning about conversion
proj4 = crs_obj.to_proj4()
with pytest.deprecated_call():
with pyproj_avail_context:
do_checks(StructuredGrid(delr=delr, delc=delc, proj4=proj4))


Expand Down Expand Up @@ -675,9 +681,14 @@ def do_checks(g, *, exp_srs=expected_srs, exp_epsg=expected_epsg):
sg.set_coord_info(crs=26915, merge_coord_info=False)
do_checks(sg, exp_srs="EPSG:26915", exp_epsg=26915)

# only check deprecations if pyproj is available
pyproj_avail_context = (
pytest.deprecated_call() if HAS_PYPROJ else nullcontext()
)

# test deprecated 'epsg' parameter
if isinstance(crs, int):
with pytest.deprecated_call():
with pyproj_avail_context:
sg.set_coord_info(epsg=crs)
do_checks(sg)

Expand Down Expand Up @@ -827,8 +838,9 @@ def test_grid_crs_exceptions():

# test non-existing file
not_a_file = "not-a-file"
with pytest.raises(FileNotFoundError):
StructuredGrid(delr=delr, delc=delc, prjfile=not_a_file)
if HAS_PYPROJ:
with pytest.raises(FileNotFoundError):
StructuredGrid(delr=delr, delc=delc, prjfile=not_a_file)
# note "sg.prjfile = not_a_file" intentionally does not raise anything

# test unhandled keyword
Expand Down
1 change: 1 addition & 0 deletions autotest/test_gridgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from flopy.utils.gridgen import Gridgen


@requires_exe("gridgen")
def test_ctor_accepts_path_or_string(function_tmpdir):
grid = GridCases().structured_small()

Expand Down
4 changes: 2 additions & 2 deletions autotest/test_gridintersect.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from flopy.utils.gridintersect import GridIntersect
from flopy.utils.triangle import Triangle

if has_pkg("shapely"):
if has_pkg("shapely", strict=True):
from shapely.geometry import (
LineString,
MultiLineString,
Expand Down Expand Up @@ -1221,7 +1221,7 @@ def test_polygon_offset_rot_structured_grid_shapely(rtree):
# %% test rasters


@requires_pkg("rasterstats", "scipy")
@requires_pkg("rasterstats", "scipy", "shapely")
def test_rasters(example_data_path):
ws = example_data_path / "options"
raster_name = "dem.img"
Expand Down
3 changes: 2 additions & 1 deletion autotest/test_mf6.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import numpy as np
import pytest
from modflow_devtools.markers import requires_exe
from modflow_devtools.markers import requires_exe, requires_pkg
from modflow_devtools.misc import set_dir

import flopy
Expand Down Expand Up @@ -637,6 +637,7 @@ def test_binary_write(function_tmpdir, layered):


@requires_exe("mf6")
@requires_pkg("shapely", "scipy")
@pytest.mark.parametrize("layered", [True, False])
def test_vor_binary_write(function_tmpdir, layered):
# build voronoi grid
Expand Down
1 change: 1 addition & 0 deletions autotest/test_model_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ def test_metis_splitting_with_lak_sfr(function_tmpdir):


@requires_exe("mf6")
@requires_pkg("pymetis")
def test_save_load_node_mapping(function_tmpdir):
sim_path = get_example_data_path() / "mf6-freyberg"
new_sim_path = function_tmpdir / "mf6-freyberg/split_model"
Expand Down

0 comments on commit 0418f85

Please sign in to comment.