Skip to content

fix: flaky test

fix: flaky test #619

Workflow file for this run

name: Rust
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
env:
CARGO_TERM_COLOR: always
jobs:
# Quick check: every sub-crate compiles independently with default features.
# This catches broken inter-crate boundaries before the heavier coverage job
# exercises the full umbrella.
check_subcrates:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
crate:
- stochastic-rs-core
- stochastic-rs-distributions
- stochastic-rs-stochastic
- stochastic-rs-copulas
- stochastic-rs-stats
- stochastic-rs-quant
- stochastic-rs-ai
- stochastic-rs-viz
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- name: Cargo check (${{ matrix.crate }})
run: cargo check -p ${{ matrix.crate }} --no-default-features
# Umbrella + features matrix. Covers feature combinations that exercise
# cross-crate cfg-gated code (notably the python+gpu Array2 overlap caught
# by the 2026-05-07 audit §4.1).
check_umbrella:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
features:
- "" # default
- "ai"
- "openblas"
- "yahoo"
- "python"
- "gpu"
- "python,gpu"
- "openblas,ai,yahoo"
steps:
- uses: actions/checkout@v3
- name: Install OpenBLAS (when needed)
if: contains(matrix.features, 'openblas')
run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
- uses: dtolnay/rust-toolchain@stable
- name: Cargo check (features=${{ matrix.features }})
run: |
if [ -z "${{ matrix.features }}" ]; then
cargo check -p stochastic-rs --all-targets
else
cargo check -p stochastic-rs --all-targets --features "${{ matrix.features }}"
fi
# Lint + format gates. Catches drift independently of the feature matrix.
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: cargo fmt --check
run: cargo fmt --all -- --check
- name: cargo clippy (workspace)
run: cargo clippy --workspace --no-default-features --all-targets -- -D warnings
# In-process Python smoke test (audit §7.9): builds the cdylib via
# `maturin develop` and exercises the 5 main API surfaces (distributions,
# stochastic, copulas, stats, quant) including seed-determinism for the
# samplers that accept a `seed=` keyword.
python_smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install OpenBLAS
run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
# `maturin develop` requires an active virtualenv on Linux; the
# ubuntu-latest runner does not have one by default. Create one and
# invoke maturin / python via the venv's binaries (no `source` so
# each `run:` block — which is its own shell — still picks it up).
- name: Set up venv + install build deps
run: |
python -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install maturin numpy pytest scipy
- name: maturin develop (openblas)
run: .venv/bin/maturin develop --release --features openblas
- name: Run python_smoke.py
# `-u` flushes prints so the last `[OK]`/`[FAIL]` line before any
# crash shows up in the log; `-X faulthandler` prints a C+Python
# traceback on SIGSEGV, pinning the crashing native call.
run: .venv/bin/python -X faulthandler -u stochastic-rs-py/python_smoke.py
- name: Run pytest suite
# The pytest suite (stochastic-rs-py/tests/) covers all five API
# surfaces beyond the smoke script; conftest.py skips with a clear
# message if the extension is somehow not importable.
run: .venv/bin/python -m pytest stochastic-rs-py/tests/ -v
# Fast correctness gate: the whole workspace test-suite via cargo-nextest
# (no coverage instrumentation), so a failing/flaky test surfaces in minutes
# instead of only inside the multi-hour coverage job. The `ci` nextest
# profile prints one line per finished test with a running `(N/total)`
# counter, so the log shows which test is executing and how far along the
# run is.
test:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- name: Install OpenBLAS
run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install nextest
uses: taiki-e/install-action@v2
with:
tool: nextest
# `--release` because the heavy Monte-Carlo tests (Malliavin, rBergomi,
# Fourier-Malliavin) are unusably slow unoptimised; `--exclude
# stochastic-rs-py` because the cdylib is exercised by pytest in the
# python_smoke job. Mirrors the coverage job's feature set for parity.
- name: cargo nextest run (workspace)
run: cargo nextest run --workspace --exclude stochastic-rs-py --features openblas --profile ci
# nextest does not run doctests; this gates the crates' doc examples
# (e.g. the README quickstart rendered on docs.rs) that plain `cargo
# test` used to cover. `--release` reuses the artifacts built above.
- name: cargo test --doc (workspace)
run: cargo test --release --workspace --exclude stochastic-rs-py --features openblas --doc
# Full test run with openblas + coverage. Heavyweight + instrumented, so it
# only runs on pushes to main; the fast `test` job gates pull requests.
test_with_coverage:
runs-on: ubuntu-latest
if: github.event_name == 'push'
timeout-minutes: 240
steps:
- uses: actions/checkout@v3
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
- uses: dtolnay/rust-toolchain@nightly
with:
components: llvm-tools-preview
- name: Add llvm-tools to PATH
run: echo "${HOME}/.rustup/toolchains/$(rustup show active-toolchain | cut -d' ' -f1)/bin" >> $GITHUB_PATH
- name: Install grcov
run: cargo install grcov
- name: Install nextest
uses: taiki-e/install-action@v2
with:
tool: nextest
- name: Clean Build Artifacts
run: cargo clean
- name: Run tests with coverage
env:
CARGO_INCREMENTAL: "0"
CARGO_PROFILE_RELEASE_LTO: "false"
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "1"
CARGO_PROFILE_RELEASE_DEBUG: "1"
RUSTFLAGS: "-C instrument-coverage"
LLVM_PROFILE_FILE: "coverage-%p-%m.profraw"
# nextest runs each test in its own process (one profraw per test, kept
# unique by the %p pid above) and the `ci-coverage` profile prints the
# running (N/total) counter; grcov then merges every profraw as before.
run: |
cargo nextest run --release --workspace --exclude stochastic-rs-py --features openblas --profile ci-coverage
- name: Generate coverage report
run: |
grcov . \
--binary-path ./target/release/ \
-s . \
-t lcov \
--branch \
--ignore-not-existing \
--llvm \
-o lcov.info
# nextest writes one profraw per test (~1300 files, >1 GB total).
# grcov has merged them into lcov.info above, so delete them — else
# the Codecov upload sweeps up a gigabyte of raw profiling data
# instead of the single lcov report, which Codecov cannot parse.
find . -name '*.profraw' -delete
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: lcov.info
# Upload only lcov.info; do not let the CLI discover/attach anything
# else in the tree (belt-and-braces with the profraw cleanup above).
disable_search: true
fail_ci_if_error: true
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}