diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e08df536e..b1d27c684 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,3 +119,47 @@ jobs: cd docs echo 'nb_execution_mode = "off"' >> conf.py make linkcheck + + test-optional-deps: + # Ensures can install with various combinations of optional dependencies, + # and that some corresponding tests pass or crash appropriately. + # Just a single machine and single Python version should be good enough, + # the goal here is to spot-check that optional deps work as expected, + # not to run an exhaustive set of tests with each combination of deps. + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v7 + + - name: setup-python + uses: actions/setup-python@v7 + with: + python-version: "3.13" + + - name: no optional deps + run: | + python -m venv "$RUNNER_TEMP/venv-none" + source "$RUNNER_TEMP/venv-none/bin/activate" + python -m pip install "." pytest + python -m pytest test_optional_deps/test_installed_with_no_opts.py + + - name: geo only + run: | + python -m venv "$RUNNER_TEMP/venv-geo" + source "$RUNNER_TEMP/venv-geo/bin/activate" + python -m pip install ".[geo]" pytest + python -m pytest test_optional_deps/test_installed_with_geo.py + + - name: viz only + run: | + python -m venv "$RUNNER_TEMP/venv-viz" + source "$RUNNER_TEMP/venv-viz/bin/activate" + python -m pip install ".[viz]" pytest + python -m pytest test_optional_deps/test_installed_with_viz.py + + - name: viz and geo + run: | + python -m venv "$RUNNER_TEMP/venv-viz_and_geo" + source "$RUNNER_TEMP/venv-viz_and_geo/bin/activate" + python -m pip install ".[viz,geo]" pytest + python -m pytest test_optional_deps/test_installed_with_viz_and_geo.py diff --git a/pyproject.toml b/pyproject.toml index 166ba8657..5503429f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,3 +98,6 @@ known-first-party = ["uxarray"] [tool.ruff.format] docstring-code-format = true + +[tool.pytest.ini_options] +testpaths = ["tests"] # (intentionally excludes test_optional_deps) diff --git a/test_optional_deps/_optional_deps_helpers.py b/test_optional_deps/_optional_deps_helpers.py new file mode 100644 index 000000000..186e2452b --- /dev/null +++ b/test_optional_deps/_optional_deps_helpers.py @@ -0,0 +1,58 @@ +""" +File Purpose: defines helper functions to be used for testing optional dependencies. + +The goal here is to spot-check that optional deps work as expected, +not to run an exhaustive set of tests with each combination of deps. +""" + + +def check_requires_no_opts(): + """run some checks which should not require any optional dependencies""" + import uxarray as ux + + uxds = ux.tutorial.open_dataset("quad-hexagon") + uxds.compute() + + +def check_requires_only_viz(): + """run some checks which should require viz optional dependencies, + but not any other optional dependencies. + """ + import uxarray as ux + + arr = ux.tutorial.open_dataset("quad-hexagon")["t2m"] + plot_obj = arr.plot.points() # points() doesn't need geo projection details. + + # actually try to render the plot, too: + import holoviews as hv + + renderer = hv.renderer("matplotlib") + renderer.get_plot(plot_obj) + + +def check_requires_only_geo(): + """run some checks which should require geo optional dependencies, + but not any other optional dependencies. + """ + import uxarray as ux + + arr = ux.tutorial.open_dataset("quad-hexagon")["t2m"] + arr.to_geodataframe() + + ux.Grid.from_healpix(zoom=1) + + +def check_requires_viz_and_geo(): + """run some checks which should require both viz and geo optional dependencies, + but not any other optional dependencies. + """ + import uxarray as ux + + arr = ux.tutorial.open_dataset("quad-hexagon")["t2m"] + plot_obj = arr.plot.polygons() # polygons() uses geo projection details. + + # actually try to render the plot, too: + import holoviews as hv + + renderer = hv.renderer("matplotlib") + renderer.get_plot(plot_obj) diff --git a/test_optional_deps/test_installed_with_geo.py b/test_optional_deps/test_installed_with_geo.py new file mode 100644 index 000000000..5895dbe26 --- /dev/null +++ b/test_optional_deps/test_installed_with_geo.py @@ -0,0 +1,35 @@ +""" +Purpose: test expected behaviors when installed with only geo optional dependency. +Tests should all pass if and only if installed accordingly, i.e. something like: + pip install ".[geo]" +""" + +import pytest +from _optional_deps_helpers import ( + check_requires_no_opts, + check_requires_only_geo, + check_requires_only_viz, + check_requires_viz_and_geo, +) + + +def test_check_requires_no_opts(): + """ensure success for checks which should not require any optional dependencies""" + check_requires_no_opts() + + +def test_check_requires_only_viz(): + """ensure failure for checks which should require viz optional dependencies""" + with pytest.raises(ImportError): + check_requires_only_viz() + + +def test_check_requires_only_geo(): + """ensure success for checks which should require geo optional dependencies""" + check_requires_only_geo() + + +def test_check_requires_viz_and_geo(): + """ensure failure for checks which should require both viz and geo optional dependencies""" + with pytest.raises(ImportError): + check_requires_viz_and_geo() diff --git a/test_optional_deps/test_installed_with_no_opts.py b/test_optional_deps/test_installed_with_no_opts.py new file mode 100644 index 000000000..4408078f6 --- /dev/null +++ b/test_optional_deps/test_installed_with_no_opts.py @@ -0,0 +1,36 @@ +""" +Purpose: test expected behaviors when installed without any optional dependencies. +Tests should all pass if and only if installed accordingly, i.e. something like: + pip install "." +""" + +import pytest +from _optional_deps_helpers import ( + check_requires_no_opts, + check_requires_only_geo, + check_requires_only_viz, + check_requires_viz_and_geo, +) + + +def test_check_requires_no_opts(): + """ensure success for checks which should not require any optional dependencies""" + check_requires_no_opts() + + +def test_check_requires_only_viz(): + """ensure failure for checks which should require viz optional dependencies""" + with pytest.raises(ImportError): + check_requires_only_viz() + + +def test_check_requires_only_geo(): + """ensure failure for checks which should require geo optional dependencies""" + with pytest.raises(ImportError): + check_requires_only_geo() + + +def test_check_requires_viz_and_geo(): + """ensure failure for checks which should require both viz and geo optional dependencies""" + with pytest.raises(ImportError): + check_requires_viz_and_geo() diff --git a/test_optional_deps/test_installed_with_viz.py b/test_optional_deps/test_installed_with_viz.py new file mode 100644 index 000000000..fd551169d --- /dev/null +++ b/test_optional_deps/test_installed_with_viz.py @@ -0,0 +1,35 @@ +""" +Purpose: test expected behaviors when installed with only viz optional dependency. +Tests should all pass if and only if installed accordingly, i.e. something like: + pip install ".[viz]" +""" + +import pytest +from _optional_deps_helpers import ( + check_requires_no_opts, + check_requires_only_geo, + check_requires_only_viz, + check_requires_viz_and_geo, +) + + +def test_check_requires_no_opts(): + """ensure success for checks which should not require any optional dependencies""" + check_requires_no_opts() + + +def test_check_requires_only_viz(): + """ensure success for checks which should require viz optional dependencies""" + check_requires_only_viz() + + +def test_check_requires_only_geo(): + """ensure failure for checks which should require geo optional dependencies""" + with pytest.raises(ImportError): + check_requires_only_geo() + + +def test_check_requires_viz_and_geo(): + """ensure failure for checks which should require both viz and geo optional dependencies""" + with pytest.raises(ImportError): + check_requires_viz_and_geo() diff --git a/test_optional_deps/test_installed_with_viz_and_geo.py b/test_optional_deps/test_installed_with_viz_and_geo.py new file mode 100644 index 000000000..b2cabc398 --- /dev/null +++ b/test_optional_deps/test_installed_with_viz_and_geo.py @@ -0,0 +1,32 @@ +""" +Purpose: test expected behaviors when installed with geo and viz optional dependencies. +Tests should all pass if and only if installed accordingly, i.e. something like: + pip install ".[geo,viz]" +""" + +from _optional_deps_helpers import ( + check_requires_no_opts, + check_requires_only_geo, + check_requires_only_viz, + check_requires_viz_and_geo, +) + + +def test_check_requires_no_opts(): + """ensure success for checks which should not require any optional dependencies""" + check_requires_no_opts() + + +def test_check_requires_only_viz(): + """ensure success for checks which should require viz optional dependencies""" + check_requires_only_viz() + + +def test_check_requires_only_geo(): + """ensure success for checks which should require geo optional dependencies""" + check_requires_only_geo() + + +def test_check_requires_viz_and_geo(): + """ensure success for checks which should require both viz and geo optional dependencies""" + check_requires_viz_and_geo()