use alloc::{vec, vec::Vec};
use super::CallTraceRecorder;
fn stack(depth: usize, top: &[u64]) -> Vec<u64> {
let mut stack = top.to_vec();
stack.resize(depth, 0);
stack
}
fn observe_felt_arg(recorder: &mut CallTraceRecorder, index: u32, name: &str, value: u64) {
recorder.observe_arg(index, name, Some(1), Some(vec![value]));
}
#[test]
fn frames_follow_entry_and_exit_order() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", Some(2), None);
recorder.enter(20, Vec::new());
recorder.observe_callee("function_2", Some(1), None);
recorder.exit(30, &stack(17, &[42]));
recorder.enter(40, Vec::new());
recorder.observe_callee("function_3", Some(1), None);
recorder.exit(50, &stack(18, &[7, 42]));
recorder.exit(60, &stack(18, &[7, 42]));
let trace = recorder.finish();
assert_eq!(trace.roots.len(), 1);
let function_1 = &trace.roots[0];
assert_eq!(function_1.callee.as_deref(), Some("function_1"));
assert_eq!((function_1.enter_clk, function_1.exit_clk), (10, Some(60)));
assert_eq!(function_1.results.as_deref(), Some([7, 42].as_slice()));
let names: Vec<_> = function_1.children.iter().map(|frame| frame.callee.as_deref()).collect();
assert_eq!(names, [Some("function_2"), Some("function_3")]);
assert_eq!(function_1.children[0].results.as_deref(), Some([42].as_slice()));
assert_eq!(function_1.children[1].results.as_deref(), Some([7].as_slice()));
}
#[test]
fn min_depth_padding_is_not_counted_as_result() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("consume_u64", Some(0), None);
recorder.observe_arg(0, "n", Some(2), Some(vec![0, 0]));
recorder.exit(20, &stack(16, &[]));
let consume = &recorder.finish().roots[0];
assert_eq!(consume.results.as_deref(), Some([].as_slice()));
}
#[test]
fn an_unmarked_call_does_not_rename_the_frame_it_runs_in() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_2", None, None);
recorder.observe_callee("intrinsics::mem::load_sw", None, None);
recorder.exit(20, &stack(17, &[42]));
let trace = recorder.finish();
assert_eq!(trace.roots[0].callee.as_deref(), Some("function_2"));
}
#[test]
fn recursive_records_keep_their_selected_callee() {
fn fib_value(n: u64) -> u64 {
if n < 2 {
n
} else {
fib_value(n - 1) + fib_value(n - 2)
}
}
fn drive(recorder: &mut CallTraceRecorder, n: u64, clk: &mut usize) {
*clk += 10;
recorder.enter(*clk, Vec::new());
recorder.observe_callee("fib", Some(1), None);
if n >= 2 {
drive(recorder, n - 1, clk);
drive(recorder, n - 2, clk);
}
*clk += 10;
recorder.exit(*clk, &stack(16, &[fib_value(n)]));
}
let mut recorder = CallTraceRecorder::default();
drive(&mut recorder, 4, &mut 0);
let root = recorder.finish().roots.remove(0);
assert_eq!(root.callee.as_deref(), Some("fib"));
assert_eq!(root.results.as_deref(), Some([3].as_slice())); assert_eq!(root.children.len(), 2);
let fib3 = &root.children[0];
let fib2 = &root.children[1];
assert_eq!(fib3.callee.as_deref(), Some("fib"));
assert_eq!(fib3.results.as_deref(), Some([2].as_slice())); assert_eq!(fib2.callee.as_deref(), Some("fib"));
assert_eq!(fib2.results.as_deref(), Some([1].as_slice()));
let deep = &fib3.children[0];
assert_eq!(deep.callee.as_deref(), Some("fib")); assert_eq!(deep.children[0].results.as_deref(), Some([1].as_slice())); assert_eq!(deep.children[1].results.as_deref(), Some([0].as_slice())); }
#[test]
fn a_frame_left_open_has_no_exit() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", None, None);
recorder.enter(20, Vec::new());
recorder.observe_callee("function_2", None, None);
recorder.exit(30, &stack(17, &[42]));
let trace = recorder.finish();
let function_1 = &trace.roots[0];
assert_eq!(function_1.exit_clk, None);
assert_eq!(function_1.cycles(), None);
assert_eq!(function_1.children[0].exit_clk, Some(30));
}
#[test]
fn arguments_are_not_collected_after_the_frame_makes_a_call() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", None, None);
observe_felt_arg(&mut recorder, 1, "a", 5);
recorder.enter(20, Vec::new());
recorder.observe_callee("function_2", None, None);
observe_felt_arg(&mut recorder, 1, "b", 7);
observe_felt_arg(&mut recorder, 2, "c", 9);
recorder.exit(30, &stack(17, &[0]));
observe_felt_arg(&mut recorder, 2, "c", 9);
recorder.exit(40, &stack(17, &[0]));
let trace = recorder.finish();
let function_1 = &trace.roots[0];
let names: Vec<_> = function_1.args.iter().map(|arg| arg.name.as_str()).collect();
assert_eq!(names, ["a"]);
let function_2 = &function_1.children[0];
let names: Vec<_> = function_2.args.iter().map(|arg| arg.name.as_str()).collect();
assert_eq!(names, ["b", "c"]);
}
#[test]
fn an_argument_already_recorded_is_reported_as_held() {
let mut recorder = CallTraceRecorder::default();
assert!(!recorder.has_arg(1), "no frame is open");
recorder.enter(10, Vec::new());
assert!(!recorder.has_arg(1));
observe_felt_arg(&mut recorder, 1, "a", 3);
assert!(recorder.has_arg(1));
assert!(!recorder.has_arg(2));
recorder.enter(20, Vec::new());
assert!(!recorder.has_arg(1));
}
#[test]
fn arguments_are_ordered_and_the_first_observation_is_kept() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", None, None);
observe_felt_arg(&mut recorder, 2, "amount", 5);
observe_felt_arg(&mut recorder, 1, "op", 3);
observe_felt_arg(&mut recorder, 1, "op", 99);
recorder.exit(20, &stack(17, &[8]));
let trace = recorder.finish();
let args: Vec<_> = trace.roots[0]
.args
.iter()
.map(|arg| (arg.index, arg.name.as_str(), arg.values.as_deref()))
.collect();
assert_eq!(args, [(1, "op", Some([3].as_slice())), (2, "amount", Some([5].as_slice()))]);
}
#[test]
fn a_felt_result_is_the_top_of_the_stack() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", Some(1), None);
observe_felt_arg(&mut recorder, 1, "a", 3);
observe_felt_arg(&mut recorder, 2, "b", 5);
recorder.exit(20, &stack(17, &[8]));
let trace = recorder.finish();
assert_eq!(trace.roots[0].results.as_deref(), Some([8].as_slice()));
}
#[test]
fn an_argument_of_unknown_width_does_not_affect_the_results() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", Some(1), None);
observe_felt_arg(&mut recorder, 1, "a", 3);
recorder.observe_arg(2, "b", None, None);
recorder.exit(20, &stack(17, &[8]));
let trace = recorder.finish();
let function_1 = &trace.roots[0];
assert_eq!(function_1.results.as_deref(), Some([8].as_slice()));
assert_eq!(function_1.args[1].felt_count, None);
assert_eq!(function_1.args[1].values, None);
}
#[test]
fn a_u64_result_is_two_felts() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", Some(2), None);
recorder.observe_arg(1, "x", Some(2), Some(vec![0, 1]));
recorder.exit(20, &stack(18, &[9, 8]));
let trace = recorder.finish();
let function_1 = &trace.roots[0];
assert_eq!(function_1.results.as_deref(), Some([9, 8].as_slice()));
assert_eq!(function_1.args[0].values.as_deref(), Some([0, 1].as_slice()));
}
#[test]
fn a_call_that_returns_nothing_reports_no_results() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", Some(0), None);
recorder.observe_arg(1, "w", Some(4), Some(vec![1, 2, 3, 4]));
observe_felt_arg(&mut recorder, 2, "n", 5);
recorder.exit(20, &stack(16, &[99]));
let trace = recorder.finish();
assert_eq!(trace.roots[0].results.as_deref(), Some([].as_slice()));
}
#[test]
fn a_call_without_results_reports_empty() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", Some(0), None);
observe_felt_arg(&mut recorder, 1, "a", 3);
recorder.exit(20, &stack(16, &[99]));
let trace = recorder.finish();
assert_eq!(trace.roots[0].results.as_deref(), Some([].as_slice()));
}
#[test]
fn results_are_unknown_without_a_signature() {
let mut recorder = CallTraceRecorder::default();
recorder.enter(10, Vec::new());
recorder.observe_callee("function_1", None, None);
recorder.exit(20, &stack(16, &[8]));
let trace = recorder.finish();
assert_eq!(trace.roots[0].results, None);
}