use qa::RequirementType::*;
use qa::{Format, Matrix, traces};
qa::requirements! {
pub REQ_MACRO_SINGLE: Functional {
description: "requirement! macro defines a single requirement",
}
pub REQ_MACRO_BATCH: Functional {
description: "requirements! macro defines multiple requirements in one block",
}
pub REQ_METADATA: Functional {
description: "Freeform metadata is accessible via Requirement::get",
source: "design spec",
priority: "high",
category: "ergonomics",
}
pub REQ_TRACES_ATTR: Functional {
description: "#[traces] links tests to requirements",
}
pub REQ_MULTI_TRACE: Functional {
description: "A test can trace multiple requirements",
}
pub REQ_CUSTOM_TYPE: Functional {
description: "Custom requirement types via RequirementType::Custom",
}
}
const REGULATORY: qa::RequirementType = qa::RequirementType::Custom("regulatory");
qa::requirement! {
pub REQ_CUSTOM_CONST: REGULATORY {
description: "Custom types work via const aliases",
}
}
#[test]
#[traces(REQ_DEFINE_AS_CODE, REQ_MACRO_SINGLE)]
fn test_requirement_macro_creates_static() {
assert_eq!(REQ_MACRO_SINGLE.id, "REQ_MACRO_SINGLE");
assert_eq!(REQ_MACRO_SINGLE.kind, Functional);
assert_eq!(
REQ_MACRO_SINGLE.description,
"requirement! macro defines a single requirement"
);
}
#[test]
#[traces(REQ_DEFINE_AS_CODE, REQ_MACRO_BATCH)]
fn test_requirements_batch_macro() {
assert_eq!(REQ_MACRO_BATCH.id, "REQ_MACRO_BATCH");
assert_eq!(REQ_MACRO_BATCH.kind, Functional);
}
#[test]
#[traces(REQ_DEFINE_AS_CODE, REQ_METADATA)]
fn test_metadata_access() {
assert_eq!(REQ_METADATA.get("source"), Some("design spec"));
assert_eq!(REQ_METADATA.get("priority"), Some("high"));
assert_eq!(REQ_METADATA.get("category"), Some("ergonomics"));
assert_eq!(REQ_METADATA.get("nonexistent"), None);
}
#[test]
#[traces(REQ_CUSTOM_TYPE, REQ_CUSTOM_CONST)]
fn test_custom_requirement_type() {
assert_eq!(
REQ_CUSTOM_CONST.kind,
qa::RequirementType::Custom("regulatory")
);
assert_eq!(format!("{}", REQ_CUSTOM_CONST.kind), "regulatory");
}
#[test]
#[traces(REQ_TRACES_ATTR, REQ_TEST_TRACING)]
fn test_traces_registers_in_distributed_slice() {
let matrix = Matrix::collect();
let traces = matrix.traces_for("REQ_TRACES_ATTR");
assert!(
traces
.iter()
.any(|t| t.test_name == "test_traces_registers_in_distributed_slice"),
"expected this test to appear in traces for REQ_TRACES_ATTR"
);
}
#[test]
#[traces(REQ_MULTI_TRACE, REQ_TRACES_ATTR)]
fn test_multi_requirement_trace() {
let matrix = Matrix::collect();
let t1 = matrix.traces_for("REQ_MULTI_TRACE");
let t2 = matrix.traces_for("REQ_TRACES_ATTR");
assert!(
t1.iter()
.any(|t| t.test_name == "test_multi_requirement_trace")
);
assert!(
t2.iter()
.any(|t| t.test_name == "test_multi_requirement_trace")
);
}
#[test]
#[traces(REQ_MATRIX_GENERATION)]
fn test_matrix_collect() {
let matrix = Matrix::collect();
assert!(
!matrix.requirements().is_empty(),
"matrix should contain registered requirements"
);
assert!(
!matrix.traces().is_empty(),
"matrix should contain registered traces"
);
}
#[test]
#[traces(REQ_GAP_ANALYSIS)]
fn test_orphan_trace_detection() {
let matrix = Matrix::collect();
let orphans = matrix.orphan_traces();
assert!(
orphans.is_empty(),
"unexpected orphan traces: {:?}",
orphans.iter().map(|t| t.requirement_id).collect::<Vec<_>>()
);
}
#[test]
#[traces(REQ_MATRIX_GENERATION)]
fn test_matrix_coverage() {
let matrix = Matrix::collect();
let coverage = matrix.coverage();
assert!(
coverage > 0.0,
"coverage should be > 0 with traced requirements"
);
assert!(coverage <= 1.0);
}
#[test]
#[traces(REQ_MATRIX_GENERATION)]
fn test_markdown_rendering() {
let matrix = Matrix::collect();
let md = matrix.render(Format::Markdown);
assert!(md.contains("# Traceability Matrix"));
assert!(md.contains("REQ_MACRO_SINGLE"));
assert!(md.contains("**Coverage**"));
}
#[test]
#[traces(REQ_MATRIX_GENERATION)]
fn test_tabled_rendering() {
let matrix = Matrix::collect();
matrix.print(); }
#[test]
#[traces(REQ_GAP_ANALYSIS)]
fn test_filter_by_metadata() {
let matrix = Matrix::collect();
let filtered = matrix.filter_by("priority", "high");
assert!(
filtered
.requirements()
.iter()
.all(|r| r.get("priority") == Some("high"))
);
}
#[test]
#[traces(REQ_GAP_ANALYSIS)]
fn test_group_by_metadata() {
let matrix = Matrix::collect();
let groups = matrix.group_by("priority");
assert!(
groups.contains_key("high"),
"expected a 'high' priority group"
);
}
#[test]
#[traces(REQ_NO_STD)]
fn test_no_std_types_are_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<qa::Requirement>();
assert_send_sync::<qa::TestTrace>();
assert_send_sync::<qa::RequirementType>();
}
#[test]
#[should_panic(expected = "intentional")]
#[traces(REQ_COMPOSABILITY)]
fn test_traces_with_should_panic() {
panic!("intentional");
}
#[test]
#[traces(REQ_MATRIX_GENERATION, REQ_GAP_ANALYSIS)]
fn traceability_matrix() {
let matrix = Matrix::collect();
matrix.print();
let md = matrix.render(Format::Markdown);
assert!(!md.is_empty());
let untraced = matrix.untraced();
if !untraced.is_empty() {
let listing: Vec<_> = untraced.iter().map(|r| r.id).collect();
panic!("untraced requirements: {listing:?}");
}
}