Skip to content

Commit 7486dfd

Browse files
committed
ci(codecov): add tarpaulin coverage upload
- Add Codecov workflow and .codecov.yml (targets + ignored paths) - Add `just coverage`/`just coverage-ci` recipes (tarpaulin HTML + Cobertura XML) - Improve test/doc coverage (notably Vector) and tweak dot loop for better coverage attribution - Ignore coverage artifacts and update cspell dictionary
1 parent 6380f56 commit 7486dfd

File tree

9 files changed

+424
-64
lines changed

9 files changed

+424
-64
lines changed

.codecov.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
coverage:
3+
status:
4+
project:
5+
default:
6+
target: 60%
7+
removed_code_behavior: removals_only
8+
threshold: 5%
9+
patch:
10+
default:
11+
target: 50%
12+
13+
ignore:
14+
- "src/lib.rs"
15+
- "benches/**"
16+
- "examples/**"

.github/workflows/codecov.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Codecov
2+
concurrency:
3+
# Ensure only one Codecov analysis runs per branch/ref
4+
group: codecov-${{ github.ref_name }}
5+
cancel-in-progress: true
6+
7+
on:
8+
push:
9+
branches: ["main"]
10+
pull_request:
11+
branches: ["main"]
12+
13+
# Least-privilege permissions
14+
permissions:
15+
contents: read
16+
checks: write
17+
pull-requests: write
18+
19+
jobs:
20+
coverage:
21+
name: Code Coverage
22+
runs-on: ubuntu-latest
23+
env:
24+
TARPAULIN_VERSION: "0.32.8"
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
28+
with:
29+
fetch-depth: 0 # Needed for Codecov diff analysis
30+
31+
- name: Install Rust toolchain
32+
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
33+
with:
34+
cache: true # toolchain/components are specified in rust-toolchain.toml
35+
36+
- name: Cache tarpaulin
37+
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
38+
with:
39+
path: ~/.cargo/bin/cargo-tarpaulin
40+
key: tarpaulin-${{ runner.os }}-${{ env.TARPAULIN_VERSION }}
41+
restore-keys: |
42+
tarpaulin-${{ runner.os }}-
43+
44+
- name: Install tarpaulin
45+
run: |
46+
if ! command -v cargo-tarpaulin &> /dev/null; then
47+
cargo install cargo-tarpaulin --locked --version "${TARPAULIN_VERSION}"
48+
fi
49+
50+
- name: Install just
51+
uses: taiki-e/install-action@50708e9ba8d7b6587a2cb575ddaa9a62e927bc06 # v2.62.63
52+
with:
53+
tool: just
54+
55+
- name: Run coverage
56+
run: |
57+
mkdir -p coverage
58+
chmod 755 coverage
59+
60+
echo "::group::Running coverage"
61+
# Single source of truth: coverage configuration lives in justfile
62+
just coverage-ci
63+
echo "::endgroup::"
64+
65+
echo "::group::Coverage verification"
66+
ls -la coverage/ || true
67+
68+
if [ ! -f coverage/cobertura.xml ]; then
69+
echo "::error::coverage/cobertura.xml not found. Tarpaulin failed to generate XML output."
70+
exit 2
71+
fi
72+
echo "::notice::Coverage report generated successfully: $(wc -l < coverage/cobertura.xml) lines"
73+
echo "::endgroup::"
74+
env:
75+
RUST_BACKTRACE: 1
76+
77+
- name: Upload coverage to Codecov
78+
if: ${{ success() && hashFiles('coverage/cobertura.xml') != '' }}
79+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
80+
with:
81+
files: coverage/cobertura.xml
82+
flags: unittests
83+
name: codecov-umbrella
84+
fail_ci_if_error: false
85+
env:
86+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
87+
88+
- name: Archive coverage results
89+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
90+
if: always()
91+
with:
92+
name: coverage-report
93+
path: coverage/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
22
/.cspellcache
3+
/coverage/*

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"words": [
55
"Clippy",
66
"clippy",
7+
"codacy",
78
"const",
89
"elif",
910
"f128",

justfile

Lines changed: 94 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,30 @@
66
# Use bash with strict error handling for all recipes
77
set shell := ["bash", "-euo", "pipefail", "-c"]
88

9-
# Default recipe shows available commands
10-
default:
11-
@just --list
9+
# GitHub Actions workflow validation (optional)
10+
action-lint:
11+
#!/usr/bin/env bash
12+
set -euo pipefail
13+
if ! command -v actionlint >/dev/null; then
14+
echo "⚠️ 'actionlint' not found. Install: https://github.com/rhysd/actionlint"
15+
exit 0
16+
fi
17+
files=()
18+
while IFS= read -r -d '' file; do
19+
files+=("$file")
20+
done < <(git ls-files -z '.github/workflows/*.yaml' '.github/workflows/*.yml')
21+
if [ "${#files[@]}" -gt 0 ]; then
22+
printf '%s\0' "${files[@]}" | xargs -0 actionlint
23+
else
24+
echo "No workflow files found to lint."
25+
fi
26+
27+
# Benchmarks
28+
bench:
29+
cargo bench
30+
31+
bench-compile:
32+
cargo bench --no-run
1233

1334
# Build commands
1435
build:
@@ -20,81 +41,83 @@ build-release:
2041
check:
2142
cargo check
2243

23-
# Code quality and formatting
24-
fmt:
25-
cargo fmt
44+
# CI simulation (matches delaunay's `just ci` shape)
45+
ci: lint test test-integration bench-compile
46+
@echo "🎯 CI simulation complete!"
2647

27-
fmt-check:
28-
cargo fmt --check
48+
clean:
49+
cargo clean
2950

51+
# Code quality and formatting
3052
clippy:
3153
cargo clippy --all-targets --all-features -- -D warnings
3254

33-
doc-check:
34-
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
35-
36-
# Lint groups (delaunay-style)
37-
lint: lint-code lint-docs lint-config
38-
39-
lint-code: fmt-check clippy doc-check
55+
# Pre-commit workflow: comprehensive validation before committing
56+
# Runs: linting + all Rust tests (lib + doc + integration) + examples
57+
commit-check: lint test-all examples
58+
@echo "🚀 Ready to commit! All checks passed!"
4059

41-
lint-docs: spell-check
60+
# Coverage (cargo-tarpaulin)
61+
#
62+
# Common tarpaulin arguments for all coverage runs
63+
# Note: -t 300 sets per-test timeout to 5 minutes (needed for slow CI environments)
64+
_coverage_base_args := '''--exclude-files 'benches/*' --exclude-files 'examples/*' \
65+
--workspace --lib --tests --all-features \
66+
-t 300 --verbose --implicit-test-threads'''
67+
68+
# Coverage analysis for local development (HTML output)
69+
coverage:
70+
#!/usr/bin/env bash
71+
set -euo pipefail
4272

43-
lint-config: action-lint
73+
if ! command -v cargo-tarpaulin >/dev/null 2>&1; then
74+
echo "cargo-tarpaulin not found. Install with: cargo install cargo-tarpaulin"
75+
exit 1
76+
fi
4477

45-
# Testing (delaunay-style split)
46-
# - test: lib + doc tests (fast)
47-
# - test-integration: tests/ (if present)
48-
# - test-all: everything in Rust
78+
mkdir -p target/tarpaulin
79+
cargo tarpaulin {{_coverage_base_args}} --out Html --output-dir target/tarpaulin
80+
echo "Coverage report generated: target/tarpaulin/tarpaulin-report.html"
4981

50-
test:
51-
cargo test --lib --verbose
52-
cargo test --doc --verbose
82+
# Coverage analysis for CI (XML output for codecov/codacy)
83+
coverage-ci:
84+
#!/usr/bin/env bash
85+
set -euo pipefail
5386

54-
test-integration:
55-
cargo test --tests --verbose
87+
if ! command -v cargo-tarpaulin >/dev/null 2>&1; then
88+
echo "cargo-tarpaulin not found. Install with: cargo install cargo-tarpaulin"
89+
exit 1
90+
fi
5691

57-
test-all: test test-integration
58-
@echo "✅ All tests passed"
92+
mkdir -p coverage
93+
cargo tarpaulin {{_coverage_base_args}} --out Xml --output-dir coverage
5994

60-
# Benchmarks
61-
bench:
62-
cargo bench
95+
# Default recipe shows available commands
96+
default:
97+
@just --list
6398

64-
bench-compile:
65-
cargo bench --no-run
99+
doc-check:
100+
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
66101

67102
# Examples
68103
examples:
69104
cargo run --quiet --example det_3x3
70105
cargo run --quiet --example solve_3x3
71106

72-
# CI simulation (matches delaunay's `just ci` shape)
73-
ci: lint test test-integration bench-compile
74-
@echo "🎯 CI simulation complete!"
107+
fmt:
108+
cargo fmt --all
75109

76-
# Pre-commit workflow: comprehensive validation before committing
77-
# Runs: linting + all Rust tests (lib + doc + integration) + examples
78-
commit-check: lint test-all examples
79-
@echo "🚀 Ready to commit! All checks passed!"
110+
fmt-check:
111+
cargo fmt --check
80112

81-
# GitHub Actions workflow validation (optional)
82-
action-lint:
83-
#!/usr/bin/env bash
84-
set -euo pipefail
85-
if ! command -v actionlint >/dev/null; then
86-
echo "⚠️ 'actionlint' not found. Install: https://github.com/rhysd/actionlint"
87-
exit 0
88-
fi
89-
files=()
90-
while IFS= read -r -d '' file; do
91-
files+=("$file")
92-
done < <(git ls-files -z '.github/workflows/*.yml' '.github/workflows/*.yaml')
93-
if [ "${#files[@]}" -gt 0 ]; then
94-
printf '%s\0' "${files[@]}" | xargs -0 actionlint
95-
else
96-
echo "No workflow files found to lint."
97-
fi
113+
# Lint groups (delaunay-style)
114+
lint: lint-code lint-docs lint-config
115+
116+
lint-code: fmt-check clippy doc-check
117+
118+
lint-config: action-lint
119+
120+
lint-docs: spell-check
98121

99122
# Spell check (cspell)
100123
#
@@ -114,5 +137,17 @@ spell-check:
114137
exit 1
115138
fi
116139

117-
clean:
118-
cargo clean
140+
# Testing (delaunay-style split)
141+
# - test: lib + doc tests (fast)
142+
# - test-all: everything in Rust
143+
# - test-integration: tests/ (if present)
144+
145+
test:
146+
cargo test --lib --verbose
147+
cargo test --doc --verbose
148+
149+
test-all: test test-integration
150+
@echo "✅ All tests passed"
151+
152+
test-integration:
153+
cargo test --tests --verbose

src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,11 @@ impl std::error::Error for LaError {}
5555
pub use lu::Lu;
5656
pub use matrix::Matrix;
5757
pub use vector::Vector;
58+
59+
/// Common imports for ergonomic usage.
60+
///
61+
/// This prelude re-exports the primary types and constants: [`Matrix`], [`Vector`], [`Lu`],
62+
/// [`LaError`], and [`DEFAULT_PIVOT_TOL`].
63+
pub mod prelude {
64+
pub use crate::{DEFAULT_PIVOT_TOL, LaError, Lu, Matrix, Vector};
65+
}

src/lu.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,24 @@ impl<const D: usize> Lu<D> {
8484

8585
/// Solve `A x = b` using this LU factorization.
8686
///
87+
/// # Examples
88+
/// ```
89+
/// #![allow(unused_imports)]
90+
/// use la_stack::prelude::*;
91+
///
92+
/// # fn main() -> Result<(), LaError> {
93+
/// let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
94+
/// let lu = a.lu(DEFAULT_PIVOT_TOL)?;
95+
///
96+
/// let b = Vector::<2>::new([5.0, 11.0]);
97+
/// let x = lu.solve_vec(b)?.into_array();
98+
///
99+
/// assert!((x[0] - 1.0).abs() <= 1e-12);
100+
/// assert!((x[1] - 2.0).abs() <= 1e-12);
101+
/// # Ok(())
102+
/// # }
103+
/// ```
104+
///
87105
/// # Errors
88106
/// Returns [`LaError::Singular`] if a diagonal of `U` is (numerically) zero.
89107
/// Returns [`LaError::NonFinite`] if NaN/∞ is detected.
@@ -131,6 +149,21 @@ impl<const D: usize> Lu<D> {
131149
}
132150

133151
/// Determinant of the original matrix.
152+
///
153+
/// # Examples
154+
/// ```
155+
/// #![allow(unused_imports)]
156+
/// use la_stack::prelude::*;
157+
///
158+
/// # fn main() -> Result<(), LaError> {
159+
/// let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
160+
/// let lu = a.lu(DEFAULT_PIVOT_TOL)?;
161+
///
162+
/// let det = lu.det();
163+
/// assert!((det - (-2.0)).abs() <= 1e-12);
164+
/// # Ok(())
165+
/// # }
166+
/// ```
134167
#[inline]
135168
#[must_use]
136169
pub fn det(&self) -> f64 {

0 commit comments

Comments
 (0)