From 5c248b536220cfec0011c75401ee92a7a6a25c54 Mon Sep 17 00:00:00 2001 From: Azrenbeth <77782548+Azrenbeth@users.noreply.github.com> Date: Mon, 16 Aug 2021 14:39:27 +0100 Subject: [PATCH] Wrote unit tests for functionality within lib.rs (#45) --- src/lib.rs | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 415 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 028c416..32f1d85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ pub struct StateGroupEntry { } /// Helper struct for parsing the `level_sizes` argument. +#[derive(PartialEq, Debug)] struct LevelSizes(Vec); impl FromStr for LevelSizes { @@ -72,7 +73,7 @@ impl FromStr for LevelSizes { /// Contains configuration information for this run of the compressor pub struct Config { // the url for the postgres database - // this should be of the form postgres://user:pass@domain/database + // this should be of the form postgresql://user:pass@domain/database db_url: String, // The file where the transactions are written that would carry out // the compression that get's calculated @@ -473,7 +474,7 @@ fn check_that_maps_match( println!("State Group: {}", sg); println!("Expected: {:#?}", expected); println!("actual: {:#?}", actual); - Err(format!("State for group {} do not match", sg)) + Err(format!("States for group {} do not match", sg)) } else { Ok(()) } @@ -487,6 +488,10 @@ fn check_that_maps_match( /// Gets the full state for a given group from the map (of deltas) fn collapse_state_maps(map: &BTreeMap, state_group: i64) -> StateMap { + if !map.contains_key(&state_group) { + panic!("Missing {}", state_group); + } + let mut entry = &map[&state_group]; let mut state_map = StateMap::new(); @@ -619,3 +624,411 @@ fn synapse_compress_state(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(run_compression, m)?)?; Ok(()) } + +// TESTS START HERE + +#[cfg(test)] +mod level_sizes_tests { + use std::str::FromStr; + + use crate::LevelSizes; + + #[test] + fn from_str_produces_correct_sizes() { + let input_string = "100,50,25"; + + let levels = LevelSizes::from_str(input_string).unwrap(); + + let mut levels_iter = levels.0.iter(); + + assert_eq!(levels_iter.next().unwrap(), &100); + assert_eq!(levels_iter.next().unwrap(), &50); + assert_eq!(levels_iter.next().unwrap(), &25); + assert_eq!(levels_iter.next(), None); + } + + #[test] + fn from_str_produces_err_if_not_list_of_numbers() { + let input_string = "100-sheep-25"; + + let result = LevelSizes::from_str(input_string); + + assert!(result.is_err()); + } +} + +#[cfg(test)] +mod lib_tests { + use std::collections::BTreeMap; + + use state_map::StateMap; + use string_cache::DefaultAtom as Atom; + + use crate::{check_that_maps_match, collapse_state_maps, StateGroupEntry}; + + #[test] + fn collapse_state_maps_works_for_non_snapshot() { + 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') where j is 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) + } + + let result_state = collapse_state_maps(&initial, 3); + + let mut expected_state: StateMap = StateMap::new(); + expected_state.insert("node", "is", "3".into()); + expected_state.insert("group", "0", "seen".into()); + expected_state.insert("group", "1", "seen".into()); + expected_state.insert("group", "2", "seen".into()); + expected_state.insert("group", "3", "seen".into()); + + assert_eq!(result_state, expected_state); + } + + #[test] + fn collapse_state_maps_works_for_snapshot() { + 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') where j is 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) + } + + let result_state = collapse_state_maps(&initial, 0); + + let mut expected_state: StateMap = StateMap::new(); + expected_state.insert("node", "is", "0".into()); + expected_state.insert("group", "0", "seen".into()); + + assert_eq!(result_state, expected_state); + } + + #[test] + #[should_panic(expected = "Missing")] + fn collapse_state_maps_panics_if_pred_not_in_map() { + let mut initial: BTreeMap = BTreeMap::new(); + let mut prev = Some(14); // note will not be in map + + // This starts with the following structure + // + // N.B. Group 14 will only exist as the predecessor of 0 + // There is no group 14 in the map + // + // (14)-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 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) + } + + collapse_state_maps(&initial, 0); + } + + #[test] + fn check_that_maps_match_returns_if_both_empty() { + check_that_maps_match(&BTreeMap::new(), &BTreeMap::new()); + assert!(true); + } + + #[test] + #[should_panic(expected = "Missing")] + fn check_that_maps_match_panics_if_just_new_map_is_empty() { + let mut old_map: BTreeMap = BTreeMap::new(); + let mut prev = None; // note will not be in map + + // 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') where j is 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()); + + old_map.insert(i, entry); + + prev = Some(i) + } + + check_that_maps_match(&old_map, &BTreeMap::new()); + assert!(true); + } + + #[test] + fn check_that_maps_match_returns_if_just_old_map_is_empty() { + // note that this IS the desired behaviour as only want to ensure that + // all groups that existed BEFORE compression, will still collapse to the same + // states (i.e. no visible changes to rest of synapse + + let mut new_map: BTreeMap = BTreeMap::new(); + let mut prev = None; // note will not be in map + + // 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') where j is 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()); + + new_map.insert(i, entry); + + prev = Some(i) + } + + check_that_maps_match(&BTreeMap::new(), &new_map); + assert!(true); + } + + #[test] + fn check_that_maps_match_returns_if_no_changes() { + let mut old_map: BTreeMap = BTreeMap::new(); + let mut prev = None; // note will not be in map + + // 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') where j is 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()); + + old_map.insert(i, entry); + + prev = Some(i) + } + + check_that_maps_match(&BTreeMap::new(), &old_map.clone()); + assert!(true); + } + + #[test] + #[should_panic(expected = "States for group")] + fn check_that_maps_match_panics_if_same_preds_but_different_deltas() { + let mut old_map: BTreeMap = BTreeMap::new(); + let mut prev = None; // note will not be in map + + // 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') where j is 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()); + + old_map.insert(i, entry); + + prev = Some(i) + } + + // new_map will have the same structure but the (node, is) values will be + // different + let mut new_map: BTreeMap = BTreeMap::new(); + let mut prev = None; // note will not be in map + + // 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+1) <- NOTE DIFFERENCE + // ('group', j, 'seen') where j is 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 + 1).to_string().into()); + + new_map.insert(i, entry); + + prev = Some(i) + } + + check_that_maps_match(&old_map, &new_map); + assert!(true); + } + + #[test] + fn check_that_maps_match_returns_if_same_states_but_different_structure() { + let mut old_map: BTreeMap = BTreeMap::new(); + let mut prev = None; // note will not be in map + + // This starts with the following structure + // + // 0-1-2-3-4-5-6 + // + // Each group i has state: + // ('node','is', i) + // ('group', j, 'seen') where j is less than i + for i in 0i64..=6i64 { + 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()); + + old_map.insert(i, entry); + + prev = Some(i) + } + + // This is a structure that could be produced by the compressor + // and should pass the maps_match test: + // + // 0 3\ + // 1 4 6 + // 2 5 + // + // State contents should be the same as before + let mut new_map: BTreeMap = BTreeMap::new(); + + // 0-1-2 is left the same + new_map.insert(0, old_map.get(&0).unwrap().clone()); + new_map.insert(1, old_map.get(&1).unwrap().clone()); + new_map.insert(2, old_map.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, + }, + ); + + // 4 and 5 are also left the same + new_map.insert(4, old_map.get(&4).unwrap().clone()); + new_map.insert(5, old_map.get(&5).unwrap().clone()); + + // 6 is a "partial" snapshot now + let mut entry_6: StateMap = StateMap::new(); + entry_6.insert("node", "is", "6".into()); + entry_6.insert("group", "4", "seen".into()); + entry_6.insert("group", "5", "seen".into()); + entry_6.insert("group", "6", "seen".into()); + new_map.insert( + 6, + StateGroupEntry { + in_range: true, + prev_state_group: Some(3), + state_map: entry_6, + }, + ); + + check_that_maps_match(&old_map, &new_map); + assert!(true); + } + + //TODO: tests for correct SQL code produced by output_sql +}