diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 5be56c1d80..d0bcd342cb 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -28,7 +28,7 @@ jobs: IRIS_TEST_DATA_PATH: benchmarks/iris-test-data IRIS_TEST_DATA_VERSION: "2.19" # Lets us manually bump the cache to rebuild - ENV_CACHE_BUILD: "0" + ENV_CACHE_BUILD: "1" TEST_DATA_CACHE_BUILD: "2" steps: @@ -37,16 +37,16 @@ jobs: with: fetch-depth: 0 - - name: Install ASV & Nox + - name: Install ASV & tox run: | - pip install asv nox + pip install asv tox - name: Cache environment directories id: cache-env-dir uses: actions/cache@v3 with: path: | - .nox + .tox benchmarks/.asv/env $CONDA/pkgs key: ${{ runner.os }}-${{ hashFiles('requirements/') }}-${{ env.ENV_CACHE_BUILD }} diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 5c48966ce8..04a3d11ead 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -35,18 +35,18 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest"] - python-version: ["3.11"] - session: ["doctest", "gallery", "linkcheck"] + python-version: ["311"] + session: ["docs-tests", "docs-linkcheck", "gallery_tests"] include: - os: "ubuntu-latest" - python-version: "3.11" + python-version: "311" session: "tests" - coverage: "--coverage" + posargs: "--cov=lib/iris --cov-report=xml" - os: "ubuntu-latest" - python-version: "3.10" + python-version: "310" session: "tests" - os: "ubuntu-latest" - python-version: "3.9" + python-version: "39" session: "tests" env: @@ -63,7 +63,7 @@ jobs: CACHE_WEEKS: 2 run: | echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} - echo "LOCK_FILE=requirements/locks/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} + echo "LOCK_FILE=requirements/locks/py$(echo ${{ matrix.python-version }})-linux-64.lock" >> ${GITHUB_ENV} - name: "data cache" uses: ./.github/workflows/composite/iris-data-cache @@ -91,10 +91,10 @@ jobs: - name: "conda environment cache" uses: ./.github/workflows/composite/conda-env-cache with: - cache_build: 0 + cache_build: 1 cache_period: ${{ env.CACHE_PERIOD }} env_name: ${{ env.ENV_NAME }} - install_packages: "cartopy nox pip" + install_packages: "cartopy tox'<4'" - name: "conda info" run: | @@ -108,8 +108,8 @@ jobs: cache_period: ${{ env.CACHE_PERIOD }} env_name: ${{ env.ENV_NAME }} - - name: "nox cache" - uses: ./.github/workflows/composite/nox-cache + - name: "tox cache" + uses: ./.github/workflows/composite/tox-cache with: cache_build: 2 env_name: ${{ env.ENV_NAME }} @@ -134,11 +134,9 @@ jobs: cat ${MPL_RC} - name: "iris ${{ matrix.session }}" - env: - PY_VER: ${{ matrix.python-version }} run: | - nox --session ${{ matrix.session }} -- --verbose ${{ matrix.coverage }} + tox -e py${{ matrix.python-version }}-${{ matrix.session }} -- ${{ matrix.posargs }} - name: Upload coverage report uses: codecov/codecov-action@v3 - if: ${{ matrix.coverage }} + if: contains(${{ matrix.posargs }}, "--cov=lib/iris --cov-report=xml") diff --git a/.github/workflows/ci-wheels.yml b/.github/workflows/ci-wheels.yml index 942d528f6d..6b585c7d98 100644 --- a/.github/workflows/ci-wheels.yml +++ b/.github/workflows/ci-wheels.yml @@ -52,7 +52,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["39", "310", "311"] session: ["wheel"] env: ENV_NAME: "ci-wheels" @@ -72,7 +72,7 @@ jobs: CACHE_WEEKS: 2 run: | echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} - echo "LOCK_FILE=requirements/locks/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} + echo "LOCK_FILE=requirements/locks/py$(echo ${{ matrix.python-version }})-linux-64.lock" >> ${GITHUB_ENV} - name: "conda package cache" uses: ./.github/workflows/composite/conda-pkg-cache @@ -93,23 +93,21 @@ jobs: - name: "conda environment cache" uses: ./.github/workflows/composite/conda-env-cache with: - cache_build: 0 + cache_build: 1 cache_period: ${{ env.CACHE_PERIOD }} env_name: ${{ env.ENV_NAME }} - install_packages: "nox pip" + install_packages: "tox'<4' pip" - - name: "nox cache" - uses: ./.github/workflows/composite/nox-cache + - name: "tox cache" + uses: ./.github/workflows/composite/tox-cache with: cache_build: 1 env_name: ${{ env.ENV_NAME }} lock_file: ${{ env.LOCK_FILE }} - - name: "nox install and test wheel" - env: - PY_VER: ${{ matrix.python-version }} + - name: "tox install and test wheel" run: | - nox --session ${{ matrix.session }} -- --verbose + tox -e py${{ matrix.python-version }}-${{ matrix.session }} show-artifacts: needs: build diff --git a/.github/workflows/composite/nox-cache/action.yml b/.github/workflows/composite/tox-cache/action.yml similarity index 65% rename from .github/workflows/composite/nox-cache/action.yml rename to .github/workflows/composite/tox-cache/action.yml index 468dd22d81..48962f9d51 100644 --- a/.github/workflows/composite/nox-cache/action.yml +++ b/.github/workflows/composite/tox-cache/action.yml @@ -1,9 +1,9 @@ -name: "nox cache" -description: "cache the nox test environments" +name: "tox cache" +description: "cache the tox test environments" inputs: cache_build: - description: "nox cache build number" + description: "tox cache build number" required: false default: "0" env_name: @@ -18,5 +18,5 @@ runs: steps: - uses: actions/cache@v3 with: - path: ${{ github.workspace }}/.nox - key: ${{ runner.os }}-nox-${{ inputs.env_name }}-s${{ matrix.session }}-py${{ matrix.python-version }}-b${{ inputs.cache_build }}-${{ hashFiles(inputs.lock_file) }} + path: ${{ github.workspace }}/.tox + key: ${{ runner.os }}-tox-${{ inputs.env_name }}-s${{ matrix.session }}-py${{ matrix.python-version }}-b${{ inputs.cache_build }}-${{ hashFiles(inputs.lock_file) }} diff --git a/MANIFEST.in b/MANIFEST.in index 329cf79c5d..abfbba5a5e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -20,7 +20,7 @@ exclude codecov.yml include COPYING include COPYING.LESSER exclude Makefile -exclude noxfile.py +exclude tox.ini # files required to build iris.std_names module include etc/cf-standard-name-table.xml diff --git a/benchmarks/README.md b/benchmarks/README.md index 316c8f9e32..e0147c0bc2 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -21,7 +21,7 @@ by the PR. (This run is managed by [the aforementioned GitHub Action](../.github/workflows/benchmark.yml)). `asv ...` commands must be run from this directory. You will need to have ASV -installed, as well as Nox (see +installed, as well as tox (see [Benchmark environments](#benchmark-environments)). The benchmark runner ([bm_runner.py](./bm_runner.py)) provides conveniences for @@ -107,8 +107,7 @@ suites for the UK Met Office NG-VAT project. ## Benchmark environments We have disabled ASV's standard environment management, instead using an -environment built using the same Nox scripts as Iris' test environments. This -is done using ASV's plugin architecture - see +environment built using tox. This is done using ASV's plugin architecture - see [asv_delegated_conda.py](asv_delegated_conda.py) and the extra config items in [asv.conf.json](asv.conf.json). @@ -116,4 +115,4 @@ is done using ASV's plugin architecture - see minimising external factors and also allowing it to compare between a matrix of dependencies (each in a separate environment). We have chosen to sacrifice these features in favour of testing each commit with its intended dependencies, -controlled by Nox + lock-files). +controlled by tox + lock-files). diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json index faa7f6daee..f088685b24 100644 --- a/benchmarks/asv.conf.json +++ b/benchmarks/asv.conf.json @@ -20,9 +20,9 @@ // * No build-time environment variables. // * Is run in the same environment as the ASV install itself. "delegated_env_commands": [ - "PY_VER=3.11 nox --envdir={conf_dir}/.asv/env/nox01 --session=tests --install-only --no-error-on-external-run --verbose" + "tox -c {conf_dir}/.. --devenv={conf_dir}/.asv/env/tox01/bm_env -e py311 --verbose" ], // The parent directory of the above environment. // The most recently modified environment in the directory will be used. - "delegated_env_parent": "{conf_dir}/.asv/env/nox01" + "delegated_env_parent": "{conf_dir}/.asv/env/tox01" } diff --git a/benchmarks/bm_runner.py b/benchmarks/bm_runner.py index f3efb0ea31..38e5a6c806 100644 --- a/benchmarks/bm_runner.py +++ b/benchmarks/bm_runner.py @@ -62,27 +62,29 @@ def _prep_data_gen_env() -> None: """ root_dir = BENCHMARKS_DIR.parent - python_version = "3.11" + python_version = "311" data_gen_var = "DATA_GEN_PYTHON" if data_gen_var in environ: print("Using existing data generation environment.") else: print("Setting up the data generation environment ...") - # Get Nox to build an environment for the `tests` session, but don't - # run the session. Will re-use a cached environment if appropriate. + # Get tox to build an environment. It will re-use a cached environment + # if appropriate. _subprocess_run_print( [ - "nox", - f"--noxfile={root_dir / 'noxfile.py'}", - "--session=tests", - "--install-only", - f"--python={python_version}", + "tox", + "-c", + str(root_dir), + "-e", + f"py{python_version}", ] ) # Find the environment built above, set it to be the data generation # environment. data_gen_python = next( - (root_dir / ".nox").rglob(f"tests*/bin/python{python_version}") + (root_dir / ".tox").rglob( + f"py{python_version}/bin/python{python_version[:1]}.{python_version[1:]}" + ) ).resolve() environ[data_gen_var] = str(data_gen_python) @@ -112,7 +114,7 @@ def _prep_data_gen_env() -> None: def _setup_common() -> None: _check_requirements("asv") - _check_requirements("nox") + _check_requirements("tox") _prep_data_gen_env() @@ -154,7 +156,7 @@ def _asv_compare(*commits: str, overnight_mode: bool = False) -> None: class _SubParserGenerator(ABC): - """Convenience for holding all the necessary argparse info in 1 place.""" + """Convenience for holding all the necessary argparse info in one place.""" name: str = NotImplemented description: str = NotImplemented diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index ba24141d87..c807eeff93 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -37,6 +37,7 @@ .. _sphinx: https://www.sphinx-doc.org/en/master/ .. _sphinx-apidoc: https://github.com/sphinx-contrib/apidoc .. _test-iris-imagehash: https://github.com/SciTools/test-iris-imagehash +.. _tox: https://tox.readthedocs.io/en/latest/ .. _using git: https://docs.github.com/en/github/using-git .. _requirements: https://github.com/SciTools/iris/tree/main/requirements .. _CF-UGRID: https://ugrid-conventions.github.io/ugrid-conventions/ diff --git a/docs/src/conf.py b/docs/src/conf.py index a204263a24..d7c04f2130 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -384,6 +384,7 @@ def _dotv(version): "http://www.nationalarchives.gov.uk/doc/open-government-licence", "https://www.metoffice.gov.uk/", "https://biggus.readthedocs.io/", + "https://stickler-ci.com/", ] # list of sources to exclude from the build. diff --git a/docs/src/developers_guide/contributing_pull_request_checklist.rst b/docs/src/developers_guide/contributing_pull_request_checklist.rst index 11d68ace46..74002e7325 100644 --- a/docs/src/developers_guide/contributing_pull_request_checklist.rst +++ b/docs/src/developers_guide/contributing_pull_request_checklist.rst @@ -30,7 +30,7 @@ is merged. Before submitting a pull request please consider the following: :ref:`code_formatting`. #. **Check all new dependencies added to the** `requirements`_ **yaml - files.** If dependencies have been added then new nox testing lockfiles + files.** If dependencies have been added then new testing lockfiles should be generated too, see :ref:`gha_test_env`. #. **Check the source documentation been updated to explain all new or changed diff --git a/docs/src/developers_guide/contributing_running_tests.rst b/docs/src/developers_guide/contributing_running_tests.rst index f60cedba05..fdf8530566 100644 --- a/docs/src/developers_guide/contributing_running_tests.rst +++ b/docs/src/developers_guide/contributing_running_tests.rst @@ -12,8 +12,8 @@ There are two options for running the tests: the tests or use ``python`` interactively to investigate any issues. See :ref:`test manual env`. -* Use ``nox``. This will automatically generate an environment and run test - sessions consistent with our GitHub continuous integration. See :ref:`using nox`. +* Use ``tox``. This will automatically generate an environment and run test + sessions consistent with our GitHub continuous integration. See :ref:`using tox`. .. _test manual env: @@ -101,97 +101,84 @@ using the commands ``python test_mapping.py -h`` or ``python test_mapping.py --help``. .. tip:: A useful command line option to use is ``-d``. This will display - matplotlib_ figures as the tests are run. For example:: + `matplotlib_` figures as the tests are run. For example:: python test_mapping.py -d -.. _using nox: +.. _using tox: -Using Nox for Testing Iris +Using tox for Testing Iris ========================== -The `nox`_ tool has for adopted for automated testing on `Iris GitHub Actions`_ +The `tox`_ tool has been adopted for automated testing on `Iris GitHub Actions`_ and also locally on the command-line for developers. -`nox`_ is similar to `tox`_, but instead leverages the expressiveness and power of a Python -configuration file rather than an `.ini` style file. As with `tox`_, `nox`_ can use `virtualenv`_ -to create isolated Python environments, but in addition also supports `conda`_ as a testing -environment backend. +`tox`_ uses `virtualenv`_ to create isolated Python environments and is +configured within a tox.ini file. - -Where is Nox Used? +Where is tox Used? ------------------ -Iris uses `nox`_ as a convenience to fully automate the process of executing the Iris tests, but also -automates the process of: +Iris uses `tox`_ as a convenience to fully automate the process of: -* building the documentation and executing the doc-tests -* building the documentation gallery +* executing the Iris tests, +* building the documentation, +* executing the doc-tests, +* executing the gallery tests, and * running the documentation URL link check -You can perform all of these tasks manually yourself, however the onus is on you to first ensure -that all of the required package dependencies are installed and available in the testing environment. +You can perform all of these tasks manually yourself, however the onus is on you +to first ensure that all of the required package dependencies are installed and +available in the testing environment. -`Nox`_ has been configured to automatically do this for you, and provides a means to easily replicate -the remote testing behaviour of `Iris GitHub Actions`_ locally for the developer. +`tox`_ has been configured to automatically do this for you, and provides a +means to easily replicate the remote testing behaviour of `Iris GitHub Actions`_ +locally for the developer. -Installing Nox +Installing tox -------------- -We recommend installing `nox`_ using `conda`_. To install `nox`_ in a separate `conda`_ environment:: +We recommend installing `tox`_ using `conda`_. To install `tox`_ in a separate +`conda`_ environment:: - conda create -n nox -c conda-forge nox - conda activate nox + conda create -n tox -c conda-forge tox + conda activate tox -To install `nox`_ in an existing active `conda`_ environment:: +To install `tox`_ in an existing active `conda`_ environment:: - conda install -c conda-forge nox + conda install -c conda-forge tox -The `nox`_ package is also available on PyPI, however `nox`_ has been configured to use the `conda`_ -backend for Iris, so an installation of `conda`_ must always be available. +The `tox`_ package is also available on PyPI, however `tox`_ has been configured +to use `conda`_ to create the testing environments and so an installation of +`conda`_ must always be available. -Testing with Nox +Testing with tox ---------------- -The `nox`_ configuration file `noxfile.py` is available in the root ``iris`` project directory, and -defines all the `nox`_ sessions (i.e., tasks) that may be performed. `nox`_ must always be executed -from the ``iris`` root directory. - -To list the configured `nox`_ sessions for Iris:: - - nox --list - -To run the Iris tests for all configured versions of Python:: +The `tox`_ configuration file `tox.ini` is available in the root ``iris`` +project directory, and defines all the `tox`_ test environments (i.e., tasks) +that may be performed. `tox`_ must always be executed from the ``iris`` root +directory. - nox --session tests +To list the configured `tox`_ sessions for Iris:: -To build the Iris documentation specifically for Python 3.7:: + tox --listenvs-all - nox --session doctest-3.7 +To run the Iris tests for Python 3.11:: -To run all the Iris `nox`_ sessions:: + tox -e py311-tests - nox +To build the Iris documentation specifically for Python 3.11:: -For further `nox`_ command-line options:: - - nox --help - -.. tip:: - For `nox`_ sessions that use the `conda`_ backend, you can use the ``-v`` or ``--verbose`` - flag to display the `nox`_ `conda`_ environment package details and environment info. - For example:: + tox -e py311-docs - nox --session tests -- --verbose +For further `tox`_ command-line options:: + tox --help -.. note:: `nox`_ will cache its testing environments in the `.nox` root ``iris`` project directory. +.. note:: `tox`_ will cache its testing environments in the `.tox` root ``iris`` project directory. -.. _setuptools: https://setuptools.readthedocs.io/en/latest/ -.. _tox: https://tox.readthedocs.io/en/latest/ -.. _virtualenv: https://virtualenv.pypa.io/en/latest/ -.. _PyPI: https://pypi.org/project/nox/ -.. _v41.5.0: https://setuptools.readthedocs.io/en/latest/history.html#v41-5-0 +.. _virtualenv: https://virtualenv.pypa.io/en/latest/ \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9b62715be6..7a02fa492d 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -84,6 +84,9 @@ This document explains the changes made to Iris for this release `"Xarray bridge" `_ facility. (:pull:`5214`) +# `@lbdreyer`_ replaced nox with tox as the automated test runner. + (:pull:`5184`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index 6cea9dc001..1431e1b59f 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -71,8 +71,8 @@ def test_python_versions(): This test is designed to fail whenever Iris' supported Python versions are updated, insisting that versions are updated EVERYWHERE in-sync. """ - latest_supported = "3.11" - all_supported = ["3.9", "3.10", latest_supported] + latest_supported = "311" + all_supported = ["39", "310", latest_supported] root_dir = Path(__file__).parents[3] workflows_dir = root_dir / ".github" / "workflows" @@ -81,7 +81,7 @@ def test_python_versions(): # Places that are checked: pyproject_toml_file = root_dir / "pyproject.toml" requirements_dir = root_dir / "requirements" - nox_file = root_dir / "noxfile.py" + tox_config_file = root_dir / "tox.ini" ci_wheels_file = workflows_dir / "ci-wheels.yml" ci_tests_file = workflows_dir / "ci-tests.yml" asv_config_file = benchmarks_dir / "asv.conf.json" @@ -92,35 +92,35 @@ def test_python_versions(): pyproject_toml_file, "\n ".join( [ - f'"Programming Language :: Python :: {ver}",' + f'"Programming Language :: Python :: {ver[:1]}.{ver[1:]}",' for ver in all_supported ] ), ), - ( - nox_file, - "_PY_VERSIONS_ALL = [" - + ", ".join([f'"{ver}"' for ver in all_supported]), - ), ( ci_wheels_file, "python-version: [" - + ", ".join([f'"{ver}"' for ver in all_supported]), + + ", ".join([f'"{ver}"' for ver in all_supported]) + + "]", ), ( ci_tests_file, ( f'python-version: ["{latest_supported}"]\n' - f'{" " * 8}session: ["doctest", "gallery", "linkcheck"]' + f'{" " * 8}session: ["docs-tests", "docs-linkcheck", "gallery_tests"]' ), ), - (asv_config_file, f"PY_VER={latest_supported}"), + ( + asv_config_file, + f"-e py{latest_supported}", + ), (benchmark_runner_file, f'python_version = "{latest_supported}"'), + (tox_config_file, "[testenv:py{" + ",".join(all_supported) + "}-"), ] for ver in all_supported: - req_yaml = requirements_dir / f"py{ver.replace('.', '')}.yml" - text_searches.append((req_yaml, f"- python ={ver}")) + req_yaml = requirements_dir / f"py{ver}.yml" + text_searches.append((req_yaml, f"- python ={ver[:1]}.{ver[1:]}")) text_searches.append( ( @@ -129,6 +129,13 @@ def test_python_versions(): ) ) + text_searches.append( + ( + tox_config_file, + f'{" " * 4}py{ver}: {{toxinidir}}{{/}}requirements', + ) + ) + for path, search in text_searches: assert search in path.read_text() @@ -194,7 +201,6 @@ def last_change_by_fname(): def test_license_headers(self): exclude_patterns = ( "setup.py", - "noxfile.py", "build/*", "dist/*", "docs/gallery_code/*/*.py", diff --git a/lib/iris/tests/unit/analysis/stats/test_pearsonr.py b/lib/iris/tests/unit/analysis/stats/test_pearsonr.py index 63cf4e2abe..5f5b76d9ac 100644 --- a/lib/iris/tests/unit/analysis/stats/test_pearsonr.py +++ b/lib/iris/tests/unit/analysis/stats/test_pearsonr.py @@ -36,11 +36,11 @@ def setUp(self): def test_perfect_corr(self): r = stats.pearsonr(self.cube_a, self.cube_a, ["latitude", "longitude"]) - self.assertArrayEqual(r.data, np.array([1.0] * 6)) + self.assertArrayAlmostEqual(r.data, np.array([1.0] * 6)) def test_perfect_corr_all_dims(self): r = stats.pearsonr(self.cube_a, self.cube_a) - self.assertArrayEqual(r.data, np.array([1.0])) + self.assertArrayAlmostEqual(r.data, np.array([1.0])) def test_incompatible_cubes(self): with self.assertRaises(ValueError): diff --git a/noxfile.py b/noxfile.py deleted file mode 100755 index 601a1d576e..0000000000 --- a/noxfile.py +++ /dev/null @@ -1,301 +0,0 @@ -""" -Perform test automation with nox. - -For further details, see https://nox.thea.codes/en/stable/# - -""" - -import hashlib -import os -from pathlib import Path - -import nox -from nox.logger import logger - -#: Default to reusing any pre-existing nox environments. -nox.options.reuse_existing_virtualenvs = True - -#: Python versions we can run sessions under -_PY_VERSIONS_ALL = ["3.9", "3.10", "3.11"] -_PY_VERSION_LATEST = _PY_VERSIONS_ALL[-1] - -#: One specific python version for docs builds -_PY_VERSION_DOCSBUILD = _PY_VERSION_LATEST - -#: Cirrus-CI environment variable hook. -PY_VER = os.environ.get("PY_VER", _PY_VERSIONS_ALL) - -#: Default cartopy cache directory. -CARTOPY_CACHE_DIR = os.environ.get("HOME") / Path(".local/share/cartopy") - -# https://github.com/numpy/numpy/pull/19478 -# https://github.com/matplotlib/matplotlib/pull/22099 -#: Common session environment variables. -ENV = dict(NPY_DISABLE_CPU_FEATURES="AVX512F,AVX512CD,AVX512_SKX") - - -def session_lockfile(session: nox.sessions.Session) -> Path: - """Return the path of the session lockfile.""" - return Path( - f"requirements/locks/py{session.python.replace('.', '')}-linux-64.lock" - ) - - -def session_cachefile(session: nox.sessions.Session) -> Path: - """Returns the path of the session lockfile cache.""" - lockfile = session_lockfile(session) - tmp_dir = Path(session.create_tmp()) - cache = tmp_dir / lockfile.name - return cache - - -def venv_populated(session: nox.sessions.Session) -> bool: - """Returns True if the conda venv has been created - and the list of packages in the lockfile installed.""" - return session_cachefile(session).is_file() - - -def venv_changed(session: nox.sessions.Session) -> bool: - """Returns True if the installed session is different to that specified - in the lockfile.""" - changed = False - cache = session_cachefile(session) - lockfile = session_lockfile(session) - if cache.is_file(): - with open(lockfile, "rb") as fi: - expected = hashlib.sha256(fi.read()).hexdigest() - with open(cache, "r") as fi: - actual = fi.read() - changed = actual != expected - return changed - - -def cache_venv(session: nox.sessions.Session) -> None: - """ - Cache the nox session environment. - - This consists of saving a hexdigest (sha256) of the associated - conda lock file. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - lockfile = session_lockfile(session) - cache = session_cachefile(session) - with open(lockfile, "rb") as fi: - hexdigest = hashlib.sha256(fi.read()).hexdigest() - with open(cache, "w") as fout: - fout.write(hexdigest) - - -def cache_cartopy(session: nox.sessions.Session) -> None: - """ - Determine whether to cache the cartopy natural earth shapefiles. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - if not CARTOPY_CACHE_DIR.is_dir(): - session.run_always( - "python", - "-c", - "import cartopy; cartopy.io.shapereader.natural_earth()", - ) - - -def prepare_venv(session: nox.sessions.Session) -> None: - """ - Create and cache the nox session conda environment, and additionally - provide conda environment package details and info. - - Note that, iris is installed into the environment using pip. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - Notes - ----- - See - - https://github.com/theacodes/nox/issues/346 - - https://github.com/theacodes/nox/issues/260 - - """ - lockfile = session_lockfile(session) - venv_dir = session.virtualenv.location_name - - if not venv_populated(session): - # environment has been created but packages not yet installed - # populate the environment from the lockfile - logger.debug(f"Populating conda env at {venv_dir}") - session.conda_install("--file", str(lockfile)) - cache_venv(session) - - elif venv_changed(session): - # destroy the environment and rebuild it - logger.debug(f"Lockfile changed. Re-creating conda env at {venv_dir}") - _re_orig = session.virtualenv.reuse_existing - session.virtualenv.reuse_existing = False - session.virtualenv.create() - session.conda_install("--file", str(lockfile)) - session.virtualenv.reuse_existing = _re_orig - cache_venv(session) - - logger.debug(f"Environment {venv_dir} is up to date") - - cache_cartopy(session) - - # Determine whether verbose diagnostics have been requested - # from the command line. - verbose = "-v" in session.posargs or "--verbose" in session.posargs - - if verbose: - session.run_always("conda", "info") - session.run_always("conda", "list", f"--prefix={venv_dir}") - session.run_always( - "conda", - "list", - f"--prefix={venv_dir}", - "--explicit", - ) - - -@nox.session(python=PY_VER, venv_backend="conda") -def tests(session: nox.sessions.Session): - """ - Perform iris system, integration and unit tests. - - Coverage testing is enabled if the "--coverage" or "-c" flag is used. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - prepare_venv(session) - session.install("--no-deps", "--editable", ".") - session.env.update(ENV) - run_args = [ - "pytest", - "-n", - "auto", - "lib/iris/tests", - ] - if "-c" in session.posargs or "--coverage" in session.posargs: - run_args[-1:-1] = ["--cov=lib/iris", "--cov-report=xml"] - session.run(*run_args) - - -@nox.session(python=_PY_VERSION_DOCSBUILD, venv_backend="conda") -def doctest(session: nox.sessions.Session): - """ - Perform iris doctests and gallery. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - prepare_venv(session) - session.install("--no-deps", "--editable", ".") - session.env.update(ENV) - session.cd("docs") - session.run( - "make", - "clean", - "html", - external=True, - ) - session.run( - "make", - "doctest", - external=True, - ) - - -@nox.session(python=_PY_VERSION_DOCSBUILD, venv_backend="conda") -def gallery(session: nox.sessions.Session): - """ - Perform iris gallery doc-tests. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - prepare_venv(session) - session.install("--no-deps", "--editable", ".") - session.env.update(ENV) - session.run( - "pytest", - "-n", - "auto", - "docs/gallery_tests", - ) - - -@nox.session(python=_PY_VERSION_DOCSBUILD, venv_backend="conda") -def linkcheck(session: nox.sessions.Session): - """ - Perform iris doc link check. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - prepare_venv(session) - session.install("--no-deps", "--editable", ".") - session.cd("docs") - session.run( - "make", - "clean", - "html", - external=True, - ) - session.run( - "make", - "linkcheck", - external=True, - ) - - -@nox.session(python=PY_VER, venv_backend="conda") -def wheel(session: nox.sessions.Session): - """ - Perform iris local wheel install and import test. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - prepare_venv(session) - session.cd("dist") - fname = list(Path(".").glob("scitools_iris-*.whl")) - if len(fname) == 0: - raise ValueError("Cannot find wheel to install.") - if len(fname) > 1: - emsg = ( - f"Expected to find 1 wheel to install, found {len(fname)} instead." - ) - raise ValueError(emsg) - session.install(fname[0].name) - session.run( - "python", - "-c", - "import iris; print(f'{iris.__version__=}')", - external=True, - ) diff --git a/tools/update_lockfiles.py b/tools/update_lockfiles.py index 073f86cda6..990bf5bcc7 100755 --- a/tools/update_lockfiles.py +++ b/tools/update_lockfiles.py @@ -5,7 +5,7 @@ # licensing details. """ A command line utility for generating conda-lock files for the environments -that nox uses for testing each different supported version of python. +that tox uses for testing each different supported version of python. Typical usage: python tools/update_lockfiles.py -o requirements/locks requirements/py*.yml diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000000..47a3204a25 --- /dev/null +++ b/tox.ini @@ -0,0 +1,40 @@ +[tox] +requires = + tox-conda + +[testenv] +conda_spec = + py39: {toxinidir}{/}requirements{/}locks{/}py39-linux-64.lock + py310: {toxinidir}{/}requirements{/}locks{/}py310-linux-64.lock + py311: {toxinidir}{/}requirements{/}locks{/}py311-linux-64.lock +usedevelop = + true + +[testenv:py{39,310,311}-tests] +description = Perform Iris unit, integration and system tests. +commands = + pytest -n auto {posargs} {toxinidir}{/}lib{/}iris{/}tests + +[testenv:py{39,310,311}-gallery_tests] +description = Perform Iris gallery tests. +commands = + pytest -n auto {posargs} docs/gallery_tests + +[testenv:py{39,310,311}-docs{,-linkcheck,-tests}] +description = Build, and optionally linkcheck or test, the Iris documentation +allowlist_externals = + make +changedir = + {toxinidir}{/}docs +commands = + make clean html + linkcheck: make linkcheck + tests: make doctest + +[testenv:py{39,310,311}-wheel] +description = Install wheel and test the Iris import +skip_install = true +deps = + {toxinidir}{/}dist{/}scitools_iris*.whl +commands = + python -c "import iris; print(f'\{iris.__version__=\}')"