use super::{BssReadState, bss_read_state, bss_state_label};
use crate::monitor::reader::GuestMem;
fn build_mem(size: usize) -> (GuestMem, Vec<u8>) {
let mut buf = vec![0u8; size];
let mem = unsafe { GuestMem::new(buf.as_mut_ptr(), buf.len() as u64) };
(mem, buf)
}
#[test]
fn not_resolved_when_pa_none() {
let (mem, _buf) = build_mem(0x1000);
let state = bss_read_state(Some(&mem), None);
assert_eq!(state, BssReadState::NotResolved);
}
#[test]
fn not_resolved_when_mem_none() {
let state = bss_read_state(None, Some(0x100));
assert_eq!(state, BssReadState::NotResolved);
}
#[test]
fn not_resolved_when_both_none() {
let state = bss_read_state(None, None);
assert_eq!(state, BssReadState::NotResolved);
}
#[test]
fn not_triggered_when_field_is_zero() {
let (mem, _buf) = build_mem(0x1000);
let state = bss_read_state(Some(&mem), Some(0x100));
assert_eq!(state, BssReadState::NotTriggered);
}
#[test]
fn triggered_when_field_is_nonzero() {
let (mem, mut buf) = build_mem(0x1000);
buf[0x100..0x100 + 4].copy_from_slice(&1u32.to_le_bytes());
let state = bss_read_state(Some(&mem), Some(0x100));
assert_eq!(state, BssReadState::Triggered);
}
#[test]
fn triggered_on_arbitrary_nonzero_value() {
let (mem, mut buf) = build_mem(0x1000);
buf[0x80..0x80 + 4].copy_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
let state = bss_read_state(Some(&mem), Some(0x80));
assert_eq!(state, BssReadState::Triggered);
}
#[test]
fn out_of_bounds_pa_distinguished_from_zero() {
let (mem, _buf) = build_mem(0x1000);
let state = bss_read_state(Some(&mem), Some(0x2000));
assert_eq!(state, BssReadState::OutOfBounds);
}
#[test]
fn wrap_around_pa_lands_oob() {
let (mem, _buf) = build_mem(0x1000);
let pa = u64::MAX - 16;
let state = bss_read_state(Some(&mem), Some(pa));
assert_eq!(state, BssReadState::OutOfBounds);
}
#[test]
fn pa_at_last_valid_4byte_window() {
let size: usize = 0x1000;
let (mem, _buf) = build_mem(size);
let state = bss_read_state(Some(&mem), Some((size - 4) as u64));
assert_eq!(state, BssReadState::NotTriggered);
}
#[test]
fn pa_straddles_end_of_region_returns_oob() {
let size: usize = 0x1000;
let (mem, _buf) = build_mem(size);
let state = bss_read_state(Some(&mem), Some((size - 3) as u64));
assert_eq!(state, BssReadState::OutOfBounds);
}
#[test]
fn pa_one_byte_before_end_returns_oob() {
let size: usize = 0x1000;
let (mem, _buf) = build_mem(size);
let state = bss_read_state(Some(&mem), Some((size - 1) as u64));
assert_eq!(state, BssReadState::OutOfBounds);
}
#[test]
fn err_triggered_or_gate_combinations() {
struct Row {
wp_hit: bool,
bss_state: BssReadState,
expected_err: bool,
label: &'static str,
}
let rows = [
Row {
wp_hit: false,
bss_state: BssReadState::NotResolved,
expected_err: false,
label: "neither side fired (clean run)",
},
Row {
wp_hit: false,
bss_state: BssReadState::NotTriggered,
expected_err: false,
label: "bss resolved but not yet latched",
},
Row {
wp_hit: false,
bss_state: BssReadState::OutOfBounds,
expected_err: false,
label: "OOB must NOT count as a fire (stale-cache invariant)",
},
Row {
wp_hit: false,
bss_state: BssReadState::Triggered,
expected_err: true,
label: "bss-only fire (degraded watchpoint config)",
},
Row {
wp_hit: true,
bss_state: BssReadState::NotResolved,
expected_err: true,
label: "watchpoint-only fire (probe never loaded)",
},
Row {
wp_hit: true,
bss_state: BssReadState::NotTriggered,
expected_err: true,
label: "watchpoint fired before bss latch caught up",
},
Row {
wp_hit: true,
bss_state: BssReadState::OutOfBounds,
expected_err: true,
label: "watchpoint fires through; OOB on bss does not retract it",
},
Row {
wp_hit: true,
bss_state: BssReadState::Triggered,
expected_err: true,
label: "both fired (steady-state late-trigger path)",
},
];
for row in rows {
let bss_triggered = matches!(row.bss_state, BssReadState::Triggered);
let err = row.wp_hit || bss_triggered;
assert_eq!(
err, row.expected_err,
"row '{}': wp_hit={}, bss_state={:?}",
row.label, row.wp_hit, row.bss_state,
);
}
}
#[test]
fn helper_is_pure_under_repeated_calls() {
let (mem, _buf) = build_mem(0x1000);
let s1 = bss_read_state(Some(&mem), Some(0x100));
let s2 = bss_read_state(Some(&mem), Some(0x100));
let s3 = bss_read_state(Some(&mem), Some(0x100));
assert_eq!(s1, BssReadState::NotTriggered);
assert_eq!(s1, s2);
assert_eq!(s2, s3);
}
#[test]
fn helper_observes_post_write_flip() {
let (mem, mut buf) = build_mem(0x1000);
let pa: u64 = 0x40;
let state_before = bss_read_state(Some(&mem), Some(pa));
assert_eq!(state_before, BssReadState::NotTriggered);
buf[pa as usize..pa as usize + 4].copy_from_slice(&1u32.to_le_bytes());
let state_after = bss_read_state(Some(&mem), Some(pa));
assert_eq!(state_after, BssReadState::Triggered);
}
#[test]
fn pa_at_region_start() {
let (mem, mut buf) = build_mem(0x1000);
buf[0..4].copy_from_slice(&7u32.to_le_bytes());
let state = bss_read_state(Some(&mem), Some(0));
assert_eq!(state, BssReadState::Triggered);
}
#[test]
fn bss_state_label_triggered() {
assert_eq!(bss_state_label(BssReadState::Triggered), "triggered");
}
#[test]
fn bss_state_label_not_triggered() {
assert_eq!(bss_state_label(BssReadState::NotTriggered), "not_triggered");
}
#[test]
fn bss_state_label_out_of_bounds() {
assert_eq!(bss_state_label(BssReadState::OutOfBounds), "out_of_bounds");
}
#[test]
fn bss_state_label_not_resolved() {
assert_eq!(bss_state_label(BssReadState::NotResolved), "not_resolved");
}
#[test]
fn bss_state_label_no_wildcard_unknown_fallback() {
let variants = [
BssReadState::Triggered,
BssReadState::NotTriggered,
BssReadState::OutOfBounds,
BssReadState::NotResolved,
];
let allowlist = [
"triggered",
"not_triggered",
"out_of_bounds",
"not_resolved",
];
for v in variants {
let label = bss_state_label(v);
assert!(
!label.is_empty(),
"bss_state_label({v:?}) must be non-empty"
);
assert!(
allowlist.contains(&label),
"bss_state_label({v:?}) returned {label:?} — not in the \
operator-known allowlist {allowlist:?}. Either an \
unintended wildcard-arm refactor landed (`_ => \"...\"`) \
or a new BssReadState variant was added without updating \
the allowlist here. Operator tooling (jq filters, \
auto-repro tail renderer) has no behavior defined for \
strings outside the allowlist."
);
}
}