1 unstable release
Uses new Rust 2024
| 0.1.1 | Feb 26, 2026 |
|---|
#1848 in Rust patterns
26KB
438 lines
qa
Requirements traceability for safety-critical Rust software. Define requirements as code, link them to tests with an attribute macro, and auto-generate a traceability matrix.
Quick Start
# Cargo.toml
[dependencies]
qa = "0.1"
use qa::RequirementType::*;
// Define a requirement
qa::requirement! {
pub REQ_ENERGY_CONSERVATION: Safety {
description: "Total energy must be conserved across transport events",
source: "NRC Reg Guide 1.190 §3.1",
}
}
// Link a test to it
#[test]
#[qa::traces(REQ_ENERGY_CONSERVATION)]
fn test_energy_is_conserved() {
// ...
}
// Generate the matrix
#[test]
fn traceability_matrix() {
let matrix = qa::Matrix::collect();
matrix.print();
matrix.write("traceability.md", qa::Format::Markdown).unwrap();
matrix.assert_complete(); // panics if any requirement has no tests
}
Defining Requirements
Single requirement
qa::requirement! {
pub REQ_VOID_TRACKING: Safety {
description: "Particles must be tracked through void regions without energy loss",
source: "NRC Reg Guide 1.190 §4.2",
priority: "critical",
}
}
Batch definition
qa::requirements! {
pub REQ_VOID_TRACKING: Safety {
description: "Particles must be tracked through void regions without energy loss",
source: "NRC Reg Guide 1.190 §4.2",
}
pub REQ_ENERGY_CONSERVATION: Safety {
description: "Total energy must be conserved across transport events",
source: "NRC Reg Guide 1.190 §3.1",
}
pub REQ_XS_INTERPOLATION: Functional {
description: "Cross-section lookup must interpolate between tabulated energy points",
source: "ENDF/B-VIII.0 format manual",
}
}
Requirement fields
description(required, must be first): human-readable text- type (required, after
:): aRequirementTypevariant - All other key-value pairs become freeform metadata
RequirementType
Built-in variants: Functional, NonFunctional, Performance, Safety, Security, Interface.
Extend with Custom:
const REGULATORY: qa::RequirementType = qa::RequirementType::Custom("regulatory");
qa::requirement! {
pub REQ_NRC_COMPLIANCE: REGULATORY {
description: "System must comply with NRC regulations",
}
}
Accessing metadata
let source = REQ_VOID_TRACKING.get("source"); // Some("NRC Reg Guide 1.190 §4.2")
let missing = REQ_VOID_TRACKING.get("nonexistent"); // None
Tracing Tests
use qa::traces;
#[test]
#[traces(REQ_VOID_TRACKING)]
fn test_void_traversal() { /* ... */ }
// Multiple requirements per test
#[test]
#[traces(REQ_VOID_TRACKING, REQ_ENERGY_CONSERVATION)]
fn test_energy_through_void() { /* ... */ }
Composability
#[traces] emits the test function unchanged and appends registration as a separate item. It composes with any attribute macro regardless of ordering:
#[rstest]
#[traces(REQ_FIXTURES)]
fn test_with_fixture(fixture: MyFixture) { /* ... */ }
#[test]
#[should_panic(expected = "negative energy")]
#[traces(REQ_ERROR_HANDLING)]
fn test_negative_energy_panics() { /* ... */ }
Generating the Matrix
let matrix = qa::Matrix::collect();
Analysis
matrix.requirements() // all registered requirements
matrix.traces() // all registered traces
matrix.traces_for("REQ_VOID_TRACKING") // tests for a specific requirement
matrix.untraced() // requirements with no tests
matrix.orphan_traces() // traces referencing nonexistent requirement IDs
matrix.coverage() // fraction of requirements with ≥1 test (0.0..=1.0)
matrix.assert_complete() // panics if any requirement is untraced
Output
// Terminal table (via tabled)
matrix.print();
// Markdown file
matrix.write("traceability.md", qa::Format::Markdown).unwrap();
// JSON file (requires `json` feature)
matrix.write("traceability.json", qa::Format::Json).unwrap();
Organizing Requirements
Place requirements at the top of the files they govern:
// src/transport.rs
qa::requirements! {
pub REQ_VOID_TRACKING: Safety {
description: "Particles must be tracked through void regions without energy loss",
source: "NRC Reg Guide 1.190 §4.2",
}
pub REQ_ENERGY_CONSERVATION: Safety {
description: "Total energy must be conserved across transport events",
source: "NRC Reg Guide 1.190 §3.1",
}
}
// ... implementation code ...
Requirements register globally via linkme distributed slices regardless of which file or crate they're defined in. No central manifest to maintain.
Filtering and Grouping
Operate on freeform metadata for custom views:
// Only safety requirements
let safety = matrix.filter_by("priority", "critical");
safety.print();
// Group by source document
let by_source = matrix.group_by("source");
for (source, sub_matrix) in &by_source {
println!("{}: {} requirements", source, sub_matrix.requirements().len());
}
no_std Support
The core types, macros, and linkme slices work without std. Disable default features:
[dependencies]
qa = { version = "0.1", default-features = false }
This gives you Requirement, RequirementType, TestTrace, requirement!, requirements!, and #[traces] on bare-metal targets. Matrix and all rendering require std.
Typical workflow: define requirements in your no_std library crate, then collect and render the matrix from a host-side test binary that enables std.
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
yes | Matrix, terminal tables, file output |
json |
no | JSON rendering via serde (implies std) |
Workspace
| Crate | Description |
|---|---|
qa |
Core library — types, macros, matrix generation |
qa-macros |
Proc-macro crate — #[traces] attribute |
Development
cargo test --workspace # all tests
cargo test --workspace --features json # with JSON output
cargo clippy --workspace --all-targets -- -D warnings
no_std verification
rustup target add thumbv7em-none-eabihf
cargo build -p qa --no-default-features --target thumbv7em-none-eabihf
Releasing
Releases are triggered manually from GitLab CI. Go to CI/CD → Pipelines → Run pipeline, set the BUMP variable to patch, minor, or major, then run the release job. This bumps versions across the workspace, publishes to crates.io, and pushes a version commit + tag.
License
MIT
Dependencies
~0.1–1.1MB
~23K SLoC