use super::*;
#[test]
fn note_value_from_impls_route_to_correct_variant() {
assert_eq!(NoteValue::from(42i64), NoteValue::Int(42));
assert_eq!(NoteValue::from(42u64), NoteValue::Uint(42));
assert_eq!(NoteValue::from(0.5_f64), NoteValue::Float(0.5));
assert_eq!(NoteValue::from(true), NoteValue::Bool(true));
assert_eq!(
NoteValue::from("hello".to_string()),
NoteValue::Text("hello".to_string()),
);
assert_eq!(
NoteValue::from("borrowed"),
NoteValue::Text("borrowed".to_string()),
);
}
#[test]
fn note_value_records_without_altering_verdict() {
let mut r = AssertResult::pass();
let was_pass = r.is_pass();
let was_skip = r.is_skip();
let was_outcomes = r.outcomes.len();
r.note_value("max_wchar", 12345i64);
r.note_value("psi_available", true);
assert_eq!(r.is_pass(), was_pass);
assert_eq!(r.is_skip(), was_skip);
assert_eq!(r.outcomes.len(), was_outcomes);
assert_eq!(r.measurements.len(), 2);
assert_eq!(r.measurements["max_wchar"], NoteValue::Int(12345));
assert_eq!(r.measurements["psi_available"], NoteValue::Bool(true));
}
#[test]
fn note_value_overwrites_on_duplicate_key() {
let mut r = AssertResult::pass();
r.note_value("counter", 1i64);
r.note_value("counter", 2i64);
assert_eq!(r.measurements["counter"], NoteValue::Int(2));
assert_eq!(r.measurements.len(), 1);
}
#[test]
fn merge_unions_measurements_last_write_wins() {
let mut a = AssertResult::pass();
a.note_value("a_only", 1i64);
a.note_value("shared", 100i64);
let mut b = AssertResult::pass();
b.note_value("b_only", 2i64);
b.note_value("shared", 200i64);
a.merge(b);
assert_eq!(a.measurements.len(), 3);
assert_eq!(a.measurements["a_only"], NoteValue::Int(1));
assert_eq!(a.measurements["b_only"], NoteValue::Int(2));
assert_eq!(
a.measurements["shared"],
NoteValue::Int(200),
"merge must adopt other's value on key collision (last write wins)",
);
}
#[test]
fn note_value_survives_serde_roundtrip() {
let mut r = AssertResult::pass();
r.note_value("answer", 42i64);
r.note_value("ratio", 0.5_f64);
r.note_value("name", "fio");
let json = serde_json::to_string(&r).unwrap();
assert!(
json.contains("\"measurements\""),
"measurements key must appear in JSON when populated: {json}",
);
let r2: AssertResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.measurements["answer"], NoteValue::Int(42));
assert_eq!(r2.measurements["ratio"], NoteValue::Float(0.5));
assert_eq!(r2.measurements["name"], NoteValue::Text("fio".to_string()));
}
#[test]
fn empty_measurements_present_in_wire_format() {
let r = AssertResult::pass();
let json = serde_json::to_string(&r).unwrap();
assert!(
json.contains("\"measurements\":{}"),
"empty measurements must be present in JSON for postcard compat: {json}",
);
}
#[test]
fn any_of_chooses_passing_branch() {
let r = AssertResult::any_of([
{
let mut a = AssertResult::pass();
a.record_fail(AssertDetail::new(DetailKind::Other, "boom"));
a
},
AssertResult::pass(),
]);
assert!(r.is_pass());
assert!(
!r.failure_details().any(|d| d.message.contains("boom")),
"failed-branch details must be dropped: {:?}",
r.outcomes,
);
assert!(
r.info_notes
.iter()
.any(|n| n.message.contains("any_of: branch 1 satisfied")),
"chosen-branch annotation missing: {:?}",
r.info_notes,
);
}
#[test]
fn any_of_all_fail_prefixes_info_notes_with_branch_index() {
let r = AssertResult::any_of([
{
let mut a = AssertResult::fail(AssertDetail::new(DetailKind::Other, "boom_0"));
a.note("context_from_branch_0");
a
},
{
let mut b = AssertResult::fail(AssertDetail::new(DetailKind::Other, "boom_1"));
b.note("context_from_branch_1");
b
},
]);
assert!(r.is_fail());
let messages: Vec<&str> = r.info_notes.iter().map(|n| n.message.as_str()).collect();
assert!(
messages.contains(&"any_of[0]: context_from_branch_0"),
"branch 0 note must carry index prefix: {:?}",
r.info_notes,
);
assert!(
messages.contains(&"any_of[1]: context_from_branch_1"),
"branch 1 note must carry index prefix: {:?}",
r.info_notes,
);
}
#[test]
fn any_of_pass_path_unions_passing_branch_info_notes_with_prefix() {
let r = AssertResult::any_of([
{
let mut a = AssertResult::pass();
a.note("context_from_branch_0");
a
},
{
let mut b = AssertResult::pass();
b.note("context_from_branch_1");
b
},
{
let mut c = AssertResult::fail(AssertDetail::new(DetailKind::Other, "boom"));
c.note("context_from_branch_2_failed");
c
},
]);
assert!(r.is_pass());
let messages: Vec<&str> = r.info_notes.iter().map(|n| n.message.as_str()).collect();
assert!(
messages.contains(&"any_of[0]: context_from_branch_0"),
"branch 0 passing note must survive with prefix: {messages:?}"
);
assert!(
messages.contains(&"any_of[1]: context_from_branch_1"),
"branch 1 passing note must survive with prefix: {messages:?}"
);
assert!(
!messages.iter().any(|m| m.contains("branch_2_failed")),
"failed-branch note must be dropped: {messages:?}"
);
assert!(
messages.contains(&"any_of: branch 0 satisfied the disjunction"),
"bare arbiter annotation missing: {messages:?}"
);
}
#[test]
fn any_of_concatenates_branch_failures_with_index_prefixes() {
let r = AssertResult::any_of([
AssertResult::fail(AssertDetail::new(DetailKind::Other, "first boom")),
AssertResult::fail(AssertDetail::new(DetailKind::Other, "second boom")),
]);
assert!(r.is_fail());
assert!(
r.failure_details()
.any(|d| d.message == "any_of[0]: first boom"),
"branch 0 detail must carry index prefix: {:?}",
r.outcomes,
);
assert!(
r.failure_details()
.any(|d| d.message == "any_of[1]: second boom"),
"branch 1 detail must carry index prefix: {:?}",
r.outcomes,
);
assert!(
r.failure_details()
.any(|d| d.message.contains("2 failed") && d.message.contains("of 2 branches")),
"summary line missing per-disposition counts: {:?}",
r.outcomes,
);
}
#[test]
fn any_of_empty_input_fails() {
let r = AssertResult::any_of(std::iter::empty());
assert!(r.is_fail());
assert!(
r.failure_details()
.any(|d| d.message.contains("empty branch list")),
"empty disjunction must surface as named failure: {:?}",
r.outcomes,
);
}
#[test]
fn any_of_all_inconclusive_branches_yields_inconclusive() {
let mut b0 = AssertResult::pass();
b0.record_inconclusive(AssertDetail::new(
DetailKind::Migration,
"zero denom branch 0",
));
let mut b1 = AssertResult::pass();
b1.record_inconclusive(AssertDetail::new(
DetailKind::Benchmark,
"zero denom branch 1",
));
let r = AssertResult::any_of([b0, b1]);
assert!(
r.is_inconclusive(),
"all-Inconclusive disjunction must yield Inconclusive, not Fail or Pass: {:?}",
r.outcomes,
);
assert!(!r.is_fail(), "must not promote Inconclusive to Fail");
assert!(
!r.is_pass(),
"must not silently pass on unevaluated branches"
);
assert!(
r.inconclusive_details()
.any(|d| d.message.contains("2 inconclusive") && d.message.contains("of 2 branches")),
"summary line missing inconclusive count: {:?}",
r.outcomes,
);
let inconc_messages: Vec<&str> = r
.inconclusive_details()
.map(|d| d.message.as_str())
.collect();
assert!(
inconc_messages
.iter()
.any(|m| m.contains("any_of[0]: zero denom branch 0")),
"branch 0 inconclusive missing prefix: {inconc_messages:?}",
);
assert!(
inconc_messages
.iter()
.any(|m| m.contains("any_of[1]: zero denom branch 1")),
"branch 1 inconclusive missing prefix: {inconc_messages:?}",
);
}
#[test]
fn any_of_fail_plus_inconclusive_yields_fail() {
let failing = AssertResult::fail(AssertDetail::new(DetailKind::Other, "real boom"));
let mut inconc = AssertResult::pass();
inconc.record_inconclusive(AssertDetail::new(DetailKind::Migration, "zero denom"));
let r = AssertResult::any_of([failing, inconc]);
assert!(
r.is_fail(),
"Fail+Inconclusive disjunction must yield Fail: {:?}",
r.outcomes,
);
assert!(!r.is_inconclusive());
assert!(
r.failure_details()
.any(|d| d.message.contains("1 failed") && d.message.contains("1 inconclusive")),
"summary line missing per-disposition counts: {:?}",
r.outcomes,
);
}
#[test]
fn any_of_inconclusive_plus_skip_yields_inconclusive() {
let mut inconc = AssertResult::pass();
inconc.record_inconclusive(AssertDetail::new(DetailKind::Migration, "zero denom"));
let skip = AssertResult::skip("topology missing");
let r = AssertResult::any_of([inconc, skip]);
assert!(
r.is_inconclusive(),
"Inconclusive+Skip disjunction must yield Inconclusive (Inconclusive dominates Skip): {:?}",
r.outcomes,
);
assert!(!r.is_skip());
assert!(!r.is_pass());
}
#[test]
fn all_of_passes_when_every_branch_passes() {
let r = AssertResult::all_of([AssertResult::pass(), AssertResult::pass()]);
assert!(r.is_pass());
let r = AssertResult::all_of([
AssertResult::pass(),
AssertResult::fail(AssertDetail::new(DetailKind::Other, "boom")),
]);
assert!(r.is_fail());
let r = AssertResult::all_of(std::iter::empty());
assert!(r.is_pass());
assert!(r.outcomes.is_empty());
}
#[test]
fn verdict_note_value_records_into_underlying_result() {
let mut v = Verdict::new();
v.note_value("max_wchar", 12345i64);
v.note_value("psi_available", false);
let r = v.into_result();
assert!(r.is_pass());
assert_eq!(r.measurements.len(), 2);
assert_eq!(r.measurements["max_wchar"], NoteValue::Int(12345));
assert_eq!(r.measurements["psi_available"], NoteValue::Bool(false));
}