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:


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'

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

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

    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')
        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)

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

    # NOTE: support building packages with metadata 2.3.
    session.run('twine', *session.posargs)

def tests(session):
    """Run test cases and record the test coverage."""
    # Run the test cases and report the test coverage.
    package = 'pypfilt'
        Path(session.bin) / 'pytest',
    # Ensure that regression test outputs have not changed.
        'git', 'diff', '--exit-code', '--stat', 'tests/', external=True

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

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)