use std::collections::BTreeMap;
use std::collections::BTreeSet;
use maplit::btreeset;
use validit::Validate;
use super::VecProgress;
use crate::Node;
use crate::QuorumTree;
use crate::progress::IdVal;
use crate::progress::VecProgressEntry;
use crate::progress::VecProgressEntryData;
use crate::quorum::QuorumSet;
const LCG_A: u64 = 6364136223846793005;
const LCG_C: u64 = 1442695040888963407;
#[derive(Clone, Debug, PartialEq, Eq)]
struct IdValData<ID, Val, Data> {
id: ID,
val: Val,
data: Data,
}
impl<ID, Val, Data> IdValData<ID, Val, Data> {
fn new(id: ID, val: Val, data: Data) -> Self {
Self { id, val, data }
}
}
impl<ID, Val, Data> VecProgressEntry for IdValData<ID, Val, Data>
where
ID: PartialEq,
Val: Clone + Default + Ord,
{
type Id = ID;
type Progress = Val;
fn id(&self) -> &Self::Id {
&self.id
}
fn progress(&self) -> &Self::Progress {
&self.val
}
fn progress_mut(&mut self) -> &mut Self::Progress {
&mut self.val
}
}
impl<ID, Val, Data> VecProgressEntryData for IdValData<ID, Val, Data>
where
ID: PartialEq,
Val: Clone + Default + Ord,
{
type Data = Data;
fn data(&self) -> &Self::Data {
&self.data
}
fn data_mut(&mut self) -> &mut Self::Data {
&mut self.data
}
}
#[derive(Clone, Debug)]
struct RequiredSetQuorum {
ids: BTreeSet<u64>,
required: BTreeSet<u64>,
}
impl RequiredSetQuorum {
fn new(ids: impl IntoIterator<Item = u64>, required: impl IntoIterator<Item = u64>) -> Self {
Self {
ids: ids.into_iter().collect(),
required: required.into_iter().collect(),
}
}
}
impl QuorumSet for RequiredSetQuorum {
type Id = u64;
type Iter = std::collections::btree_set::IntoIter<u64>;
fn is_quorum<'a, I: Iterator<Item = &'a Self::Id> + Clone>(&self, ids: I) -> bool {
let granted = ids.copied().collect::<BTreeSet<_>>();
self.required.is_subset(&granted)
}
fn ids(&self) -> Self::Iter {
self.ids.clone().into_iter()
}
}
fn next_random(seed: &mut u64) -> u64 {
*seed = seed.wrapping_mul(LCG_A).wrapping_add(LCG_C);
*seed
}
fn learner_ids_for<QS>(quorum_set: &QS, known_ids: impl IntoIterator<Item = u64>) -> Vec<u64>
where QS: QuorumSet<Id = u64> {
let voter_ids = quorum_set.ids().collect::<BTreeSet<_>>();
known_ids.into_iter().filter(|id| !voter_ids.contains(id)).collect()
}
fn copy_option(res: Option<&u64>) -> Option<u64> {
res.copied()
}
fn model_quorum_accepted<QS>(quorum_set: &QS, entries: &[(u64, u64)]) -> u64
where QS: QuorumSet<Id = u64> {
let values = entries.iter().map(|item| (item.0, item.1)).collect::<BTreeMap<_, _>>();
let mut candidates = quorum_set.ids().map(|id| values[&id]).collect::<Vec<_>>();
candidates.sort_unstable_by(|a, b| b.cmp(a));
candidates.dedup();
for candidate in candidates {
let ids = values.iter().filter_map(|(id, val)| (*val >= candidate).then_some(id));
if quorum_set.is_quorum(ids) {
return candidate;
}
}
0
}
fn assert_matches_model<QS>(progress: &VecProgress<(u64, u64), QS>, context: &str)
where QS: QuorumSet<Id = u64> {
let want = model_quorum_accepted(&progress.quorum_set, &progress.entries);
assert_eq!(
&want,
progress.quorum_accepted(),
"{}: entries: {:?}",
context,
progress.entries
);
assert_voter_prefix_is_sorted(progress, context);
}
fn assert_voter_prefix_is_sorted<QS>(progress: &VecProgress<(u64, u64), QS>, context: &str)
where QS: QuorumSet<Id = u64> {
let quorum_accepted = *progress.quorum_accepted();
let mut previous = None;
let mut seen_unsorted_suffix = false;
for item in &progress.entries[..progress.voter_count] {
if item.1 <= quorum_accepted {
seen_unsorted_suffix = true;
continue;
}
assert!(
!seen_unsorted_suffix,
"{}: non-prefix above-quorum entry: {:?}",
context, progress.entries
);
if let Some(prev) = previous {
assert!(
prev >= item.1,
"{}: unsorted voters: {:?}",
context,
progress.entries
);
}
previous = Some(item.1);
}
}
#[test]
fn vec_progress_new() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
assert_eq!(
vec![(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (6, 0), (7, 0),],
progress.entries
);
assert_eq!(5, progress.voter_count);
}
#[test]
fn vec_progress_new_with_tree() {
let group_a = QuorumTree::new(2, [Node::Id(1), Node::Id(2), Node::Id(3)]).unwrap();
let group_b = QuorumTree::new(2, [Node::Id(4), Node::Id(5), Node::Id(6)]).unwrap();
let quorum_set = QuorumTree::new(2, [Node::Subtree(group_a), Node::Subtree(group_b)]).unwrap();
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
assert_eq!(
vec![(1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0)],
progress.entries
);
assert_eq!(6, progress.voter_count);
assert_eq!(Some(&0), progress.update_progress(&1, 10));
assert_eq!(Some(&0), progress.update_progress(&2, 10));
assert_eq!(Some(&0), progress.update_progress(&4, 10));
assert_eq!(Some(&10), progress.update_progress(&5, 10));
}
#[test]
fn vec_progress_new_computes_initial_quorum_accepted() {
let quorum_set = vec![btreeset! {1, 2, 3}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [4], |id| (id, id * 10));
assert_eq!(&20, progress.quorum_accepted());
assert_matches_model(&progress, "initial progress above default");
assert_eq!(Some(&30), progress.update_progress(&1, 100));
assert_eq!(Some(&100), progress.update_progress(&2, 100));
assert_eq!(Some(&100), progress.update_progress(&3, 100));
assert_matches_model(&progress, "updates after elevated initial progress");
}
#[test]
fn vec_progress_new_computes_initial_quorum_accepted_below_default() {
let quorum_set = vec![btreeset! {1, 2, 3}];
let progress = VecProgress::<(u64, i64), _>::new(quorum_set, [], |id| (id, -5));
assert_eq!(&-5, progress.quorum_accepted());
}
#[test]
fn vec_progress_validate_reports_voter_order_mismatches() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
progress.quorum_accepted = 4;
progress.entries[0].1 = 7;
progress.entries[1].1 = 3;
progress.entries[2].1 = 6;
progress.entries[3].1 = 9;
let err = progress.validate_voter_order().unwrap_err().to_string();
assert!(
err.contains("appears after the unsorted suffix"),
"error: {err}"
);
assert!(err.contains("out_of_order_entry=(2, 2, 6)"), "error: {err}");
assert!(
err.contains("voter_progress=[(0, 7), (1, 3), (2, 6)]"),
"error: {err}"
);
assert!(err.contains("learner_progress=[(3, 9)]"), "error: {err}");
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
progress.quorum_accepted = 4;
progress.entries[0].1 = 5;
progress.entries[1].1 = 6;
progress.entries[3].1 = 9;
let err = progress.validate_voter_order().unwrap_err().to_string();
assert!(err.contains("previous_entry=(0, 0, 5)"), "error: {err}");
assert!(err.contains("out_of_order_entry=(1, 1, 6)"), "error: {err}");
assert!(
err.contains("voter_progress=[(0, 5), (1, 6), (2, 0)]"),
"error: {err}"
);
assert!(err.contains("learner_progress=[(3, 9)]"), "error: {err}");
}
#[test]
fn vec_progress_validate_accepts_reset_below_quorum_accepted() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
progress.update_progress(&0, 10).unwrap();
progress.update_progress(&1, 10).unwrap();
progress.reset_entry_with(&0, |entry| entry.1 = 0).unwrap();
assert!(progress.validate().is_ok());
}
#[test]
fn vec_progress_tuple_entry() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
assert_eq!(
vec![(0, 0), (1, 0), (2, 0), (3, 0)],
progress.iter().cloned().collect::<Vec<_>>()
);
assert_eq!(Some(&0), progress.update_progress(&0, 5));
assert_eq!(Some(&5), progress.update_progress(&1, 5));
assert_eq!(Some(&(0, 5)), progress.try_get(&0));
assert_eq!(Some(&(1, 5)), progress.try_get(&1));
assert_eq!(&5, progress.quorum_accepted());
}
#[test]
fn vec_progress_index() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
assert_eq!(Some(0), progress.index(&0));
assert_eq!(Some(1), progress.index(&1));
assert_eq!(Some(4), progress.index(&4));
assert_eq!(Some(5), progress.index(&6));
assert_eq!(Some(6), progress.index(&7));
assert_eq!(None, progress.index(&9));
assert_eq!(None, progress.index(&100));
}
#[test]
fn vec_progress_get() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
progress.update_progress(&6, 5);
assert_eq!(Some(&(6, 5)), progress.try_get(&6));
assert_eq!(Some(&5), progress.try_get(&6).map(|x| &x.1));
assert_eq!(None, progress.try_get(&9));
progress.update_progress(&6, 10);
assert_eq!(Some(&10), progress.try_get(&6).map(|x| &x.1));
}
#[test]
fn vec_progress_iter() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
progress.update_progress(&7, 7);
progress.update_progress(&3, 3);
progress.update_progress(&1, 1);
assert_eq!(
vec![(3, 3), (1, 1), (0, 0), (2, 0), (4, 0), (6, 0), (7, 7),],
progress.iter().cloned().collect::<Vec<_>>(),
"iter() returns voter first, followed by learners"
);
}
#[test]
fn vec_progress_move_up() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
let cases = [
(
(1, 2),
vec![(1, 2), (0, 0), (2, 0), (3, 0), (4, 0), (6, 0)],
0,
),
(
(2, 3),
vec![(2, 3), (1, 2), (0, 0), (3, 0), (4, 0), (6, 0)],
0,
),
(
(1, 3),
vec![(2, 3), (1, 3), (0, 0), (3, 0), (4, 0), (6, 0)],
1,
), (
(4, 8),
vec![(4, 8), (2, 3), (1, 3), (0, 0), (3, 0), (6, 0)],
0,
),
(
(0, 5),
vec![(4, 8), (0, 5), (2, 3), (1, 3), (3, 0), (6, 0)],
1,
), ];
for (ith, ((id, v), want_vec, want_new_index)) in cases.iter().enumerate() {
let index = progress.index(id).unwrap();
progress.entries[index].1 = *v;
let got = progress.move_up(index);
assert_eq!(
want_vec, &progress.entries,
"{}-th case: idx:{}, v:{}",
ith, *id, *v
);
assert_eq!(
*want_new_index, got,
"{}-th case: idx:{}, v:{}",
ith, *id, *v
);
}
}
#[test]
fn vec_progress_update_progress() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
let cases = vec![
((6, 9), Some(&0)), ((1, 2), Some(&0)), ((2, 3), Some(&0)), ((3, 1), Some(&1)), ((4, 5), Some(&2)), ((0, 4), Some(&3)), ((3, 2), Some(&3)), ((3, 3), Some(&3)), ((1, 4), Some(&4)), ((9, 1), None), ];
for (ith, ((id, v), want_quorum_accepted)) in cases.iter().enumerate() {
let got = progress.update_progress_with(id, |x| *x = *v);
assert_eq!(
want_quorum_accepted.clone(),
got,
"{}-th case: id:{}, v:{}",
ith,
id,
v
);
}
}
#[test]
fn vec_progress_matches_reference_model_for_monotonic_updates() {
let cases = [
(vec![btreeset! {0, 1, 2, 3, 4}], vec![5, 6]),
(vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}], vec![5, 6]),
];
for (case_id, (quorum_set, learners)) in cases.into_iter().enumerate() {
for seed in 0..32 {
let mut seed = seed + 1;
let mut progress =
VecProgress::<(u64, u64), _>::new(quorum_set.clone(), learners.clone(), |id| {
(id, 0)
});
assert_matches_model(&progress, &format!("case-{case_id} seed-{seed} initial"));
for step in 0..128 {
let id = next_random(&mut seed) % 8;
let value = progress.try_get(&id).map(|entry| entry.1).unwrap_or_default()
+ next_random(&mut seed) % 7
+ 1;
let got = copy_option(progress.update_progress(&id, value));
let want = model_quorum_accepted(&progress.quorum_set, &progress.entries);
let want_result = progress.try_get(&id).map(|_| want);
let context = format!("case-{case_id} seed-{seed} step-{step} update-{id}-{value}");
assert_eq!(
want_result, got,
"{context}: entries: {:?}",
progress.entries
);
assert_matches_model(&progress, &context);
}
}
}
}
#[test]
fn vec_progress_matches_reference_model_after_random_quorum_upgrades() {
let quorum_sets = [
vec![btreeset! {0, 1, 2, 3, 4}],
vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}],
vec![btreeset! {2, 3, 4}],
vec![btreeset! {1, 3, 5}, btreeset! {3, 4, 5, 6}],
vec![btreeset! {0, 5, 6}],
];
let known_ids = (0..=8).collect::<Vec<_>>();
for seed in 0..16 {
let mut seed = seed + 11;
let quorum_set = quorum_sets[0].clone();
let learner_ids = learner_ids_for(&quorum_set, known_ids.clone());
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, learner_ids, |id| (id, 0));
assert_matches_model(&progress, &format!("seed-{seed} initial"));
for round in 0..24 {
for step in 0..16 {
let id = next_random(&mut seed) % 10;
let value = progress.try_get(&id).map(|entry| entry.1).unwrap_or_default()
+ next_random(&mut seed) % 11
+ 1;
progress.update_progress(&id, value);
assert_matches_model(
&progress,
&format!("seed-{seed} round-{round} step-{step} update"),
);
}
let quorum_index = next_random(&mut seed) as usize % quorum_sets.len();
let quorum_set = quorum_sets[quorum_index].clone();
let learner_ids = learner_ids_for(&quorum_set, known_ids.clone());
progress = progress.upgrade_quorum_set(quorum_set, learner_ids, |id| (id, 0));
assert_matches_model(
&progress,
&format!("seed-{seed} round-{round} upgrade-{quorum_index}"),
);
}
}
}
#[test]
fn vec_progress_joint_quorum_update_progress() {
let quorum_set = vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [5, 6], |id| (id, 0));
let cases = [
(0, 5, 0),
(1, 5, 0),
(2, 4, 0),
(3, 4, 4),
(4, 6, 4),
(3, 6, 5),
(2, 7, 5),
(0, 7, 6),
];
for (ith, (id, value, want_quorum_accepted)) in cases.iter().enumerate() {
let got = copy_option(progress.update_progress(id, *value));
let context = format!("{ith}-th case: id:{id}, value:{value}");
assert_eq!(
Some(*want_quorum_accepted),
got,
"{context}: entries: {:?}",
progress.entries
);
assert_matches_model(&progress, &context);
}
let entries: Vec<_> = progress.collect_mapped(|item| (item.0, item.1));
assert_eq!(
vec![(2, 7), (0, 7), (4, 6), (3, 6), (1, 5), (5, 0), (6, 0)],
entries
);
}
#[test]
fn vec_progress_non_member_and_learner_edge_cases() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [1, 3, 3], |id| (id, 0));
assert_eq!(vec![(0, 0), (1, 0), (2, 0), (3, 0)], progress.entries);
assert_eq!(3, progress.voter_count);
assert_eq!(Some(true), progress.is_voter(&1));
assert_eq!(Some(false), progress.is_voter(&3));
assert_eq!(None, progress.is_voter(&9));
assert_eq!(Some(0), copy_option(progress.update_progress(&3, 7)));
assert_eq!(vec![(0, 0), (1, 0), (2, 0), (3, 7)], progress.entries);
assert_eq!(Some(0), copy_option(progress.update_progress(&1, 5)));
assert_eq!(vec![(1, 5), (0, 0), (2, 0), (3, 7)], progress.entries);
assert_eq!(Some(4), copy_option(progress.update_progress(&2, 4)));
assert_eq!(vec![(1, 5), (2, 4), (0, 0), (3, 7)], progress.entries);
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut no_learners = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
assert_eq!(vec![(0, 0), (1, 0), (2, 0)], no_learners.entries);
assert_eq!(3, no_learners.voter_count);
assert_eq!(None, copy_option(no_learners.update_progress(&9, 5)));
assert_eq!(vec![(0, 0), (1, 0), (2, 0)], no_learners.entries);
}
#[test]
fn vec_progress_custom_quorum_set() {
let quorum_set = RequiredSetQuorum::new([0, 1, 2, 3], [0, 3]);
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
assert_eq!(Some(0), copy_option(progress.update_progress(&1, 10)));
assert_eq!(Some(0), copy_option(progress.update_progress(&2, 9)));
assert_eq!(Some(0), copy_option(progress.update_progress(&0, 8)));
assert_matches_model(&progress, "custom quorum before required set is reached");
assert_eq!(vec![(1, 10), (2, 9), (0, 8), (3, 0)], progress.entries);
assert_eq!(Some(7), copy_option(progress.update_progress(&3, 7)));
assert_eq!(&7, progress.quorum_accepted());
assert_matches_model(&progress, "custom quorum reaches required set");
assert_eq!(Some(8), copy_option(progress.update_progress(&3, 11)));
assert_eq!(vec![(3, 11), (1, 10), (2, 9), (0, 8)], progress.entries);
assert_matches_model(&progress, "custom quorum follows required set threshold");
}
#[test]
fn vec_progress_update_progress_with() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
let got = progress.update_progress_with(&1, |x| *x += 2);
assert_eq!(Some(&0), got, "case 0: id:1, +=2");
let got = progress.update_progress_with(&2, |x| *x += 3);
assert_eq!(Some(&0), got, "case 1: id:2, +=3");
let got = progress.update_progress_with(&3, |x| *x = 1);
assert_eq!(Some(&1), got, "case 2: id:3, =1");
let got = progress.update_progress_with(&4, |x| *x += 5);
assert_eq!(Some(&2), got, "case 3: id:4, +5");
let got = progress.update_progress_with(&0, |x| {
*x += 4;
assert_eq!(4, *x, "closure sees the updated value");
});
assert_eq!(Some(&3), got, "case 4: id:0, +=4");
let got = progress.update_progress_with(&3, |x| *x = (*x).max(2));
assert_eq!(Some(&3), got, "case 5: id:3, max(2)");
let got = progress.update_progress_with(&1, |x| *x *= 2);
assert_eq!(Some(&4), got, "case 6: id:1, *=2");
assert_eq!(Some(&(0, 4)), progress.try_get(&0));
assert_eq!(Some(&(1, 4)), progress.try_get(&1));
assert_eq!(Some(&(2, 3)), progress.try_get(&2));
assert_eq!(Some(&(3, 2)), progress.try_get(&3));
assert_eq!(Some(&(4, 5)), progress.try_get(&4));
assert_eq!(Some(&(6, 0)), progress.try_get(&6));
let got = progress.update_progress_with(&9, |x| *x = 10);
assert_eq!(None, got, "nonexistent id returns None");
}
#[test]
fn vec_progress_update_data_with() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress =
VecProgress::<IdValData<u64, u64, &'static str>, _>::new(quorum_set, [3], |id| {
IdValData::new(id, 0, "foo")
});
assert_eq!(Some(&0), progress.update_progress(&1, 2));
let stats_before = (
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
);
assert_eq!(
Some(&"bar"),
progress.update_data_with(&1, |data| *data = "bar")
);
assert_eq!(
None,
progress.update_data_with(&9, |data| *data = "unknown")
);
assert_eq!(
vec![
IdValData::new(1, 2, "bar"),
IdValData::new(0, 0, "foo"),
IdValData::new(2, 0, "foo"),
IdValData::new(3, 0, "foo"),
],
progress.iter().cloned().collect::<Vec<_>>()
);
assert_eq!(&0, progress.quorum_accepted());
assert_eq!(
stats_before,
(
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
)
);
}
#[test]
fn vec_progress_update_does_not_move_learner_elt() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
assert_eq!(Some(5), progress.index(&6));
progress.update_progress(&6, 6);
assert_eq!(Some(5), progress.index(&6), "learner is not moved");
progress.update_progress(&4, 4);
assert_eq!(Some(0), progress.index(&4), "voter is not moved");
}
#[test]
fn vec_progress_upgrade_quorum_set() {
let qs012 = vec![btreeset! {0, 1, 2}];
let qs012_345 = vec![btreeset! {0, 1, 2}, btreeset! {3, 4, 5}];
let qs345 = vec![btreeset! {3, 4, 5}];
let mut p012 = VecProgress::<(u64, u64), _>::new(qs012, [5], |id| (id, 0));
p012.update_progress(&0, 5);
p012.update_progress(&1, 6);
p012.update_progress(&5, 9);
assert_eq!(&5, p012.quorum_accepted());
let mut p012_345 = p012.upgrade_quorum_set(qs012_345, [6], |id| (id, 0));
assert_eq!(
&0,
p012_345.quorum_accepted(),
"quorum extended from 012 to 012_345, quorum-accepted falls back"
);
assert_eq!(
Some(&(5, 9)),
p012_345.try_get(&5),
"inherit learner progress"
);
p012_345.update_progress(&3, 7);
p012_345.update_progress(&4, 8);
assert_eq!(&5, p012_345.quorum_accepted());
let p345 = p012_345.upgrade_quorum_set(qs345, [1], |id| (id, 0));
assert_eq!(
&8,
p345.quorum_accepted(),
"shrink quorum set, greater value becomes quorum-accepted"
);
assert_eq!(Some(&(1, 6)), p345.try_get(&1), "inherit voter progress");
}
#[test]
fn vec_progress_upgrade_joint_quorum_set() {
let qs01234 = vec![btreeset! {0, 1, 2, 3, 4}];
let qs012_234 = vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}];
let qs345 = vec![btreeset! {3, 4, 5}];
let mut p = VecProgress::<(u64, u64), _>::new(qs01234, [5], |id| (id, 0));
for (id, value) in [(0, 9), (1, 8), (2, 7), (3, 2), (4, 1), (5, 10)] {
p.update_progress(&id, value);
}
assert_eq!(&7, p.quorum_accepted());
let mut joint = p.upgrade_quorum_set(qs012_234, [5, 6], |id| (id, 0));
assert_eq!(
&2,
joint.quorum_accepted(),
"joint quorum lowers the accepted value"
);
let entries: Vec<_> = joint.collect_mapped(|item| (item.0, item.1));
assert_eq!(
vec![(0, 9), (1, 8), (2, 7), (3, 2), (4, 1), (5, 10), (6, 0)],
entries
);
assert_matches_model(&joint, "after upgrade to joint quorum");
joint.update_progress(&3, 8);
joint.update_progress(&4, 8);
assert_eq!(&8, joint.quorum_accepted());
assert_matches_model(&joint, "after joint quorum catches up");
let shrunk = joint.upgrade_quorum_set(qs345, [0], |id| (id, 0));
assert_eq!(&8, shrunk.quorum_accepted());
let entries: Vec<_> = shrunk.collect_mapped(|item| (item.0, item.1));
assert_eq!(vec![(5, 10), (3, 8), (4, 8), (0, 9)], entries);
assert_matches_model(&shrunk, "after shrinking joint quorum");
}
#[test]
fn vec_progress_is_voter() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
assert_eq!(Some(true), progress.is_voter(&1));
assert_eq!(Some(true), progress.is_voter(&3));
assert_eq!(Some(false), progress.is_voter(&7));
assert_eq!(None, progress.is_voter(&8));
}
#[test]
fn vec_progress_display() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
progress.update_progress(&1, 5);
progress.update_progress(&2, 3);
let display = format!(
"{}",
progress.display_with(|f, item| write!(f, "{}: {}", item.0, item.1))
);
assert_eq!("{1: 5, 2: 3, 0: 0, 3: 0}", display);
}
#[test]
fn vec_progress_iter_mut_without_reorder() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
for item in progress.iter_mut_without_reorder() {
if item.0 == 1 {
item.1 = 10;
}
}
assert_eq!(Some(&(1, 10)), progress.try_get(&1));
assert_eq!(Some(&(0, 0)), progress.try_get(&0));
assert_eq!(Some(&(2, 0)), progress.try_get(&2));
}
#[test]
fn vec_progress_stat() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
assert_eq!(
(0, 0, 0),
(
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
)
);
progress.update_progress(&3, 10);
assert_eq!(
(1, 0, 0),
(
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
)
);
progress.update_progress(&1, 5);
assert_eq!(
(2, 1, 1),
(
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
)
);
progress.update_progress(&2, 4);
assert_eq!(
(3, 2, 2),
(
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
)
);
progress.update_progress(&1, 6);
assert_eq!(
(4, 3, 2),
(
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
)
);
progress.update_progress(&9, 7);
assert_eq!(
(5, 3, 2),
(
progress.stat().update_count,
progress.stat().move_count,
progress.stat().is_quorum_count,
)
);
}
#[test]
fn vec_progress_display_with() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
progress.update_progress(&1, 5);
progress.update_progress(&2, 3);
let display = progress.display_with(|f, item| write!(f, "{}={}", item.0, item.1));
let output = format!("{}", display);
assert_eq!("{1=5, 2=3, 0=0, 3=0}", output);
}
#[test]
fn vec_progress_increase_to() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
progress.increase_to(&1, 5);
assert_eq!(Some(&(1, 5)), progress.try_get(&1));
progress.increase_to(&1, 3);
assert_eq!(Some(&(1, 5)), progress.try_get(&1));
progress.increase_to(&1, 7);
assert_eq!(Some(&(1, 7)), progress.try_get(&1));
let result = progress.increase_to(&9, 10);
assert!(result.is_none());
}
#[test]
fn vec_progress_collect_mapped() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
progress.update_progress(&1, 5);
progress.update_progress(&2, 3);
let ids: Vec<u64> = progress.collect_mapped(|item| item.0);
assert_eq!(vec![1, 2, 0, 3], ids);
let values: Vec<u64> = progress.collect_mapped(|item| item.1);
assert_eq!(vec![5, 3, 0, 0], values);
let pairs: Vec<(u64, u64)> = progress.collect_mapped(|item| (item.0, item.1));
assert_eq!(vec![(1, 5), (2, 3), (0, 0), (3, 0)], pairs);
}
#[test]
fn vec_progress_reset_entry_with() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4, 5, 6}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [7], |id| (id, 0));
progress.update_progress(&0, 12);
progress.update_progress(&1, 11);
progress.update_progress(&2, 10);
progress.update_progress(&3, 9);
assert_eq!(&9, progress.quorum_accepted());
let entry = progress.reset_entry_with(&1, |entry| entry.1 = 0);
assert_eq!(Some(&(1, 0)), entry);
assert_eq!(
&9,
progress.quorum_accepted(),
"reset never lowers quorum-accepted"
);
assert_eq!(
vec![
(0, 12),
(2, 10),
(3, 9),
(1, 0),
(4, 0),
(5, 0),
(6, 0),
(7, 0)
],
progress.entries
);
assert_voter_prefix_is_sorted(&progress, "after reset");
assert_eq!(Some(9), copy_option(progress.update_progress(&4, 10)));
assert_matches_model(&progress, "after catching up to 10");
assert_eq!(Some(10), copy_option(progress.update_progress(&5, 10)));
assert_matches_model(&progress, "after a real quorum at 10");
assert_eq!(
Some(&(7, 0)),
progress.reset_entry_with(&7, |entry| entry.1 = 0)
);
assert_eq!(None, progress.reset_entry_with(&9, |entry| entry.1 = 0));
}
#[test]
fn vec_progress_matches_reference_model_with_resets() {
let cases = [
(vec![btreeset! {0, 1, 2, 3, 4, 5, 6}], vec![7]),
(vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}], vec![5, 6]),
];
for (case_id, (quorum_set, learners)) in cases.into_iter().enumerate() {
for seed in 0..32 {
let mut seed = seed + 3;
let mut progress =
VecProgress::<(u64, u64), _>::new(quorum_set.clone(), learners.clone(), |id| {
(id, 0)
});
let mut want = 0;
for step in 0..128 {
let id = (next_random(&mut seed) >> 32) % 8;
let context = format!("case-{case_id} seed-{seed} step-{step} id-{id}");
if (next_random(&mut seed) >> 32).is_multiple_of(8) {
let current = progress.try_get(&id).map(|entry| entry.1).unwrap_or_default();
let value = next_random(&mut seed) % (current + 1);
progress.reset_entry_with(&id, |entry| entry.1 = value);
} else {
let value = progress.try_get(&id).map(|entry| entry.1).unwrap_or_default()
+ next_random(&mut seed) % 7
+ 1;
progress.update_progress(&id, value);
}
want = want.max(model_quorum_accepted(
&progress.quorum_set,
&progress.entries,
));
assert_eq!(
&want,
progress.quorum_accepted(),
"{context}: entries: {:?}",
progress.entries
);
assert_voter_prefix_is_sorted(&progress, &context);
}
}
}
}
#[test]
fn vec_progress_sub_quorum_commit_regression() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
progress.update_progress(&0, 5); progress.update_progress(&1, 3); progress.update_progress(&2, 4); progress.update_progress(&2, 10); let qa = progress.update_progress(&3, 6);
assert_eq!(Some(&5), qa);
}
#[test]
fn vec_progress_display_entries() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<IdVal<u64, u64>, _>::new(quorum_set, [3], IdVal::new_default);
progress.update_progress(&1, 5);
progress.update_progress(&2, 3);
assert_eq!("{1: 5, 2: 3, 0: 0, 3: 0}", progress.to_string());
}
#[test]
fn vec_progress_into_iter() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
progress.update_progress(&1, 5);
progress.update_progress(&2, 3);
assert_eq!(
vec![(1, 5), (2, 3), (0, 0), (3, 0)],
progress.into_iter().collect::<Vec<_>>()
);
}
#[test]
fn vec_progress_update_entry_with() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress =
VecProgress::<IdValData<u64, u64, &'static str>, _>::new(quorum_set, [3], |id| {
IdValData::new(id, 0, "init")
});
let got = copy_option(progress.update_entry_with(&1, |entry| {
entry.val = 5;
entry.data = "replicated";
}));
assert_eq!(Some(0), got);
let got = copy_option(progress.update_entry_with(&2, |entry| {
entry.val = 3;
entry.data = "replicated";
}));
assert_eq!(Some(3), got);
assert_eq!(
vec![
IdValData::new(1, 5, "replicated"),
IdValData::new(2, 3, "replicated"),
IdValData::new(0, 0, "init"),
IdValData::new(3, 0, "init"),
],
progress.iter().cloned().collect::<Vec<_>>()
);
assert_eq!(
None,
copy_option(progress.update_entry_with(&9, |entry| entry.val = 1))
);
}
#[test]
fn vec_progress_zero_voters() {
let quorum_set: Vec<BTreeSet<u64>> = vec![];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [1, 2], |id| (id, 0));
assert_eq!(0, progress.voter_count);
assert_eq!(vec![(1, 0), (2, 0)], progress.entries);
assert_eq!(Some(false), progress.is_voter(&1));
assert_eq!(Some(&0), progress.update_progress(&1, 5));
assert_eq!(&0, progress.quorum_accepted());
assert_matches_model(&progress, "zero voters");
}
#[test]
fn vec_progress_reset_entry_with_partial_lowering() {
let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
progress.update_progress(&0, 20);
progress.update_progress(&1, 15);
progress.update_progress(&2, 10);
assert_eq!(&10, progress.quorum_accepted());
let entry = progress.reset_entry_with(&0, |entry| entry.1 = 12);
assert_eq!(Some(&(0, 12)), entry);
assert_eq!(&10, progress.quorum_accepted());
assert_eq!(
vec![(1, 15), (0, 12), (2, 10), (3, 0), (4, 0)],
progress.entries
);
assert_matches_model(&progress, "after partial lowering");
assert_eq!(Some(12), copy_option(progress.update_progress(&3, 12)));
assert_matches_model(&progress, "after catching up to 12");
}
#[test]
fn vec_progress_matches_reference_model_with_tree_quorum_set() {
let group =
|ids: [u64; 3]| Node::Subtree(QuorumTree::new(2, ids.into_iter().map(Node::Id)).unwrap());
for seed in 0..16 {
let mut seed = seed + 7;
let quorum_set = QuorumTree::new(2, [group([0, 1, 2]), group([3, 4, 5])]).unwrap();
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
let mut want = 0;
for step in 0..128 {
let id = (next_random(&mut seed) >> 32) % 8;
let context = format!("seed-{seed} step-{step} id-{id}");
if (next_random(&mut seed) >> 32).is_multiple_of(8) {
let current = progress.try_get(&id).map(|entry| entry.1).unwrap_or_default();
let value = next_random(&mut seed) % (current + 1);
progress.reset_entry_with(&id, |entry| entry.1 = value);
} else {
let value = progress.try_get(&id).map(|entry| entry.1).unwrap_or_default()
+ next_random(&mut seed) % 7
+ 1;
progress.update_progress(&id, value);
}
want = want.max(model_quorum_accepted(
&progress.quorum_set,
&progress.entries,
));
assert_eq!(
&want,
progress.quorum_accepted(),
"{context}: entries: {:?}",
progress.entries
);
assert_voter_prefix_is_sorted(&progress, &context);
}
}
}
#[test]
fn vec_progress_upgrade_tree_quorum_set() {
let group =
|ids: [u64; 3]| Node::Subtree(QuorumTree::new(2, ids.into_iter().map(Node::Id)).unwrap());
let tree_a = QuorumTree::new(2, [group([0, 1, 2]), group([3, 4, 5])]).unwrap();
let tree_b = QuorumTree::new(2, [group([3, 4, 5]), group([6, 7, 8])]).unwrap();
let mut progress = VecProgress::<(u64, u64), _>::new(tree_a, [9], |id| (id, 0));
for (id, value) in [(0, 9), (1, 8), (3, 7), (4, 6), (9, 5)] {
progress.update_progress(&id, value);
}
assert_eq!(&6, progress.quorum_accepted());
let upgraded = progress.upgrade_quorum_set(tree_b, [0, 9], |id| (id, 0));
assert_eq!(&0, upgraded.quorum_accepted());
assert_eq!(Some(&(0, 9)), upgraded.try_get(&0), "voter becomes learner");
assert_eq!(
Some(&(9, 5)),
upgraded.try_get(&9),
"learner progress is kept"
);
assert_eq!(
Some(&(3, 7)),
upgraded.try_get(&3),
"voter progress is kept"
);
assert_matches_model(&upgraded, "after tree quorum upgrade");
}
#[test]
fn vec_progress_upgrade_quorum_set_preserves_stat() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
progress.update_progress(&0, 5);
progress.update_progress(&1, 3);
let stat = progress.stat().clone();
assert_eq!(
(2, 2, 2),
(stat.update_count, stat.move_count, stat.is_quorum_count)
);
let upgraded = progress.upgrade_quorum_set(vec![btreeset! {1, 2, 3}], [0], |id| (id, 0));
assert_eq!(&stat, upgraded.stat());
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "progress() >= &prev_progress")]
fn vec_progress_update_entry_with_panics_on_lowered_progress() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
progress.update_progress(&0, 5);
progress.update_entry_with(&0, |entry| entry.1 = 3);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "progress() <= &prev_progress")]
fn vec_progress_reset_entry_with_panics_on_raised_progress() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
progress.reset_entry_with(&0, |entry| entry.1 = 5);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "id() == id")]
fn vec_progress_update_entry_with_panics_on_id_change() {
let quorum_set = vec![btreeset! {0, 1, 2}];
let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
progress.update_entry_with(&0, |entry| entry.0 = 9);
}