Skip to content

Commit

Permalink
chore: restore trie_ops tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xaatif committed Jul 16, 2024
1 parent 25a8145 commit a07a16a
Showing 1 changed file with 346 additions and 0 deletions.
346 changes: 346 additions & 0 deletions mpt_trie/src/trie_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,3 +773,349 @@ fn create_node_if_ins_val_not_hash<F: FnOnce(Vec<u8>) -> Node>(
ValOrHash::Hash(h) => Err(TrieOpError::ExistingHashNodeError(h)),
}
}

#[cfg(test)]
mod tests {
use std::{collections::HashSet, iter::once};

use log::debug;

use super::ValOrHash;
use crate::{
partial_trie::Node,
testing_utils::{
common_setup, entry, entry_with_value,
generate_n_hash_nodes_entries_for_empty_slots_in_trie,
generate_n_random_fixed_trie_value_entries,
generate_n_random_variable_trie_value_entries, get_non_hash_values_in_trie,
unwrap_iter_item_to_val, TestInsertValEntry,
},
trie_ops::TrieOpResult,
utils::{create_mask_of_1s, TryFromIterator},
};

const MASSIVE_TRIE_SIZE: usize = 100000;
const COW_TEST_TRIE_SIZE: usize = 500;

fn insert_entries_and_assert_all_exist_in_trie_with_no_extra(
entries: &[TestInsertValEntry],
) -> TrieOpResult<()> {
let trie = Node::try_from_iter(entries.iter().cloned())?;
assert_all_entries_in_trie(entries, &trie);

Ok(())
}

fn assert_all_entries_in_trie(entries: &[TestInsertValEntry], trie: &Node) {
let entries_in_trie = get_non_hash_values_in_trie(trie);

let all_entries_retrieved: Vec<_> = entries
.iter()
.filter(|e| !entries_in_trie.contains(e))
.collect();

// HashSet to avoid the linear search below.
let entries_hashset: HashSet<TestInsertValEntry> =
HashSet::from_iter(entries.iter().cloned());
let additional_entries_inserted: Vec<_> = entries_in_trie
.iter()
.filter(|e| !entries_hashset.contains(e))
.collect();

let all_entries_retrievable_from_trie = all_entries_retrieved.is_empty();
let no_additional_entries_inserted = additional_entries_inserted.is_empty();

if !all_entries_retrievable_from_trie || !no_additional_entries_inserted {
println!(
"Total retrieved/expected: {}/{}",
entries_in_trie.len(),
entries.len()
);

println!("Missing: {all_entries_retrieved:#?}");
println!("Unexpected retrieved: {additional_entries_inserted:#?}");
}

assert!(all_entries_retrievable_from_trie);
assert!(no_additional_entries_inserted);
}

#[test]
fn single_insert() -> TrieOpResult<()> {
common_setup();
insert_entries_and_assert_all_exist_in_trie_with_no_extra(&[entry(0x1234)])
}

#[test]
fn two_disjoint_inserts_works() -> TrieOpResult<()> {
common_setup();
let entries = [entry(0x1234), entry(0x5678)];

insert_entries_and_assert_all_exist_in_trie_with_no_extra(&entries)
}

#[test]
fn two_inserts_that_share_one_nibble_works() -> TrieOpResult<()> {
common_setup();
let entries = [entry(0x1234), entry(0x1567)];

insert_entries_and_assert_all_exist_in_trie_with_no_extra(&entries)
}

#[test]
fn two_inserts_that_differ_on_last_nibble_works() -> TrieOpResult<()> {
common_setup();
let entries = [entry(0x1234), entry(0x1235)];

insert_entries_and_assert_all_exist_in_trie_with_no_extra(&entries)
}

#[test]
fn diagonal_inserts_to_base_of_trie_works() -> TrieOpResult<()> {
common_setup();
let entries: Vec<_> = (0..=64).map(|i| entry(create_mask_of_1s(i * 4))).collect();

insert_entries_and_assert_all_exist_in_trie_with_no_extra(&entries)
}

#[test]
fn updating_an_existing_node_works() -> TrieOpResult<()> {
common_setup();
let mut entries = [entry(0x1234), entry(0x1234)];
entries[1].1 = vec![100];

let trie = Node::try_from_iter(entries)?;
assert_eq!(trie.get(0x1234), Some([100].as_slice()));

Ok(())
}

#[test]
fn cloning_a_trie_creates_two_separate_tries() -> TrieOpResult<()> {
common_setup();

let trie = Node::try_from_iter(once(entry(0x1234)))?;
let mut cloned_trie = trie.clone();

cloned_trie.extend(once(entry(0x5678)))?;

assert_ne!(trie, cloned_trie);
assert_ne!(trie.hash(), cloned_trie.hash());

Ok(())
}

#[test]
fn mass_inserts_fixed_sized_keys_all_entries_are_retrievable() -> TrieOpResult<()> {
common_setup();
let entries: Vec<_> =
generate_n_random_fixed_trie_value_entries(MASSIVE_TRIE_SIZE, 0).collect();

insert_entries_and_assert_all_exist_in_trie_with_no_extra(&entries)
}

#[test]
fn mass_inserts_variable_sized_keys_all_entries_are_retrievable() -> TrieOpResult<()> {
common_setup();
let entries: Vec<_> =
generate_n_random_variable_trie_value_entries(MASSIVE_TRIE_SIZE, 0).collect();

insert_entries_and_assert_all_exist_in_trie_with_no_extra(&entries)
}

#[test]
fn mass_inserts_variable_sized_keys_with_hash_nodes_all_entries_are_retrievable(
) -> TrieOpResult<()> {
common_setup();
let non_hash_entries: Vec<_> =
generate_n_random_variable_trie_value_entries(MASSIVE_TRIE_SIZE, 0).collect();
let mut trie = Node::try_from_iter(non_hash_entries.iter().cloned())?;

let extra_hash_entries = generate_n_hash_nodes_entries_for_empty_slots_in_trie(
&trie,
MASSIVE_TRIE_SIZE / 10,
51,
);
assert!(trie.extend(extra_hash_entries.iter().cloned()).is_ok());

let all_nodes: HashSet<_> = trie.items().collect();

// Too much work to make `assert_all_entries_in_trie` work with hash nodes. Do a
// quick hack for this test.
assert!(non_hash_entries
.into_iter()
.all(|(k, v)| all_nodes.contains(&(k, ValOrHash::Val(v)))));
assert!(extra_hash_entries
.into_iter()
.all(|(k, h)| all_nodes.contains(&(k, ValOrHash::Hash(h)))));

Ok(())
}

#[test]
fn equivalency_check_works() -> TrieOpResult<()> {
common_setup();

let entries = generate_n_random_fixed_trie_value_entries(MASSIVE_TRIE_SIZE, 0);
let big_trie_1 = Node::try_from_iter(entries)?;
assert_eq!(big_trie_1, big_trie_1);

let entries = generate_n_random_fixed_trie_value_entries(MASSIVE_TRIE_SIZE, 1);
let big_trie_2 = Node::try_from_iter(entries)?;

assert_ne!(big_trie_1, big_trie_2);

Ok(())
}

#[test]
fn two_variable_length_keys_with_overlap_are_queryable() -> TrieOpResult<()> {
common_setup();

let entries = [entry_with_value(0x1234, 1), entry_with_value(0x12345678, 2)];
let trie = Node::try_from_iter(entries.iter().cloned())?;

assert_eq!(trie.get(0x1234), Some([1].as_slice()));
assert_eq!(trie.get(0x12345678), Some([2].as_slice()));

Ok(())
}

#[test]
fn get_massive_trie_works() -> TrieOpResult<()> {
common_setup();

let random_entries: Vec<_> =
generate_n_random_fixed_trie_value_entries(MASSIVE_TRIE_SIZE, 9001).collect();
let trie = Node::try_from_iter(random_entries.iter().cloned())?;

for (k, v) in random_entries.into_iter() {
debug!("Attempting to retrieve {:?}...", (k, &v));
let res = trie.get(k);

assert_eq!(res, Some(v.as_slice()));
}

Ok(())
}

#[test]
fn held_trie_cow_references_do_not_change_as_trie_changes() -> TrieOpResult<()> {
common_setup();

let entries = generate_n_random_variable_trie_value_entries(COW_TEST_TRIE_SIZE, 9002);

let mut all_nodes_in_trie_after_each_insert = Vec::new();
let mut root_node_after_each_insert = Vec::new();

let mut trie = Node::default();
for (k, v) in entries {
trie.insert(k, v)?;

all_nodes_in_trie_after_each_insert.push(get_non_hash_values_in_trie(&trie));
root_node_after_each_insert.push(trie.clone());
}

for (old_trie_nodes_truth, old_root_node) in all_nodes_in_trie_after_each_insert
.into_iter()
.zip(root_node_after_each_insert.into_iter())
{
let nodes_retrieved = get_non_hash_values_in_trie(&old_root_node);
assert_eq!(old_trie_nodes_truth, nodes_retrieved)
}

Ok(())
}

#[test]
fn trie_iter_works() -> TrieOpResult<()> {
common_setup();

let entries: HashSet<_> =
generate_n_random_variable_trie_value_entries(MASSIVE_TRIE_SIZE, 9003).collect();
let trie = Node::try_from_iter(entries.iter().cloned())?;

let trie_items: HashSet<_> = trie
.items()
.map(|(k, v)| (k, unwrap_iter_item_to_val(v)))
.collect();

assert!(entries.iter().all(|e| trie_items.contains(e)));
assert!(trie_items.iter().all(|item| entries.contains(item)));

Ok(())
}

#[test]
fn deleting_a_non_existent_node_returns_none() -> TrieOpResult<()> {
common_setup();

let mut trie = Node::default();
trie.insert(0x1234, vec![91])?;

let res = trie.delete(0x5678)?;
assert!(res.is_none());

Ok(())
}

#[test]
fn existent_node_key_contains_returns_true() -> TrieOpResult<()> {
common_setup();

let mut trie = Node::default();
trie.insert(0x1234, vec![91])?;
assert!(trie.contains(0x1234));

Ok(())
}

#[test]
fn non_existent_node_key_contains_returns_false() -> TrieOpResult<()> {
common_setup();

let mut trie = Node::default();
trie.insert(0x1234, vec![91])?;
assert!(!trie.contains(0x5678));

Ok(())
}

#[test]
fn deleting_from_an_empty_trie_returns_none() -> TrieOpResult<()> {
common_setup();

let mut trie = Node::default();
let res = trie.delete(0x1234)?;
assert!(res.is_none());

Ok(())
}

#[test]
fn deletion_massive_trie() -> TrieOpResult<()> {
common_setup();

let entries: Vec<_> =
generate_n_random_variable_trie_value_entries(MASSIVE_TRIE_SIZE, 7).collect();
let mut trie = Node::try_from_iter(entries.iter().cloned())?;

// Delete half of the elements
let half_entries = entries.len() / 2;

let entries_to_delete = entries.iter().take(half_entries);
for (k, v) in entries_to_delete {
let res = trie.delete(*k)?;

assert!(trie.get(*k).is_none());
assert_eq!(res.as_ref(), Some(v));
}

let entries_that_still_should_exist = entries.into_iter().skip(half_entries);
for (k, v) in entries_that_still_should_exist {
assert_eq!(trie.get(k), Some(v.as_slice()));
}

Ok(())
}
}

0 comments on commit a07a16a

Please sign in to comment.