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_retry_after_parent_null_unlinks() {
let mut parent = DagMapRaw::new(None);
let mut child = DagMapRaw::new(Some(&mut parent));
child.insert("k", "v");
let linked = child.parent.get_value().unwrap();
child
.parent
.set_aux(super::DESTROY_PARENT_KEY, &Some(linked));
*child.parent.get_mut() = None;
child.data.clear();
assert!(!parent.no_children());
child.destroy();
assert!(parent.no_children());
assert!(child.get("k").is_none());
}
#[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 linebuf = head.prune_collect_mainline().unwrap();
assert_eq!(linebuf.len(), 2); let mut staged = StagedRows::new();
DagMapRaw::prune_fold_node(&mut staged, &linebuf[0]);
assert_merged_view(&head); assert_eq!(genesis.get("kg").as_deref(), Some(b"doomed".as_slice()));
assert!(genesis.get("k1").is_none());
drop(staged); 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).unwrap();
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_retry_does_not_regress_reparented_child() {
let (_genesis, _i1, mut head) = build_prune_fixture();
let mut child = DagMapRaw::new(Some(&mut head));
let child_id = head.child_id(&child).unwrap();
let mut linebuf = head.prune_collect_mainline().unwrap();
head.prune_merge_into_genesis(&mut linebuf).unwrap();
let genesis = linebuf.last_mut().unwrap();
*child.parent.get_mut() = Some(unsafe { genesis.shadow() });
genesis.children.insert(child_id, &child);
assert_merged_view(&child);
let mut staged = StagedRows::new();
DagMapRaw::prune_fold_node(&mut staged, &linebuf[0]);
assert_merged_view(&child);
drop(staged);
assert_merged_view(&head.prune().unwrap());
assert_merged_view(&child);
}
#[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).unwrap();
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");
}
fn prepare_marked_clear(head: &mut DagMapRaw) {
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).unwrap();
head.prune_reparent_children(linebuf.last_mut().unwrap());
head.mark_consumed_clearing(&mut linebuf);
}
#[test]
fn prune_retry_completes_partial_clearing_markers() {
for marker_mask in 0..4 {
let (genesis, i1, mut previous_head) = build_prune_fixture();
let mut head = DagMapRaw::new(Some(&mut previous_head));
let head_id = head.save_meta().unwrap();
let mut survivor = DagMapRaw::new(Some(&mut head));
survivor.insert("survivor", "value");
let mut linebuf = head.prune_collect_mainline().unwrap();
head.prune_merge_into_genesis(&mut linebuf).unwrap();
head.namespace().flush();
head.prune_reparent_children(linebuf.last_mut().unwrap());
head.namespace().flush();
let marker = Some(unsafe { genesis.shadow() });
head.parent.set_aux(PRUNE_CLEARING_KEY, &marker);
for (bit, node) in linebuf[..2].iter_mut().enumerate() {
if marker_mask & (1 << bit) != 0 {
node.parent.set_aux(PRUNE_CLEARING_KEY, &marker);
}
}
head.namespace().flush();
let pruned = DagMapRaw::from_meta(head_id).unwrap().prune().unwrap();
assert_eq!(pruned.instance_id(), genesis.instance_id());
assert_merged_view(&pruned);
for consumed in [&head, &previous_head, &i1] {
assert!(consumed.is_dead(), "partial marker set {marker_mask}");
assert!(consumed.no_children());
assert!(consumed.get("k1").is_none());
}
assert_merged_view(&survivor);
assert_eq!(survivor.get("survivor").as_deref(), Some(&b"value"[..]));
assert_eq!(pruned.children.iter().count(), 1);
let retried = DagMapRaw::from_meta(head_id).unwrap().prune().unwrap();
assert_eq!(retried.instance_id(), genesis.instance_id());
assert_merged_view(&retried);
assert_merged_view(&survivor);
assert_eq!(retried.children.iter().count(), 1);
}
}
thread_local! {
static CLEAR_STEPS_LEFT: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
}
pub(super) fn after_clear_step() {
CLEAR_STEPS_LEFT.with(|steps| {
if let Some(left) = steps.get() {
steps.set(left.checked_sub(1));
assert!(left > 0, "interrupted consumed-node cleanup");
}
});
}
#[test]
fn prune_cleanup_survives_repeated_interruptions() {
use std::panic::{AssertUnwindSafe, catch_unwind};
for already_marked in [false, true] {
for cut in 0..8 {
let mut ancestors = vec![DagMapRaw::new(None)];
for i in 1..5u8 {
let mut node = DagMapRaw::new(ancestors.last_mut());
node.insert([i], [i]);
ancestors.push(node);
}
let mut head = ancestors.pop().unwrap();
let mut survivor = DagMapRaw::new(Some(&mut head));
survivor.insert("survivor", "value");
if already_marked {
prepare_marked_clear(&mut head);
}
let head_id = head.save_meta().unwrap();
head.namespace().flush();
CLEAR_STEPS_LEFT.with(|steps| steps.set(Some(cut)));
let interrupted = catch_unwind(AssertUnwindSafe(|| head.prune()));
CLEAR_STEPS_LEFT.with(|steps| steps.set(None));
assert!(interrupted.is_err(), "cleanup cut {cut} was not reached");
ancestors[0].namespace().flush();
if cut < 4 {
for node in &ancestors {
assert!(!node.no_children());
}
} else {
for node in &ancestors[1..] {
assert!(node.data.iter().next().is_none());
assert!(node.parent.get_value().is_none());
}
}
let recovered = DagMapRaw::from_meta(head_id).unwrap().prune().unwrap();
assert_eq!(recovered.instance_id(), ancestors[0].instance_id());
for i in 1..5u8 {
assert_eq!(recovered.get([i]), Some(vec![i]));
assert_eq!(survivor.get([i]), Some(vec![i]));
}
assert_eq!(
survivor.get("survivor").as_deref(),
Some(b"value".as_slice())
);
for node in &ancestors[1..] {
assert!(node.is_dead(), "cut {cut} stranded an intermediate node");
assert!(node.no_children());
}
let head = DagMapRaw::from_meta(head_id).unwrap();
assert!(head.is_dead());
assert!(head.no_children());
assert_eq!(recovered.children.iter().count(), 1);
}
}
}
#[test]
fn prune_retry_after_parent_null_returns_genesis() {
let (genesis, i1, mut head) = build_prune_fixture();
let genesis_id = genesis.instance_id();
let mut child = DagMapRaw::new(Some(&mut head));
child.insert("c", "cv");
prepare_marked_clear(&mut head);
*head.parent.get_mut() = None;
let pruned = head.prune().unwrap();
assert_eq!(pruned.instance_id(), genesis_id);
assert_merged_view(&pruned);
assert_eq!(child.get("c").unwrap().as_slice(), b"cv");
assert_eq!(child.get("k2").unwrap().as_slice(), b"v2");
assert!(i1.is_dead());
}
#[test]
fn prune_retry_after_data_clear_does_not_regress_genesis() {
let (genesis, i1, mut head) = build_prune_fixture();
let genesis_id = genesis.instance_id();
let mut child = DagMapRaw::new(Some(&mut head));
child.insert("c", "cv");
prepare_marked_clear(&mut head);
head.children.clear();
head.data.clear();
let pruned = head.prune().unwrap();
assert_eq!(pruned.instance_id(), genesis_id);
assert_merged_view(&pruned);
assert_merged_view(&child);
assert_eq!(child.get("c").unwrap().as_slice(), b"cv");
assert!(i1.is_dead());
}
#[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).unwrap();
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());
}
#[test]
fn get_mut_tombstone_edit_during_panic_does_not_abort() {
use std::panic::{AssertUnwindSafe, catch_unwind};
let mut d = DagMapRaw::new(None);
d.insert("k", "v");
let r = catch_unwind(AssertUnwindSafe(|| {
let mut g = d.get_mut("k").unwrap();
g.clear();
panic!("interrupted edit");
}));
assert!(r.is_err());
assert_eq!(d.get("k").unwrap().as_slice(), b"v");
}
#[test]
fn clone_prune_preserves_original_graph() {
let mut root = DagMapRaw::new(None);
root.insert(b"inherited", b"root");
let mut head = DagMapRaw::new(Some(&mut root));
head.insert(b"head", b"value");
let mut child = DagMapRaw::new(Some(&mut head));
child.insert(b"child", b"value");
let child_id = head.child_id(&child).unwrap();
let copy = head.clone();
assert_ne!(copy.instance_id(), head.instance_id());
assert_eq!(copy.namespace().id(), head.namespace().id());
assert_eq!(copy.get(b"inherited"), Some(b"root".to_vec()));
assert_eq!(copy.child_ids(), vec![child_id.clone()]);
let copied_child = copy.children.get(&child_id).unwrap();
assert_ne!(copied_child.instance_id(), child.instance_id());
let mut copied_root = copy.prune().unwrap();
assert_ne!(copied_root.instance_id(), root.instance_id());
assert_eq!(copied_child.get(b"head"), Some(b"value".to_vec()));
assert_eq!(copied_child.get(b"child"), Some(b"value".to_vec()));
assert_eq!(head.get(b"head"), Some(b"value".to_vec()));
assert_eq!(child.get(b"child"), Some(b"value".to_vec()));
assert!(root.child_id(&head).is_some());
copied_root.destroy();
assert!(copied_child.is_dead());
assert_eq!(child.get(b"child"), Some(b"value".to_vec()));
assert_eq!(child.get(b"inherited"), Some(b"root".to_vec()));
}
#[test]
fn clone_remaps_prune_recovery_markers() {
let mut root = DagMapRaw::new(None);
let mut head = DagMapRaw::new(Some(&mut root));
head.insert(b"key", b"value");
let bytes = postcard::to_allocvec(&head).unwrap();
let original_root = head.prune().unwrap();
let consumed: DagMapRaw = postcard::from_bytes(&bytes).unwrap();
let copied_root = consumed.clone().prune().unwrap();
assert_ne!(copied_root.instance_id(), original_root.instance_id());
assert_eq!(copied_root.get(b"key"), Some(b"value".to_vec()));
assert_eq!(original_root.get(b"key"), Some(b"value".to_vec()));
}
#[test]
fn prune_retry_visits_repeated_marked_nodes_once() {
let mut genesis = DagMapRaw::new(None);
genesis.insert(b"base", b"base-value");
let mut head = DagMapRaw::new(Some(&mut genesis));
head.insert(b"head", b"head-value");
let survivor = DagMapRaw::new(Some(&mut head));
let bytes = postcard::to_allocvec(&head).unwrap();
let expected = head.prune().unwrap().instance_id();
let consumed: DagMapRaw = postcard::from_bytes(&bytes).unwrap();
let (_, _, mut children): (
MapxRaw,
Orphan<Option<DagMapRaw>>,
MapxOrdRawKey<DagMapRaw>,
) = postcard::from_bytes(&bytes).unwrap();
children.insert(b"repeat-a", &consumed);
children.insert(b"repeat-b", &consumed);
let recovered = consumed.prune().unwrap();
assert_eq!(recovered.instance_id(), expected);
assert_eq!(recovered.get(b"base").unwrap(), b"base-value");
assert_eq!(recovered.get(b"head").unwrap(), b"head-value");
assert_eq!(survivor.get(b"head").unwrap(), b"head-value");
assert!(children.iter().next().is_none());
}