use super::{BssReadState, bss_read_state};
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 (the F11(a) 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);
}