in merkledb/src/tests.rs [287:379]
fn test_randomized_union_diff() {
// In this test, we basically make a chain of diffs
// start from an empty MDB. add a bunch of things to it, take a diff
// and repeat.
//
// We should be able to merge the diffs in an arbitrary order
// and obtain back the same MDB
use rand::seq::SliceRandom;
use rand::{RngCore, SeedableRng};
use crate::MerkleNodeId;
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
let mut mdb = MerkleMemDB::default();
// the collection of all the diffs
let mut diffs: Vec<MerkleMemDB> = Vec::new();
for _iters in 0..100 {
// we keep the previous db so we can compute a idff
let prevmdb = mdb.clone();
// in each iteration we add a "file" and a "cas" with a different
// set of chunks in each.
// We limit to a set of no more than 1000 unique chunks to get
// a decent amount of intersection in the tree structures
let filechunks: Vec<ChunkInfo> = (0..(1 + (rng.next_u64() % 10)))
.map(|_| {
let h = compute_data_hash(&generate_random_string(rng.next_u64() % 1000, 100));
ChunkInfo { hash: h, length: 100 }
})
.collect();
let filenodes: Vec<_> = filechunks.iter().map(|x| mdb.add_chunk(x).0).collect();
mdb.merge_to_file(&filenodes);
let caschunks: Vec<ChunkInfo> = (0..(1 + (rng.next_u64() % 10)))
.map(|_| {
let h = compute_data_hash(&generate_random_string(rng.next_u64() % 1000, 100));
ChunkInfo { hash: h, length: 100 }
})
.collect();
let casnodes: Vec<_> = caschunks.iter().map(|x| mdb.add_chunk(x).0).collect();
mdb.merge_to_cas(&casnodes);
let mut diff = MerkleMemDB::default();
diff.difference(&mdb, &prevmdb);
diffs.push(diff);
}
// now we have collected a 100 diffs. we will union them arbitrarily
diffs.shuffle(&mut rng);
let mut unionmdb = MerkleMemDB::default();
for d in diffs {
unionmdb.union_with(&d);
}
unionmdb.union_finalize().unwrap();
// do all checks
mdb.only_file_invariant_checks();
unionmdb.only_file_invariant_checks();
// now, mdb and unionmdb should be identical.
// apart form parent attributes which may differ.
for i in 0..mdb.get_sequence_number() {
let mdbnode = mdb.find_node_by_id(i as MerkleNodeId).unwrap();
let unionnode = unionmdb.find_node(mdbnode.hash()).unwrap();
// check that they have the same length and children count
assert_eq!(mdbnode.len(), unionnode.len());
assert_eq!(mdbnode.children().len(), unionnode.children().len());
// check that is_cas and is_file attributes match
let mdbnodeattr = mdb.node_attributes(mdbnode.id()).unwrap();
let unionnodeattr = unionmdb.node_attributes(unionnode.id()).unwrap();
assert_eq!(mdbnodeattr.is_cas(), unionnodeattr.is_cas());
assert_eq!(mdbnodeattr.is_file(), unionnodeattr.is_file());
assert_eq!(mdbnodeattr.has_cas_data(), unionnodeattr.has_cas_data());
assert_eq!(mdbnodeattr.has_file_data(), unionnodeattr.has_file_data());
// loop through each child validating they are the same
for (chidx, chid) in mdbnode.children().iter().enumerate() {
let unionchid = unionnode.children()[chidx];
assert_eq!(chid.1, unionchid.1);
let chnode = mdb.find_node_by_id(chid.0).unwrap();
let unionchnode = unionmdb.find_node_by_id(unionchid.0).unwrap();
assert_eq!(chnode.hash(), unionchnode.hash());
}
// we just assert that they can both reconstruct (or not)
// The exact reconstruction is not important.
assert_eq!(
mdb.reconstruct_from_file(&[mdbnode.clone()]).is_ok(),
unionmdb.reconstruct_from_file(&[unionnode.clone()]).is_ok()
);
assert_eq!(
mdb.reconstruct_from_cas(&[mdbnode.clone()]).is_ok(),
unionmdb.reconstruct_from_cas(&[unionnode.clone()]).is_ok()
);
}
}