Skip to content

Commit

Permalink
Use latest pytest and xdoctest (#1632)
Browse files Browse the repository at this point in the history
### What kind of change does this PR introduce?

* Unpins the `pytest` version now that `xdoctest` supports it.
* Pins to the latest `xdoctest` supporting `pytest~=8.0`.

### Does this PR introduce a breaking change?

No.

### Other information:

FYI @RondeauG 

The newest `pytest` (v8.0.0) introduced some regressions for Windows
users. An issue is already up and a patch should arrive shortly
(pytest-dev/pytest#11895). Until then, use
`pytest<8.0` on Windows.

See also: pytest-dev/pytest#11969
  • Loading branch information
Zeitsperre authored Jul 31, 2024
2 parents 2dfe738 + 9552997 commit da4a79a
Show file tree
Hide file tree
Showing 16 changed files with 286 additions and 219 deletions.
21 changes: 11 additions & 10 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ jobs:
run: |
python -m tox -e lint
test-py39:
test-preliminary:
name: Python${{ matrix.python-version }} (${{ matrix.tox-env }}, ${{ matrix.os }})
needs: lint
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- tox-env: "py39"
- tox-env: "py39-coverage"
python-version: "3.9"
os: ubuntu-latest
steps:
Expand Down Expand Up @@ -123,32 +123,32 @@ jobs:
markers: -m 'not slow'
os: windows-latest
# macOS builds
- tox-env: py310-coverage-lmoments-doctest
- tox-env: py310-coverage-extras-lmoments
python-version: "3.10"
markers: -m 'not slow'
os: macos-latest
# Linux builds
- tox-env: py39-coverage-sbck-doctest
- tox-env: py39-coverage-offline-prefetch
python-version: "3.9"
markers: -m 'not slow'
markers: -m 'not slow and not requires_internet'
os: ubuntu-latest
- tox-env: py310-coverage-lmoments # No markers -- includes slow tests
python-version: "3.10"
os: ubuntu-latest
- tox-env: py311-coverage-sbck-extras
- tox-env: py311-coverage-extras-sbck
python-version: "3.11"
markers: -m 'not slow'
os: ubuntu-latest
- tox-env: py312-coverage-lmoments-doctest
- tox-env: py312-coverage-extras-lmoments
python-version: "3.12"
markers: -m 'not slow'
os: ubuntu-latest
# Specialized tests
- tox-env: notebooks
python-version: "3.10"
os: ubuntu-latest
- tox-env: offline-prefetch
python-version: "3.11"
markers: -m 'not slow and not requires_internet'
- tox-env: doctests
python-version: "3.12"
os: ubuntu-latest
steps:
- name: Harden Runner
Expand Down Expand Up @@ -269,6 +269,7 @@ jobs:

finish:
needs:
- test-preliminary
- test-pypi
- test-conda
runs-on: ubuntu-latest
Expand Down
6 changes: 4 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (:

New features and enhancements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``xclim.sdba.nbutils.quantile`` and its child functions are now faster. If the module `fastnanquantile` is installed, it is used as the backend for the computation of quantiles and yields even faster results. (:issue:`1255`, :pull:`1513`).
* ``xclim.sdba.nbutils.quantile`` and its child functions are now faster. If the module `fastnanquantile` is installed, it is used as the backend for the computation of quantiles and yields even faster results. This dependency is now listed in the `xclim[extras]` recipe. (:issue:`1255`, :pull:`1513`).
* New multivariate bias adjustment class `MBCn`, giving a faster and more accurate implementation of the 'MBCn' algorithm (:issue:`1551`, :pull:`1580`).
* `xclim` is now compatible with `pytest` versions `>=8.0.0`. (:pull:`1632`).

Bug fixes
^^^^^^^^^
Expand All @@ -18,7 +19,8 @@ Bug fixes

Internal changes
^^^^^^^^^^^^^^^^
* Changed french translation of "wet days" from "jours mouillés" to "jours pluvieux". (:issue:`1825`, :pull:`1826`).
* Changed the French translation of "wet days" from "jours mouillés" to "jours pluvieux". (:issue:`1825`, :pull:`1826`).
* In order to adapt to changes in `pytest`, the doctest fixtures have been split from the main testing suite and doctests are now run using ``$ python -c 'from xclim.testing.utils import run_doctests; run_doctests()'``. (:pull:`1632`).

CI changes
^^^^^^^^^^
Expand Down
4 changes: 2 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ dependencies:
- pre-commit >=3.7
- pybtex >=0.24.0
- pylint >=3.1
- pytest <8.0 # Pinned due to breakage with xdoctest. See: https://github.com/Erotemic/xdoctest/issues/151
- pytest >=8.0.0
- pytest-cov
- pytest-socket
- pytest-xdist >=3.2
Expand All @@ -77,7 +77,7 @@ dependencies:
- tox >=4.15.1
# - tox-conda # Will be added when a [email protected]+ compatible plugin is released.
- vulture # ==2.11 # The conda-forge version is out of date.
- xdoctest
- xdoctest >=1.1.5
- yamllint
- pip
- pip:
Expand Down
15 changes: 9 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ dependencies = [
"cftime>=1.4.1",
"click>=8.1",
"dask[array]>=2.6",
"filelock",
"jsonpickle",
"numba",
"numpy>=1.20.0,<2.0.0",
"packaging",
"pandas>=2.2",
"pint>=0.10,<0.24",
"platformdirs >=3.2",
Expand Down Expand Up @@ -78,7 +80,7 @@ dev = [
"pooch",
"pre-commit >=3.7",
"pylint >=3.2.4",
"pytest <8.0", # Pinned due to breakage with xdoctest. See: https://github.com/Erotemic/xdoctest/issues/151
"pytest >=8.0.0",
"pytest-cov",
"pytest-socket",
"pytest-xdist[psutil] >=3.2",
Expand All @@ -88,7 +90,7 @@ dev = [
# "tox-conda", # Will be added when a [email protected]+ compatible plugin is released.
"tox-gh >=1.3.1",
"vulture ==2.11",
"xdoctest",
"xdoctest >=1.1.5",
"yamllint ==1.35.1"
]
docs = [
Expand Down Expand Up @@ -166,7 +168,7 @@ ignore-words-list = "absolue,astroid,bloc,bui,callendar,degreee,environnement,ha

[tool.coverage.run]
relative_files = true
omit = ["tests/*.py"]
omit = ["tests/*.py", "src/xclim/testing/conftest.py"]

[tool.deptry]
extend_exclude = ["docs"]
Expand All @@ -180,7 +182,7 @@ pep621_dev_dependency_groups = ["all", "dev", "docs"]
[tool.deptry.per_rule_ignores]
DEP001 = ["SBCK"]
DEP002 = ["bottleneck", "pyarrow"]
DEP004 = ["matplotlib", "pytest_socket"]
DEP004 = ["matplotlib", "pytest", "pytest_socket"]

[tool.flit.sdist]
include = [
Expand Down Expand Up @@ -259,8 +261,9 @@ addopts = [
]
norecursedirs = ["docs/notebooks/*"]
filterwarnings = ["ignore::UserWarning"]
testpaths = "tests tests/test_sdba"
usefixtures = "xdoctest_namespace"
testpaths = [
"tests"
]
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL", "NUMBER", "ELLIPSIS"]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
Expand Down
112 changes: 5 additions & 107 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,14 @@
from __future__ import annotations

import os
import re
import shutil
import sys
import time
import warnings
from datetime import datetime as dt
from functools import partial
from pathlib import Path

import numpy as np
import pandas as pd
import pytest
import xarray as xr
from filelock import FileLock
from packaging.version import Version

import xclim
from xclim import __version__ as __xclim_version__
from xclim.core import indicator
from xclim.core.calendar import max_doy
from xclim.testing import helpers
Expand All @@ -28,37 +18,6 @@
from xclim.testing.utils import get_file
from xclim.testing.utils import open_dataset as _open_dataset

if (
re.match(r"^\d+\.\d+\.\d+$", __xclim_version__)
and helpers.TESTDATA_BRANCH == "main"
):
# This does not need to be emitted on GitHub Workflows and ReadTheDocs
if not os.getenv("CI") and not os.getenv("READTHEDOCS"):
warnings.warn(
f'`xclim` {__xclim_version__} is running tests against the "main" branch of `Ouranosinc/xclim-testdata`. '
"It is possible that changes in xclim-testdata may be incompatible with test assertions in this version. "
"Please be sure to check https://github.com/Ouranosinc/xclim-testdata for more information.",
UserWarning,
)

if re.match(r"^v\d+\.\d+\.\d+", helpers.TESTDATA_BRANCH):
# Find the date of last modification of xclim source files to generate a calendar version
install_date = dt.strptime(
time.ctime(os.path.getmtime(xclim.__file__)),
"%a %b %d %H:%M:%S %Y",
)
install_calendar_version = (
f"{install_date.year}.{install_date.month}.{install_date.day}"
)

if Version(helpers.TESTDATA_BRANCH) > Version(install_calendar_version):
warnings.warn(
f"Installation date of `xclim` ({install_date.ctime()}) "
f"predates the last release of `xclim-testdata` ({helpers.TESTDATA_BRANCH}). "
"It is very likely that the testing data is incompatible with this build of `xclim`.",
UserWarning,
)


@pytest.fixture
def random() -> np.random.Generator:
Expand Down Expand Up @@ -366,42 +325,6 @@ def _open_session_scoped_file(
return _open_session_scoped_file


@pytest.fixture(autouse=True, scope="session")
def add_imports(xdoctest_namespace, threadsafe_data_dir) -> None:
"""Add these imports into the doctests scope."""
ns = xdoctest_namespace
ns["np"] = np
ns["xr"] = xclim.testing # xr.open_dataset(...) -> xclim.testing.open_dataset(...)
ns["xclim"] = xclim
ns["open_dataset"] = partial(
_open_dataset,
cache_dir=threadsafe_data_dir,
branch=helpers.TESTDATA_BRANCH,
engine="h5netcdf",
) # Needed for modules where xarray is imported as `xr`


@pytest.fixture(autouse=True, scope="function")
def add_example_dataarray(xdoctest_namespace, tas_series, pr_series) -> None:
ns = xdoctest_namespace
ns["tas"] = tas_series(np.random.rand(365) * 20 + 253.15)
ns["pr"] = pr_series(np.random.rand(365) * 5)


@pytest.fixture(autouse=True, scope="session")
def is_matplotlib_installed(xdoctest_namespace) -> None:
def _is_matplotlib_installed():
try:
import matplotlib # noqa

return
except ImportError:
return pytest.skip("This doctest requires matplotlib to be installed.")

ns = xdoctest_namespace
ns["is_matplotlib_installed"] = _is_matplotlib_installed


@pytest.fixture
def official_indicators():
# Remove unofficial indicators (as those created during the tests, and those from YAML-built modules)
Expand All @@ -423,7 +346,7 @@ def atmosds(threadsafe_data_dir) -> xr.Dataset:


@pytest.fixture(scope="function")
def ensemble_dataset_objects() -> dict:
def ensemble_dataset_objects() -> dict[str, str]:
edo = dict()
edo["nc_files_simple"] = [
"EnsembleStats/BCCAQv2+ANUSPLIN300_ACCESS1-0_historical+rcp45_r1i1p1_1950-2100_tg_mean_YS.nc",
Expand Down Expand Up @@ -463,43 +386,18 @@ def lafferty_sriver_ds() -> xr.Dataset:


@pytest.fixture(scope="session", autouse=True)
def gather_session_data(threadsafe_data_dir, worker_id, xdoctest_namespace):
def gather_session_data(threadsafe_data_dir, worker_id):
"""Gather testing data on pytest run.
When running pytest with multiple workers, one worker will copy data remotely to _default_cache_dir while
other workers wait using lockfile. Once the lock is released, all workers will then copy data to their local
threadsafe_data_dir.As this fixture is scoped to the session, it will only run once per pytest run.
Additionally, this fixture is also used to generate the `atmosds` synthetic testing dataset as well as add the
example file paths to the xdoctest_namespace, used when running doctests.
Additionally, this fixture is also used to generate the `atmosds` synthetic testing dataset.
"""
if (
not _default_cache_dir.joinpath(helpers.TESTDATA_BRANCH).exists()
or helpers.PREFETCH_TESTING_DATA
):
if helpers.PREFETCH_TESTING_DATA:
print("`XCLIM_PREFETCH_TESTING_DATA` set. Prefetching testing data...")
if sys.platform == "win32":
raise OSError(
"UNIX-style file-locking is not supported on Windows. "
"Consider running `$ xclim prefetch_testing_data` to download testing data."
)
elif worker_id in ["master"]:
helpers.populate_testing_data(branch=helpers.TESTDATA_BRANCH)
else:
_default_cache_dir.mkdir(exist_ok=True, parents=True)
lockfile = _default_cache_dir.joinpath(".lock")
test_data_being_written = FileLock(lockfile)
with test_data_being_written:
# This flag prevents multiple calls from re-attempting to download testing data in the same pytest run
helpers.populate_testing_data(branch=helpers.TESTDATA_BRANCH)
_default_cache_dir.joinpath(".data_written").touch()
with test_data_being_written.acquire():
if lockfile.exists():
lockfile.unlink()
shutil.copytree(_default_cache_dir, threadsafe_data_dir)
helpers.testing_setup_warnings()
helpers.gather_testing_data(threadsafe_data_dir, worker_id)
helpers.generate_atmos(threadsafe_data_dir)
xdoctest_namespace.update(helpers.add_example_file_paths(threadsafe_data_dir))


@pytest.fixture(scope="session", autouse=True)
Expand Down
11 changes: 3 additions & 8 deletions tests/test_temperature.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
import warnings

import numpy as np
import pytest
Expand Down Expand Up @@ -826,7 +827,8 @@ def test_convert_units(self, open_dataset):
# put a nan somewhere
tasmin.values[180, 1, 0] = np.nan

with pytest.warns(None) as record:
with warnings.catch_warnings():
warnings.simplefilter("error")
frzthw = atmos.daily_freezethaw_cycles(
tasmin,
tasmax,
Expand All @@ -840,15 +842,8 @@ def test_convert_units(self, open_dataset):

frzthw1 = (((min1 < 0) & (max1 > 0)) * 1.0).sum()

assert (
"This index calculation will soon require user-specified thresholds."
not in [str(q.message) for q in record]
)

np.testing.assert_allclose(frzthw1, frzthw.values[0, 0, 0])

assert np.isnan(frzthw.values[0, 1, 0])

assert np.isnan(frzthw.values[0, -1, -1])


Expand Down
Loading

0 comments on commit da4a79a

Please sign in to comment.