test: refine test, dag node may be merged

This commit is contained in:
Zixuan Chen 2022-10-17 16:46:18 +08:00
parent d47fe2df8c
commit 3400a4e4b7
5 changed files with 105 additions and 63 deletions

View file

@ -6,10 +6,13 @@
//! - Each node has its ID (client_id, counter). //! - Each node has its ID (client_id, counter).
//! - We use ID to refer to node rather than content addressing (hash) //! - We use ID to refer to node rather than content addressing (hash)
//! //!
use std::collections::{BinaryHeap, HashMap}; use std::{
collections::{BinaryHeap, HashMap},
fmt::Debug,
};
use fxhash::{FxHashMap, FxHashSet}; use fxhash::{FxHashMap, FxHashSet};
use rle::rle_tree::node::Node; use rle::{rle_tree::node::Node, HasLength};
use smallvec::SmallVec; use smallvec::SmallVec;
mod iter; mod iter;
mod mermaid; mod mermaid;
@ -19,7 +22,7 @@ mod test;
use crate::{ use crate::{
change::Lamport, change::Lamport,
id::{ClientID, Counter, ID}, id::{ClientID, Counter, ID},
span::{CounterSpan, IdSpan}, span::{CounterSpan, HasId, IdSpan},
version::VersionVector, version::VersionVector,
}; };
@ -29,10 +32,8 @@ use self::{
}; };
// TODO: use HasId, HasLength // TODO: use HasId, HasLength
pub(crate) trait DagNode { pub(crate) trait DagNode: HasId + HasLength + Debug {
fn id_start(&self) -> ID;
fn lamport_start(&self) -> Lamport; fn lamport_start(&self) -> Lamport;
fn len(&self) -> usize;
fn deps(&self) -> &[ID]; fn deps(&self) -> &[ID];
#[inline] #[inline]
@ -40,25 +41,6 @@ pub(crate) trait DagNode {
self.len() == 0 self.len() == 0
} }
#[inline]
fn id_span(&self) -> IdSpan {
let id = self.id_start();
IdSpan {
client_id: id.client_id,
counter: CounterSpan::new(id.counter, id.counter + self.len() as Counter),
}
}
/// inclusive end
#[inline]
fn id_end(&self) -> ID {
let id = self.id_start();
ID {
client_id: id.client_id,
counter: id.counter + self.len() as Counter - 1,
}
}
#[inline] #[inline]
fn get_offset(&self, c: Counter) -> Counter { fn get_offset(&self, c: Counter) -> Counter {
c - self.id_start().counter c - self.id_start().counter
@ -250,7 +232,7 @@ where
let node = get(node_id).unwrap(); let node = get(node_id).unwrap();
let node_id_start = node.id_start(); let node_id_start = node.id_start();
if !visited.contains(&node_id_start) { if !visited.contains(&node_id_start) {
vv.try_update_end(node_id); vv.try_update_last(node_id);
for dep in node.deps() { for dep in node.deps() {
if !visited.contains(dep) { if !visited.contains(dep) {
stack.push(dep); stack.push(dep);

View file

@ -71,8 +71,6 @@ impl<'a, T: DagNode> Iterator for DagIterator<'a, T> {
let item = self.heap.pop().unwrap(); let item = self.heap.pop().unwrap();
let id = item.id; let id = item.id;
let node = self.dag.get(id).unwrap(); let node = self.dag.get(id).unwrap();
debug_assert_eq!(id, node.id_start());
// push next node from the same client to the heap // push next node from the same client to the heap
let next_id = id.inc(node.len() as i32); let next_id = id.inc(node.len() as i32);
if self.dag.contains(next_id) { if self.dag.contains(next_id) {
@ -154,7 +152,7 @@ impl<'a, T: DagNode> Iterator for DagIteratorVV<'a, T> {
vv.unwrap_or_else(VersionVector::new) vv.unwrap_or_else(VersionVector::new)
}; };
vv.try_update_end(id); vv.try_update_last(id);
self.vv_map.insert(node.id_start(), vv.clone()); self.vv_map.insert(node.id_start(), vv.clone());
// push next node from the same client to the heap // push next node from the same client to the heap

View file

@ -1,11 +1,13 @@
#![cfg(test)] #![cfg(test)]
use std::cmp::Ordering;
use super::*; use super::*;
use crate::{ use crate::{
array_mut_ref, array_mut_ref,
change::Lamport, change::Lamport,
id::{ClientID, Counter, ID}, id::{ClientID, Counter, ID},
span::IdSpan, span::{HasIdSpan, IdSpan},
}; };
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@ -28,20 +30,26 @@ impl TestNode {
} }
impl DagNode for TestNode { impl DagNode for TestNode {
fn id_start(&self) -> ID {
self.id
}
fn lamport_start(&self) -> Lamport { fn lamport_start(&self) -> Lamport {
self.lamport self.lamport
} }
fn len(&self) -> usize {
self.len
}
fn deps(&self) -> &[ID] { fn deps(&self) -> &[ID] {
&self.deps &self.deps
} }
} }
impl HasId for TestNode {
fn id_start(&self) -> ID {
self.id
}
}
impl HasLength for TestNode {
fn len(&self) -> usize {
self.len
}
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct TestDag { struct TestDag {
nodes: FxHashMap<ClientID, Vec<TestNode>>, nodes: FxHashMap<ClientID, Vec<TestNode>>,
@ -61,9 +69,17 @@ impl Dag for TestDag {
type Node = TestNode; type Node = TestNode;
fn get(&self, id: ID) -> Option<&Self::Node> { fn get(&self, id: ID) -> Option<&Self::Node> {
self.nodes.get(&id.client_id)?.iter().find(|node| { let arr = self.nodes.get(&id.client_id)?;
id.counter >= node.id.counter && id.counter < node.id.counter + node.len as Counter arr.binary_search_by(|node| {
if node.id.counter > id.counter {
Ordering::Greater
} else if node.id.counter + node.len as i32 <= id.counter {
Ordering::Less
} else {
Ordering::Equal
}
}) })
.map_or(None, |x| Some(&arr[x]))
} }
fn frontier(&self) -> &[ID] { fn frontier(&self) -> &[ID] {
@ -75,10 +91,7 @@ impl Dag for TestDag {
} }
fn contains(&self, id: ID) -> bool { fn contains(&self, id: ID) -> bool {
self.version_vec self.version_vec.includes_id(id)
.get(&id.client_id)
.and_then(|x| if *x > id.counter { Some(()) } else { None })
.is_some()
} }
fn vv(&self) -> VersionVector { fn vv(&self) -> VersionVector {
@ -111,12 +124,19 @@ impl TestDag {
let id = ID::new(client_id, *counter); let id = ID::new(client_id, *counter);
*counter += len as Counter; *counter += len as Counter;
let deps = std::mem::replace(&mut self.frontier, vec![id]); let deps = std::mem::replace(&mut self.frontier, vec![id]);
self.nodes.entry(client_id).or_default().push(TestNode::new( if deps.len() == 1 && deps[0].client_id == client_id {
id, // can merge two op
self.next_lamport, let arr = self.nodes.get_mut(&client_id).unwrap();
deps, let mut last = arr.last_mut().unwrap();
len, last.len += len;
)); } else {
self.nodes.entry(client_id).or_default().push(TestNode::new(
id,
self.next_lamport,
deps,
len,
));
}
self.next_lamport += len as u32; self.next_lamport += len as u32;
} }
@ -156,21 +176,23 @@ impl TestDag {
i: usize, i: usize,
) -> bool { ) -> bool {
let client_id = node.id.client_id; let client_id = node.id.client_id;
if self.contains(node.id) { if self.contains(node.id_last()) {
return false; return false;
} }
if node.deps.iter().any(|dep| !self.contains(*dep)) { if node.deps.iter().any(|dep| !self.contains(*dep)) {
pending.push((client_id, i)); pending.push((client_id, i));
return true; return true;
} }
update_frontier( update_frontier(&mut self.frontier, node.id_last(), &node.deps);
&mut self.frontier, let contains_start = self.contains(node.id_start());
node.id.inc((node.len() - 1) as Counter), let mut arr = self.nodes.entry(client_id).or_default();
&node.deps, if contains_start {
); arr.pop();
self.nodes.entry(client_id).or_default().push(node.clone()); arr.push(node.clone());
self.version_vec } else {
.insert(client_id, node.id.counter + node.len as Counter); arr.push(node.clone());
}
self.version_vec.set_end(node.id_end());
self.next_lamport = self.next_lamport.max(node.lamport + node.len as u32); self.next_lamport = self.next_lamport.max(node.lamport + node.len as u32);
false false
} }
@ -292,6 +314,8 @@ mod iter {
mod mermaid { mod mermaid {
use rand::{rngs::StdRng, SeedableRng};
use super::*; use super::*;
#[test] #[test]
@ -334,7 +358,7 @@ mod mermaid {
#[test] #[test]
fn gen() { fn gen() {
let num = 5; let num = 5;
let mut rng = rand::thread_rng(); let mut rng = StdRng::seed_from_u64(100);
let mut dags = (0..num).map(TestDag::new).collect::<Vec<_>>(); let mut dags = (0..num).map(TestDag::new).collect::<Vec<_>>();
for _ in 0..100 { for _ in 0..100 {
Interaction::gen(&mut rng, num as usize).apply(&mut dags); Interaction::gen(&mut rng, num as usize).apply(&mut dags);
@ -436,6 +460,17 @@ mod find_path {
mod find_common_ancestors { mod find_common_ancestors {
use super::*; use super::*;
#[test]
fn siblings() {
let mut a = TestDag::new(0);
a.push(5);
let actual = a
.find_common_ancestor(ID::new(0, 2), ID::new(0, 4))
.first()
.copied();
assert_eq!(actual, Some(ID::new(0, 2)));
}
#[test] #[test]
fn no_common_ancestors() { fn no_common_ancestors() {
let mut a = TestDag::new(0); let mut a = TestDag::new(0);
@ -474,7 +509,7 @@ mod find_common_ancestors {
b.merge(&a); b.merge(&a);
b.frontier.retain(|x| x.client_id == 1); b.frontier.retain(|x| x.client_id == 1);
let k = b.nodes.get_mut(&1).unwrap(); let k = b.nodes.get_mut(&1).unwrap();
k[1].deps.push(ID::new(0, 2)); k[0].deps.push(ID::new(0, 2));
assert_eq!( assert_eq!(
b.find_common_ancestor(ID::new(0, 3), ID::new(1, 8)) b.find_common_ancestor(ID::new(0, 3), ID::new(1, 8))
.first() .first()
@ -520,9 +555,11 @@ mod find_common_ancestors {
} }
mod find_common_ancestors_proptest { mod find_common_ancestors_proptest {
use std::thread::panicking;
use proptest::prelude::*; use proptest::prelude::*;
use crate::{array_mut_ref, tests::PROPTEST_FACTOR_10, unsafe_array_mut_ref}; use crate::{array_mut_ref, span::HasIdSpan, tests::PROPTEST_FACTOR_10, unsafe_array_mut_ref};
use super::*; use super::*;
@ -603,6 +640,22 @@ mod find_common_ancestors_proptest {
} }
} }
#[test]
fn issue() {
if let Err(err) = test_single_common_ancestor(
4,
vec![Interaction {
dag_idx: 0,
merge_with: None,
len: 1,
}],
vec![],
) {
println!("{}", err);
panic!();
}
}
fn test_single_common_ancestor( fn test_single_common_ancestor(
dag_num: i32, dag_num: i32,
mut before_merge_insertion: Vec<Interaction>, mut before_merge_insertion: Vec<Interaction>,
@ -644,8 +697,8 @@ mod find_common_ancestors_proptest {
let (dag0, dag1) = array_mut_ref!(&mut dags, [0, 1]); let (dag0, dag1) = array_mut_ref!(&mut dags, [0, 1]);
dag1.push(1); dag1.push(1);
dag0.merge(dag1); dag0.merge(dag1);
let a = dags[0].nodes.get(&0).unwrap().last().unwrap().id; let a = dags[0].nodes.get(&0).unwrap().last().unwrap().id_last();
let b = dags[1].nodes.get(&1).unwrap().last().unwrap().id; let b = dags[1].nodes.get(&1).unwrap().last().unwrap().id_last();
let actual = dags[0].find_common_ancestor(a, b); let actual = dags[0].find_common_ancestor(a, b);
prop_assert_eq!(actual.first().copied().unwrap(), expected); prop_assert_eq!(actual.first().copied().unwrap(), expected);
Ok(()) Ok(())

View file

@ -175,6 +175,15 @@ pub trait HasIdSpan: HasId + HasLength {
fn id_last(&self) -> ID { fn id_last(&self) -> ID {
self.id_start().inc(self.len() as i32 - 1) self.id_start().inc(self.len() as i32 - 1)
} }
fn contains_id(&self, id: ID) -> bool {
let id_start = self.id_start();
if id.client_id != id_start.client_id {
return false;
}
id_start.counter <= id.counter && id.counter < id_start.counter + self.len() as Counter
}
} }
impl<T: HasId + HasLength> HasIdSpan for T {} impl<T: HasId + HasLength> HasIdSpan for T {}

View file

@ -125,7 +125,7 @@ impl VersionVector {
/// update the end counter of the given client, if the end is greater /// update the end counter of the given client, if the end is greater
/// return whether updated /// return whether updated
#[inline] #[inline]
pub fn try_update_end(&mut self, id: ID) -> bool { pub fn try_update_last(&mut self, id: ID) -> bool {
if let Some(end) = self.0.get_mut(&id.client_id) { if let Some(end) = self.0.get_mut(&id.client_id) {
if *end < id.counter + 1 { if *end < id.counter + 1 {
*end = id.counter + 1; *end = id.counter + 1;