diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ccc6e98..0ccedf2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,25 +1,10 @@ name: Documentation -on: - push: - paths: - - "docs/**" - - "AUTHORS.rst" - - "CHANGES.rst" - - "CONTRIBUTING.rst" - - "LICENSE.rst" - - "README.rst" - pull_request: - paths: - - "docs/**" - - "AUTHORS.rst" - - "CHANGES.rst" - - "CONTRIBUTING.rst" - - "LICENSE.rst" - - "README.rst" +on: [push, pull_request] jobs: build: + name: Build documentation # We want to run on external PRs, but not on our own internal PRs as they'll be run # by the push to the branch. Without this if check, checks are duplicated since # internal PRs match both the push and pull_request events. @@ -35,24 +20,13 @@ jobs: steps: - uses: actions/checkout@v4 - - - uses: conda-incubator/setup-miniconda@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 with: - python-version: "3.11" - environment-file: environment.yml - mamba-version: "*" - channels: conda-forge,defaults - channel-priority: true - - - name: Show conda installation info - run: | - mamba info - mamba list + python-version: 3.11 - - name: Install package - run: | - make install + - name: Install dependencies + run: pip install nox - name: Build documentation - run: | - make docs + run: nox -s build-docs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b5e2f7..6a779f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,32 +21,44 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.10", "3.11", "3.12"] + fail-fast: false steps: - uses: actions/checkout@v4 - - uses: conda-incubator/setup-miniconda@v3 with: python-version: ${{ matrix.python-version }} - environment-file: environment.yml - mamba-version: "*" - channels: conda-forge,defaults - channel-priority: true - - - name: Show conda installation info - run: | - mamba info - mamba list + miniforge-variant: Mambaforge + miniforge-version: latest - - name: Build and install package + - name: Test run: | - make install + pip install nox + nox -s test --force-pythons="${{ matrix.python-version }}" - - name: Test + - name: Test CLI run: | - python -c 'import bmi_tester; print(bmi_tester.__version__)' - pytest --cov=bmi_tester --cov-report=xml:$(pwd)/coverage.xml -vvv + nox -s test-cli --force-pythons="${{ matrix.python-version }}" + # pip install model-metadata + # conda install gimli.units pymt_hydrotrend -c conda-forge + # pip install . + # bmi-test pymt_hydrotrend:Hydrotrend - name: Coveralls - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' + if: matrix.os == 'ubuntu-latest' uses: AndreMiras/coveralls-python-action@develop + with: + parallel: true + flag-name: py${{ matrix.python-version }}-${{ matrix.os }} + + debug: true + + coveralls_finish: + needs: build-and-test + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: AndreMiras/coveralls-python-action@develop + with: + parallel-finished: true + debug: true diff --git a/.gitignore b/.gitignore index 174084b..ed17120 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.egg-info/ *.py[cod] .coverage +.nox/ __pycache__/ build/ dist/ +docs/_generated/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 126de3d..88a42bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,7 +47,7 @@ repos: - id: forbid-new-submodules - id: mixed-line-ending - id: trailing-whitespace - # - id: name-tests-test + - id: name-tests-test - id: file-contents-sorter files: | (?x)^( diff --git a/.readthedocs.yaml b/.readthedocs.yaml index cce48b2..361ec15 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,10 +3,11 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "mambaforge-22.9" - -conda: - environment: environment.yml + python: "3.11" + jobs: + pre_build: + - pip install nox + - nox -s build-generated-docs sphinx: builder: html @@ -15,5 +16,6 @@ sphinx: python: install: + - requirements: requirements-docs.txt - method: pip path: . diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..49c8387 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,12 @@ +# Credits + +## Development Leads + +- [Eric Hutton](https://github.com/mcflugen) +- [Mark Piper](https://github.com/mdpiper) + +## Contributors + +- [Tian Gan](https://github.com/gantian127) +- [Scott Stewart](https://github.com/sc0tts) +- [Mike Taves](https://github.com/mwtoews) diff --git a/AUTHORS.rst b/AUTHORS.rst deleted file mode 100644 index 3e14191..0000000 --- a/AUTHORS.rst +++ /dev/null @@ -1,14 +0,0 @@ -Credits -======= - -Development Leads ------------------ - -- `Eric Hutton `_ -- `Mark Piper `_ - -Contributors ------------- - -- `Tian Gan ` -- `Scott Stewart `_ diff --git a/CHANGES.rst b/CHANGES.md similarity index 61% rename from CHANGES.rst rename to CHANGES.md index 74f8aad..d5f6c12 100644 --- a/CHANGES.rst +++ b/CHANGES.md @@ -1,124 +1,75 @@ -Changelog for bmi-tester -======================== +# Changelog for bmi-tester -0.5.7 (unreleased) ------------------- +## 0.5.7 (unreleased) - Nothing changed yet. - -0.5.6 (2023-12-15) ------------------- +## 0.5.6 (2023-12-15) - Fix docs build (#36) - - Updates for Python 3.12 (#35) - - Move static metadata to setup.cfg; update requirements; update CI workflows (#32) - -0.5.5 (2021-03-31) ------------------- +## 0.5.5 (2021-03-31) - Fixed documentation builds on readthedocs (#31) - - Added usage and installation instructions to the README, and did a general cleaning up of the docs (#30) - - Fixed a bug when validating some cf-compliant units (#29) - - Added gimli.units as a requirement for unit parsing (#29) - - Added GitHub actions for continuous integration (#28) - -0.5.4 (2020-10-31) ------------------- +## 0.5.4 (2020-10-31) - Removed the test for set_value as it's just too dangerous (#26) - - Fixed tests that use get_grid_node_count on grids that are not unstructured (#27) - -0.5.3 (2020-10-19) ------------------- +## 0.5.3 (2020-10-19) - Change tests that use ID arrays (e.g. face_nodes, edge_nodes, etc.) to allocate those array buffers as int32 (#25) - -0.5.2 (2020-10-09) ------------------- +## 0.5.2 (2020-10-09) - Fixed a bug in the unstructured grid tests (#24) - -0.5.1 (2020-09-10) ------------------- +## 0.5.1 (2020-09-10) - Fixed the time units check to allow dimensionless units (#22) - - Fixed remaining bmi version strings to 2.0 (#23) -0.5 (2020-09-02) ----------------- +## 0.5 (2020-09-02) - Fixed an error with empty_var_buffer where it returned a read-only array (#20) - - Fixed MANIFEST.in to include necessary source files (#21) - - Added test stages where successive stages depend on one another (#19) - - Cleaned up continuous integration and added Python 3.8 builds (#18) - -0.4.4 (2020-03-23) ------------------- +## 0.4.4 (2020-03-23) - Added test for get_grid_node_count (#17) -0.4.3 (2019-11-12) ------------------- - - -0.4.2 (2019-07-24) ------------------- - - -0.4.1 (2019-05-16) ------------------- - - -0.4.0 (2018-10-25) ------------------- - - -0.3.1 (2018-10-16) ------------------- - - -0.3.0 (2018-09-30) ------------------- +## 0.4.3 (2019-11-12) +## 0.4.2 (2019-07-24) -0.2.4 (2018-07-04) ------------------- +## 0.4.1 (2019-05-16) +## 0.4.0 (2018-10-25) -0.2.2 (2018-07-03) ------------------- +## 0.3.1 (2018-10-16) +## 0.3.0 (2018-09-30) -0.2.1 (2018-06-04) ------------------- +## 0.2.4 (2018-07-04) +## 0.2.2 (2018-07-03) -0.2 (2018-06-04) ----------------- +## 0.2.1 (2018-06-04) +## 0.2 (2018-06-04) -0.1.0 (2018-04-14) ------------------- +## 0.1.0 (2018-04-14) - Initial release diff --git a/CREDITS.rst b/CREDITS.rst deleted file mode 100644 index f5a488d..0000000 --- a/CREDITS.rst +++ /dev/null @@ -1,15 +0,0 @@ -Credits -======= - -Author ------- - -* Eric Hutton - -Contributors ------------- - -* Eric Hutton -* Mark Piper -* Scott Stewart -* Mike Taves diff --git a/LICENSE.rst b/LICENSE.md similarity index 90% rename from LICENSE.rst rename to LICENSE.md index 8c80f6d..876d3a4 100644 --- a/LICENSE.rst +++ b/LICENSE.md @@ -1,7 +1,6 @@ -The MIT License (MIT) -===================== +# The MIT License (MIT) -Copyright © `2020` `Community Surface Dynamics Modeling System` +Copyright (c) `2020` `Community Surface Dynamics Modeling System` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a7b5a51..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,15 +0,0 @@ -include *.rst -include requirements*.txt -include Makefile - -recursive-include bmi_tester/data/udunits * -recursive-include docs/_static *png -recursive-include docs/_templates *html -recursive-include docs *.bat -recursive-include docs *.py -recursive-include docs *.rst -recursive-include docs Makefile -recursive-include tests *.py - -exclude environment.yml -exclude .readthedocs.yaml diff --git a/Makefile b/Makefile deleted file mode 100644 index b910c4e..0000000 --- a/Makefile +++ /dev/null @@ -1,93 +0,0 @@ -.PHONY: clean clean-test clean-pyc clean-build docs help -.DEFAULT_GOAL := help - -define BROWSER_PYSCRIPT -import os, webbrowser, sys - -try: - from urllib import pathname2url -except: - from urllib.request import pathname2url - -webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) -endef -export BROWSER_PYSCRIPT - -define PRINT_HELP_PYSCRIPT -import re, sys - -for line in sys.stdin: - match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) - if match: - target, help = match.groups() - print("%-20s %s" % (target, help)) -endef -export PRINT_HELP_PYSCRIPT - -BROWSER := python -c "$$BROWSER_PYSCRIPT" - -help: - @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) - -clean: clean-build clean-docs clean-pyc clean-test ## remove all build, test, coverage and Python artifacts - -clean-build: ## remove build artifacts - rm -fr build/ - rm -fr dist/ - rm -fr .eggs/ - find . -name '*.egg-info' -exec rm -fr {} + - find . -name '*.egg' -exec rm -f {} + - -clean-docs: ## remove artifacts from docs build - rm -f docs/api/bmi_tester.rst - rm -f docs/api/modules.rst - $(MAKE) -C docs clean - -clean-pyc: ## remove Python file artifacts - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + - -clean-test: ## remove test and coverage artifacts - rm -fr .tox/ - rm -f .coverage - rm -fr htmlcov/ - rm -fr .pytest_cache - -lint: ## check style with flake8 - flake8 bmi_tester tests - -pretty: - find bmi_tester -name '*.py' | xargs isort - black tests bmi_tester - -test: ## run tests quickly with the default Python - pytest - -test-all: ## run tests on every Python version with tox - tox - -coverage: ## check code coverage quickly with the default Python - coverage run --source bmi_tester -m pytest - coverage report -m - coverage html - $(BROWSER) htmlcov/index.html - -docs: clean-docs ## generate Sphinx HTML documentation, including API docs - sphinx-apidoc --force -o docs/api bmi_tester bmi_tester/tests/** bmi_tester/bootstrap/** - $(MAKE) -C docs html - $(BROWSER) docs/_build/html/index.html - -servedocs: docs ## compile the docs watching for changes - watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . - -release: dist ## package and upload a release - twine upload dist/* - -dist: clean ## builds source and wheel package - python -m build - ls -l dist - -install: clean ## install the package to the active Python's site-packages - pip install -e . diff --git a/README.rst b/README.md similarity index 50% rename from README.rst rename to README.md index 5d42433..6d01dd2 100644 --- a/README.rst +++ b/README.md @@ -1,18 +1,30 @@ -bmi-tester: Test Basic Model Interface implementations -====================================================== - -|Build Status| |Documentation Status| |Coverage Status| |Conda Version| -|Conda Downloads| - -About ------ +# bmi-tester + +[anaconda-badge]: https://anaconda.org/conda-forge/bmi-tester/badges/version.svg +[anaconda-link]: https://anaconda.org/conda-forge/bmi-tester +[build-badge]: https://github.com/csdms/bmi-tester/actions/workflows/test.yml/badge.svg +[build-link]: https://github.com/csdms/bmi-tester/actions/workflows/test.yml +[coverage-badge]: https://coveralls.io/repos/github/csdms/bmi-tester/badge.svg +[coverage-link]: https://coveralls.io/github/csdms/bmi-tester +[docs-badge]: https://readthedocs.org/projects/bmi-tester/badge/?version=latest +[docs-link]: https://readthedocs.org/projects/bmi-tester/ +[pypi-badge]: https://badge.fury.io/py/bmi-tester.svg +[pypi-link]: https://pypi.org/project/bmi-tester/ +[python-badge]: https://img.shields.io/pypi/pyversions/bmi-tester.svg + +![[Build Status][build-link]][build-badge] +![[PyPI][pypi-link]][pypi-badge] +![[Anaconda][anaconda-link]][anaconda-badge] +![[Python][pypi-link]][python-badge] +![[Documentation][docs-link]][docs-badge] +![[Coverage][coverage-link]][coverage-badge] + +## About The *bmi-tester* is a command-line utility and Python library for testing Basic Model Interface (BMI) implementations. - -Requirements ------------- +## Requirements The *bmi-tester* requires Python 3. Additional dependencies can be found in the project's *requirements.txt* file and can be installed using either @@ -29,95 +41,75 @@ that things are working as they should. These dependencies are listed in *requirements-testing.txt* and can all be install with either *pip* or *conda*. - -Installation ------------- +## Installation To install, first create a new environment in which the project will be installed. This, although not necessary, will isolate the installation so that there won't be conflicts with your base *Python* installation. This can be done with *conda* as, -.. code:: bash +```bash +conda create -n bmi-tester python=3 +conda activate bmi-tester +``` - $ conda create -n bmi-tester python=3 - $ conda activate bmi-tester - - -Stable Release -++++++++++++++ +### Stable Release The *bmi-tester*, and its dependencies, can most easily be installed with *conda*, -.. code:: bash +```bash +conda install bmi-tester -c conda-forge +``` - $ conda install bmi-tester -c conda-forge - -From Source -+++++++++++ +### From Source After downloading the *bmi-tester* source code, run the following from the project's top-level folder (the one that contains *pyproject.toml*) to install into the current environment, -.. code:: bash - - $ pip install -e . +```bash +pip install -e . +``` -Usage ------ +## Usage You can access the *bmi-tester* from the command line with the *bmi-test* command. Use the *--help* option to get a brief description of the command line arguments, -.. code:: bash - - $ bmi-test --help +```bash +bmi-test --help +``` The *bmi-test* command takes a single argument, the name of the entry point of the class that implements the BMI you would like to test. To demonstrate how this works, we will use the *Hydrotrend* model as an example. To install the Python BMI for *Hydrotrend*, use *conda*, -.. code:: bash - - $ conda install pymt_hydrotrend -c conda-forge +```bash +conda install pymt_hydrotrend -c conda-forge +``` Once installed, the following will test the BMI implementation for the *Hydrotrend* class, -.. code:: bash +```bash +bmi-test pymt_hydrotrend:Hydrotrend +``` - $ bmi-test pymt_hydrotrend:Hydrotrend - -The entry point is given as *:*. That is, in Python you would +The entry point is given as *\:\*. That is, in Python you would import the *Hydrotrend* class as, -.. code:: python - - >>> from pymt_hydrotrend import Hydrotrend - - -Links ------ - -- `Source code `__: The - *bmi-tester* source code repository. -- `Documentation `__: User - documentation for *bmi-tester* -- `Get `__: - Installation instructions +```python +>>> from pymt_hydrotrend import Hydrotrend +``` +## Links -.. |Build Status| image:: https://github.com/csdms/bmi-tester/actions/workflows/test.yml/badge.svg - :target: https://github.com/csdms/bmi-tester/actions/workflows/test.yml -.. |Documentation Status| image:: https://readthedocs.org/projects/bmi-tester/badge/?version=latest - :target: http://bmi-tester.readthedocs.io/en/latest/?badge=latest -.. |Coverage Status| image:: https://coveralls.io/repos/github/csdms/bmi-tester/badge.svg - :target: https://coveralls.io/github/csdms/bmi-tester -.. |Conda Version| image:: https://anaconda.org/conda-forge/bmi-tester/badges/version.svg - :target: https://anaconda.org/conda-forge/bmi-tester -.. |Conda Downloads| image:: https://anaconda.org/conda-forge/bmi-tester/badges/downloads.svg - :target: https://anaconda.org/conda-forge/bmi-tester +- [Source code](http://github.com/csdms/bmi-tester): The + *bmi-tester* source code repository. +- [Documentation](http://bmi-tester.readthedocs.io/): User + documentation for *bmi-tester* +- [Get](http://bmi-tester.readthedocs.io/en/latest/getting.html): + Installation instructions diff --git a/bmi_tester/api.py b/bmi_tester/api.py deleted file mode 100644 index 09e301f..0000000 --- a/bmi_tester/api.py +++ /dev/null @@ -1,112 +0,0 @@ -import contextlib -import os - -import gimli -import numpy as np -import pkg_resources -import pytest - -SECONDS = gimli.units.Unit("s") - - -def check_bmi( - package, - tests_dir=None, - input_file=None, - manifest=None, - bmi_version="2.0", - extra_args=None, - help_pytest=False, -): - if tests_dir is None: - tests_dir = pkg_resources.resource_filename(__name__, "bootstrap") - args = [tests_dir] - - os.environ["BMITEST_CLASS"] = package - os.environ["BMITEST_INPUT_FILE"] = input_file or "" - os.environ["BMI_VERSION_STRING"] = bmi_version - - if manifest: - if isinstance(manifest, str): - with open(manifest) as fp: - manifest = fp.read() - else: - manifest = os.linesep.join(manifest) - os.environ["BMITEST_MANIFEST"] = manifest - - extra_args = extra_args or [] - if help_pytest: - extra_args.append("--help") - args += extra_args - - return pytest.main(args) - - -@contextlib.contextmanager -def suppress_stdout(streams): - null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)] - # Save the actual stdout (1) and stderr (2) file descriptors. - save_fds = [os.dup(1), os.dup(2)] - - os.dup2(null_fds[0], 1) - os.dup2(null_fds[1], 2) - - yield - - # Re-assign the real stdout/stderr back to (1) and (2) - os.dup2(save_fds[0], 1) - os.dup2(save_fds[1], 2) - # Close the null files - for fd in null_fds + save_fds: - os.close(fd) - - -def empty_var_buffer(bmi, var_name): - """Create an empty value buffer for a BMI variable. - - Examples - -------- - >>> import numpy as np - >>> from bmi_tester.api import empty_var_buffer - - >>> class Bmi: - ... def get_var_nbytes(self, name): - ... return 128 - ... def get_var_type(self, name): - ... return "int" - - >>> buffer = empty_var_buffer(Bmi(), "var-name") - >>> np.any(buffer != 0) - True - >>> buffer[:] = 0 - >>> np.all(buffer == 0) - True - """ - nbytes = bmi.get_var_nbytes(var_name) - dtype = np.dtype(bmi.get_var_type(var_name)) - - values = np.frombuffer(np.random.bytes(nbytes), dtype=dtype).copy() - - return values - - -def check_unit_is_valid(unit): - try: - gimli.units.Unit(unit) - except gimli.UnitNameError: - return False - else: - return True - - -def check_unit_is_time(unit): - try: - gimli.units.Unit(unit).to(SECONDS) - except gimli.IncompatibleUnitsError: - return False - else: - return True - - -def check_unit_is_dimensionless(unit): - return gimli.units.Unit(unit).is_dimensionless diff --git a/bmi_tester/bmipytest.py b/bmi_tester/bmipytest.py deleted file mode 100644 index d061f16..0000000 --- a/bmi_tester/bmipytest.py +++ /dev/null @@ -1,232 +0,0 @@ -#! /usr/bin/env python -import contextlib -import importlib -import os -import pathlib -import re -import sys -import tempfile -from collections.abc import Generator -from functools import partial - -import click -import pkg_resources -from model_metadata import MetadataNotFoundError -from model_metadata.api import query -from model_metadata.api import stage -from pytest import ExitCode - -from . import __version__ -from .api import check_bmi - -out = partial(click.secho, bold=True, err=True) -err = partial(click.secho, fg="red", err=True) - - -def validate_entry_point(ctx, param, value): - MODULE_REGEX = r"^(?!.*\.\.)(?!.*\.$)[A-Za-z][\w\.]*$" - CLASS_REGEX = r"^[_a-zA-Z][_a-zA-Z0-9]+$" - if value is not None: - try: - module_name, class_name = value.split(":") - except ValueError: - raise click.BadParameter( - "Bad entry point", param=value, param_hint="module_name:ClassName" - ) - if not re.match(MODULE_REGEX, module_name): - raise click.BadParameter( - f"Bad module name ({module_name})", - param_hint="module_name:ClassName", - ) - if not re.match(CLASS_REGEX, class_name): - raise click.BadParameter( - f"Bad class name ({class_name})", - param_hint="module_name:ClassName", - ) - return value - - -def load_component(entry_point): - module_name, cls_name = entry_point.split(":") - - component = None - try: - module = importlib.import_module(module_name) - except ImportError: - raise - else: - try: - component = module.__dict__[cls_name] - # component = module.__dict__[cls_name].__name__ - except KeyError: - raise ImportError(cls_name) - - return component - - -def _stage_component(entry_point, stage_dir="."): - config_file = query(entry_point, "run.config_file.path") - manifest = stage(entry_point, str(stage_dir)) - - return config_file, manifest - - -def _tree(files): - tree = [] - prefix = ["|--"] * (len(files) - 1) + ["`--"] - for p, fname in zip(prefix, files): - tree.append(f"{p} {fname}") - return os.linesep.join(tree) - - -@click.command( - context_settings={"ignore_unknown_options": True, "allow_extra_args": True} -) -@click.version_option(version=__version__) -@click.option( - "-q", - "--quiet", - is_flag=True, - help=( - "Don't emit non-error messages to stderr. Errors are still emitted, " - "silence those with 2>/dev/null." - ), -) -@click.option( - "-v", "--verbose", is_flag=True, help="Also emit status messages to stderr." -) -@click.option("--help-pytest", is_flag=True, help="Print help about pytest.") -@click.option( - "--root-dir", - type=click.Path( - exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True - ), - help="Define root directory for BMI tests", -) -@click.option( - "--config-file", - type=click.Path( - exists=True, file_okay=True, dir_okay=False, writable=True, resolve_path=True - ), - help="Name of model configuration file", -) -@click.option( - "--manifest", - type=click.Path( - exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True - ), - help="Path to manifest file of staged model input files.", -) -@click.option("--bmi-version", default="2.0", help="BMI version to test against") -@click.argument("entry_point", callback=validate_entry_point) -@click.argument("pytest_args", nargs=-1, type=click.UNPROCESSED) -def main( - entry_point, - root_dir, - bmi_version, - config_file, - manifest, - quiet, - verbose, - pytest_args, - help_pytest, -): - """Validate a BMI implementation. - - \b - Examples: - - Test a BMI for the class *Hydrotrend* in module *pymt_hydrotrend*, - - $ bmi-test pymt_hydrotrend:Hydrotrend - - This will test the BMI with a default set of input files as obtained - through the model metadata associated with the component. - - If the component you would like to test does not have model metadata - that bmi-tester recognizes, or you would like to test with a non-default - set of input files, use the *--root-dir* and *--config-file* options. - - $ bmi-tests pymt_hydrotrend:Hydrotrend --root-dir=my_files/ --config-file=config.txt - - where *my_files* is a folder that contains the input files to test with - and *config.txt* is the configuration file, which will be passed to the - *initialize* method. - """ - if root_dir and not config_file: - err("using --root-dir but no config file specified (use --config-file)") - raise click.Abort() - - module_name, class_name = entry_point.split(":") - - try: - Bmi = load_component(entry_point) - except ImportError: - err(f"unable to import BMI implementation, {class_name}, from {module_name}") - raise click.Abort() - - if root_dir: - stage_dir = root_dir - if manifest is None: - manifest = os.listdir(stage_dir) - else: - stage_dir = tempfile.mkdtemp() - try: - config_file, manifest = _stage_component(entry_point, stage_dir) - except MetadataNotFoundError: - config_file, manifest = _stage_component(class_name, stage_dir) - - stages = sorted( - [pathlib.Path(pkg_resources.resource_filename(__name__, "bootstrap"))] - + list( - pathlib.Path(pkg_resources.resource_filename(__name__, "tests")).glob( - "stage_*" - ) - ) - ) - - if not quiet: - out("Location of tests:") - for stage_path in (str(stage_path) for stage_path in stages): - out(f"- {stage_path}") - out(f"Entry point: {entry_point}") - out(repr(Bmi())) - out(f"BMI version: {bmi_version}") - out(f"Stage folder: {stage_dir}") - out(f"> tree -d {stage_dir}") - if manifest: - out(_tree(manifest)) - out(f"> cat {stage_dir}/{config_file}") - with open(os.path.join(stage_dir, config_file)) as fp: - out(fp.read()) - - with as_cwd(stage_dir): - for stage_path in sorted(stages): - status = check_bmi( - entry_point, - tests_dir=str(stage_path), - # tests_dir=tests_dir, - input_file=config_file, - manifest=manifest, - bmi_version=bmi_version, - extra_args=pytest_args + ("-vvv",), - help_pytest=help_pytest, - ) - if status != ExitCode.OK: - break - - if not quiet: - if status == ExitCode.OK: - out("🎉 All tests passed!") - else: - err("😞 There were errors") - - sys.exit(status) - - -@contextlib.contextmanager -def as_cwd(path: str) -> Generator[None, None, None]: - prev_cwd = os.getcwd() - os.chdir(path) - yield - os.chdir(prev_cwd) diff --git a/bmi_tester/data/udunits/README b/bmi_tester/data/udunits/README deleted file mode 100644 index 366b741..0000000 --- a/bmi_tester/data/udunits/README +++ /dev/null @@ -1,18 +0,0 @@ -UDUNITS version 2.1.24 - -The following units have been removed: - rem - -The following units have been changed: - sievert - sverdrup - -The following units have been added: - practical_salinity_unit - level - sigma_level - layer - decibel - bel - calendar_month - calendar_year diff --git a/bmi_tester/data/udunits/udunits2-accepted.xml b/bmi_tester/data/udunits/udunits2-accepted.xml deleted file mode 100644 index f8701c2..0000000 --- a/bmi_tester/data/udunits/udunits2-accepted.xml +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - 60 s - minute - min - - - 60 min - hour - h - hr - - - 24 h - day - d - - - - 3.141592653589793238462643383279 - - pi - π - - - - (pi/180) rad - arc_degree - ° - - angular_degree - degree - arcdeg - - - - °/60 - arc_minute - ' - - - angular_minute - arcminute - arcmin - - - - '/60 - arc_second - " - - - angular_second - arcsecond - arcsec - - - - - dm^3 - liter - L - - litre - l - - - - 1000 kg - metric_ton - t - tonne - - - - - 1.60217733e-19 J - electronvolt - eV - - electron_volt - - - - 1.6605402e-27 kg - unified_atomic_mass_unit - u - - atomic_mass_unit - atomicmassunit - amu - - - - 1.495979e11 m - - astronomical_unit - ua - - - - - - 1852 m - - nautical_mile - - - - nautical_mile/hour - - international_knot - knot_international - knot - - - - 1e-10 m - - angstrom - ångström - Å - - - - - dam^2 - - are - a - - - - 100 are - hectare - - - 100 fm^2 - - barn - b - - - - 1000 hPa - - bar - - - - cm/s^2 - - gal - - - - 3.7e10 Bq - - curie - Ci - - - - 2.58e-4 C/kg - - roentgen - R - - - - - - 0.01 sievert - - rem - - - diff --git a/bmi_tester/data/udunits/udunits2-accepted.xml.nonCF b/bmi_tester/data/udunits/udunits2-accepted.xml.nonCF deleted file mode 100644 index fd11e76..0000000 --- a/bmi_tester/data/udunits/udunits2-accepted.xml.nonCF +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - 60 s - minute - min - - - 60 min - hour - h - hr - - - 24 h - day - d - - - - 3.141592653589793238462643383279 - - pi - π - - - - (pi/180) rad - arc_degree - ° - - angular_degree - degree - arcdeg - - - - °/60 - arc_minute - ' - - - angular_minute - arcminute - arcmin - - - - '/60 - arc_second - " - - - angular_second - arcsecond - arcsec - - - - - dm^3 - liter - L - - litre - l - - - - 1000 kg - metric_ton - t - tonne - - - - - 1.60217733e-19 J - electronvolt - eV - - electron_volt - - - - 1.6605402e-27 kg - unified_atomic_mass_unit - u - - atomic_mass_unit - atomicmassunit - amu - - - - 1.495979e11 m - - astronomical_unit - ua - - - - - - 1852 m - - nautical_mile - - - - nautical_mile/hour - - international_knot - knot_international - knot - - - - 1e-10 m - - angstrom - ångström - Å - - - - - dam^2 - - are - a - - - - 100 are - hectare - - - 100 fm^2 - - barn - b - - - - 1000 hPa - - bar - - - - cm/s^2 - - gal - - - - 3.7e10 Bq - - curie - Ci - - - - 2.58e-4 C/kg - - roentgen - R - - - - - cSv - - rem - - - diff --git a/bmi_tester/data/udunits/udunits2-base.xml b/bmi_tester/data/udunits/udunits2-base.xml deleted file mode 100644 index 7b7f3f9..0000000 --- a/bmi_tester/data/udunits/udunits2-base.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - meter - m - metre - - - - - kilogram - kg - - - - - second - s - - - - - ampere - A - - - - - kelvin - K - - - - - mole - mol - - - - - candela - cd - - - - - calendar_year - cY - - diff --git a/bmi_tester/data/udunits/udunits2-common.xml b/bmi_tester/data/udunits/udunits2-common.xml deleted file mode 100644 index 2a10cb7..0000000 --- a/bmi_tester/data/udunits/udunits2-common.xml +++ /dev/null @@ -1,1707 +0,0 @@ - - - - - - s - sec - - - A - amp - - - K - - °K - - degree_kelvin - degrees_kelvin - - - degree_K - degrees_K - - - degreeK - degreesK - - - deg_K - degs_K - - - degK - degsK - - - - - cd - candle - - - mole - einstein - - - Hz - - baud - Bd - bps - - - - degree_Celsius - - - celsius - - degree_C - degrees_C - - - degreeC - degreesC - - - deg_C - degs_C - - - degC - degsC - - - - - knot - - kt - kts - - - - - - 6.02214179e23/mol - - avogadro_constant - - - - - 0.01 - - percent - - % - - - - 1e-6 - - ppm - ppmv - - - - 1e-9 - - ppb - ppbv - - - - 1e-12 - - ppt - pptv - - - - 1e-15 - - ppq - ppqv - - - - - - 0.9 arc_degree - grade - - - 2 pi rad - - circle - cycle - turn - revolution - rotation - - - - arc_degree - - - degree_north - degrees_north - - - degree_N - degrees_N - - - degreeN - degreesN - - - degree_east - degrees_east - - - degree_E - degrees_E - - - degreeE - degreesE - - - degree_true - degrees_true - - - degree_T - degrees_T - - - degreeT - degreesT - - - - - -1 degree_east - - - degree_west - degrees_west - - - degree_W - degrees_W - - - degreeW - degreesW - - - - - - - 2.916667e-2 kg - - assay_ton - - - - 2.834952e-2 kg - - avoirdupois_ounce - - - - 4.5359237e-1 kg - - avoirdupois_pound - pound - lb - - - - 2e-4 kg - - carat - - - - 6.479891e-5 kg - - grain - gr - - - - 5.080235e1 kg - - long_hundredweight - - - - 1.555174e-3 kg - - pennyweight - - - - 4.535924e1 kg - - short_hundredweight - - - - 14.59390 kg - - slug - - - - 3.110348e-2 kg - - troy_ounce - apothecary_ounce - - - - 3.732417e-1 kg - - troy_pound - apothecary_pound - - - - 20 grain - scruple - - - 60 grain - apdram - - - 480 grain - apounce - - - 5760 grain - appound - - - 94 pound - bag - - - 2000 pound - - short_ton - ton - - - - 2240 pound - - long_ton - - - - - - 1e-15 m - - fermi - - - - 9.46073e15 m - - light_year - - - - 1e-6 m - - micron - - - - 2.54e-5 m - - mil - - - - 3.085678e16 m - - parsec - - - - 3.514598e-4 m - - printers_point - - - - 2.011684e1 m - - chain - - - - 12 printers_point - - printers_pica - pica - - - - nautical_mile - - nmile - - - - (1200/3937) m - - - US_survey_foot - US_survey_feet - - - - - 3 US_survey_feet - - US_survey_yard - - - - 5280 US_survey_feet - - US_survey_mile - US_statute_mile - - - - 16.5 US_survey_feet - - rod - pole - perch - - - - 660 US_survey_feet - furlong - - - 6 US_survey_feet - fathom - - - 2.54 cm - - international_inch - inch - in - - - - 12 international_inches - - - international_foot - international_feet - - - foot - feet - - ft - - - - 3 international_feet - - international_yard - yard - yd - - - - 5280 international_feet - - international_mile - mile - mi - - - - inch/72 - - big_point - - - - inch/3 - - barleycorn - - - - 191.835 foot - - arpentlin - - - - - - rotation/second - - - rotation_per_second - rotations_per_second - - rps - cps - - - - rotation/minute - rpm - - - - - 1.111111e-7 kg/m - - denier - - - - 1e-6 kg/m - - tex - - - - - - 5.72135e-11 kg/(Pa.s.m^2) - - - perm_0C - perms_0C - - - - - 5.74525e-11 kg/(Pa.s.m^2) - - - perm_23C - perms_23C - - - - - - - 5.067075e-10 m^2 - - circular_mil - - - - 9.869233e-13 m^2 - darcy - - - 160 rod^2 - - acre - - - - - - 1.233489e3 m^3 - - - - acre_foot - acre_feet - - - - - 2.359737e-3 m^3 - - - board_foot - board_feet - - - - - 3.523907e-2 m^3 - - bushel - bu - - - - bushel/4 - - peck - pk - - - - 4.546090e-3 m^3 - - Canadian_liquid_gallon - - - - 4.404884e-3 m^3 - - US_dry_gallon - - - - cm^3 - cc - - - 1 m^3 - - stere - - - - 2.831685 m^3 - - register_ton - - - - US_dry_gallon/4 - - US_dry_quart - dry_quart - - - - US_dry_gallon/8 - - US_dry_pint - dry_pint - - - - 3.785412e-3 m^3 - - US_liquid_gallon - liquid_gallon - gallon - - - - - 42 US_liquid_gallon - - barrel - bbl - - - - - barrel/4 - - firkin - - - - US_liquid_gallon/4 - - US_liquid_quart - liquid_quart - quart - - - - US_liquid_gallon/8 - - US_liquid_pint - liquid_pint - pint - pt - - - - US_liquid_gallon/16 - - US_liquid_cup - liquid_cup - cup - - - - US_liquid_gallon/32 - - US_liquid_gill - liquid_gill - gill - - - - US_liquid_gallon/128 - - US_fluid_ounce - US_liquid_ounce - fluid_ounce - liquid_ounce - oz - floz - - - - US_fluid_ounce/2 - - tablespoon - Tbl - Tbsp - tbsp - Tblsp - tblsp - - - - US_fluid_ounce/8 - fldr - - - US_fluid_ounce/16 - - dram - dr - - - - tablespoon/3 - - teaspoon - tsp - - - - 4.546090e-3 m^3 - - UK_liquid_gallon - - - - UK_liquid_gallon/4 - - UK_liquid_quart - - - - UK_liquid_gallon/8 - - UK_liquid_pint - - - - UK_liquid_gallon/16 - - UK_liquid_cup - - - - UK_liquid_gallon/32 - - UK_liquid_gill - - - - UK_liquid_gallon/160 - - UK_fluid_ounce - UK_liquid_ounce - - - - lg(re (1e-6 m)^3) - BZ - - - - - 1e-8 s - shake - - - 8.616409e4 s - - sidereal_day - - - - 3.590170e3 s - - sidereal_hour - - - - 5.983617e1 s - - sidereal_minute - - - - 0.9972696 s - - sidereal_second - - - - 3.155815e7 s - - sidereal_year - - - - - 3.15569259747e7 s - - tropical_year - year - - yr - - - - 29.530589 day - - lunar_month - - - - 365 day - - common_year - - - - 366 day - - leap_year - - - - 365.25 day - - Julian_year - - - - 365.2425 day - - Gregorian_year - - - - 27.321661 day - - sidereal_month - - - - 27.321582 day - - tropical_month - - - - 14 day - - fortnight - - - - 7 day - week - - - 0.01 s - jiffy - - - 1e9 year - eon - - - year/12 - month - - - - - 1e6 m^3/s - - sverdrup - - Sv - - - - - - 9.806650 m/s^2 - - standard_free_fall - - - - standard_free_fall - gravity - - - - - gravity 1000 kg/m^3 - - conventional_water - water - H2O - h2o - - - - gravity 999.972 kg/m^3 - - - water_4C - waters_4C - - - water_39F - waters_39F - - - - - gravity 999.001 kg/m^3 - - - water_60F - waters_60F - - - - - gravity 13595.10 kg/m^3 - - - mercury_0C - mercuries_0C - - - mercury_32F - mercuries_32F - - - conventional_mercury - conventional_mercuries - - Hg - - - - gravity 13556.8 kg/m^3 - - - mercury_60F - mercuries_60F - - - - - - - standard_free_fall - force - - - 1e-5 N - dyne - - - 9.806650e-3 N - pond - - - 9.806650 N - - force_kilogram - - kilogram_force - kilograms_force - - kgf - - - - 2.780139e-1 N - - force_ounce - - ounce_force - ounces_force - - ozf - - - - 4.4482216152605 N - - force_pound - - pound_force - pounds_force - - lbf - - - - 1.382550e-1 N - - poundal - - - - gram force - - - gram_force - grams_force - - - force_gram - - gf - - - - 2000 force_pound - - force_ton - - ton_force - tons_force - - - - - 1000 lbf - kip - - - - - 1.01325e5 Pa - - standard_atmosphere - atmosphere - atm - - - - 1 kg gravity/cm2 - - technical_atmosphere - at - - - - cm H2O - - cm_H2O - cmH2O - - - - inch water_39F - - - inch_H2O_39F - inches_H2O_39F - - - - - inch water_60F - - - inch_H2O_60F - inches_H2O_60F - - - - - foot water - - - foot_water - feet_water - - - foot_H2O - feet_H2O - - - footH2O - feetH2O - - ftH2O - fth2o - - - - cm Hg - - cm_Hg - cmHg - - - - mm mercury_0C - - - millimeter_Hg_0C - millimeters_Hg_0C - - - - - inch mercury_32F - - - inch_Hg_32F - inches_Hg_32F - - - - - inch mercury_60F - - - inch_Hg_60F - inches_Hg_60F - - - - - mm Hg - - - millimeter_Hg - millimeters_Hg - - torr - mm_Hg - mm_hg - mmHg - mmhg - - - - inch Hg - - - inch_Hg - inches_Hg - - in_Hg - inHg - - - - 1 pound gravity/in^2 - psi - - - kip/in^2 - ksi - - - 0.1 N/m^2 - - barie - barye - - - - lg(re 20e-6 Pa) - B_SPL - - - - - 1e-1 Pa.s - - poise - - - - 1e-4 m^2/s - - stokes - St - - - - 10/(Pa.s) - rhe - - - - - 1e-7 J - erg - - - 1.05505585262e3 J - - - IT_Btu - IT_Btus - - - Btu - Btus - - - - - 1.05506e8 J - - EC_therm - - - - 4.184000 J - - thermochemical_calorie - - - - 4.1868 J - - IT_calorie - calorie - cal - - - - 4.184 MJ/kg - - TNT - - - - 4.184e9 J - - - ton_TNT - tons_TNT - - - - - 1.054804e8 J - - US_therm - therm - thm - - - - watt.hour - - watthour - - - - 1e9 eV - bev - - - - - V.A - - voltampere - VA - - - - 9.80950e3 W - - boiler_horsepower - - - - 7.456999e2 W - - shaft_horsepower - horsepower - hp - - - - 7.35499e2 W - - metric_horsepower - - - - 7.460000e2 W - - electric_horsepower - - - - 7.46043e2 W - - water_horsepower - - - - 7.4570e2 W - - UK_horsepower - - - - 12000 Btu/hr - - refrigeration_ton - - ton_of_refrigeration - tons_of_refrigeration - - - - - lg(re 1 W) - BW - - - lg(re 1 mW) - Bm - - - - - 1.55e-1 K.m^2/W - clo - - - - - 10 A - abampere - - - 7.957747e-1 A - gilbert - - - 3.335640e-10 A - - statampere - - - - 10 A - biot - - - 1e9 F - abfarad - - - 1e-9 H - - abhenry - - - - 1e9 S - abmho - - - 1e-9 ohm - abohm - - - 1e-8 V - abvolt - - - 1.602176487e-19 C - e - - - 9.64957e4 C - - chemical_faraday - - - - 9.65219e4 C - - physical_faraday - - - - 9.648531e4 C - - C12_faraday - faraday - - - - 1e-9 T - - gamma - - - - 1e-4 T - - gauss - - - - 1e-8 Wb - - maxwell - - - - 7.957747e1 A/m - - oersted - Oe - - - - 3.335640e-10 C - - statcoulomb - - - - 1.112650e-12 F - - statfarad - - - - 8.987554e11 H - - stathenry - - - - 1.112650e-12 S - - statmho - - - - 8.987554e11 ohm - - statohm - - - - 2.997925e2 V - - statvolt - - - - 1.256637e-7 Wb - - unit_pole - - - - lg(re 1 V) - BV - - - lg(re 0.775 V) - Bv - - - lg(re 1e-6 V) - BµV - - - - - - K/1.8 - - °R - - degree_rankine - degrees_rankine - - - degreeR - degreesR - - - degree_R - degrees_R - - - degR - degsR - - - deg_R - degs_R - - - - - °R @ 459.67 - - °F - - fahrenheit - - degree_fahrenheit - degrees_fahrenheit - - - degreeF - degreesF - - - degree_F - degrees_F - - - degF - degsF - - - deg_F - degs_F - - - - - - - 1.076391e-1 lx - - footcandle - - - - 3.426259 cd/m^2 - - footlambert - - - - (1e4/pi) cd/m^2 - - lambert - - - - 1e4 cd/m^2 - - stilb - sb - - - - 1e4 lm/m^2 - - phot - ph - - - - 1 cd/m^2 - - nit - nt - - - - 4.184000e4 J/m^2 - - langley - - - - cd/(pi m^2) - - blondel - apostilb - - - - - - 100/m - kayser - - - gravity - - geopotential - dynamic - gp - - - - 2056 hours - - work_year - - - - work_year/12 - - work_month - - - - 1e-6 m^2 s^-1 K kg^-1 - - potential_vorticity_unit - PVU - - - - 1 - - count - - - - 446.2 micromoles/meter^2 - - dobson - DU - - - - - 1e-3 - - - practical_salinity_unit - practical_salinity_units - - psu - - - - calendar_year/12 - - - calendar_month - - cM - - - - 1 - - - level - levels - - - - - 1 - - - layer - layers - - - - - 1 - - - sigma_level - sigma_levels - - - - - 1 - - - decibel - decibels - - dB - - - - 10 dB - - - bel - bels - - - - - diff --git a/bmi_tester/data/udunits/udunits2-common.xml.nonCF b/bmi_tester/data/udunits/udunits2-common.xml.nonCF deleted file mode 100644 index 07a69a9..0000000 --- a/bmi_tester/data/udunits/udunits2-common.xml.nonCF +++ /dev/null @@ -1,1640 +0,0 @@ - - - - - - s - sec - - - A - amp - - - K - - °K - - degree_kelvin - degrees_kelvin - - - degree_K - degrees_K - - - degreeK - degreesK - - - deg_K - degs_K - - - degK - degsK - - - - - cd - candle - - - mole - einstein - - - Hz - - baud - Bd - bps - - - - degree_Celsius - - - celsius - - degree_C - degrees_C - - - degreeC - degreesC - - - deg_C - degs_C - - - degC - degsC - - - - - knot - - kt - kts - - - - - - 6.02214179e23/mol - - avogadro_constant - - - - - 0.01 - - percent - - % - - - - 1e-6 - - ppm - ppmv - - - - 1e-9 - - ppb - ppbv - - - - 1e-12 - - ppt - pptv - - - - 1e-15 - - ppq - ppqv - - - - - - 0.9 arc_degree - grade - - - 2 pi rad - - circle - cycle - turn - revolution - rotation - - - - arc_degree - - - degree_north - degrees_north - - - degree_N - degrees_N - - - degreeN - degreesN - - - degree_east - degrees_east - - - degree_E - degrees_E - - - degreeE - degreesE - - - degree_true - degrees_true - - - degree_T - degrees_T - - - degreeT - degreesT - - - - - -1 degree_east - - - degree_west - degrees_west - - - degree_W - degrees_W - - - degreeW - degreesW - - - - - - - 2.916667e-2 kg - - assay_ton - - - - 2.834952e-2 kg - - avoirdupois_ounce - - - - 4.5359237e-1 kg - - avoirdupois_pound - pound - lb - - - - 2e-4 kg - - carat - - - - 6.479891e-5 kg - - grain - gr - - - - 5.080235e1 kg - - long_hundredweight - - - - 1.555174e-3 kg - - pennyweight - - - - 4.535924e1 kg - - short_hundredweight - - - - 14.59390 kg - - slug - - - - 3.110348e-2 kg - - troy_ounce - apothecary_ounce - - - - 3.732417e-1 kg - - troy_pound - apothecary_pound - - - - 20 grain - scruple - - - 60 grain - apdram - - - 480 grain - apounce - - - 5760 grain - appound - - - 94 pound - bag - - - 2000 pound - - short_ton - ton - - - - 2240 pound - - long_ton - - - - - - 1e-15 m - - fermi - - - - 9.46073e15 m - - light_year - - - - 1e-6 m - - micron - - - - 2.54e-5 m - - mil - - - - 3.085678e16 m - - parsec - - - - 3.514598e-4 m - - printers_point - - - - 2.011684e1 m - - chain - - - - 12 printers_point - - printers_pica - pica - - - - nautical_mile - - nmile - - - - (1200/3937) m - - - US_survey_foot - US_survey_feet - - - - - 3 US_survey_feet - - US_survey_yard - - - - 5280 US_survey_feet - - US_survey_mile - US_statute_mile - - - - 16.5 US_survey_feet - - rod - pole - perch - - - - 660 US_survey_feet - furlong - - - 6 US_survey_feet - fathom - - - 2.54 cm - - international_inch - inch - in - - - - 12 international_inches - - - international_foot - international_feet - - - foot - feet - - ft - - - - 3 international_feet - - international_yard - yard - yd - - - - 5280 international_feet - - international_mile - mile - mi - - - - inch/72 - - big_point - - - - inch/3 - - barleycorn - - - - 191.835 foot - - arpentlin - - - - - - rotation/second - - - rotation_per_second - rotations_per_second - - rps - cps - - - - rotation/minute - rpm - - - - - 1.111111e-7 kg/m - - denier - - - - 1e-6 kg/m - - tex - - - - - - 5.72135e-11 kg/(Pa.s.m^2) - - - perm_0C - perms_0C - - - - - 5.74525e-11 kg/(Pa.s.m^2) - - - perm_23C - perms_23C - - - - - - - 5.067075e-10 m^2 - - circular_mil - - - - 9.869233e-13 m^2 - darcy - - - 160 rod^2 - - acre - - - - - - 1.233489e3 m^3 - - - - acre_foot - acre_feet - - - - - 2.359737e-3 m^3 - - - board_foot - board_feet - - - - - 3.523907e-2 m^3 - - bushel - bu - - - - bushel/4 - - peck - pk - - - - 4.546090e-3 m^3 - - Canadian_liquid_gallon - - - - 4.404884e-3 m^3 - - US_dry_gallon - - - - cm^3 - cc - - - 1 m^3 - - stere - - - - 2.831685 m^3 - - register_ton - - - - US_dry_gallon/4 - - US_dry_quart - dry_quart - - - - US_dry_gallon/8 - - US_dry_pint - dry_pint - - - - 3.785412e-3 m^3 - - US_liquid_gallon - liquid_gallon - gallon - - - - - 42 US_liquid_gallon - - barrel - bbl - - - - - barrel/4 - - firkin - - - - US_liquid_gallon/4 - - US_liquid_quart - liquid_quart - quart - - - - US_liquid_gallon/8 - - US_liquid_pint - liquid_pint - pint - pt - - - - US_liquid_gallon/16 - - US_liquid_cup - liquid_cup - cup - - - - US_liquid_gallon/32 - - US_liquid_gill - liquid_gill - gill - - - - US_liquid_gallon/128 - - US_fluid_ounce - US_liquid_ounce - fluid_ounce - liquid_ounce - oz - floz - - - - US_fluid_ounce/2 - - tablespoon - Tbl - Tbsp - tbsp - Tblsp - tblsp - - - - US_fluid_ounce/8 - fldr - - - US_fluid_ounce/16 - - dram - dr - - - - tablespoon/3 - - teaspoon - tsp - - - - 4.546090e-3 m^3 - - UK_liquid_gallon - - - - UK_liquid_gallon/4 - - UK_liquid_quart - - - - UK_liquid_gallon/8 - - UK_liquid_pint - - - - UK_liquid_gallon/16 - - UK_liquid_cup - - - - UK_liquid_gallon/32 - - UK_liquid_gill - - - - UK_liquid_gallon/160 - - UK_fluid_ounce - UK_liquid_ounce - - - - lg(re (1e-6 m)^3) - BZ - - - - - 1e-8 s - shake - - - 8.616409e4 s - - sidereal_day - - - - 3.590170e3 s - - sidereal_hour - - - - 5.983617e1 s - - sidereal_minute - - - - 0.9972696 s - - sidereal_second - - - - 3.155815e7 s - - sidereal_year - - - - - 3.15569259747e7 s - - tropical_year - year - - yr - - - - 29.530589 day - - lunar_month - - - - 365 day - - common_year - - - - 366 day - - leap_year - - - - 365.25 day - - Julian_year - - - - 365.2425 day - - Gregorian_year - - - - 27.321661 day - - sidereal_month - - - - 27.321582 day - - tropical_month - - - - 14 day - - fortnight - - - - 7 day - week - - - 0.01 s - jiffy - - - 1e9 year - eon - - - year/12 - month - - - - - 1e6 m^3/s - - sverdrup - - - - - - - 9.806650 m/s^2 - - standard_free_fall - - - - standard_free_fall - gravity - - - - - gravity 1000 kg/m^3 - - conventional_water - water - H2O - h2o - - - - gravity 999.972 kg/m^3 - - - water_4C - waters_4C - - - water_39F - waters_39F - - - - - gravity 999.001 kg/m^3 - - - water_60F - waters_60F - - - - - gravity 13595.10 kg/m^3 - - - mercury_0C - mercuries_0C - - - mercury_32F - mercuries_32F - - - conventional_mercury - conventional_mercuries - - Hg - - - - gravity 13556.8 kg/m^3 - - - mercury_60F - mercuries_60F - - - - - - - standard_free_fall - force - - - 1e-5 N - dyne - - - 9.806650e-3 N - pond - - - 9.806650 N - - force_kilogram - - kilogram_force - kilograms_force - - kgf - - - - 2.780139e-1 N - - force_ounce - - ounce_force - ounces_force - - ozf - - - - 4.4482216152605 N - - force_pound - - pound_force - pounds_force - - lbf - - - - 1.382550e-1 N - - poundal - - - - gram force - - - gram_force - grams_force - - - force_gram - - gf - - - - 2000 force_pound - - force_ton - - ton_force - tons_force - - - - - 1000 lbf - kip - - - - - 1.01325e5 Pa - - standard_atmosphere - atmosphere - atm - - - - 1 kg gravity/cm2 - - technical_atmosphere - at - - - - cm H2O - - cm_H2O - cmH2O - - - - inch water_39F - - - inch_H2O_39F - inches_H2O_39F - - - - - inch water_60F - - - inch_H2O_60F - inches_H2O_60F - - - - - foot water - - - foot_water - feet_water - - - foot_H2O - feet_H2O - - - footH2O - feetH2O - - ftH2O - fth2o - - - - cm Hg - - cm_Hg - cmHg - - - - mm mercury_0C - - - millimeter_Hg_0C - millimeters_Hg_0C - - - - - inch mercury_32F - - - inch_Hg_32F - inches_Hg_32F - - - - - inch mercury_60F - - - inch_Hg_60F - inches_Hg_60F - - - - - mm Hg - - - millimeter_Hg - millimeters_Hg - - torr - mm_Hg - mm_hg - mmHg - mmhg - - - - inch Hg - - - inch_Hg - inches_Hg - - in_Hg - inHg - - - - 1 pound gravity/in^2 - psi - - - kip/in^2 - ksi - - - 0.1 N/m^2 - - barie - barye - - - - lg(re 20e-6 Pa) - B_SPL - - - - - 1e-1 Pa.s - - poise - - - - 1e-4 m^2/s - - stokes - St - - - - 10/(Pa.s) - rhe - - - - - 1e-7 J - erg - - - 1.05505585262e3 J - - - IT_Btu - IT_Btus - - - Btu - Btus - - - - - 1.05506e8 J - - EC_therm - - - - 4.184000 J - - thermochemical_calorie - - - - 4.1868 J - - IT_calorie - calorie - cal - - - - 4.184 MJ/kg - - TNT - - - - 4.184e9 J - - - ton_TNT - tons_TNT - - - - - 1.054804e8 J - - US_therm - therm - thm - - - - watt.hour - - watthour - - - - 1e9 eV - bev - - - - - V.A - - voltampere - VA - - - - 9.80950e3 W - - boiler_horsepower - - - - 7.456999e2 W - - shaft_horsepower - horsepower - hp - - - - 7.35499e2 W - - metric_horsepower - - - - 7.460000e2 W - - electric_horsepower - - - - 7.46043e2 W - - water_horsepower - - - - 7.4570e2 W - - UK_horsepower - - - - 12000 Btu/hr - - refrigeration_ton - - ton_of_refrigeration - tons_of_refrigeration - - - - - lg(re 1 W) - BW - - - lg(re 1 mW) - Bm - - - - - 1.55e-1 K.m^2/W - clo - - - - - 10 A - abampere - - - 7.957747e-1 A - gilbert - - - 3.335640e-10 A - - statampere - - - - 10 A - biot - - - 1e9 F - abfarad - - - 1e-9 H - - abhenry - - - - 1e9 S - abmho - - - 1e-9 ohm - abohm - - - 1e-8 V - abvolt - - - 1.602176487e-19 C - e - - - 9.64957e4 C - - chemical_faraday - - - - 9.65219e4 C - - physical_faraday - - - - 9.648531e4 C - - C12_faraday - faraday - - - - 1e-9 T - - gamma - - - - 1e-4 T - - gauss - - - - 1e-8 Wb - - maxwell - - - - 7.957747e1 A/m - - oersted - Oe - - - - 3.335640e-10 C - - statcoulomb - - - - 1.112650e-12 F - - statfarad - - - - 8.987554e11 H - - stathenry - - - - 1.112650e-12 S - - statmho - - - - 8.987554e11 ohm - - statohm - - - - 2.997925e2 V - - statvolt - - - - 1.256637e-7 Wb - - unit_pole - - - - lg(re 1 V) - BV - - - lg(re 0.775 V) - Bv - - - lg(re 1e-6 V) - BµV - - - - - - K/1.8 - - °R - - degree_rankine - degrees_rankine - - - degreeR - degreesR - - - degree_R - degrees_R - - - degR - degsR - - - deg_R - degs_R - - - - - °R @ 459.67 - - °F - - fahrenheit - - degree_fahrenheit - degrees_fahrenheit - - - degreeF - degreesF - - - degree_F - degrees_F - - - degF - degsF - - - deg_F - degs_F - - - - - - - 1.076391e-1 lx - - footcandle - - - - 3.426259 cd/m^2 - - footlambert - - - - (1e4/pi) cd/m^2 - - lambert - - - - 1e4 cd/m^2 - - stilb - sb - - - - 1e4 lm/m^2 - - phot - ph - - - - 1 cd/m^2 - - nit - nt - - - - 4.184000e4 J/m^2 - - langley - - - - cd/(pi m^2) - - blondel - apostilb - - - - - - 100/m - kayser - - - gravity - - geopotential - dynamic - gp - - - - 2056 hours - - work_year - - - - work_year/12 - - work_month - - - - 1e-6 m^2 s^-1 K kg^-1 - - potential_vorticity_unit - PVU - - - - 1 - - count - - - - 446.2 micromoles/meter^2 - - dobson - DU - - - diff --git a/bmi_tester/data/udunits/udunits2-derived.xml b/bmi_tester/data/udunits/udunits2-derived.xml deleted file mode 100644 index d01250a..0000000 --- a/bmi_tester/data/udunits/udunits2-derived.xml +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - radian - rad - - - rad^2 - steradian - sr - - - 1/s - hertz - Hz - - - 1e-3 kg - gram - g - - - m.kg/s^2 - newton - N - - - N/m^2 - pascal - Pa - - - N.m - joule - J - - - J/s - watt - W - - - s.A - coulomb - C - - - W/A - volt - V - - - C/V - farad - F - - - V/A - ohm - Ω - - - - - - A/V - siemens - S - - - V.s - weber - Wb - - - Wb/m^2 - tesla - T - - - Wb/A - henry - H - - - K @ 273.15 - - degree_Celsius - degrees_Celsius - - °C - - - cd.sr - lumen - lm - - - lm/m^2 - lux - lx - - - mol/s - katal - kat - - - - - 1/s - - - becquerel - Bq - - - - J/kg - gray - Gy - - - J/kg - - sievert - - - - diff --git a/bmi_tester/data/udunits/udunits2-derived.xml.nonCF b/bmi_tester/data/udunits/udunits2-derived.xml.nonCF deleted file mode 100644 index 72432bd..0000000 --- a/bmi_tester/data/udunits/udunits2-derived.xml.nonCF +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - radian - rad - - - rad^2 - steradian - sr - - - 1/s - hertz - Hz - - - 1e-3 kg - gram - g - - - m.kg/s^2 - newton - N - - - N/m^2 - pascal - Pa - - - N.m - joule - J - - - J/s - watt - W - - - s.A - coulomb - C - - - W/A - volt - V - - - C/V - farad - F - - - V/A - ohm - Ω - - - - - - A/V - siemens - S - - - V.s - weber - Wb - - - Wb/m^2 - tesla - T - - - Wb/A - henry - H - - - K @ 273.15 - - degree_Celsius - degrees_Celsius - - °C - - - cd.sr - lumen - lm - - - lm/m^2 - lux - lx - - - mol/s - katal - kat - - - - - 1/s - - - becquerel - Bq - - - - J/kg - gray - Gy - - - J/kg - - sievert - Sv - - - diff --git a/bmi_tester/data/udunits/udunits2-prefixes.xml b/bmi_tester/data/udunits/udunits2-prefixes.xml deleted file mode 100644 index 686b3e5..0000000 --- a/bmi_tester/data/udunits/udunits2-prefixes.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - 1e24 yotta Y - - - 1e21 zetta Z - - - 1e18 exa E - - - 1e15 peta P - - - 1e12 tera T - - - 1e9 giga G - - - 1e6 mega M - - - 1e3 kilo k - - - 100 hecto h - - - 10 deka da - - - .1 deci d - - - .01 centi c - - - 1e-3 milli m - - - 1e-6 - micro - µ - μ - u - - - 1e-9 nano n - - - 1e-12 pico p - - - 1e-15 femto f - - - 1e-18 atto a - - - 1e-21 zepto z - - - 1e-24 yocto y - - diff --git a/bmi_tester/data/udunits/udunits2.xml b/bmi_tester/data/udunits/udunits2.xml deleted file mode 100644 index 729828f..0000000 --- a/bmi_tester/data/udunits/udunits2.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - udunits2-prefixes.xml - udunits2-base.xml - udunits2-derived.xml - udunits2-accepted.xml - udunits2-common.xml - diff --git a/docs/api/index.rst b/docs/api/index.rst index 5fbee2f..774ef3c 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,7 +1,7 @@ -Developer Documentation ------------------------ +Python API for ``bmi-tester`` +----------------------------- .. toctree:: :maxdepth: 2 - modules + /_generated/api/bmi_tester diff --git a/docs/authors.md b/docs/authors.md new file mode 100644 index 0000000..73a1374 --- /dev/null +++ b/docs/authors.md @@ -0,0 +1,2 @@ +```{include} ../AUTHORS.md +``` diff --git a/docs/authors.rst b/docs/authors.rst deleted file mode 100644 index e122f91..0000000 --- a/docs/authors.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../AUTHORS.rst diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..c6d99d6 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,2 @@ +```{include} ../CHANGES.md +``` diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index d9e113e..0000000 --- a/docs/changelog.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CHANGES.rst diff --git a/docs/conf.py b/docs/conf.py index ef4e25e..db04f8b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,12 +30,15 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + "myst_parser", "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.todo", "sphinx.ext.mathjax", "sphinx.ext.napoleon", "sphinx.ext.autosummary", + "sphinx_copybutton", + "sphinx_inline_tabs", ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..4ac9d6b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,42 @@ +```{image} _static/bmi-tester-logo-text-lowercase.png +:align: center +:alt: Sequence +:scale: 10% +:target: https://sequence.readthedocs.org/ +``` + +The `bmi_tester` package provides command-line utilities for testing +a Python classes that implement the Basic Model Interface (BMI). +`bmi_tester` also provides a Python interface to the tester that allows +users to run tests programmatically. The package is also easily +extendable so that new tests can be added to the suite. + +```{include} ../README.md +``` + +# API Reference + +If you are looking for information on a specific function, class, or +method, this part of the documentation is for you. + +```{toctree} +:maxdepth: 2 + +api/index +``` + +# Miscellaneous Pages + +```{toctree} +:maxdepth: 2 + +authors +changelog +license +``` + +## Indices and tables + +- {ref}`genindex` +- {ref}`modindex` +- {ref}`search` diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index acbd982..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,54 +0,0 @@ -.. image:: _static/bmi-tester-logo-text-lowercase.png - :align: center - :scale: 10% - :alt: Sequence - :target: https://sequence.readthedocs.org/ - - -The `bmi_tester` package provides command-line utilities for testing -a Python classes that implement the Basic Model Interface (BMI). -`bmi_tester` also provides a Python interface to the tester that allows -users to run tests programmatically. The package is also easily -extendable so that new tests can be added to the suite. - - -Getting Started ---------------- - -.. toctree:: - :maxdepth: 3 - - readme - -.. usage -.. installation - -API Reference -------------- - -If you are looking for information on a specific function, class, or -method, this part of the documentation is for you. - -.. toctree:: - :maxdepth: 2 - - api/index - - -Miscellaneous Pages -------------------- - -.. toctree:: - :maxdepth: 2 - - authors - changelog - license - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000..84798e5 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,2 @@ +```{include} ../LICENSE.md +``` diff --git a/docs/license.rst b/docs/license.rst deleted file mode 100644 index 47dcb56..0000000 --- a/docs/license.rst +++ /dev/null @@ -1,5 +0,0 @@ -======= -License -======= - -.. include:: ../LICENSE.rst diff --git a/docs/readme.rst b/docs/readme.rst deleted file mode 100644 index 72a3355..0000000 --- a/docs/readme.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../README.rst diff --git a/noxfile.py b/noxfile.py index 0b8865c..3d5a93f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -6,14 +6,16 @@ import nox -PROJECT = "bmi-tester" +PROJECT = "bmi_tester" ROOT = pathlib.Path(__file__).parent +PYTHON_VERSION = "3.12" -@nox.session +@nox.session(python=PYTHON_VERSION, venv_backend="conda") def test(session: nox.Session) -> None: """Run the tests.""" session.install(".[testing]") + session.conda_install("gimli.units", channel=["nodefaults", "conda-forge"]) args = ["--cov", PROJECT, "-vvv"] + session.posargs @@ -25,6 +27,17 @@ def test(session: nox.Session) -> None: session.run("coverage", "report", "--ignore-errors", "--show-missing") +@nox.session(name="test-cli", python=PYTHON_VERSION, venv_backend="conda") +def test_cli(session: nox.Session) -> None: + """Run the tests.""" + session.install(".") + session.conda_install( + "gimli.units", "pymt_topography", channel=["nodefaults", "conda-forge"] + ) + + session.run("bmi-test", "pymt_topography:Topography") + + @nox.session def lint(session: nox.Session) -> None: """Look for lint.""" @@ -41,6 +54,55 @@ def build(session: nox.Session) -> None: session.run("python", "-m", "build", "--outdir", "./build/wheelhouse") +@nox.session(name="build-docs") +def build_docs(session: nox.Session) -> None: + """Build the docs.""" + + build_generated_docs(session) + + session.install( + *("-r", "requirements-docs.txt"), + *("-r", "requirements.txt"), + ) + session.install(".") + + pathlib.Path("build").mkdir(exist_ok=True) + + session.run( + "sphinx-build", + "-b", + "html", + "-W", + "--keep-going", + "docs", + "build/html", + ) + session.log("generated docs at build/html") + + +@nox.session(name="build-generated-docs", reuse_venv=True) +def build_generated_docs(session: nox.Session) -> None: + """Build auto-generated files used by the docs.""" + # FOLDER["docs_generated"].mkdir(exist_ok=True) + + session.install("sphinx") + session.install("-e", ".") + + with session.chdir(ROOT): + os.makedirs("src/docs/_generated/api", exist_ok=True) + session.log("generating api docs in docs/api") + session.run( + "sphinx-apidoc", + "-e", + "-force", + "--no-toc", + "--module-first", + "-o", + "docs/_generated/api", + "src/bmi_tester", + ) + + @nox.session(name="publish-testpypi") def publish_testpypi(session): """Publish wheelhouse/* to TestPyPI.""" @@ -83,7 +145,7 @@ def clean(session): shutil.rmtree("build", ignore_errors=True) shutil.rmtree("dist", ignore_errors=True) - shutil.rmtree(f"{PROJECT}.egg-info", ignore_errors=True) + shutil.rmtree(f"src/{PROJECT}.egg-info", ignore_errors=True) shutil.rmtree(".pytest_cache", ignore_errors=True) shutil.rmtree(".venv", ignore_errors=True) diff --git a/pyproject.toml b/pyproject.toml index b2113ad..a5229a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,8 +31,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Physics", ] dependencies = [ - "click", - "gimli.units", + "importlib-resources; python_version < '3.12'", "model-metadata>=0.8", "numpy", "pytest", @@ -48,6 +47,9 @@ dynamic = [ text = "MIT" [project.optional-dependencies] +units = [ + "gimli.units" +] dev = [ "black", "flake8", @@ -74,10 +76,10 @@ Homepage = "https://csdms.colorado.edu" Repository = "https://github.com/csdms/bmi-tester" [project.scripts] -bmi-test = "bmi_tester.bmipytest:main" +bmi-test = "bmi_tester.main:main" [project.entry-points."bmi.plugins"] -bmi_test = "bmi_tester.bmipytest:configure_parser_test" +bmi_test = "bmi_tester.main:configure_parser_test" [build-system] requires = [ @@ -87,12 +89,17 @@ requires = [ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic.readme] -file = "README.rst" -content-type = "text/x-rst" +file = "README.md" +content-type = "text/markdown" [tool.setuptools.dynamic.version] attr = "bmi_tester._version.__version__" +[tool.setuptools.packages.find] +where = [ + "src", +] + [tool.isort] multi_line_output = 3 include_trailing_comma = true @@ -128,4 +135,4 @@ doctest_optionflags = [ [tool.zest-releaser] tag-format = "v{version}" -python-file-with-version = "bmi_tester/_version.py" +python-file-with-version = "src/bmi_tester/_version.py" diff --git a/requirements-docs.txt b/requirements-docs.txt index d5feb86..f02d7cd 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1 +1,4 @@ -sphinx>=1.5.1 +myst-parser +sphinx +sphinx-inline-tabs +sphinx_copybutton diff --git a/requirements.txt b/requirements.txt index b038a0f..881f1d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -click -gimli.units model_metadata numpy pytest diff --git a/bmi_tester/__init__.py b/src/bmi_tester/__init__.py similarity index 100% rename from bmi_tester/__init__.py rename to src/bmi_tester/__init__.py diff --git a/src/bmi_tester/__main__.py b/src/bmi_tester/__main__.py new file mode 100644 index 0000000..3a19821 --- /dev/null +++ b/src/bmi_tester/__main__.py @@ -0,0 +1,4 @@ +from bmi_tester.main import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/bmi_tester/bootstrap/__init__.py b/src/bmi_tester/_bootstrap/__init__.py similarity index 100% rename from bmi_tester/bootstrap/__init__.py rename to src/bmi_tester/_bootstrap/__init__.py diff --git a/bmi_tester/bootstrap/test_control.py b/src/bmi_tester/_bootstrap/control_test.py similarity index 100% rename from bmi_tester/bootstrap/test_control.py rename to src/bmi_tester/_bootstrap/control_test.py diff --git a/bmi_tester/tests/__init__.py b/src/bmi_tester/_tests/__init__.py similarity index 100% rename from bmi_tester/tests/__init__.py rename to src/bmi_tester/_tests/__init__.py diff --git a/bmi_tester/tests/conftest.py b/src/bmi_tester/_tests/conftest.py similarity index 100% rename from bmi_tester/tests/conftest.py rename to src/bmi_tester/_tests/conftest.py diff --git a/bmi_tester/tests/stage_1/__init__.py b/src/bmi_tester/_tests/stage_1/__init__.py similarity index 100% rename from bmi_tester/tests/stage_1/__init__.py rename to src/bmi_tester/_tests/stage_1/__init__.py diff --git a/bmi_tester/tests/stage_1/test_info.py b/src/bmi_tester/_tests/stage_1/info_test.py similarity index 100% rename from bmi_tester/tests/stage_1/test_info.py rename to src/bmi_tester/_tests/stage_1/info_test.py diff --git a/bmi_tester/tests/stage_1/test_time.py b/src/bmi_tester/_tests/stage_1/time_test.py similarity index 93% rename from bmi_tester/tests/stage_1/test_time.py rename to src/bmi_tester/_tests/stage_1/time_test.py index e020130..a93690d 100644 --- a/bmi_tester/tests/stage_1/test_time.py +++ b/src/bmi_tester/_tests/stage_1/time_test.py @@ -6,6 +6,7 @@ import pytest from pytest import approx +from bmi_tester.api import WITH_GIMLI_UNITS from bmi_tester.api import check_unit_is_dimensionless from bmi_tester.api import check_unit_is_time from bmi_tester.api import check_unit_is_valid @@ -35,7 +36,7 @@ def test_time_units_is_str(initialized_bmi): assert isinstance(units, str) -# @pytest.mark.skipif(cfunits is None, reason="cfunits is broken on this platform") +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") def test_time_units_is_valid(initialized_bmi): """Test the units of time are valid.""" units = initialized_bmi.get_time_units() diff --git a/bmi_tester/tests/stage_2/__init__.py b/src/bmi_tester/_tests/stage_2/__init__.py similarity index 100% rename from bmi_tester/tests/stage_2/__init__.py rename to src/bmi_tester/_tests/stage_2/__init__.py diff --git a/bmi_tester/tests/stage_2/test_var.py b/src/bmi_tester/_tests/stage_2/var_test.py similarity index 94% rename from bmi_tester/tests/stage_2/test_var.py rename to src/bmi_tester/_tests/stage_2/var_test.py index cc59f4f..c47d667 100644 --- a/bmi_tester/tests/stage_2/test_var.py +++ b/src/bmi_tester/_tests/stage_2/var_test.py @@ -1,6 +1,7 @@ import numpy as np import pytest +from bmi_tester.api import WITH_GIMLI_UNITS from bmi_tester.api import check_unit_is_valid @@ -52,6 +53,7 @@ def test_get_var_type(initialized_bmi, var_name): raise AssertionError(f"get_var_type: bad data type name ({dtype})") +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") def test_get_var_units(initialized_bmi, var_name): """Test the units of the variables.""" units = initialized_bmi.get_var_units(var_name) diff --git a/bmi_tester/tests/stage_3/__init__.py b/src/bmi_tester/_tests/stage_3/__init__.py similarity index 100% rename from bmi_tester/tests/stage_3/__init__.py rename to src/bmi_tester/_tests/stage_3/__init__.py diff --git a/bmi_tester/tests/stage_3/test_grid.py b/src/bmi_tester/_tests/stage_3/grid_test.py similarity index 97% rename from bmi_tester/tests/stage_3/test_grid.py rename to src/bmi_tester/_tests/stage_3/grid_test.py index 84e24ee..0709c82 100644 --- a/bmi_tester/tests/stage_3/test_grid.py +++ b/src/bmi_tester/_tests/stage_3/grid_test.py @@ -2,8 +2,8 @@ import pytest from packaging.version import Version -from ..conftest import BMI_VERSION -from ..conftest import skip_if_grid_type_is_not +from bmi_tester._tests.conftest import BMI_VERSION +from bmi_tester._tests.conftest import skip_if_grid_type_is_not VALID_GRID_TYPES = ( "none", diff --git a/bmi_tester/tests/stage_3/test_grid_uniform_rectilinear.py b/src/bmi_tester/_tests/stage_3/grid_uniform_rectilinear_test.py similarity index 96% rename from bmi_tester/tests/stage_3/test_grid_uniform_rectilinear.py rename to src/bmi_tester/_tests/stage_3/grid_uniform_rectilinear_test.py index 5c12c97..16e13c1 100644 --- a/bmi_tester/tests/stage_3/test_grid_uniform_rectilinear.py +++ b/src/bmi_tester/_tests/stage_3/grid_uniform_rectilinear_test.py @@ -2,7 +2,7 @@ import numpy as np -from ..conftest import skip_if_grid_type_is_not +from bmi_tester._tests.conftest import skip_if_grid_type_is_not # @pytest.mark.dependency(depends=["test_get_grid_rank"], scope="session") diff --git a/bmi_tester/tests/stage_3/test_grid_unstructured.py b/src/bmi_tester/_tests/stage_3/grid_unstructured_test.py similarity index 97% rename from bmi_tester/tests/stage_3/test_grid_unstructured.py rename to src/bmi_tester/_tests/stage_3/grid_unstructured_test.py index 7a0d27e..7d3f393 100644 --- a/bmi_tester/tests/stage_3/test_grid_unstructured.py +++ b/src/bmi_tester/_tests/stage_3/grid_unstructured_test.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from ..conftest import skip_if_grid_type_is +from bmi_tester._tests.conftest import skip_if_grid_type_is # @pytest.mark.dependency(depends=["test_get_grid_type", "test_get_grid_rank"], scope="session") diff --git a/bmi_tester/tests/stage_3/test_value.py b/src/bmi_tester/_tests/stage_3/value_test.py similarity index 93% rename from bmi_tester/tests/stage_3/test_value.py rename to src/bmi_tester/_tests/stage_3/value_test.py index 426aad4..971c462 100644 --- a/bmi_tester/tests/stage_3/test_value.py +++ b/src/bmi_tester/_tests/stage_3/value_test.py @@ -2,11 +2,10 @@ import pytest from packaging.version import Version -from bmi_tester.api import empty_var_buffer - -from ..conftest import BMI_VERSION_STRING -from ..conftest import INPUT_FILE -from ..conftest import Bmi +from bmi_tester._tests.conftest import BMI_VERSION_STRING +from bmi_tester._tests.conftest import INPUT_FILE +from bmi_tester._tests.conftest import Bmi +from bmi_tester._utils import empty_var_buffer # from pytest_dependency import depends diff --git a/src/bmi_tester/_utils.py b/src/bmi_tester/_utils.py new file mode 100644 index 0000000..30fb36f --- /dev/null +++ b/src/bmi_tester/_utils.py @@ -0,0 +1,52 @@ +import contextlib +import os + +import numpy as np + + +@contextlib.contextmanager +def suppress_stdout(): + null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)] + # Save the actual stdout (1) and stderr (2) file descriptors. + save_fds = [os.dup(1), os.dup(2)] + + os.dup2(null_fds[0], 1) + os.dup2(null_fds[1], 2) + + yield + + # Re-assign the real stdout/stderr back to (1) and (2) + os.dup2(save_fds[0], 1) + os.dup2(save_fds[1], 2) + # Close the null files + for fd in null_fds + save_fds: + os.close(fd) + + +def empty_var_buffer(bmi, var_name): + """Create an empty value buffer for a BMI variable. + + Examples + -------- + >>> import numpy as np + >>> from bmi_tester.api import empty_var_buffer + + >>> class Bmi: + ... def get_var_nbytes(self, name): + ... return 128 + ... def get_var_type(self, name): + ... return "int" + + >>> buffer = empty_var_buffer(Bmi(), "var-name") + >>> np.any(buffer != 0) + True + >>> buffer[:] = 0 + >>> np.all(buffer == 0) + True + """ + nbytes = bmi.get_var_nbytes(var_name) + dtype = np.dtype(bmi.get_var_type(var_name)) + + values = np.frombuffer(np.random.bytes(nbytes), dtype=dtype).copy() + + return values diff --git a/bmi_tester/_version.py b/src/bmi_tester/_version.py similarity index 100% rename from bmi_tester/_version.py rename to src/bmi_tester/_version.py diff --git a/src/bmi_tester/api.py b/src/bmi_tester/api.py new file mode 100644 index 0000000..c064a13 --- /dev/null +++ b/src/bmi_tester/api.py @@ -0,0 +1,77 @@ +import os +import sys +from collections.abc import Iterable +from collections.abc import Sequence + +try: + import gimli +except ImportError: + WITH_GIMLI_UNITS = False + SECONDS = None +else: + WITH_GIMLI_UNITS = True + SECONDS = gimli.units.Unit("s") + +import pytest + +if sys.version_info >= (3, 12): # pragma: no cover (PY12+) + from importlib.resources import files +else: # pragma: no cover ( int: + if tests_dir is None: + tests_dir = str(files(__name__) / "_bootstrap") + if isinstance(tests_dir, str): + args = [tests_dir] + else: + args = list(tests_dir) + + os.environ["BMITEST_CLASS"] = package + os.environ["BMITEST_INPUT_FILE"] = input_file + os.environ["BMI_VERSION_STRING"] = bmi_version + + if manifest: + if isinstance(manifest, str): + with open(manifest) as fp: + manifest = fp.read() + else: + manifest = os.linesep.join(manifest) + os.environ["BMITEST_MANIFEST"] = manifest + + extra_args = list(extra_args or []) + if help_pytest: + extra_args.append("--help") + args += extra_args + return pytest.main(args) + + +def check_unit_is_valid(unit): + try: + gimli.units.Unit(unit) + except gimli.UnitNameError: + return False + else: + return True + + +def check_unit_is_time(unit): + try: + gimli.units.Unit(unit).to(SECONDS) + except gimli.IncompatibleUnitsError: + return False + else: + return True + + +def check_unit_is_dimensionless(unit): + return gimli.units.Unit(unit).is_dimensionless diff --git a/src/bmi_tester/main.py b/src/bmi_tester/main.py new file mode 100644 index 0000000..942d67c --- /dev/null +++ b/src/bmi_tester/main.py @@ -0,0 +1,256 @@ +from __future__ import annotations + +import argparse +import os +import pathlib +import sys +import tempfile +from collections.abc import Iterator +from collections.abc import Sequence +from functools import partial +from typing import Any + +from model_metadata._utils import as_cwd +from model_metadata._utils import load_component +from model_metadata._utils import parse_entry_point +from model_metadata.api import query +from model_metadata.api import stage +from model_metadata.errors import BadEntryPointError +from pytest import ExitCode + +if sys.version_info >= (3, 12): # pragma: no cover (PY12+) + import importlib.resources as importlib_resources +else: # pragma: no cover ( int: + """Validate a BMI implementation. + + \b + Examples: + + Test a BMI for the class *Hydrotrend* in module *pymt_hydrotrend*, + + $ bmi-test pymt_hydrotrend:Hydrotrend + + This will test the BMI with a default set of input files as obtained + through the model metadata associated with the component. + + If the component you would like to test does not have model metadata + that bmi-tester recognizes, or you would like to test with a non-default + set of input files, use the *--root-dir* and *--config-file* options. + + $ bmi-tests pymt_hydrotrend:Hydrotrend --root-dir=my_files/ --config-file=config.txt + + where *my_files* is a folder that contains the input files to test with + and *config.txt* is the configuration file, which will be passed to the + *initialize* method. + """ + parser = argparse.ArgumentParser(prog="bmi-test") + parser.add_argument( + "--version", action="version", version=f"bmi-test {__version__}" + ) + parser.add_argument("entry_point", action=ValidateEntryPoint) + parser.add_argument( + "--quiet", + action="store_true", + help=( + "Don't emit non-error messages to stderr. Errors are still emitted, " + "silence those with 2>/dev/null." + ), + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Also emit status messages to stderr.", + ) + parser.add_argument( + "--help-pytest", action="store_true", help="Print help about pytest." + ) + parser.add_argument( + "--root-dir", + action=ValidatePathExists, + help="Define root directory for BMI tests", + ) + parser.add_argument( + "--config-file", + action=ValidatePathExists, + help="Name of model configuration file", + ) + parser.add_argument( + "--manifest", + action=ValidatePathExists, + help="Path to manifest file of staged model input files.", + ) + parser.add_argument( + "--bmi-version", default="2.0", help="BMI version to test against" + ) + + args = parser.parse_args(argv) + + if args.root_dir: + if not args.config_file: + err("using --root-dir but no config file specified (use --config-file)") + return -1 + + stage = Stage( + args.root_dir, + config_file=args.config_file, + manifest=args.manifest, + ) + else: + stage = Stage.from_entry_point(":".join(args.entry_point)) + + with as_cwd(stage.dir): + status = run_the_tests( + ":".join(args.entry_point), + stage.config_file, + stage.manifest, + bmi_version=args.bmi_version, + ) + + if not args.quiet: + if status == ExitCode.OK: + out("🎉 All tests passed!") + else: + err("😞 There were errors") + + return status + + +class Stage: + def __init__( + self, + stage_dir: str, + config_file: str, + manifest: str | Iterator[str] | None = None, + ): + self._stage_dir = stage_dir + self._config_file = config_file + if manifest is None: + manifest = stage_dir + if isinstance(manifest, str): + self._manifest = tuple(os.listdir(manifest)) + else: + self._manifest = tuple(manifest) + + @property + def dir(self) -> str: + return self._stage_dir + + @property + def manifest(self) -> tuple[str, ...]: + return self._manifest + + @property + def config_file(self) -> str: + return self._config_file + + @classmethod + def from_entry_point(cls, entry_point: str) -> Stage: + module_name, class_name = parse_entry_point(entry_point) + try: + Bmi = load_component(module_name, class_name) + except ImportError: + err( + f"unable to import BMI implementation, {class_name}," + f" from {module_name}" + ) + raise + + stage_dir = tempfile.mkdtemp() + manifest = stage(Bmi, str(stage_dir)) + config_file = query(Bmi, "run.config_file.path") + + return cls(stage_dir, config_file=config_file, manifest=manifest) + + +def run_the_tests( + entry_point: str, + config_file: str, + manifest: tuple[str, ...], + bmi_version: str = "2.0", + pytest_help: bool = False, +) -> int: + path_to_tests = pathlib.Path(str(importlib_resources.files(__name__))).resolve() + stages = [ + str(p) + for p in [path_to_tests / "_bootstrap"] + + sorted((path_to_tests / "_tests").glob("stage_*")) + ] + + status = 0 + for stage_dir in stages: + status = check_bmi( + entry_point, + tests_dir=stage_dir, + input_file=config_file, + manifest=manifest, + bmi_version=bmi_version, + # extra_args=pytest_args + ("-vvv",), + help_pytest=pytest_help, + ) + if status != ExitCode.OK: + break + + return status + + +class ValidateEntryPoint(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: str | Sequence[Any] | None, + option_string: str | None = None, + ) -> None: + if not isinstance(values, str): + parser.error(f"{values}: invalid entry-point: not a string") + + entry_point = values + try: + module_name, class_name = parse_entry_point(entry_point) + except BadEntryPointError as error: + parser.error(f"{entry_point}: invalid entry-point: {str(error)}") + else: + setattr(namespace, self.dest, (module_name, class_name)) + + +class ValidatePathExists(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: str | Sequence[Any] | None, + option_string: str | None = None, + ) -> None: + if not isinstance(values, str): + parser.error(f"{values}: invalid path: not a string") + + path = values + + # if not os.path.isdir(path): + if not os.path.exists(path): + parser.error(f"{path}: path does not exist") + else: + setattr(namespace, self.dest, path) + + +def _tree(files): + tree = [] + prefix = ["|--"] * (len(files) - 1) + ["`--"] + for p, fname in zip(prefix, files): + tree.append(f"{p} {fname}") + return os.linesep.join(tree) + + +if __name__ == "__main__": + SystemExit(main()) diff --git a/testing/__init__.py b/testing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bmi_tester/bmi.py b/testing/bmi.py similarity index 99% rename from bmi_tester/bmi.py rename to testing/bmi.py index e49a481..3eab0ec 100644 --- a/bmi_tester/bmi.py +++ b/testing/bmi.py @@ -1,7 +1,7 @@ import numpy as np -class Bmi: +class BmiExample: _input_var_names = ("land_surface__elevation", "land_surface_air__temperature") _output_var_names = ( "land_surface__elevation", diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_api.py b/tests/api_test.py similarity index 85% rename from tests/test_api.py rename to tests/api_test.py index ac7c426..372fd01 100644 --- a/tests/test_api.py +++ b/tests/api_test.py @@ -13,7 +13,7 @@ def test_bmi_check(tmpdir): touch_file("input.yaml") assert ( check_bmi( - "bmi_tester.bmi:Bmi", input_file="input.yaml", extra_args=["-vvv"] + "testing.bmi:BmiExample", input_file="input.yaml", extra_args=["-vvv"] ) == 0 ) @@ -24,7 +24,7 @@ def test_bmi_check_with_manifest_as_list(tmpdir): touch_file("input.yaml") assert ( check_bmi( - "bmi_tester.bmi:Bmi", + "testing.bmi:BmiExample", extra_args=["-vvv"], input_file="input.yaml", manifest=["input.yaml"], @@ -41,7 +41,7 @@ def test_bmi_check_with_manifest_as_string(tmpdir): touch_file("data.dat") assert ( check_bmi( - "bmi_tester.bmi:Bmi", + "testing.bmi:BmiExample", extra_args=["-vvv"], input_file="input.yaml", manifest="manifest.txt", diff --git a/tests/test_bmipytest.py b/tests/test_bmipytest.py deleted file mode 100644 index 7b08e37..0000000 --- a/tests/test_bmipytest.py +++ /dev/null @@ -1,14 +0,0 @@ -from bmi_tester.bmipytest import load_component - -entry_point = "os:getcwd" -module_name, cls_name = entry_point.split(":") - - -def test_component_is_not_string(): - component = load_component(entry_point) - assert not isinstance(component, str) - - -def test_component_is_classname(): - component = load_component(entry_point) - assert component.__name__ == cls_name diff --git a/tests/test_units.py b/tests/test_units.py deleted file mode 100644 index f28b5dd..0000000 --- a/tests/test_units.py +++ /dev/null @@ -1,40 +0,0 @@ -from bmi_tester.api import check_unit_is_dimensionless -from bmi_tester.api import check_unit_is_time -from bmi_tester.api import check_unit_is_valid - - -def test_check_valid_units(): - assert check_unit_is_valid("m") - assert check_unit_is_valid("m / s") - assert check_unit_is_valid("m s-1") - assert check_unit_is_valid("N m") - assert check_unit_is_valid("N.m") - assert check_unit_is_valid("m^2") - assert check_unit_is_valid("m2") - assert check_unit_is_valid("") - assert check_unit_is_valid("1") - - -def test_check_invalid_units(): - assert not check_unit_is_valid("foo") - assert not check_unit_is_valid("m ** 2") - assert not check_unit_is_valid("-") - - -def test_dimensionless_units(): - assert check_unit_is_dimensionless("") - assert check_unit_is_dimensionless("1") - assert not check_unit_is_dimensionless("m") - # assert not check_unit_is_dimensionless("-") - - -def test_time_units(): - assert check_unit_is_time("s") - assert check_unit_is_time("d") - assert check_unit_is_time("yr") - assert check_unit_is_time("seconds since 1970-01-01") - assert check_unit_is_time("seconds since 1970-01-01 00:00:00 UTC") - assert check_unit_is_time("days since 1970-01-01 00:00:00 UTC") - assert check_unit_is_time("years since 1970-01-01 00:00:00 UTC") - - assert not check_unit_is_time("m") diff --git a/tests/units_test.py b/tests/units_test.py new file mode 100644 index 0000000..6b616b9 --- /dev/null +++ b/tests/units_test.py @@ -0,0 +1,53 @@ +import pytest + +from bmi_tester.api import WITH_GIMLI_UNITS +from bmi_tester.api import check_unit_is_dimensionless +from bmi_tester.api import check_unit_is_time +from bmi_tester.api import check_unit_is_valid + + +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") +@pytest.mark.parametrize( + "unit", ("m", "m / s", "m s-1", "N m", "N.m", "m^2", "m2", "", "1") +) +def test_check_valid_units(unit): + assert check_unit_is_valid(unit) + + +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") +@pytest.mark.parametrize("unit", ("foo", "m ** 2", "-")) +def test_check_invalid_units(unit): + assert not check_unit_is_valid(unit) + + +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") +@pytest.mark.parametrize("unit", ("", "1")) +def test_dimensionless_units(unit): + assert check_unit_is_dimensionless(unit) + + +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") +def test_not_dimensionless_units(): + assert not check_unit_is_dimensionless("m") + + +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") +@pytest.mark.parametrize( + "unit", + ( + "s", + "d", + "yr", + "seconds since 1970-01-01", + "seconds since 1970-01-01 00:00:00 UTC", + "days since 1970-01-01 00:00:00 UTC", + "years since 1970-01-01 00:00:00 UTC", + ), +) +def test_time_units(unit): + assert check_unit_is_time(unit) + + +@pytest.mark.skipif(not WITH_GIMLI_UNITS, reason="gimli.units is not installed") +def test_not_time_units(): + assert not check_unit_is_time("m")