hnsw: split graph.rs tests into sibling files (4313 -> 2849 lines) #348
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |