@@ -640,26 +640,38 @@ test "batchGetRoot matches getRoot for dirty branches at various depths" {
640640 const depth : Depth = @intCast (depth_usize );
641641 const max_length = @as (usize , 1 ) << @intCast (depth );
642642
643- // Build a full tree, then modify some leaves to create dirty branches.
644643 var leaves = try allocator .alloc (Node .Id , max_length );
645644 defer allocator .free (leaves );
646645 for (0.. max_length ) | i | {
647646 leaves [i ] = try pool .createLeafFromUint (@intCast (i + 1 ));
647+ try pool .ref (leaves [i ]);
648648 }
649+ defer for (leaves ) | leaf | pool .unref (leaf );
649650
650- const root = try Node .fillWithContents (p , leaves , depth );
651- defer pool .unref (root );
651+ // Build two identical dirty trees. `getRoot` mutates in-place, so the
652+ // batch path must run on a separate tree to exercise dirty branches.
653+ const leaves_for_get_root = try allocator .dupe (Node .Id , leaves );
654+ defer allocator .free (leaves_for_get_root );
655+
656+ const leaves_for_batch = try allocator .dupe (Node .Id , leaves );
657+ defer allocator .free (leaves_for_batch );
658+
659+ const root_get = try Node .fillWithContents (p , leaves_for_get_root , depth );
660+ defer pool .unref (root_get );
661+
662+ const root_batch = try Node .fillWithContents (p , leaves_for_batch , depth );
663+ defer pool .unref (root_batch );
664+
665+ const new_leaf_get = try pool .createLeafFromUint (0xBEEF );
666+ const dirty_get = try root_get .setNodeAtDepth (p , depth , 0 , new_leaf_get );
667+ defer pool .unref (dirty_get );
652668
653- // Modify a few leaves to make branches dirty.
654- const new_leaf = try pool .createLeafFromUint (0xBEEF );
655- const dirty_root = try root .setNodeAtDepth (p , depth , 0 , new_leaf );
656- defer pool .unref (dirty_root );
669+ const new_leaf_batch = try pool .createLeafFromUint (0xBEEF );
670+ const dirty_batch = try root_batch .setNodeAtDepth (p , depth , 0 , new_leaf_batch );
671+ defer pool .unref (dirty_batch );
657672
658- // Clone the tree for getRoot (since getRoot mutates state in-place).
659- // Both should produce the same hash.
660- const expected = dirty_root .getRoot (p );
661- // getRoot already computed the hashes, but batchGetRoot should return the same result.
662- const actual = try dirty_root .batchGetRoot (p , depth , allocator );
673+ const expected = dirty_get .getRoot (p );
674+ const actual = try dirty_batch .batchGetRoot (p , depth , allocator );
663675 try std .testing .expectEqualSlices (u8 , expected , actual );
664676 }
665677}
@@ -673,31 +685,47 @@ test "batchGetRoot matches getRoot with multiple dirty leaves" {
673685 const depth : Depth = 4 ;
674686 const max_length = @as (usize , 1 ) << depth ;
675687
676- // Build a full tree with all leaves set.
677688 var leaves = try allocator .alloc (Node .Id , max_length );
678689 defer allocator .free (leaves );
679690 for (0.. max_length ) | i | {
680691 leaves [i ] = try pool .createLeafFromUint (@intCast (i + 1 ));
692+ try pool .ref (leaves [i ]);
681693 }
694+ defer for (leaves ) | leaf | pool .unref (leaf );
695+
696+ // Build two identical dirty trees. `getRoot` mutates in-place, so the
697+ // batch path must run on a separate tree to exercise dirty branches.
698+ const leaves_for_get_root = try allocator .dupe (Node .Id , leaves );
699+ defer allocator .free (leaves_for_get_root );
700+
701+ const leaves_for_batch = try allocator .dupe (Node .Id , leaves );
702+ defer allocator .free (leaves_for_batch );
703+
704+ const root_get = try Node .fillWithContents (p , leaves_for_get_root , depth );
705+ defer pool .unref (root_get );
706+ var modified_get = root_get ;
682707
683- const root = try Node .fillWithContents (p , leaves , depth );
708+ const root_batch = try Node .fillWithContents (p , leaves_for_batch , depth );
709+ defer pool .unref (root_batch );
710+ var modified_batch = root_batch ;
684711
685- // Modify multiple scattered leaves.
686- var modified = root ;
687712 const modify_indices = [_ ]usize { 0 , 3 , 7 , 12 , 15 };
688713 for (modify_indices ) | idx | {
689- const new_leaf = try pool .createLeafFromUint (@intCast (0xF000 + idx ));
690- const old = modified ;
691- modified = try modified .setNodeAtDepth (p , depth , idx , new_leaf );
692- if (old != root ) pool .unref (old );
714+ const new_leaf_get = try pool .createLeafFromUint (@intCast (0xF000 + idx ));
715+ const old_get = modified_get ;
716+ modified_get = try modified_get .setNodeAtDepth (p , depth , idx , new_leaf_get );
717+ if (old_get != root_get ) pool .unref (old_get );
718+
719+ const new_leaf_batch = try pool .createLeafFromUint (@intCast (0xF000 + idx ));
720+ const old_batch = modified_batch ;
721+ modified_batch = try modified_batch .setNodeAtDepth (p , depth , idx , new_leaf_batch );
722+ if (old_batch != root_batch ) pool .unref (old_batch );
693723 }
694- defer pool .unref (modified );
695- pool .unref (root );
724+ defer pool .unref (modified_get );
725+ defer pool .unref (modified_batch );
696726
697- // Use getRoot on a copy-by-reference to get expected hash, then verify batchGetRoot matches.
698- // Since getRoot computes in-place, calling it first then batchGetRoot should still agree.
699- const expected = modified .getRoot (p );
700- const actual = try modified .batchGetRoot (p , depth , allocator );
727+ const expected = modified_get .getRoot (p );
728+ const actual = try modified_batch .batchGetRoot (p , depth , allocator );
701729 try std .testing .expectEqualSlices (u8 , expected , actual );
702730}
703731
0 commit comments