Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Template for unit tests #187

Merged
merged 26 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e2a30df
start unit tests
RondeauG Apr 13, 2023
08614f0
Merge branch 'main' into unittests
RondeauG May 9, 2023
8fc3ca5
use test_timeseries
RondeauG May 9, 2023
795aaf5
cleanup and reqs
RondeauG May 9, 2023
5b1bf90
Merge branch 'main' into unittests
RondeauG May 10, 2023
2b58541
proper climatological_mean tests and small fixes
RondeauG May 10, 2023
8710457
remove outdated comments
RondeauG May 10, 2023
df6e569
manage deltas with no time
RondeauG May 11, 2023
159880f
convert_calendar
RondeauG May 11, 2023
dac02be
test Datetime360Day
RondeauG May 11, 2023
163ad8a
test more cftime calendars
RondeauG May 11, 2023
2be785d
github is eating my changes :(
RondeauG May 11, 2023
61c3b11
Use parametrize - assert for scalars
aulemahal May 26, 2023
7ebe2b0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 26, 2023
7614ce1
changes from code review, fixed QS-DEC test series
RondeauG May 31, 2023
0593e29
Merge branch 'main' into unittests
RondeauG May 31, 2023
bb201b7
Merge branch 'main' into unittests
Zeitsperre Jun 1, 2023
e0de794
adjust environment config and test logic
Zeitsperre Jun 1, 2023
b2262b5
update HISTORY.rst
Zeitsperre Jun 1, 2023
9c9dcb9
revert bad change
Zeitsperre Jun 1, 2023
f457e8a
add coveralls actions to workflows, update deprecated provision action
Zeitsperre Jun 1, 2023
5468e83
typo fix
Zeitsperre Jun 1, 2023
1581b1b
more interesting debug information
Zeitsperre Jun 1, 2023
9b070c7
update history and add coveralls badge
Zeitsperre Jun 1, 2023
95fbd29
reverse change to processing_level
RondeauG Jun 5, 2023
02ee10a
Merge remote-tracking branch 'origin/unittests' into unittests
RondeauG Jun 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 40 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
jobs:
black:
name: Code Style Compliance
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
defaults:
run:
shell: bash -l {0}
Expand All @@ -35,39 +35,57 @@ jobs:
tox -e black

testing:
name: Smoke Test with Python${{ matrix.python-version }} (${{ matrix.tox-build }})
name: Smoke Test with Python${{ matrix.python-version }}
needs: black
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- python-version: "3.9"
tox-build: "py39"
# tox-build: "py39"
- python-version: "3.10"
tox-build: "py310"
# tox-build: "py310"
defaults:
run:
shell: bash -l {0}
steps:
- uses: actions/checkout@v3
- name: Setup Conda (Micromamba) with Python ${{ matrix.python-version }}
uses: mamba-org/provision-with-micromamba@main
uses: mamba-org/setup-micromamba@v1
with:
cache-downloads: true
environment-file: environment.yml
extra-specs: |
create-args: >-
coveralls
mamba
python=${{ matrix.python-version }}
pytest
pytest-cov
xdoctest
- name: Conda and mamba versions
- name: Conda and Mamba versions
run: |
mamba --version
echo "micromamba $(micromamba --version)"
- name: Install xscen
run: |
pip install --editable .
- name: Check versions
run: |
conda list
pip check
- name: Test with pytest
run: |
pytest --cov xscen
- name: Report coverage
run: |
coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_FLAG_NAME: run-Python${{ matrix.python-version }}
COVERALLS_PARALLEL: true
COVERALLS_SERVICE_NAME: github

# - name: Install tox-current-env
# run: |
# pip install tox tox-conda tox-current-env
Expand All @@ -77,3 +95,17 @@ jobs:
# env:
# CONDA_EXE: mamba
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

finish:
needs:
- testing
runs-on: ubuntu-latest
container: python:3-slim
steps:
- name: Coveralls Finished
run: |
pip install --upgrade coveralls
coveralls --finish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_SERVICE_NAME: github
7 changes: 5 additions & 2 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ History

v0.7.0 (unreleased)
-------------------
Contributors to this version: Gabriel Rondeau-Genesse (:user:`RondeauG`).
Contributors to this version: Pascal Bourgault (:user:`aulemahal`), Gabriel Rondeau-Genesse (:user:`RondeauG`), Trevor James Smith (:user:`Zeitsperre`).

Announcements
^^^^^^^^^^^^^
* Dropped support for Python 3.8. (:pull:`199`).

New features and enhancements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* N/A
* `xscen` now tracks code coverage using `coveralls <https://coveralls.io/>`_. (:pull:`187`).

Breaking changes
^^^^^^^^^^^^^^^^
Expand All @@ -21,11 +21,14 @@ Breaking changes
Bug fixes
^^^^^^^^^
* Fix bug in ``unstack_dates`` with seasonal climatological mean. (:issue:`202`, :pull:`202`).
* Added NotImplemented errors when trying to call `climatological_mean` and `compute_deltas` with daily data. (:pull:`187`).

Internal changes
^^^^^^^^^^^^^^^^
* Removed the pin on xarray's version. (:issue:`175`, :pull:`199`).
* Updated ReadTheDocs configuration to prevent ``--eager`` installation of xscen (:pull:`209`).
* Implemented a template to be used for unit tests. (:pull:`187`).
* Updated GitHub Actions to remove deprecation warnings. (:pull:`187`).

v0.6.0 (2023-05-04)
-------------------
Expand Down
7 changes: 6 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
xscen |logo|
============

|pypi| |status| |build| |docs| |black| |pre-commit| |versions|
|pypi| |status| |build| |coverage| |docs| |black| |pre-commit| |versions|

A climate change scenario-building analysis framework, built with Intake-esm catalogs and xarray-based packages such as xclim and xESMF.

Expand Down Expand Up @@ -32,11 +32,16 @@ This package was created with Cookiecutter_ and the `Ouranosinc/cookiecutter-pyp

.. |logo| image:: https://raw.githubusercontent.com/Ouranosinc/xscen/main/docs/_static/_images/xscen-logo-small.png
:target: https://github.com/Ouranosinc/xscen
:alt: xscen Logo

.. |build| image:: https://github.com/Ouranosinc/xscen/actions/workflows/main.yml/badge.svg
:target: https://github.com/Ouranosinc/xscen/actions/workflows/main.yml
:alt: Build Status

.. |coverage| image:: https://coveralls.io/repos/github/Ouranosinc/xscen/badge.svg
:target: https://coveralls.io/github/Ouranosinc/xscen
:alt: Code Coverage

.. |pypi| image:: https://img.shields.io/pypi/v/xscen.svg
:target: https://pypi.python.org/pypi/xscen
:alt: Python Package Index Build
Expand Down
3 changes: 2 additions & 1 deletion environment-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ dependencies:
- rechunker
- shapely
- xarray
- xclim >=0.37
- xclim >=0.43.0
- xesmf >=0.7
- zarr
# Opt
- nc-time-axis >=1.3.1
- pyarrow >=1.0.0 # For lighter in-memory catalogs
# Dev
- bumpversion
- coveralls
- ipykernel
- ipython
- jupyter_client
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ dependencies:
- rechunker
- shapely
- xarray
- xclim >=0.37
- xclim >=0.43.0
- xesmf >=0.7
- zarr
# Opt
Expand Down
140 changes: 140 additions & 0 deletions tests/test_aggregate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import numpy as np
import pytest
import xarray as xr
from xclim.testing.helpers import test_timeseries as timeseries

import xscen as xs


class TestClimatologicalMean:
def test_daily(self):
ds = timeseries(
np.tile(np.arange(1, 13), 3),
variable="tas",
start="2001-01-01",
freq="D",
as_dataset=True,
)
with pytest.raises(NotImplementedError):
xs.climatological_mean(ds)

@pytest.mark.parametrize("xrfreq", ["MS", "AS-JAN"])
def test_all_default(self, xrfreq):
o = 12 if xrfreq == "MS" else 1

ds = timeseries(
np.tile(np.arange(1, o + 1), 30),
variable="tas",
start="2001-01-01",
freq=xrfreq,
as_dataset=True,
)
out = xs.climatological_mean(ds)

# Test output values
np.testing.assert_array_equal(out.tas, np.arange(1, o + 1))
assert len(out.time) == (o * len(np.unique(out.horizon.values)))
np.testing.assert_array_equal(out.time[0], ds.time[0])
assert (out.horizon == "2001-2030").all()
# Test metadata
assert (
out.tas.attrs["description"]
== f"30-year mean of {ds.tas.attrs['description']}"
)
assert (
"30-year rolling average (non-centered) with a minimum of 30 years of data"
in out.tas.attrs["history"]
)
assert out.attrs["cat:processing_level"] == "climatology"

@pytest.mark.parametrize("xrfreq", ["MS", "AS-JAN"])
def test_options(self, xrfreq):
o = 12 if xrfreq == "MS" else 1

ds = timeseries(
np.tile(np.arange(1, o + 1), 30),
variable="tas",
start="2001-01-01",
freq=xrfreq,
as_dataset=True,
)
out = xs.climatological_mean(ds, window=15, interval=5, to_level="for_testing")

# Test output values
np.testing.assert_array_equal(
out.tas,
np.tile(np.arange(1, o + 1), len(np.unique(out.horizon.values))),
)
assert len(out.time) == (o * len(np.unique(out.horizon.values)))
np.testing.assert_array_equal(out.time[0], ds.time[0])
assert {"2001-2015", "2006-2020", "2011-2025", "2016-2030"}.issubset(
out.horizon.values
)
# Test metadata
assert (
out.tas.attrs["description"]
== f"15-year mean of {ds.tas.attrs['description']}"
)
assert (
"15-year rolling average (non-centered) with a minimum of 15 years of data"
in out.tas.attrs["history"]
)
assert out.attrs["cat:processing_level"] == "for_testing"

def test_minperiods(self):
ds = timeseries(
np.tile(np.arange(1, 5), 30),
variable="tas",
start="2001-03-01",
freq="QS-DEC",
as_dataset=True,
)
ds = ds.where(ds["time"].dt.strftime("%Y-%m-%d") != "2030-12-01")

out = xs.climatological_mean(ds, window=30)
assert all(np.isreal(out.tas))
assert len(out.time) == 4
np.testing.assert_array_equal(out.tas, np.arange(1, 5))

out = xs.climatological_mean(ds, window=30, min_periods=30)
assert np.sum(np.isnan(out.tas)) == 1

with pytest.raises(ValueError):
xs.climatological_mean(ds, window=5, min_periods=6)

def test_periods(self):
ds1 = timeseries(
np.tile(np.arange(1, 2), 10),
variable="tas",
start="2001-01-01",
freq="AS-JAN",
as_dataset=True,
)
ds2 = timeseries(
np.tile(np.arange(1, 2), 10),
variable="tas",
start="2021-01-01",
freq="AS-JAN",
as_dataset=True,
)

ds = xr.concat([ds1, ds2], dim="time")
with pytest.raises(ValueError):
xs.climatological_mean(ds)

out = xs.climatological_mean(ds, periods=[["2001", "2010"], ["2021", "2030"]])
assert len(out.time) == 2
assert {"2001-2010", "2021-2030"}.issubset(out.horizon.values)

@pytest.mark.parametrize("cal", ["proleptic_gregorian", "noleap", "360_day"])
def test_calendars(self, cal):
ds = timeseries(
np.tile(np.arange(1, 2), 30),
variable="tas",
start="2001-01-01",
freq="AS-JAN",
as_dataset=True,
)

out = xs.climatological_mean(ds.convert_calendar(cal, align_on="date"))
assert out.time.dt.calendar == cal
21 changes: 19 additions & 2 deletions xscen/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ def climatological_mean(
Returns a Dataset of the climatological mean

"""
if xr.infer_freq(ds.time) == "D":
raise NotImplementedError(
"xs.climatological_mean does not currently support daily data."
)

# there is one less occurrence when a period crosses years
freq_across_year = [
f"{f}-{mon}"
Expand Down Expand Up @@ -100,9 +105,17 @@ def climatological_mean(

window = window or int(periods[0][1]) - int(periods[0][0]) + 1

if ds.attrs.get("cat:xrfreq") in freq_across_year and min_periods is None:
if (
any(
x in freq_across_year
for x in [ds.attrs.get("cat:xrfreq"), xr.infer_freq(ds.time)]
)
and min_periods is None
):
min_periods = window - 1
min_periods = min_periods or window
if min_periods > window:
raise ValueError("'min_periods' should be smaller or equal to 'window'")

for period in periods:
# Rolling average
Expand Down Expand Up @@ -174,7 +187,6 @@ def climatological_mean(
else new_history
)
ds_rolling[vv].attrs["history"] = history

if to_level is not None:
ds_rolling.attrs["cat:processing_level"] = to_level

Expand Down Expand Up @@ -234,6 +246,11 @@ def compute_deltas(
)

if "time" in ds:
if xr.infer_freq(ds.time) == "D":
raise NotImplementedError(
"xs.climatological_mean does not currently support daily data."
)

# Remove references to 'year' in REF
ind = pd.MultiIndex.from_arrays(
[ref.time.dt.month.values, ref.time.dt.day.values], names=["month", "day"]
Expand Down