diff --git a/crates/spacewasm_util/src/trace.rs b/crates/spacewasm_util/src/trace.rs index 13dbca2..433c9ee 100644 --- a/crates/spacewasm_util/src/trace.rs +++ b/crates/spacewasm_util/src/trace.rs @@ -32,6 +32,10 @@ impl StateHistory { } pub fn record(&mut self, snapshot: StateSnapshot) { + // Nothing to hold, and the branch below would index an empty Vec. + if self.capacity == 0 { + return; + } if self.snapshots.len() < self.capacity { self.snapshots.push(snapshot); } else { diff --git a/crates/spacewasm_util/tests/state_history.rs b/crates/spacewasm_util/tests/state_history.rs new file mode 100644 index 0000000..95ba668 --- /dev/null +++ b/crates/spacewasm_util/tests/state_history.rs @@ -0,0 +1,105 @@ +use spacewasm::JumpTarget; +use spacewasm_util::{RustSystemAllocator, StateHistory, StateSnapshot}; + +spacewasm::global_allocator!(RustSystemAllocator, RustSystemAllocator); + +fn snap(pc: u32, instruction: &'static str) -> StateSnapshot { + StateSnapshot { + pc: JumpTarget(pc), + sp: pc as usize, + fp: 0, + instruction, + metadata: None, + } +} + +fn pcs(history: &StateHistory) -> Vec { + history.iter().map(|s| s.pc.0).collect() +} + +#[test] +fn keeps_insertion_order_below_capacity() { + let mut h = StateHistory::new(4); + for i in 0..3 { + h.record(snap(i, "nop")); + } + assert_eq!(pcs(&h), vec![0, 1, 2]); +} + +#[test] +fn keeps_insertion_order_at_capacity() { + let mut h = StateHistory::new(3); + for i in 0..3 { + h.record(snap(i, "nop")); + } + assert_eq!(pcs(&h), vec![0, 1, 2]); +} + +#[test] +fn drops_oldest_once_wrapped() { + let mut h = StateHistory::new(3); + for i in 0..5 { + h.record(snap(i, "nop")); + } + assert_eq!(pcs(&h), vec![2, 3, 4]); +} + +#[test] +fn stays_correct_across_several_wraps() { + let mut h = StateHistory::new(3); + for i in 0..10 { + h.record(snap(i, "nop")); + } + assert_eq!(pcs(&h), vec![7, 8, 9]); +} + +#[test] +fn capacity_one_keeps_only_the_latest() { + let mut h = StateHistory::new(1); + for i in 0..4 { + h.record(snap(i, "nop")); + } + assert_eq!(pcs(&h), vec![3]); +} + +// `--limit 0` reaches this and used to panic. +#[test] +fn zero_capacity_records_nothing() { + let mut h = StateHistory::new(0); + h.record(snap(1, "nop")); + h.record(snap(2, "nop")); + assert_eq!(pcs(&h), Vec::::new()); + assert!(h.dump().contains("Execution Trace")); +} + +#[test] +fn empty_history_iterates_empty() { + let h = StateHistory::new(4); + assert_eq!(pcs(&h), Vec::::new()); +} + +#[test] +fn dump_lists_instructions_oldest_first() { + let mut h = StateHistory::new(2); + h.record(snap(0, "i32_const")); + h.record(snap(1, "i32_add")); + let out = h.dump(); + let first = out.find("i32_const").expect("i32_const missing from dump"); + let second = out.find("i32_add").expect("i32_add missing from dump"); + assert!(first < second, "dump is not oldest-first:\n{out}"); +} + +#[test] +fn dump_renders_metadata() { + let mut h = StateHistory::new(2); + h.record(StateSnapshot { + pc: JumpTarget(7), + sp: 1, + fp: 2, + instruction: "local_get", + metadata: Some(("idx", 3)), + }); + let out = h.dump(); + assert!(out.contains("local_get"), "{out}"); + assert!(out.contains("idx=3"), "{out}"); +}