Testing with nox

The pypfilt testing suite uses the pytest framework and the nox automation tool. The test cases are contained in the ./tests directory, and can be run with:

nox

This will also reproduce the images included in the online documentation.

Controlling how tests are run

You can pass arguments to pytest by running the tests session and using -- to separate nox arguments from pytest arguments. For example:

  • To run specific test cases whose names contain a specific string:

    nox -s tests -- -k PATTERN
    
  • To control the display of logging messages:

    nox -s tests -- -o log_cli=true --log-cli-level=INFO
    

For further details, see the pytest command-line documentation or run:

nox -s tests -- --help

The nox configuration

The noxfile.py contents are shown below, and include targets that check whether the documentation in ./doc builds correctly.

import nox
import os
from pathlib import Path
import shutil


# Ensure that nox supports session tags.
nox.needs_version = '>=2022.8.7'


@nox.session()
def build(session):
    """Build source and binary (wheel) packages."""
    build_dir = Path('build')
    if build_dir.exists():
        shutil.rmtree(build_dir)

    pkg_dir = Path('.nox-build')
    if pkg_dir.exists():
        shutil.rmtree(pkg_dir)

    session.install('build')
    session.run('python', '-m', 'build', '--outdir', pkg_dir)

    # On local machines, copy the wheel to a known directory that can be used
    # by upstream packages.
    if os.environ.get('GITLAB_CI') == 'true':
        print('Detected GitLab CI, will not copy package')
    else:
        packages = sorted(pkg_dir.glob('*.whl'))
        if len(packages) != 1:
            raise ValueError(f'Found {len(packages)} packages')
        package = packages[0]
        dest_dir = Path.home() / '.ci-packages'
        dest_dir.mkdir(parents=True, exist_ok=True)
        print(f'Copying {package} to {dest_dir}')
        shutil.copy(package, dest_dir)


@nox.session(reuse_venv=True)
def publish(session):
    """Publish a binary (wheel) package to PyPI."""
    if not session.posargs:
        print('No package specified, nothing to publish')
        return

    session.install('twine')
    # NOTE: support building packages with metadata 2.3.
    session.install('pkginfo>=1.10.0')
    session.run('twine', *session.posargs)


@nox.session()
def tests(session):
    """Run test cases and record the test coverage."""
    session.install('.[plot,tests]')
    # Run the test cases and report the test coverage.
    package = 'pypfilt'
    session.run(
        'python3',
        '-bb',
        Path(session.bin) / 'pytest',
        f'--cov={package}',
        '--pyargs',
        package,
        './tests',
        './doc',
        *session.posargs,
    )
    # Ensure that regression test outputs have not changed.
    session.run(
        'git', 'diff', '--exit-code', '--stat', 'tests/', external=True
    )


@nox.session(reuse_venv=True)
def docs(session):
    """Build the HTML documentation."""
    session.install('-r', 'requirements-rtd.txt')
    session.run(
        'sphinx-build', '-W', '-b', 'html', './doc', './doc/build/html'
    )


@nox.session(tags=['check'])
def ruff(session):
    """Check code for linter warnings and formatting issues."""
    check_files = ['src', 'tests', 'doc', 'noxfile.py']
    session.install('ruff ~= 0.3')
    session.run('ruff', 'check', *check_files)
    session.run('ruff', 'format', '--diff', *check_files)