diff --git a/src/compressor.rs b/src/compressor.rs index b9cdf5e..e942338 100644 --- a/src/compressor.rs +++ b/src/compressor.rs @@ -250,132 +250,7 @@ impl<'a> Compressor<'a> { } #[cfg(test)] -mod level_tests { - use crate::compressor::Level; - #[test] - fn new_produces_empty_level() { - let l = Level::new(15); - assert_eq!(l.max_length, 15); - assert_eq!(l.current_chain_length, 0); - assert_eq!(l.current, None); - } +mod level_tests; - #[test] - fn update_adds_to_non_full_level() { - let mut l = Level::new(10); - l.update(7, true); - assert_eq!(l.max_length, 10); - assert_eq!(l.current_chain_length, 1); - assert_eq!(l.current, Some(7)); - } - - #[test] - #[should_panic(expected = "Tried to add to an already full level")] - fn update_panics_if_adding_and_too_full() { - let mut l = Level::new(5); - l.update(1, true); - l.update(2, true); - l.update(3, true); - l.update(4, true); - l.update(5, true); - l.update(6, true); - } - - #[test] - fn update_resets_level_correctly() { - let mut l = Level::new(5); - l.update(1, true); - l.update(2, true); - l.update(3, true); - l.update(4, true); - l.update(5, true); - l.update(6, false); - assert_eq!(l.max_length, 5); - assert_eq!(l.current_chain_length, 1); - assert_eq!(l.current, Some(6)); - } - - #[test] - fn get_current_returns_current() { - let mut l = Level::new(5); - assert_eq!(l.get_current(), None); - l.update(23, true); - assert_eq!(l.get_current(), Some(23)); - } - - #[test] - fn has_space_returns_true_if_empty() { - let l = Level::new(15); - assert_eq!(l.has_space(), true); - } - - #[test] - fn has_space_returns_true_if_part_full() { - let mut l = Level::new(15); - l.update(12, true); - l.update(234, true); - l.update(1, true); - l.update(143, true); - l.update(15, true); - assert_eq!(l.has_space(), true); - } - - #[test] - fn has_space_returns_false_if_full() { - let mut l = Level::new(5); - l.update(1, true); - l.update(2, true); - l.update(3, true); - l.update(4, true); - l.update(5, true); - assert_eq!(l.has_space(), false); - } -} - -#[test] -fn test_new_map() { - let mut initial: BTreeMap = BTreeMap::new(); - - let mut prev = None; - for i in 0i64..=13i64 { - initial.insert( - i, - StateGroupEntry { - in_range: true, - prev_state_group: prev, - state_map: StateMap::new(), - }, - ); - - prev = Some(i) - } - - let compressor = Compressor::compress(&initial, &[3, 3]); - - let new_state = compressor.new_state_group_map; - - let expected_edges: BTreeMap = vec![ - (1, 0), - (2, 1), - (4, 3), - (5, 4), - (6, 3), - (7, 6), - (8, 7), - (9, 6), - (10, 9), - (11, 10), - (13, 12), - ] - .into_iter() - .collect(); - - for sg in 0i64..=13i64 { - assert_eq!( - expected_edges.get(&sg).cloned(), - new_state[&sg].prev_state_group, - "state group {} did not match expected", - sg, - ); - } -} +#[cfg(test)] +mod compressor_tests; diff --git a/src/compressor/compressor_tests.rs b/src/compressor/compressor_tests.rs new file mode 100644 index 0000000..a511920 --- /dev/null +++ b/src/compressor/compressor_tests.rs @@ -0,0 +1,696 @@ +use crate::{ + compressor::{Compressor, Level, Stats}, + StateGroupEntry, +}; +use state_map::StateMap; +use std::collections::BTreeMap; +use string_cache::DefaultAtom as Atom; + +#[test] +fn compress_creates_correct_compressor() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 + for i in 0i64..=13i64 { + initial.insert( + i, + StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }, + ); + + prev = Some(i) + } + + let compressor = Compressor::compress(&initial, &[3, 3]); + + let new_state = &compressor.new_state_group_map; + + // This should create the following structure + // + // 0 3\ 12 + // 1 4 6\ 13 + // 2 5 7 9 + // 8 10 + // 11 + let expected_edges: BTreeMap = vec![ + (1, 0), + (2, 1), + (4, 3), + (5, 4), + (6, 3), + (7, 6), + (8, 7), + (9, 6), + (10, 9), + (11, 10), + (13, 12), + ] + .into_iter() + .collect(); + + for sg in 0i64..=13i64 { + assert_eq!( + expected_edges.get(&sg).cloned(), + new_state[&sg].prev_state_group, + "state group {} did not match expected", + sg, + ); + } +} + +#[test] +fn create_new_tree_does_nothing_if_already_compressed() { + // This should create the following structure + // + // 0 3\ 12 + // 1 4 6\ 13 + // 2 5 7 9 + // 8 10 + // 11 + let initial_edges: BTreeMap = vec![ + (1, 0), + (2, 1), + (4, 3), + (5, 4), + (6, 3), + (7, 6), + (8, 7), + (9, 6), + (10, 9), + (11, 10), + (13, 12), + ] + .into_iter() + .collect(); + + let mut initial: BTreeMap = BTreeMap::new(); + + for i in 0i64..=13i64 { + // edge from map + let pred_group = initial_edges.get(&i); + + // Need Option not Option<&i64> + let prev; + match pred_group { + Some(i) => prev = Some(*i), + None => prev = None, + } + + // insert that edge into the initial map + initial.insert( + i, + StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }, + ); + } + + let mut compressor = Compressor { + original_state_map: &initial, + new_state_group_map: BTreeMap::new(), + levels: vec![Level::new(3), Level::new(3)], + stats: Stats::default(), + }; + + compressor.create_new_tree(); + + let new_state = &compressor.new_state_group_map; + + assert_eq!(initial, *new_state); +} + +#[test] +fn create_new_tree_respects_levels() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 + for i in 0i64..=13i64 { + initial.insert( + i, + StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }, + ); + + prev = Some(i) + } + + let mut compressor = Compressor { + original_state_map: &initial, + new_state_group_map: BTreeMap::new(), + levels: vec![Level::new(3), Level::new(3)], + stats: Stats::default(), + }; + compressor.create_new_tree(); + + let new_state = &compressor.new_state_group_map; + + // This should create the following structure + // + // 0 3\ 12 + // 1 4 6\ 13 + // 2 5 7 9 + // 8 10 + // 11 + let expected_edges: BTreeMap = vec![ + (1, 0), + (2, 1), + (4, 3), + (5, 4), + (6, 3), + (7, 6), + (8, 7), + (9, 6), + (10, 9), + (11, 10), + (13, 12), + ] + .into_iter() + .collect(); + + for sg in 0i64..=13i64 { + assert_eq!( + expected_edges.get(&sg).cloned(), + new_state[&sg].prev_state_group, + "state group {} did not match expected", + sg, + ); + } +} + +#[test] +#[should_panic(expected = "Can only call `create_new_tree` once")] +fn create_new_tree_panics_if_run_twice() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + for i in 0i64..=13i64 { + initial.insert( + i, + StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }, + ); + + prev = Some(i) + } + + let mut compressor = Compressor { + original_state_map: &initial, + new_state_group_map: BTreeMap::new(), + levels: vec![Level::new(3), Level::new(3)], + stats: Stats::default(), + }; + compressor.create_new_tree(); + compressor.create_new_tree(); +} + +#[test] +fn create_new_tree_respects_all_not_in_range() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 + for i in 0i64..=13i64 { + initial.insert( + i, + StateGroupEntry { + in_range: false, + prev_state_group: prev, + state_map: StateMap::new(), + }, + ); + + prev = Some(i) + } + + let mut compressor = Compressor { + original_state_map: &initial, + new_state_group_map: BTreeMap::new(), + levels: vec![Level::new(3), Level::new(3)], + stats: Stats::default(), + }; + compressor.create_new_tree(); + + let new_state = &compressor.new_state_group_map; + + // This should create the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 (i.e. no change!) + let expected_edges: BTreeMap = vec![ + (1, 0), + (2, 1), + (3, 2), + (4, 3), + (5, 4), + (6, 5), + (7, 6), + (8, 7), + (9, 8), + (10, 9), + (11, 10), + (12, 11), + (13, 12), + ] + .into_iter() + .collect(); + + for sg in 0i64..=13i64 { + assert_eq!( + expected_edges.get(&sg).cloned(), + new_state[&sg].prev_state_group, + "state group {} did not match expected", + sg, + ); + } +} + +#[test] +fn create_new_tree_respects_some_not_in_range() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18 + for i in 0i64..=18i64 { + initial.insert( + i, + StateGroupEntry { + in_range: i > 4, + prev_state_group: prev, + state_map: StateMap::new(), + }, + ); + + prev = Some(i) + } + + let mut compressor = Compressor { + original_state_map: &initial, + new_state_group_map: BTreeMap::new(), + levels: vec![Level::new(3), Level::new(3)], + stats: Stats::default(), + }; + compressor.create_new_tree(); + + let new_state = &compressor.new_state_group_map; + + // This should create the following structure + // + // 0 5 8\ 17 + // 1 6 9 11\ 18 + // 2 7 10 12 14 + // 3 13 15 + // 4 16 + let expected_edges: BTreeMap = vec![ + (1, 0), + (2, 1), + (3, 2), + (4, 3), // No compression of nodes 0,1,2,3,4 + (6, 5), // Compresses in 3,3 leveling starting at 5 + (7, 6), + (9, 8), + (10, 9), + (11, 8), + (12, 11), + (13, 12), + (14, 11), + (15, 14), + (16, 15), + (18, 17), + ] + .into_iter() + .collect(); + for n in new_state { + println!("{:?}", n); + } + + for sg in 0i64..=13i64 { + assert_eq!( + expected_edges.get(&sg).cloned(), + new_state[&sg].prev_state_group, + "state group {} did not match expected", + sg, + ); + } +} + +#[test] +fn create_new_tree_deals_with_impossible_preds() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // (note missing 3-4 link) + // 0-1-2-3 + // 4-5-6-7-8-9-10-11-12-13 + // + // Each group i has state: + // ('node','is', i) + // ('group', j, 'seen') where j is ancestor of i + for i in 0i64..=13i64 { + if i == 4 { + prev = None + } + let mut entry = StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }; + entry + .state_map + .insert("group", &i.to_string(), "seen".into()); + entry.state_map.insert("node", "is", i.to_string().into()); + + initial.insert(i, entry); + + prev = Some(i) + } + + let mut compressor = Compressor { + original_state_map: &initial, + new_state_group_map: BTreeMap::new(), + levels: vec![Level::new(3), Level::new(3)], + stats: Stats::default(), + }; + compressor.create_new_tree(); + + let new_state = &compressor.new_state_group_map; + + for n in new_state { + println!("{:?}", n); + } + + // This should create the following structure + // + // Brackets mean that has NO predecessor but is in that position in the + // levels tree + // + // 0 3\ 12 + // 1 (4)(6)\ 13 + // 2 5 7 9 + // 8 10 + // 11 + let expected_edges: BTreeMap = vec![ + (1, 0), + (2, 1), + (5, 4), + (7, 6), + (8, 7), + (9, 6), + (10, 9), + (11, 10), + (13, 12), + ] + .into_iter() + .collect(); + + for sg in 0i64..=13i64 { + assert_eq!( + expected_edges.get(&sg).cloned(), + new_state[&sg].prev_state_group, + "state group {} did not match expected", + sg, + ); + } +} + +#[test] +fn get_delta_returns_snapshot_if_no_prev_given() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 + // + // Each group i has state: + // ('node','is', i) + // ('group', j, 'seen') - for all j less than i + for i in 0i64..=13i64 { + let mut entry = StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }; + entry + .state_map + .insert("group", &i.to_string(), "seen".into()); + entry.state_map.insert("node", "is", i.to_string().into()); + + initial.insert(i, entry); + + prev = Some(i) + } + + // This should produce the following structure (tested above) + // + // 0 3\ 12 + // 1 4 6\ 13 + // 2 5 7 9 + // 8 10 + // 11 + // + // State contents should be the same as before + let mut compressor = Compressor::compress(&initial, &[3, 3]); + + let (found_delta, found_pred) = compressor.get_delta(None, 6); + + let mut expected_delta: StateMap = StateMap::new(); + expected_delta.insert("node", "is", "6".into()); + expected_delta.insert("group", "0", "seen".into()); + expected_delta.insert("group", "1", "seen".into()); + expected_delta.insert("group", "2", "seen".into()); + expected_delta.insert("group", "3", "seen".into()); + expected_delta.insert("group", "4", "seen".into()); + expected_delta.insert("group", "5", "seen".into()); + expected_delta.insert("group", "6", "seen".into()); + + assert_eq!(found_delta, expected_delta); + assert_eq!(found_pred, None); +} + +#[test] +fn get_delta_returns_delta_if_original_predecessor() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 + // + // Each group i has state: + // ('node','is', i) + // ('group', j, 'seen') - for all j less than i + for i in 0i64..=13i64 { + let mut entry = StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }; + entry + .state_map + .insert("group", &i.to_string(), "seen".into()); + entry.state_map.insert("node", "is", i.to_string().into()); + + initial.insert(i, entry); + + prev = Some(i) + } + + // This should produce the following structure (tested above) + // + // 0 3\ 12 + // 1 4 6\ 13 + // 2 5 7 9 + // 8 10 + // 11 + // + // State contents should be the same as before + let mut compressor = Compressor::compress(&initial, &[3, 3]); + + let (found_delta, found_pred) = compressor.get_delta(Some(5), 6); + + let mut expected_delta: StateMap = StateMap::new(); + expected_delta.insert("node", "is", "6".into()); + expected_delta.insert("group", "6", "seen".into()); + + assert_eq!(found_delta, expected_delta); + assert_eq!(found_pred, Some(5)); +} + +#[test] +fn get_delta_returns_delta_if_original_multi_hop_predecessor() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 + // + // Each group i has state: + // ('node','is', i) + // ('group', j, 'seen') - for all j less than i + for i in 0i64..=13i64 { + let mut entry = StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }; + entry + .state_map + .insert("group", &i.to_string(), "seen".into()); + entry.state_map.insert("node", "is", i.to_string().into()); + + initial.insert(i, entry); + + prev = Some(i) + } + + // This should produce the following structure (tested above) + // + // 0 3\ 12 + // 1 4 6\ 13 + // 2 5 7 9 + // 8 10 + // 11 + // + // State contents should be the same as before + let mut compressor = Compressor::compress(&initial, &[3, 3]); + + let (found_delta, found_pred) = compressor.get_delta(Some(3), 6); + + let mut expected_delta: StateMap = StateMap::new(); + expected_delta.insert("node", "is", "6".into()); + expected_delta.insert("group", "4", "seen".into()); + expected_delta.insert("group", "5", "seen".into()); + expected_delta.insert("group", "6", "seen".into()); + + assert_eq!(found_delta, expected_delta); + assert_eq!(found_pred, Some(3)); +} + +#[test] +fn get_delta_returns_snapshot_if_no_prev_possible() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = None; + + // This starts with the following structure + // + // (note missing 3-4 link) + // 0-1-2-3 + // 4-5-6-7-8-9-10-11-12-13 + // + // Each group i has state: + // ('node','is', i) + // ('group', j, 'seen') where j is ancestor of i + for i in 0i64..=13i64 { + // don't add 3-4 link + if i == 4 { + prev = None + } + + // populate the delta for this state + let mut entry = StateGroupEntry { + in_range: true, + prev_state_group: prev, + state_map: StateMap::new(), + }; + entry + .state_map + .insert("group", &i.to_string(), "seen".into()); + entry.state_map.insert("node", "is", i.to_string().into()); + + // put the entry into the initial map + initial.insert(i, entry); + + prev = Some(i) + } + + // This should create the following structure if create_new_tree() was run + // (tested in create_new_tree_deals_with_impossible_preds()) + // + // Brackets mean that has NO predecessor but is in that position in the + // levels tree + // + // 0 3\ 12 + // 1 (4)(6)\ 13 + // 2 5 7 9 + // 8 10 + // 11 + // + // State contents should be the same as before + + // build up new_tree after 0,1,2,3 added + let mut new_map: BTreeMap = BTreeMap::new(); + + // 0-1-2 is left the same + new_map.insert(0, initial.get(&0).unwrap().clone()); + new_map.insert(1, initial.get(&1).unwrap().clone()); + new_map.insert(2, initial.get(&2).unwrap().clone()); + + // 3 is now a snapshot + let mut entry_3: StateMap = StateMap::new(); + entry_3.insert("node", "is", "3".into()); + entry_3.insert("group", "0", "seen".into()); + entry_3.insert("group", "1", "seen".into()); + entry_3.insert("group", "2", "seen".into()); + entry_3.insert("group", "3", "seen".into()); + new_map.insert( + 3, + StateGroupEntry { + in_range: true, + prev_state_group: None, + state_map: entry_3, + }, + ); + + // build the compressor with this partialy built new map + let mut compressor = Compressor { + original_state_map: &initial, + new_state_group_map: new_map, + levels: vec![Level::new(3), Level::new(3)], + stats: Stats::default(), + }; + + // make the levels how they would be after 0,1,2,3 added + // they should both be of length 1 and have 3 as the current head + let mut levels_iter = compressor.levels.iter_mut(); + + let l1 = levels_iter.next().unwrap(); + l1.current = Some(3); + l1.current_chain_length = 1; + + let l2 = levels_iter.next().unwrap(); + l2.current = Some(3); + l2.current_chain_length = 1; + + // Now try and find delta for 4 with 3 as pred + let (found_delta, found_pred) = compressor.get_delta(Some(3), 4); + + let mut expected_delta: StateMap = StateMap::new(); + expected_delta.insert("node", "is", "4".into()); + expected_delta.insert("group", "4", "seen".into()); + + assert_eq!(found_delta, expected_delta); + assert_eq!(found_pred, None); +} diff --git a/src/compressor/level_tests.rs b/src/compressor/level_tests.rs new file mode 100644 index 0000000..f57ef1d --- /dev/null +++ b/src/compressor/level_tests.rs @@ -0,0 +1,80 @@ +use crate::compressor::Level; + +#[test] +fn new_produces_empty_level() { + let l = Level::new(15); + assert_eq!(l.max_length, 15); + assert_eq!(l.current_chain_length, 0); + assert_eq!(l.current, None); +} + +#[test] +fn update_adds_to_non_full_level() { + let mut l = Level::new(10); + l.update(7, true); + assert_eq!(l.max_length, 10); + assert_eq!(l.current_chain_length, 1); + assert_eq!(l.current, Some(7)); +} + +#[test] +#[should_panic(expected = "Tried to add to an already full level")] +fn update_panics_if_adding_and_too_full() { + let mut l = Level::new(5); + l.update(1, true); + l.update(2, true); + l.update(3, true); + l.update(4, true); + l.update(5, true); + l.update(6, true); +} + +#[test] +fn update_resets_level_correctly() { + let mut l = Level::new(5); + l.update(1, true); + l.update(2, true); + l.update(3, true); + l.update(4, true); + l.update(5, true); + l.update(6, false); + assert_eq!(l.max_length, 5); + assert_eq!(l.current_chain_length, 1); + assert_eq!(l.current, Some(6)); +} + +#[test] +fn get_current_returns_current() { + let mut l = Level::new(5); + assert_eq!(l.get_current(), None); + l.update(23, true); + assert_eq!(l.get_current(), Some(23)); +} + +#[test] +fn has_space_returns_true_if_empty() { + let l = Level::new(15); + assert_eq!(l.has_space(), true); +} + +#[test] +fn has_space_returns_true_if_part_full() { + let mut l = Level::new(15); + l.update(12, true); + l.update(234, true); + l.update(1, true); + l.update(143, true); + l.update(15, true); + assert_eq!(l.has_space(), true); +} + +#[test] +fn has_space_returns_false_if_full() { + let mut l = Level::new(5); + l.update(1, true); + l.update(2, true); + l.update(3, true); + l.update(4, true); + l.update(5, true); + assert_eq!(l.has_space(), false); +}