Skip to content

ci: measure PyO3 binding Rust-layer coverage (cargo-llvm-cov + pytest)#64

Merged
Navi Bot (project-navi-bot) merged 7 commits into
mainfrom
ci/python-binding-coverage
May 26, 2026
Merged

ci: measure PyO3 binding Rust-layer coverage (cargo-llvm-cov + pytest)#64
Navi Bot (project-navi-bot) merged 7 commits into
mainfrom
ci/python-binding-coverage

Conversation

@Fieldnote-Echo

Copy link
Copy Markdown
Member

Summary

Completes the "measure Python binding coverage" piece of the coverage push. The core coverage.yml job is scoped -p ordvec, so the binding's FFI guard layer — ordvec-python/src/lib.rs, ~890 lines of ensure_finite / check_width / contiguity / bounds / overflow guards — had no coverage signal at all.

Adds coverage-python.yml, which measures the Rust binding layer as exercised by the existing pytest suite, with no new dependency (reuses cargo-llvm-cov + maturin + pytest):

  1. cargo llvm-cov show-env exports a RUSTC_WRAPPER that instruments the listed crates (incl. _ordvec).
  2. maturin develop builds that instrumented extension into a venv.
  3. pytest drives it — each test process (including the red-team suite's subprocess-isolated abort probes) emits its own .profraw.
  4. cargo llvm-cov report -p ordvec-python aggregates them.

Uploaded to Codecov under a python-binding flag so the binding's number is tracked separately from the core line.

Result (measured locally, full suite)

ordvec-python/src/lib.rs84.1% line / 84.8% region / 70.0% function. The uncovered ~16% is overwhelmingly unreachable FFI error arms: the from_shape_vec(...).map_err(...) reshape-failure paths (the shape is computed correctly upstream) and the checked_mul usize-overflow guards (can't fire on a 64-bit target). Function coverage reads lower because each of those error closures is its own uncovered function.

Notes

Test plan

  • Validated end-to-end locally: show-env → instrumented maturin developpytest (365 tests, exit 0) → report -p ordvec-python = 84.1% line
  • coverage-python.yml parses as YAML
  • CI: coverage-python job runs green on this PR (its first real run) + actionlint

The core coverage.yml job is scoped -p ordvec, so the binding's FFI guard layer (ordvec-python/src/lib.rs) had no coverage signal. coverage-python.yml fills the gap: cargo-llvm-cov instruments the cdylib via show-env's RUSTC_WRAPPER, maturin builds that instrumented extension into a venv, pytest drives it, and report -p ordvec-python aggregates the per-process profraw. Uploaded to Codecov under a python-binding flag (tracked apart from the core line); informational, no floor yet.

Measured locally against the full pytest suite (incl. the binding red-team from #63) at 84.1% line / 70.0% function — the uncovered share is mostly unreachable FFI error arms (from_shape_vec map_err; checked_mul usize-overflow guards that can't fire on 64-bit). The job reports whatever the suite exercises, so the number rises once #63's red-team tests reach main.

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@gemini-code-assist

Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add PyO3 binding Rust-layer coverage measurement workflow

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds coverage measurement for PyO3 binding's Rust FFI layer
• Uses cargo-llvm-cov to instrument cdylib via RUSTC_WRAPPER
• Integrates maturin build with pytest suite for coverage collection
• Uploads binding coverage separately to Codecov under python-binding flag
Diagram
flowchart LR
  A["cargo llvm-cov show-env"] -->|RUSTC_WRAPPER| B["maturin develop"]
  B -->|instrumented extension| C["pytest suite"]
  C -->|profraw files| D["cargo llvm-cov report"]
  D -->|lcov output| E["Codecov python-binding flag"]

Loading

File Changes

1. .github/workflows/coverage-python.yml ✨ Enhancement +81/-0

New PyO3 binding coverage workflow with cargo-llvm-cov

• New workflow file measuring PyO3 binding Rust layer coverage
• Triggers on changes to ordvec-python, src, Cargo files, or workflow itself
• Instruments cdylib using cargo-llvm-cov show-env RUSTC_WRAPPER
• Builds instrumented extension with maturin, runs pytest, aggregates coverage
• Uploads lcov report to Codecov under python-binding flag without credentials

.github/workflows/coverage-python.yml


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 25, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Bash-dependent env sourcing ✓ Resolved 🐞 Bug ☼ Reliability
Description
The coverage step uses bash-only process substitution (source <(...)) without pinning the runner
shell, and cargo llvm-cov show-env failures may not stop the step, potentially running
pytest/report without instrumentation and uploading misleading/empty coverage.
Code

.github/workflows/coverage-python.yml[70]

Evidence
The workflow explicitly depends on show-env being sourced before building/running tests, but it
uses source <(cargo llvm-cov show-env --sh), which is bash-specific. Unlike other workflows that
explicitly pin bash as the run shell, this workflow does not pin the shell, increasing the chance of
portability and silent-failure issues.

.github/workflows/coverage-python.yml[62-75]
.github/workflows/ci.yml[31-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The workflow relies on bash-specific process substitution (`source <(…)`) to load `cargo llvm-cov show-env` exports. This is not portable to non-bash shells, and failures in the process-substitution command may not reliably fail the step, which can silently skip instrumentation.

### Issue Context
This job’s correctness depends on `show-env` staying active for the subsequent `maturin develop`, `pytest`, and `cargo llvm-cov report` commands.

### Fix
1. Pin the shell to bash for the workflow/job (or at least the step).
2. Change `show-env` sourcing to a failure-propagating form (e.g., write to a temp file, then `source` it), so a `show-env` failure stops the step.

Example:
```yaml
defaults:
 run:
   shell: bash
```
And in the step:
```bash
cargo llvm-cov show-env --sh > /tmp/llvmcov-env.sh
source /tmp/llvmcov-env.sh
```

### Fix Focus Areas
- .github/workflows/coverage-python.yml[62-75]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@codecov

codecov Bot commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Address qodo round 1 on #64: the coverage step used bash-only process substitution (source <(...)) without pinning the shell, and a cargo llvm-cov show-env failure would not stop the step — risking pytest/report running uninstrumented and uploading misleading coverage. Pin shell: bash and write show-env to a file before sourcing, so a failure aborts the step under set -e. Verified locally: the file-sourced env sets RUSTC_WRAPPER / LLVM_PROFILE_FILE / CARGO_LLVM_COV; yaml parses.

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@Fieldnote-Echo

Copy link
Copy Markdown
Member Author

Addressed the Bash-dependent env sourcing finding in 8f19860: pinned shell: bash on the coverage step, and cargo llvm-cov show-env is now written to a file before sourcing, so a show-env failure aborts the step under set -e instead of letting pytest/report run uninstrumented and upload misleading coverage.

@Fieldnote-Echo

Copy link
Copy Markdown
Member Author

/review

@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Warning

/review is deprecated. Use /agentic_review instead (removal date not yet scheduled).

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Shell robustness

The multi-command bash step relies on each command failing the step as expected, but it does not explicitly set strict bash options. Consider adding set -euo pipefail (and possibly IFS hardening) at the top of the script so unexpected unset variables, pipeline failures, or partial failures don’t silently proceed to produce misleading coverage artifacts.

- name: Build instrumented extension, run pytest, collect coverage
  shell: bash
  run: |
    python -m venv .venv
    . .venv/bin/activate
    pip install --require-hashes -r ordvec-python/requirements-dev.txt
    cargo llvm-cov show-env --sh > "$RUNNER_TEMP/llvm-cov-env.sh"
    source "$RUNNER_TEMP/llvm-cov-env.sh"
    cargo llvm-cov clean --workspace
    ( cd ordvec-python && maturin develop )
    python -m pytest ordvec-python/tests -q
    cargo llvm-cov report -p ordvec-python --summary-only
    cargo llvm-cov report -p ordvec-python --lcov --output-path binding-lcov.info
- name: Upload binding coverage to Codecov
Dependency pinning

The workflow pins cargo-llvm-cov by version, but maturin develop is executed without a clearly pinned maturin version in this step. Ensure maturin is installed deterministically (e.g., via the hashed requirements file or an explicit install) so coverage runs don’t become flaky due to maturin/tooling changes.

- name: Install cargo-llvm-cov (pinned)
  run: cargo install cargo-llvm-cov --version 0.8.7 --locked
# One shell: the cargo-llvm-cov env from show-env must stay live across the
# instrumented maturin build, the pytest run, and the report, so they share
# a single step. shell: bash is pinned explicitly (the step uses bash), and
# show-env is written to a file first so that a failure aborts the step
# under `set -e` instead of letting pytest/report run uninstrumented and
# upload misleading coverage. The venv isolates maturin's editable install.
- name: Build instrumented extension, run pytest, collect coverage
  shell: bash
  run: |
    python -m venv .venv
    . .venv/bin/activate
    pip install --require-hashes -r ordvec-python/requirements-dev.txt
    cargo llvm-cov show-env --sh > "$RUNNER_TEMP/llvm-cov-env.sh"
    source "$RUNNER_TEMP/llvm-cov-env.sh"
    cargo llvm-cov clean --workspace
    ( cd ordvec-python && maturin develop )
    python -m pytest ordvec-python/tests -q
    cargo llvm-cov report -p ordvec-python --summary-only
Python version risk

Using Python 3.13 may be ahead of what the binding/test ecosystem (wheels, maturin/PyO3 compatibility, dependency support) reliably supports. Confirm this is intentional for CI, or consider matrixing/switching to a more commonly supported version to reduce intermittent CI failures unrelated to coverage.

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  with:
    python-version: "3.13"
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27)
  with:
    toolchain: stable
    components: llvm-tools-preview

Round 2 (qodo) on #64: make the strict bash options explicit in the multi-command coverage step. shell: bash already runs -eo pipefail (so each command already fails the step), and this adds -u (error on unset vars) plus states the intent in the script. Verified python -m venv activate is set -u-safe on 3.13.

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@Fieldnote-Echo

Copy link
Copy Markdown
Member Author

Addressed in 3cb3660: added set -euo pipefail to the coverage step. shell: bash was already running -eo pipefail (so each command already fails the step); this makes it explicit and adds -u (error on unset vars). Verified python -m venv activate is set -u-safe on 3.13.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a dedicated GitHub Actions workflow to measure and report Rust-layer code coverage for the PyO3 Python bindings (ordvec-python) as exercised by the existing pytest suite, and uploads that coverage to Codecov under a separate flag.

Changes:

  • Introduces .github/workflows/coverage-python.yml to build an llvm-cov-instrumented binding via cargo llvm-cov show-env + maturin develop.
  • Runs pytest to generate .profraw coverage data across test processes, then aggregates via cargo llvm-cov report -p ordvec-python.
  • Uploads the resulting LCOV to Codecov under the python-binding flag (non-blocking upload).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/coverage-python.yml Outdated
Round 3 (copilot) on #64: switch pip install to python -m pip install so the pip instance always matches the active venv interpreter, consistent with the repo's other workflows and robust to PATH/activation changes.

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@project-navi-bot Navi Bot (project-navi-bot) merged commit d20afcf into main May 26, 2026
22 checks passed
@project-navi-bot Navi Bot (project-navi-bot) deleted the ci/python-binding-coverage branch May 26, 2026 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants