#![cfg(feature = "dap")]
use pmat::services::dap::types::{ExecutionSnapshot, SnapshotDelta, SourceLocation, StackFrame};
use std::collections::{HashMap, HashSet};
fn create_snapshot_with_vars(
sequence: usize,
vars: HashMap<String, serde_json::Value>,
) -> ExecutionSnapshot {
ExecutionSnapshot {
timestamp: 1000000 + (sequence as u64 * 1000),
sequence,
variables: vars,
call_stack: vec![StackFrame {
id: 1,
name: "main".to_string(),
source: None,
line: 10,
column: 0,
}],
location: SourceLocation {
file: "test.rs".to_string(),
line: 10,
column: Some(0),
},
delta: None,
}
}
#[test]
fn test_compute_delta_changed_variables() {
let mut vars1 = HashMap::new();
vars1.insert("x".to_string(), serde_json::json!(10));
vars1.insert("y".to_string(), serde_json::json!(20));
vars1.insert("z".to_string(), serde_json::json!("hello"));
let mut vars2 = HashMap::new();
vars2.insert("x".to_string(), serde_json::json!(15)); vars2.insert("y".to_string(), serde_json::json!(20)); vars2.insert("z".to_string(), serde_json::json!("world"));
let snapshot1 = create_snapshot_with_vars(0, vars1);
let snapshot2 = create_snapshot_with_vars(1, vars2);
let delta = SnapshotDelta::compute(&snapshot1, &snapshot2);
assert_eq!(
delta.changed_vars.len(),
2,
"Should have 2 changed variables (x and z)"
);
assert!(
delta.changed_vars.contains_key("x"),
"Delta should include changed 'x'"
);
assert!(
delta.changed_vars.contains_key("z"),
"Delta should include changed 'z'"
);
assert_eq!(
delta.changed_vars.get("x").unwrap(),
&serde_json::json!(15),
"Delta should have new value for 'x'"
);
}
#[test]
fn test_compute_delta_removed_variables() {
let mut vars1 = HashMap::new();
vars1.insert("x".to_string(), serde_json::json!(10));
vars1.insert("y".to_string(), serde_json::json!(20));
vars1.insert("z".to_string(), serde_json::json!(30));
let mut vars2 = HashMap::new();
vars2.insert("x".to_string(), serde_json::json!(10));
let snapshot1 = create_snapshot_with_vars(0, vars1);
let snapshot2 = create_snapshot_with_vars(1, vars2);
let delta = SnapshotDelta::compute(&snapshot1, &snapshot2);
assert_eq!(
delta.removed_vars.len(),
2,
"Should have 2 removed variables (y and z)"
);
assert!(
delta.removed_vars.contains("y"),
"Delta should mark 'y' as removed"
);
assert!(
delta.removed_vars.contains("z"),
"Delta should mark 'z' as removed"
);
}
#[test]
fn test_compute_delta_new_variables() {
let mut vars1 = HashMap::new();
vars1.insert("x".to_string(), serde_json::json!(10));
let mut vars2 = HashMap::new();
vars2.insert("x".to_string(), serde_json::json!(10)); vars2.insert("y".to_string(), serde_json::json!(20)); vars2.insert("z".to_string(), serde_json::json!(30));
let snapshot1 = create_snapshot_with_vars(0, vars1);
let snapshot2 = create_snapshot_with_vars(1, vars2);
let delta = SnapshotDelta::compute(&snapshot1, &snapshot2);
assert_eq!(
delta.changed_vars.len(),
2,
"New variables should appear in changed_vars"
);
assert!(
delta.changed_vars.contains_key("y"),
"Delta should include new 'y'"
);
assert!(
delta.changed_vars.contains_key("z"),
"Delta should include new 'z'"
);
}
#[test]
fn test_apply_delta() {
let mut vars1 = HashMap::new();
vars1.insert("x".to_string(), serde_json::json!(10));
vars1.insert("y".to_string(), serde_json::json!(20));
let snapshot1 = create_snapshot_with_vars(0, vars1);
let mut changed_vars = HashMap::new();
changed_vars.insert("x".to_string(), serde_json::json!(15)); changed_vars.insert("z".to_string(), serde_json::json!(30));
let delta = SnapshotDelta {
changed_vars,
removed_vars: HashSet::new(),
stack_delta: 0,
};
let snapshot2 = snapshot1.apply_delta(&delta);
assert_eq!(
snapshot2.variables.get("x").unwrap(),
&serde_json::json!(15),
"Applied delta should update 'x'"
);
assert_eq!(
snapshot2.variables.get("y").unwrap(),
&serde_json::json!(20),
"Applied delta should preserve 'y'"
);
assert_eq!(
snapshot2.variables.get("z").unwrap(),
&serde_json::json!(30),
"Applied delta should add 'z'"
);
}
#[test]
fn test_apply_delta_with_removals() {
let mut vars1 = HashMap::new();
vars1.insert("x".to_string(), serde_json::json!(10));
vars1.insert("y".to_string(), serde_json::json!(20));
vars1.insert("z".to_string(), serde_json::json!(30));
let snapshot1 = create_snapshot_with_vars(0, vars1);
let mut removed_vars = HashSet::new();
removed_vars.insert("y".to_string());
removed_vars.insert("z".to_string());
let delta = SnapshotDelta {
changed_vars: HashMap::new(),
removed_vars,
stack_delta: 0,
};
let snapshot2 = snapshot1.apply_delta(&delta);
assert!(
snapshot2.variables.contains_key("x"),
"Applied delta should keep 'x'"
);
assert!(
!snapshot2.variables.contains_key("y"),
"Applied delta should remove 'y'"
);
assert!(
!snapshot2.variables.contains_key("z"),
"Applied delta should remove 'z'"
);
}
#[test]
fn test_delta_identical_snapshots() {
let mut vars = HashMap::new();
vars.insert("x".to_string(), serde_json::json!(10));
vars.insert("y".to_string(), serde_json::json!(20));
let snapshot1 = create_snapshot_with_vars(0, vars.clone());
let snapshot2 = create_snapshot_with_vars(1, vars);
let delta = SnapshotDelta::compute(&snapshot1, &snapshot2);
assert_eq!(
delta.changed_vars.len(),
0,
"Identical snapshots should have no changes"
);
assert_eq!(
delta.removed_vars.len(),
0,
"Identical snapshots should have no removals"
);
}
#[test]
fn test_delta_round_trip() {
let mut vars1 = HashMap::new();
vars1.insert("x".to_string(), serde_json::json!(10));
vars1.insert("y".to_string(), serde_json::json!(20));
let mut vars2 = HashMap::new();
vars2.insert("x".to_string(), serde_json::json!(15));
vars2.insert("z".to_string(), serde_json::json!(30));
let snapshot1 = create_snapshot_with_vars(0, vars1);
let snapshot2 = create_snapshot_with_vars(1, vars2);
let delta = SnapshotDelta::compute(&snapshot1, &snapshot2);
let reconstructed = snapshot1.apply_delta(&delta);
assert_eq!(
reconstructed.variables, snapshot2.variables,
"Round-trip should reconstruct exact variables"
);
}
#[test]
fn test_delta_compression_efficiency() {
let mut snapshots = Vec::new();
for i in 0..100 {
let mut vars = HashMap::new();
vars.insert("counter".to_string(), serde_json::json!(i));
vars.insert("constant1".to_string(), serde_json::json!("unchanged"));
vars.insert("constant2".to_string(), serde_json::json!(42));
vars.insert("constant3".to_string(), serde_json::json!(true));
snapshots.push(create_snapshot_with_vars(i, vars));
}
let full_size: usize = snapshots
.iter()
.map(|s| serde_json::to_vec(&s.variables).unwrap().len())
.sum();
let first_snapshot_size = serde_json::to_vec(&snapshots[0].variables).unwrap().len();
let mut delta_sizes = Vec::new();
for i in 1..snapshots.len() {
let delta = SnapshotDelta::compute(&snapshots[i - 1], &snapshots[i]);
delta_sizes.push(serde_json::to_vec(&delta.changed_vars).unwrap().len());
}
let compressed_size = first_snapshot_size + delta_sizes.iter().sum::<usize>();
let compression_ratio = (full_size - compressed_size) as f64 / full_size as f64;
assert!(
compression_ratio > 0.75,
"Should achieve >75% compression for similar snapshots. Got {:.1}%",
compression_ratio * 100.0
);
}
#[test]
fn test_stack_delta() {
let snapshot1 = create_snapshot_with_vars(0, HashMap::new());
let mut snapshot2 = create_snapshot_with_vars(1, HashMap::new());
snapshot2.call_stack.push(StackFrame {
id: 2,
name: "helper".to_string(),
source: None,
line: 20,
column: 0,
});
let delta = SnapshotDelta::compute(&snapshot1, &snapshot2);
assert_eq!(
delta.stack_delta, 1,
"Stack delta should be +1 when frame is added"
);
}
#[test]
fn test_large_variable_value_changes() {
let mut vars1 = HashMap::new();
vars1.insert(
"data".to_string(),
serde_json::json!({"items": [1, 2, 3, 4, 5]}),
);
let mut vars2 = HashMap::new();
vars2.insert(
"data".to_string(),
serde_json::json!({"items": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}),
);
let snapshot1 = create_snapshot_with_vars(0, vars1);
let snapshot2 = create_snapshot_with_vars(1, vars2);
let delta = SnapshotDelta::compute(&snapshot1, &snapshot2);
assert_eq!(
delta.changed_vars.len(),
1,
"Should detect change in complex data structure"
);
assert!(
delta.changed_vars.contains_key("data"),
"Delta should include changed 'data'"
);
}