Skip to content

hnsw: split graph.rs tests into sibling files (4313 -> 2849 lines) #348

hnsw: split graph.rs tests into sibling files (4313 -> 2849 lines)

hnsw: split graph.rs tests into sibling files (4313 -> 2849 lines) #348

Workflow file for this run

name: CI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
# Cancel in-flight runs of the same workflow when a new commit is pushed.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: "0" # CI builds don't benefit from incremental; smaller cache.
CARGO_PROFILE_DEV_DEBUG: "0" # Reduces target/ size, improves cache hit rate.
RUSTFLAGS: -Dwarnings
# Testing strategy grounded in real-world ANN library issues:
# - hnswlib #635: M vs Mcurmax bug in neighbor selection
# - hnswlib #626: Use-after-free in deletion
# - hnswlib #592: Vector not normalized for cosine distance
# - hnswlib #608: Issues after deleting vectors
# - faiss #4295: Integer overflow on large datasets
# - usearch #405: Quantization issues with i8 + inner product
# See docs/TESTING.md for full details
jobs:
# Cheap fan-in gate: fmt + clippy + basic check on ubuntu.
# Failure here aborts every downstream matrix job (saves ~5-10 minutes per
# PR on a fmt typo). Pattern from tokio-rs/tokio.
basics:
name: Basics (fmt + clippy + check)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --check
- name: Clippy (hnsw)
run: cargo clippy --no-default-features --features hnsw --all-targets -- -D warnings
- name: Clippy (all features)
run: cargo clippy --all-features --all-targets -- -D warnings
# Primary test suite (x86) -- nextest for parallel test-binary execution.
test:
name: Test (ubuntu)
runs-on: ubuntu-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- name: Build
run: cargo build --no-default-features --features hnsw
- name: Test (unit + integration)
run: cargo nextest run --no-default-features --features hnsw
- name: Test (sq4)
run: cargo nextest run --no-default-features --features hnsw,sq4
- name: Test (sq8)
run: cargo nextest run --no-default-features --features hnsw,sq8
- name: Test (lemur)
run: cargo nextest run --no-default-features --features lemur
- name: Test (all features, lib only)
run: cargo nextest run --lib --all-features
env:
RUSTFLAGS: ""
# nextest does not support doctests yet (nextest-rs/nextest#16).
- name: Doc tests
run: cargo test --doc --no-default-features --features hnsw
- name: Property tests
run: cargo nextest run --no-default-features --features hnsw -E 'test(property_)'
# Semver-checks only on PRs.
semver:
name: Semver check
runs-on: ubuntu-latest
needs: basics
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks
- run: cargo semver-checks --default-features
# ARM native testing (M1/M2 - tests NEON code paths)
test-arm:
name: Test (macos / ARM)
runs-on: macos-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- name: Build (ARM)
run: cargo build --no-default-features --features hnsw
- name: Test (ARM)
run: cargo nextest run --no-default-features --features hnsw
# MSRV check
msrv:
name: MSRV (1.89)
runs-on: ubuntu-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.89.0
- uses: Swatinem/rust-cache@v2
- name: Check MSRV
run: |
rm -f Cargo.lock # v4 lock requires Cargo 1.83+
cargo check --no-default-features --features hnsw
# Feature-matrix compilation (catches cfg + optional-dep drift)
feature-matrix:
name: Feature matrix
runs-on: ubuntu-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-hack
- name: Compile each feature (no dev-deps)
run: cargo hack check --each-feature --no-dev-deps --exclude-features persistence
# Cross-compilation check for different targets
cross-compile:
name: Cross-compile (${{ matrix.target }})
runs-on: ubuntu-latest
needs: basics
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Check ${{ matrix.target }}
run: cargo check --target ${{ matrix.target }} --no-default-features --features hnsw
# Recall regression detection -- catches hnswlib #635-class issues.
recall-regression:
name: Recall regression
runs-on: ubuntu-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build release
run: cargo build --release --no-default-features --features hnsw
- name: Run recall benchmark
run: |
cargo run --release --example 02_measure_recall --no-default-features --features hnsw 2>&1 | tee recall_output.txt
# Fail if recall@10 drops below 80% at ef=100
# This catches issues like hnswlib #635 (wrong M parameter)
awk '/^[[:space:]]*100[[:space:]]/ {
gsub(/%/, "", $2); found=1
if ($2 + 0 < 80.0) { print "FAIL: ef=100 recall " $2 "% < 80%"; exit 1 }
}
END { if (!found) { print "FAIL: ef=100 row missing from output"; exit 1 } }' recall_output.txt
# Regression tests for known bugs
regression:
name: Regression (known bugs)
runs-on: ubuntu-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- name: Run regression tests
run: cargo nextest run --test regression_known_bugs --no-default-features --features hnsw
# Python bindings: build wheel, lint, type-check, stubtest, pytest.
# Stubtest catches drift between the hand-written `_core.pyi` and the
# compiled module the moment a Rust signature changes (voyager pattern).
python:
name: Python (pyvicinity)
runs-on: ubuntu-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.13"
# GH Actions runners don't have a venv, and maturin develop needs
# one. Pin the venv via $VIRTUAL_ENV + $GITHUB_PATH so subsequent
# steps inherit it. maturin develop is preferred over `maturin build
# + pip install` because it drops `_core.so` directly into the source
# tree, which avoids the cwd-shadows-site-packages problem when
# stubtest imports `pyvicinity._core`.
- name: Set up virtualenv
run: |
python -m venv .venv
echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV"
- name: Install build + test deps
run: python -m pip install --upgrade pip maturin pytest mypy ruff numpy
- name: Build extension (editable)
run: maturin develop --release --features hnsw,python
- name: Ruff
run: python -m ruff check pyvicinity tests/test_python.py
- name: mypy --strict
run: python -m mypy --strict pyvicinity
- name: Stubtest (.pyi vs compiled module)
run: python -m mypy.stubtest pyvicinity._core
- name: Pytest
run: python -m pytest tests/test_python.py -v
# Documentation build
docs:
name: Docs
runs-on: ubuntu-latest
needs: basics
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build docs
run: cargo doc --no-default-features --features hnsw --no-deps
env:
RUSTDOCFLAGS: -Dwarnings