use super::*;
use std::{thread, time::Duration};
#[test]
fn dagmapraw_functions() {
let mut i0 = DagMapRaw::new(None);
i0.insert("k0", "v0");
assert_eq!(i0.get("k0").unwrap().as_slice(), "v0".as_bytes());
assert!(i0.get("k1").is_none());
let mut i1 = DagMapRaw::new(Some(&mut i0));
i1.insert("k1", "v1");
assert_eq!(i1.get("k1").unwrap().as_slice(), "v1".as_bytes());
assert_eq!(i1.get("k0").unwrap().as_slice(), "v0".as_bytes());
let mut i2 = DagMapRaw::new(Some(&mut i1));
i2.insert("k2", "v2");
assert_eq!(i2.get("k2").unwrap().as_slice(), "v2".as_bytes());
assert_eq!(i2.get("k1").unwrap().as_slice(), "v1".as_bytes());
assert_eq!(i2.get("k0").unwrap().as_slice(), "v0".as_bytes());
i2.insert("k2", "v2x");
assert_eq!(i2.get("k2").unwrap().as_slice(), "v2x".as_bytes());
assert_eq!(i2.get("k1").unwrap().as_slice(), "v1".as_bytes());
assert_eq!(i2.get("k0").unwrap().as_slice(), "v0".as_bytes());
i2.insert("k1", "v1x");
assert_eq!(i2.get("k2").unwrap().as_slice(), "v2x".as_bytes());
assert_eq!(i2.get("k1").unwrap().as_slice(), "v1x".as_bytes());
assert_eq!(i2.get("k0").unwrap().as_slice(), "v0".as_bytes());
i2.insert("k0", "v0x");
assert_eq!(i2.get("k2").unwrap().as_slice(), "v2x".as_bytes());
assert_eq!(i2.get("k1").unwrap().as_slice(), "v1x".as_bytes());
assert_eq!(i2.get("k0").unwrap().as_slice(), "v0x".as_bytes());
assert!(i1.get("k2").is_none());
assert_eq!(i1.get("k1").unwrap().as_slice(), "v1".as_bytes());
assert_eq!(i1.get("k0").unwrap().as_slice(), "v0".as_bytes());
assert!(i0.get("k2").is_none());
assert!(i0.get("k1").is_none());
assert_eq!(i0.get("k0").unwrap().as_slice(), "v0".as_bytes());
let mut head = i2.prune().unwrap();
thread::sleep(Duration::from_millis(1000));
assert_eq!(head.get("k2").unwrap().as_slice(), "v2x".as_bytes());
assert_eq!(head.get("k1").unwrap().as_slice(), "v1x".as_bytes());
assert_eq!(head.get("k0").unwrap().as_slice(), "v0x".as_bytes());
assert!(i1.is_dead());
assert_eq!(i0.get("k2").unwrap().as_slice(), "v2x".as_bytes());
for i in 10u8..=255 {
head.insert(i.to_be_bytes(), i.to_be_bytes());
head = DagMapRaw::new(Some(&mut head));
}
let mut head = head.prune().unwrap();
thread::sleep(Duration::from_millis(1000));
assert!(head.parent.get_value().is_none());
assert!(head.children.iter().next().is_none());
for i in 10u8..=255 {
assert_eq!(
head.get(i.to_be_bytes()).unwrap().as_slice(),
i.to_be_bytes()
);
}
for i in 0u8..=254 {
head.remove(i.to_be_bytes());
assert!(head.get(i.to_be_bytes()).is_none());
}
*(head.get_mut(255u8.to_be_bytes()).unwrap()) = 0u8.to_be_bytes().to_vec();
assert_eq!(
head.get(255u8.to_be_bytes()).unwrap().as_slice(),
0u8.to_be_bytes()
);
}
#[test]
fn test_save_and_from_meta() {
let mut dag = DagMapRaw::new(None);
dag.insert("k1", "v1");
dag.insert("k2", "v2");
let id = dag.save_meta().unwrap();
assert_eq!(id, dag.instance_id());
let restored = DagMapRaw::from_meta(id).unwrap();
assert_eq!(restored.get("k1").unwrap().as_slice(), "v1".as_bytes());
assert_eq!(restored.get("k2").unwrap().as_slice(), "v2".as_bytes());
}
#[test]
fn test_serde_roundtrip() {
let mut dag = DagMapRaw::new(None);
dag.insert("alpha", "A");
dag.insert("beta", "B");
let bytes = postcard::to_allocvec(&dag).unwrap();
let restored: DagMapRaw = postcard::from_bytes(&bytes).unwrap();
assert_eq!(restored.get("alpha").unwrap().as_slice(), b"A");
assert_eq!(restored.get("beta").unwrap().as_slice(), b"B");
}
#[test]
fn test_from_meta_nonexistent() {
assert!(DagMapRaw::from_meta(u64::MAX).is_err());
}
#[test]
fn test_meta_restore_then_mutate() {
let mut dag = DagMapRaw::new(None);
dag.insert("k1", "v1");
let id = dag.save_meta().unwrap();
let mut restored = DagMapRaw::from_meta(id).unwrap();
restored.insert("k2", "v2");
assert_eq!(dag.get("k2").unwrap().as_slice(), b"v2");
}
#[test]
fn serde_rejects_mixed_namespace_components() {
let ns = crate::Namespace::create().unwrap();
let data = MapxRaw::new();
let parent: Orphan<Option<DagMapRaw>> = Orphan::new_in(&ns, None);
let children: MapxOrdRawKey<DagMapRaw> = MapxOrdRawKey::new();
let bytes = postcard::to_allocvec(&(data, parent, children)).unwrap();
assert!(postcard::from_bytes::<DagMapRaw>(&bytes).is_err());
}
#[test]
#[should_panic(expected = "empty value is a tombstone")]
fn insert_empty_value_panics() {
let mut dag = DagMapRaw::new(None);
dag.insert("empty", []);
}
#[test]
fn destroy_unlinks_from_parent() {
let mut parent = DagMapRaw::new(None);
let mut child = DagMapRaw::new(Some(&mut parent));
assert!(!parent.no_children());
child.destroy();
assert!(parent.no_children());
}
#[test]
fn destroy_interrupted_before_unlink_leaves_reclaimable_residue() {
let mut parent = DagMapRaw::new(None);
let mut child = DagMapRaw::new(Some(&mut parent));
child.insert("k", "v");
assert!(!parent.no_children());
*child.parent.get_mut() = None;
child.data.clear();
assert!(!parent.no_children());
let (_, residue) = parent.children.iter().next().unwrap();
assert!(DagMapRaw::owned_or_residue(parent.instance_id(), &residue));
let empty: &[RawBytes] = &[];
parent.prune_children_exclude(empty);
assert!(parent.no_children());
}
#[test]
fn prune_works_in_a_non_default_namespace() {
let ns = crate::common::Namespace::create().unwrap();
let mut head = DagMapRaw::new_in(&ns, None);
head.insert("root", "value");
let mut child = DagMapRaw::new_in(&ns, Some(&mut head));
child.insert("leaf", "cv");
let pruned = head.prune().unwrap();
assert_eq!(pruned.get("root").unwrap().as_slice(), b"value");
assert_eq!(child.get("root").unwrap().as_slice(), b"value");
assert_eq!(child.get("leaf").unwrap().as_slice(), b"cv");
}
#[test]
fn deep_acyclic_chain_remains_readable_and_prunable() {
let mut head = DagMapRaw::new(None);
head.insert("root", "value");
for _ in 0..1030 {
head = DagMapRaw::new(Some(&mut head));
}
assert_eq!(head.get("root").unwrap().as_slice(), b"value");
let pruned = head.prune().unwrap();
assert_eq!(pruned.get("root").unwrap().as_slice(), b"value");
}
#[test]
fn test_meta_with_parent_child() {
let mut i0 = DagMapRaw::new(None);
i0.insert("base", "v0");
let mut i1 = DagMapRaw::new(Some(&mut i0));
i1.insert("child", "v1");
let id = i1.save_meta().unwrap();
let restored = DagMapRaw::from_meta(id).unwrap();
assert_eq!(restored.get("child").unwrap().as_slice(), b"v1");
assert_eq!(restored.get("base").unwrap().as_slice(), b"v0");
}
#[test]
fn test_prune_with_side_branches() {
let mut i0 = DagMapRaw::new(None);
i0.insert("k0", "v0");
let mut mid = DagMapRaw::new(Some(&mut i0));
let mut side = DagMapRaw::new(Some(&mut mid));
side.insert("k_side", "v_side");
let mut side2 = DagMapRaw::new(Some(&mut mid));
side2.insert("k_side2", "v_side2");
let mut head = DagMapRaw::new(Some(&mut mid));
head.insert("k_head", "v_head");
let pruned = head.prune().unwrap();
assert_eq!(pruned.get("k0").unwrap().as_slice(), b"v0");
assert_eq!(pruned.get("k_head").unwrap().as_slice(), b"v_head");
assert!(side.get("k_side").is_none());
assert!(side2.get("k_side2").is_none());
}
#[test]
fn destroy_sibling_preserves_other_siblings_and_parent() {
let mut p = DagMapRaw::new(None);
p.insert("shared", "pval");
let mut c1 = DagMapRaw::new(Some(&mut p));
c1.insert("c1", "v1");
let c2 = DagMapRaw::new(Some(&mut p));
assert_eq!(c1.get("shared").unwrap().as_slice(), b"pval");
assert_eq!(c2.get("shared").unwrap().as_slice(), b"pval");
c1.destroy();
assert!(c1.get("c1").is_none());
assert_eq!(p.get("shared").unwrap().as_slice(), b"pval");
assert_eq!(c2.get("shared").unwrap().as_slice(), b"pval");
}
#[test]
fn selective_prune_ids_are_publicly_discoverable() {
let mut parent = DagMapRaw::new(None);
let child1 = DagMapRaw::new(Some(&mut parent));
let child2 = DagMapRaw::new(Some(&mut parent));
let id1 = parent.child_id(&child1).unwrap();
let id2 = parent.child_id(&child2).unwrap();
assert_ne!(id1, id2);
assert_eq!(parent.child_ids().len(), 2);
parent.prune_children_include(&[id1]);
assert!(child1.is_dead());
assert!(!child2.is_dead());
assert_eq!(parent.child_ids(), vec![id2]);
}
#[test]
fn destroyed_node_does_not_serve_inherited_reads() {
let mut parent = DagMapRaw::new(None);
parent.insert("k", "v");
let mut child = DagMapRaw::new(Some(&mut parent));
assert_eq!(child.get("k").unwrap().as_slice(), b"v");
let stale_alias = unsafe { child.shadow() };
child.destroy();
assert!(child.get("k").is_none());
assert!(stale_alias.get("k").is_none());
assert_eq!(parent.get("k").unwrap().as_slice(), b"v");
}
#[test]
fn destroy_is_visible_to_meta_restored_handles() {
let mut parent = DagMapRaw::new(None);
parent.insert("k", "v");
let mut child = DagMapRaw::new(Some(&mut parent));
let id = child.save_meta().unwrap();
child.destroy();
let restored = DagMapRaw::from_meta(id).unwrap();
assert!(restored.get("k").is_none());
assert!(restored.is_dead());
}
#[test]
fn destroy_deep_child_chain_does_not_overflow_stack() {
let mut genesis = DagMapRaw::new(None);
genesis.insert("root", "value");
let mut cur = unsafe { genesis.shadow() };
for _ in 0..5000 {
cur = DagMapRaw::new(Some(&mut cur));
}
genesis.destroy();
assert!(genesis.get("root").is_none());
}
fn build_prune_fixture() -> (DagMapRaw, DagMapRaw, DagMapRaw) {
let mut genesis = DagMapRaw::new(None);
genesis.insert("k0", "v0");
genesis.insert("kg", "doomed");
let mut i1 = DagMapRaw::new(Some(&mut genesis));
i1.insert("k1", "v1");
i1.remove("kg");
let mut head = DagMapRaw::new(Some(&mut i1));
head.insert("k2", "v2");
head.insert("k1", "v1x");
(genesis, i1, head)
}
fn assert_merged_view(h: &DagMapRaw) {
assert_eq!(h.get("k0").unwrap().as_slice(), b"v0");
assert_eq!(h.get("k1").unwrap().as_slice(), b"v1x");
assert_eq!(h.get("k2").unwrap().as_slice(), b"v2");
assert!(h.get("kg").is_none());
}
#[test]
fn prune_crash_mid_merge_head_view_is_exact_and_rerun_converges() {
let (genesis, i1, head) = build_prune_fixture();
let mut linebuf = head.prune_collect_mainline().unwrap();
assert_eq!(linebuf.len(), 2); let (folded, genesis_part) = linebuf.split_at_mut(1);
DagMapRaw::prune_fold_node(&mut genesis_part[0], &folded[0]);
assert_merged_view(&head); assert!(genesis.data.get("kg").is_none());
assert!(head.get("kg").is_none());
assert_eq!(i1.data.get("k1").unwrap().as_slice(), b"v1");
let pruned = head.prune().unwrap();
assert_merged_view(&pruned);
assert!(pruned.parent.get_value().is_none());
assert!(i1.is_dead());
}
#[test]
fn prune_crash_after_merge_genesis_meta_sees_merged_state() {
let (genesis, _i1, head) = build_prune_fixture();
let genesis_id = genesis.save_meta().unwrap();
let mut linebuf = head.prune_collect_mainline().unwrap();
head.prune_merge_into_genesis(&mut linebuf);
let restored = DagMapRaw::from_meta(genesis_id).unwrap();
assert_merged_view(&restored);
assert_merged_view(&head);
let pruned = head.prune().unwrap();
assert_eq!(pruned.instance_id(), genesis_id);
assert_merged_view(&DagMapRaw::from_meta(genesis_id).unwrap());
}
#[test]
fn prune_crash_mid_reparent_both_children_views_exact() {
let (_genesis, _i1, mut head) = build_prune_fixture();
let mut c1 = DagMapRaw::new(Some(&mut head));
c1.insert("c1", "w1");
let mut c2 = DagMapRaw::new(Some(&mut head));
c2.insert("c2", "w2");
let mut linebuf = head.prune_collect_mainline().unwrap();
head.prune_merge_into_genesis(&mut linebuf);
let genesis = linebuf.last_mut().unwrap();
let c1_reg_id = head
.children
.iter()
.find(|(_, c)| c.instance_id() == c1.instance_id())
.unwrap()
.0;
*c1.parent.get_mut() = Some(unsafe { genesis.shadow() });
genesis.children.insert(&c1_reg_id, &c1);
for c in [&c1, &c2] {
assert_eq!(c.get("k0").unwrap().as_slice(), b"v0");
assert_eq!(c.get("k1").unwrap().as_slice(), b"v1x");
assert_eq!(c.get("k2").unwrap().as_slice(), b"v2");
assert!(c.get("kg").is_none());
}
assert_eq!(c1.get("c1").unwrap().as_slice(), b"w1");
assert_eq!(c2.get("c2").unwrap().as_slice(), b"w2");
let pruned = head.prune().unwrap();
assert_merged_view(&pruned);
assert_eq!(c1.get("c1").unwrap().as_slice(), b"w1");
assert_eq!(c2.get("c2").unwrap().as_slice(), b"w2");
assert_eq!(c1.get("k1").unwrap().as_slice(), b"v1x");
assert_eq!(c2.get("k1").unwrap().as_slice(), b"v1x");
}
#[test]
fn prune_interrupted_before_clear_residue_is_reclaimed_by_next_prune() {
let (genesis, i1, mut head) = build_prune_fixture();
head.insert("k_head_only", "hv");
let mut child = DagMapRaw::new(Some(&mut head));
child.insert("c", "cv");
let mut linebuf = head.prune_collect_mainline().unwrap();
let mainline_ids: Vec<InstanceId> = {
let mut ids = vec![head.instance_id()];
ids.extend(linebuf.iter().map(|n| n.instance_id()));
ids
};
let pending: std::collections::HashSet<vsdb_core::common::RawBytes> =
head.children.iter().map(|(id, _)| id).collect();
DagMapRaw::prune_destroy_side_branches(&mut linebuf, &mainline_ids, &pending);
head.prune_merge_into_genesis(&mut linebuf);
head.prune_reparent_children(linebuf.last_mut().unwrap());
assert_eq!(child.get("c").unwrap().as_slice(), b"cv");
assert_eq!(child.get("k_head_only").unwrap().as_slice(), b"hv");
assert!(head.data.iter().next().is_some());
let pruned2 = child.prune().unwrap();
assert_eq!(pruned2.instance_id(), genesis.instance_id());
assert!(head.is_dead());
assert!(i1.is_dead());
assert_eq!(pruned2.get("c").unwrap().as_slice(), b"cv");
assert_eq!(pruned2.get("k_head_only").unwrap().as_slice(), b"hv");
assert_eq!(pruned2.get("k1").unwrap().as_slice(), b"v1x");
}
#[test]
fn destroy_does_not_follow_stale_foreign_registry_entries() {
let mut real_parent = DagMapRaw::new(None);
real_parent.insert("p", "pv");
let mut child = DagMapRaw::new(Some(&mut real_parent));
child.insert("c", "cv");
let mut stranger = DagMapRaw::new(None);
stranger.insert("s", "sv");
stranger.children.insert([0xffu8; 16], &child);
stranger.destroy();
assert!(stranger.is_dead());
assert_eq!(child.get("c").unwrap().as_slice(), b"cv");
assert_eq!(child.get("p").unwrap().as_slice(), b"pv");
assert!(!real_parent.no_children());
}
#[test]
fn is_dead_recognizes_tombstoned_entries() {
let mut node = DagMapRaw::new(None);
assert!(node.is_dead());
node.insert("only", "v");
assert!(!node.is_dead());
assert_eq!(node.get("only").unwrap().as_slice(), b"v");
node.remove("only");
assert!(node.get("only").is_none());
assert!(node.is_dead());
node.insert("a", "va");
node.insert("b", "vb");
node.remove("a");
assert!(!node.is_dead());
node.remove("b");
assert!(node.is_dead());
}