Skip to content

Commit

Permalink
test: add tests and docstring for RevisionMap and RevisionVec
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Rosenkranz-Costa committed Dec 4, 2023
1 parent 5687908 commit 63c30d0
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 38 deletions.
4 changes: 2 additions & 2 deletions src/core/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ pub fn update(
partitions_set: &HashMap<Partition, (EncryptionHint, AttributeStatus)>,
) -> Result<(), Error> {
// Remove keys from partitions deleted from Policy
msk.subkeys.retain_keys(partitions_set.keys().collect());
msk.subkeys.retain(|part| partitions_set.contains_key(part));
mpk.subkeys
.retain(|part, _| partitions_set.contains_key(part));

Expand Down Expand Up @@ -451,7 +451,7 @@ pub fn refresh(
verify_user_key_kmac(msk, usk)?;

// Remove partitions missing from master keys
usk.subkeys.retain_keys(msk.subkeys.keys().collect());
usk.subkeys.retain(|part| msk.subkeys.contains_key(part));

for (partition, user_chain) in usk.subkeys.iter_mut() {
let mut master_chain = msk.subkeys.iter_chain(partition).expect("at least one key");
Expand Down
45 changes: 29 additions & 16 deletions src/data_struct/revision_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ use std::{
borrow::Borrow,
collections::{
hash_map::{Entry, OccupiedEntry, VacantEntry},
HashMap, HashSet, LinkedList,
HashMap, LinkedList,
},
fmt::Debug,
hash::Hash,
};

/// a `VersionedMap` stores linked lists.
/// a `RevisionMap` stores linked lists indexed by given keys.
/// The element inside the linked list are stored in reverse insertion order
/// while the keys are stored in arbitrary order.
///
/// Map {
/// key2: b
/// key1: a" -> a' > a
/// key3: c' -> c
/// }
#[derive(Default, Debug, PartialEq, Eq)]
pub struct RevisionMap<K, V>
where
Expand Down Expand Up @@ -134,13 +142,9 @@ where
.map(|chain| chain.split_off(1).into_iter())
}

pub fn retain_keys(&mut self, keys: HashSet<&K>) {
let inner_keys: Vec<K> = self.keys().cloned().collect();
for key in inner_keys {
if !keys.contains(&key) {
let _ = self.remove_chain(&key);
}
}
/// Retains only the elements with a key validating the given predicate.
pub fn retain(&mut self, f: impl Fn(&K) -> bool) {
self.map.retain(|key, _| f(key));
}
}

Expand All @@ -157,18 +161,21 @@ mod tests {

// Insertions
map.insert("Part1".to_string(), "Part1V1".to_string());
assert_eq!(map.map.len(), 1);
assert_eq!(map.nb_chains(), 1);
map.insert("Part1".to_string(), "Part1V2".to_string());
assert_eq!(map.len(), 2);
// the inner map only has 1 entry with 2 revisions
assert_eq!(map.map.len(), 1);
// only one chain
assert_eq!(map.nb_chains(), 1);

map.insert("Part2".to_string(), "Part2V1".to_string());
map.insert("Part2".to_string(), "Part2V2".to_string());
map.insert("Part2".to_string(), "Part2V3".to_string());
assert_eq!(map.map.len(), 2);
assert_eq!(map.nb_chains(), 2);
assert_eq!(map.len(), 5);

map.insert("Part3".to_string(), "Part3V1".to_string());
assert_eq!(map.len(), 6);

// Get
assert_eq!(map.get_current_revision("Part1").unwrap(), "Part1V2");
assert_eq!(map.get_current_revision("Part2").unwrap(), "Part2V3");
Expand All @@ -188,16 +195,22 @@ mod tests {
// Remove values
let vec: Vec<_> = map.remove_chain("Part1").unwrap().collect();
assert_eq!(vec, vec!["Part1V2".to_string(), "Part1V1".to_string()]);
assert_eq!(map.len(), 3);
assert_eq!(map.map.len(), 1);
assert_eq!(map.len(), 4);
assert_eq!(map.nb_chains(), 2);

// Pop tail
let vec: Vec<_> = map.pop_tail("Part2").unwrap().collect();
assert_eq!(vec, vec!["Part2V2".to_string(), "Part2V1".to_string()]);
assert_eq!(map.len(), 1);
assert_eq!(map.len(), 2);
let vec: Vec<_> = map.remove_chain("Part2").unwrap().collect();
assert_eq!(vec, vec!["Part2V3".to_string()]);
// Empty pop tail
assert!(map.pop_tail("Part3").unwrap().next().is_none());

// Retain
map.retain(|_| true);
assert_eq!(map.len(), 1);
map.retain(|_| false);
assert!(map.is_empty());
}
}
159 changes: 139 additions & 20 deletions src/data_struct/revision_vec.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use std::{
collections::{HashSet, VecDeque},
hash::Hash,
};
use std::collections::VecDeque;

/// a `RevisionVec` stores for each entry a linked list of versions.
/// The entry versions are stored in reverse chronological order:
/// The entry versions are stored in reverse insertion order:
///
/// Vec [
/// 0: key -> a" -> a' -> a
Expand All @@ -16,8 +13,8 @@ use std::{
/// Deletions can only happen at the end of the linked list.
///
/// This guarantees that the entry versions are always ordered.
// TODO: does index matter for Eq compare?
#[derive(Default, Debug, PartialEq, Eq)]
// TODO does index matter for Eq compare?
pub struct RevisionVec<K, T> {
chains: Vec<(K, RevisionList<T>)>,
}
Expand Down Expand Up @@ -67,11 +64,9 @@ impl<K, T> RevisionVec<K, T> {
self.chains.clear();
}

pub fn retain_keys(&mut self, keys: HashSet<&K>)
where
K: Hash + Eq,
{
self.chains.retain(|(key, _)| keys.contains(key));
/// Retains only the elements with a key validating the given predicate.
pub fn retain(&mut self, f: impl Fn(&K) -> bool) {
self.chains.retain(|(key, _)| f(key));
}

/// Returns an iterator over each key-chains pair
Expand All @@ -87,19 +82,21 @@ impl<K, T> RevisionVec<K, T> {
self.chains.iter_mut().map(|(ref key, chain)| (key, chain))
}

/// Iterates through all versions of all entries
/// Iterates through all versions of all entries in a depth-first manner.
/// Returns the key and value for each entry.
pub fn flat_iter(&self) -> impl Iterator<Item = (&K, &T)> {
self.chains
.iter()
.flat_map(|(key, chain)| chain.iter().map(move |val| (key, val)))
}

/// Iterates through all versions of all entry in a breadth-first manner.
pub fn bfs(&self) -> BfsIterator<T> {
BfsIterator::new(self)
}
}

/// Breadth-first search iterator for `RevisionVec`.
pub struct BfsIterator<'a, T> {
queue: VecDeque<&'a Element<T>>,
}
Expand Down Expand Up @@ -184,18 +181,18 @@ impl<T> RevisionList<T> {

pub fn pop_tail(&mut self) -> RevisionListIter<T> {
self.length = self.head.as_ref().map_or(0, |_| 1);
match &self.head {
match &mut self.head {
Some(head) => RevisionListIter {
current_element: &head.next,
current_element: head.next.take(),
},
None => RevisionListIter {
current_element: &None,
current_element: None,
},
}
}

pub fn iter(&self) -> impl Iterator<Item = &T> {
RevisionListIter::new(self)
RefRevisionListIter::new(self)
}
}

Expand Down Expand Up @@ -226,19 +223,19 @@ impl<T> FromIterator<T> for RevisionList<T> {
}
}

pub struct RevisionListIter<'a, T> {
pub struct RefRevisionListIter<'a, T> {
current_element: &'a Option<Box<Element<T>>>,
}

impl<'a, T> RevisionListIter<'a, T> {
impl<'a, T> RefRevisionListIter<'a, T> {
pub fn new(rev_list: &'a RevisionList<T>) -> Self {
Self {
current_element: &rev_list.head,
}
}
}

impl<'a, T> Iterator for RevisionListIter<'a, T> {
impl<'a, T> Iterator for RefRevisionListIter<'a, T> {
type Item = &'a T;

fn next(&mut self) -> Option<Self::Item> {
Expand All @@ -248,11 +245,133 @@ impl<'a, T> Iterator for RevisionListIter<'a, T> {
}
}

pub struct RevisionListIter<T> {
current_element: Option<Box<Element<T>>>,
}

impl<T> Iterator for RevisionListIter<T> {
type Item = T;

fn next(&mut self) -> Option<Self::Item> {
let element = self.current_element.take()?;
self.current_element = element.next;
Some(element.data)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_revision_vec() {
todo!()
let mut revision_vec: RevisionVec<i32, String> = RevisionVec::new();
assert!(revision_vec.is_empty());
assert_eq!(revision_vec.nb_chains(), 0);

// Insert
revision_vec.insert_new_chain(
1,
vec!["a\"".to_string(), "a'".to_string(), "a".to_string()]
.into_iter()
.collect(),
);
revision_vec.create_chain_with_single_value(2, "b".to_string());
revision_vec.insert_new_chain(
3,
vec!["c'".to_string(), "c".to_string()]
.into_iter()
.collect(),
);

assert_eq!(revision_vec.len(), 6);
assert_eq!(revision_vec.nb_chains(), 3);

// Iterators
let depth_iter: Vec<_> = revision_vec.flat_iter().collect();
assert_eq!(
depth_iter,
vec![
(&1, &"a\"".to_string()),
(&1, &"a'".to_string()),
(&1, &"a".to_string()),
(&2, &"b".to_string()),
(&3, &"c'".to_string()),
(&3, &"c".to_string()),
]
);

let breadth_iter: Vec<_> = revision_vec.bfs().collect();
assert_eq!(
breadth_iter,
vec![
&"a\"".to_string(),
&"b".to_string(),
&"c'".to_string(),
&"a'".to_string(),
&"c".to_string(),
&"a".to_string(),
]
);

// Retain
revision_vec.retain(|key| key == &1);
assert_eq!(revision_vec.len(), 3);
assert_eq!(revision_vec.nb_chains(), 1);

// Clear
revision_vec.clear();
assert!(revision_vec.is_empty());
}

#[test]
fn test_revision_list() {
let mut revision_list: RevisionList<i32> = RevisionList::new();
assert!(revision_list.is_empty());
assert_eq!(revision_list.front(), None);

// Insertions
revision_list.push_front(1);
revision_list.push_front(2);
revision_list.push_front(3);
assert_eq!(revision_list.len(), 3);

// Get and iter
assert_eq!(revision_list.front(), Some(&3));
{
let mut iter = revision_list.iter();
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), None);
}

// Pop
let popped_tail = revision_list.pop_tail().collect::<Vec<_>>();
assert_eq!(popped_tail, vec![2, 1]);
assert_eq!(revision_list.len(), 1);

assert_eq!(revision_list.front(), Some(&3));
}

#[test]
fn test_revision_list_from_iterator() {
// Test creating RevisionList from iterator
let input_iter = vec![1, 2, 3].into_iter();
let revision_list: RevisionList<i32> = input_iter.collect();

assert_eq!(revision_list.len(), 3);

let mut iter = revision_list.iter();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);

// Test iterator behavior on an empty list
let revision_list: RevisionList<i32> = RevisionList::new();
let mut iter = revision_list.iter();
assert_eq!(iter.next(), None);
assert!(revision_list.is_empty());
}
}

0 comments on commit 63c30d0

Please sign in to comment.