constriction 0.4.2

Entropy coders for research and production (Rust and Python).
Documentation
name: test

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always

jobs:
  rust-test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Install latest stable Rust
        uses: dtolnay/rust-toolchain@stable
        id: install-rust
        with:
          components: rustfmt, clippy
          targets: x86_64-unknown-none

      - name: Cache Rust dependencies
        uses: actions/cache@v4
        with:
          path: |
            target
            .cargo_home
            .cargo
          key: ${{ runner.os }}-no_pybindings-rust-${{ steps.install-rust.outputs.cachekey }}-${{ hashFiles('Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-no_pybindings-rust-${{ steps.install-rust.outputs.cachekey }}-

      - name: Check rust formatting
        run: cargo fmt -- --check

      - name: Lint with clippy
        env:
          RUSTFLAGS: "-D warnings"
        run: cargo clippy --all-targets --all-features

      # The environment variable `RUSTFLAGS` doesn't actually seem to have any effect on rustdoc,
      # but we set it here to the same value as in all other cargo runs as changing it would
      # cause unnecessary recompilation of some dependencies.
      - name: Check for broken doc links
        env:
          RUSTFLAGS: "-D warnings"
        run: cargo rustdoc -- -D rustdoc::broken-intra-doc-links

      - name: Test in development mode
        env:
          RUSTFLAGS: "-D warnings"
        run: cargo test

      - name: Test in release mode
        env:
          RUSTFLAGS: "-D warnings"
        run: cargo test --release

      - name: "Install cargo-msrv"
        uses: taiki-e/install-action@v2
        if: matrix.os != 'windows-latest' # work around https://github.com/foresterre/cargo-msrv/issues/1036
        with:
          tool: cargo-msrv
      
      - name: "Verify minimum supported rust version (MSRV)"
        if: matrix.os != 'windows-latest' # work around https://github.com/foresterre/cargo-msrv/issues/1036
        run: cargo msrv verify -- cargo check --all-features

      # On MacOS, the "Check Semver" action below needs a python version that works with
      # the PyO3 library of both the current and the previous version of constriction.
      - name: Set up Python 3.13
        uses: actions/setup-python@v5
        with:
          python-version: 3.13

      - name: Check semver
        uses: obi1kenobi/cargo-semver-checks-action@v2

      - name: Test `no_std` compatibility
        shell: bash
        working-directory: ensure_no_std
        run: cargo build

  miri-test:
    name: miri
    runs-on: ${{ matrix.os }}
    needs: rust-test
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Find out latest rust nightly version that has miri
        id: rust-version
        shell: bash
        run: |
          case ${{ matrix.os }} in
            ubuntu-latest)
              arch="x86_64-unknown-linux-gnu"
              ;;

            macos-latest)
              arch="x86_64-apple-darwin"
              ;;

            windows-latest)
              arch="x86_64-pc-windows-msvc"
              ;;
          esac

          version="nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/$arch/miri)"
          echo "Found version: $version"
          echo "rustc=$version" >> $GITHUB_OUTPUT

      - name: Cache Rust dependencies
        uses: actions/cache@v4
        with:
          path: |
            target
            .cargo_home
            .cargo
          key: ${{ runner.os }}-no_pybindings-miri-${{ steps.install-rust.outputs.cachekey }}-${{ hashFiles('Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-no_pybindings-miri-${{ steps.install-rust.outputs.cachekey }}-

      - name: Install rust ${{ steps.rust-version.outputs.rustc }}
        uses: dtolnay/rust-toolchain@master
        id: install-rust
        with:
          toolchain: ${{ steps.rust-version.outputs.rustc }}
          components: miri, rust-src

      - name: Run tests in miri
        env:
          RUSTFLAGS: "-Zrandomize-layout"
          MIRIFLAGS: "-Zmiri-symbolic-alignment-check -Zmiri-disable-isolation"
        run: cargo +${{ steps.rust-version.outputs.rustc }} miri test --no-fail-fast

  python-test:
    runs-on: ${{ matrix.os }}
    needs: rust-test
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install poetry
          poetry install --no-root

      - name: Install latest stable Rust
        uses: dtolnay/rust-toolchain@stable
        id: install-rust

      - name: Cache Rust dependencies
        uses: actions/cache@v4
        with:
          path: |
            target
            .cargo_home
            .cargo
          key: ${{ runner.os }}-pybindings-rust-${{ steps.install-rust.outputs.cachekey }}-${{ hashFiles('Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-pybindings-rust-${{ steps.install-rust.outputs.cachekey }}-

      - name: Build Python package
        run: poetry run maturin develop --release --features pybindings

      - name: pytest with numpy version 1.x
        if: matrix.python-version != '3.13' && matrix.python-version != '3.14'
        shell: bash
        run: |
          poetry run python -m pip install numpy~=1.16
          INSTALLED_NUMPY_VERSION=$(poetry run python -c 'import numpy; print(numpy.__version__)')
          echo "Installed numpy version: $INSTALLED_NUMPY_VERSION"
          if [ "x${INSTALLED_NUMPY_VERSION:0:2}y" == "x1.y" ]; then
            echo "Numpy version matches expectation."
          else
            echo "ERROR: wrong numpy version."
            exit 1
          fi
          poetry run pytest tests/python | tee pytest-numpy1.out
          exit ${PIPESTATUS[0]}

      - name: Verify that pytest used correct python version
        if: matrix.python-version != '3.13' && matrix.python-version != '3.14'
        shell: bash
        run: |
          USED_PYTHON_VERSION=$(grep -A1 -iE '^=+ test session starts =+$' pytest-numpy1.out | perl -ne 'if ($_ =~ / -- Python (\d+\.\d+)\.\d+/i) { print "$1"; }')
          echo "Expected python version: ${{ matrix.python-version }}"
          echo "Found python version:    $USED_PYTHON_VERSION"
          if [ "x${{ matrix.python-version }}y" == "x${USED_PYTHON_VERSION}y" ]; then
            echo "Versions match."
          else
            echo "ERROR: versions don't match."
            exit 1
          fi

      - name: pytest with numpy version 2.x
        if: matrix.python-version != '3.9'
        shell: bash
        run: |
          if [ "${{ matrix.python-version }}" == "3.10" ]; then
            poetry run python -m pip install numpy~=2.1
          else
            poetry run python -m pip install numpy~=2.4
          fi
          INSTALLED_NUMPY_VERSION=$(poetry run python -c 'import numpy; print(numpy.__version__)')
          echo "Installed numpy version: $INSTALLED_NUMPY_VERSION"
          if [ "x${INSTALLED_NUMPY_VERSION:0:2}y" == "x2.y" ]; then
            echo "Numpy version matches expectation."
          else
            echo "ERROR: wrong numpy version."
            exit 1
          fi
          poetry run pytest tests/python | tee pytest-numpy2.out
          exit ${PIPESTATUS[0]}

      - name: Verify that pytest used correct python version
        if: matrix.python-version != '3.9'
        shell: bash
        run: |
          USED_PYTHON_VERSION=$(grep -A1 -iE '^=+ test session starts =+$' pytest-numpy2.out | perl -ne 'if ($_ =~ / -- Python (\d+\.\d+)\.\d+/i) { print "$1"; }')
          echo "Expected python version: ${{ matrix.python-version }}"
          echo "Found python version:    $USED_PYTHON_VERSION"
          if [ "x${{ matrix.python-version }}y" == "x${USED_PYTHON_VERSION}y" ]; then
            echo "Versions match."
          else
            echo "ERROR: versions don't match."
            exit 1
          fi

  testall:
    runs-on: ubuntu-latest
    name: Meta job for all tests
    needs: [rust-test, miri-test, python-test]
    steps:
      - name: Done
        run: echo "All tests successful."