mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-06 12:25:03 +00:00
test: refine test, dag node may be merged
This commit is contained in:
parent
d47fe2df8c
commit
3400a4e4b7
5 changed files with 105 additions and 63 deletions
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue