From 796317097db9c10c6c539347a59afb94e2ad703e Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 20 Oct 2022 17:34:51 +0800 Subject: [PATCH] test: start integration testing --- Cargo.lock | 2 + crates/loro-core/Cargo.toml | 2 + crates/loro-core/src/container/manager.rs | 20 ++- crates/loro-core/src/container/map/tests.rs | 4 +- .../src/container/text/text_container.rs | 25 ++- crates/loro-core/src/log_store.rs | 30 ++-- crates/loro-core/src/loro.rs | 40 ++++- crates/loro-core/src/value.rs | 168 +----------------- crates/loro-core/tests/integration.rs | 18 ++ crates/rle/src/rle_vec.rs | 45 +++-- 10 files changed, 145 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8201bc2c..50423121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,7 +366,9 @@ dependencies = [ name = "loro-core" version = "0.1.0" dependencies = [ + "color-backtrace", "crdt-list", + "ctor", "enum-as-inner", "fxhash", "im", diff --git a/crates/loro-core/Cargo.toml b/crates/loro-core/Cargo.toml index 91cf6bf9..57fc413e 100644 --- a/crates/loro-core/Cargo.toml +++ b/crates/loro-core/Cargo.toml @@ -28,6 +28,8 @@ proptest-derive = "0.3.0" rand = "0.8.5" static_assertions = "1.1.0" tabled = "0.9.0" +color-backtrace = { version = "0.5" } +ctor = "0.1.23" # See https://matklad.github.io/2021/02/27/delete-cargo-integration-tests.html [lib] diff --git a/crates/loro-core/src/container/manager.rs b/crates/loro-core/src/container/manager.rs index 3b302f98..4fb59775 100644 --- a/crates/loro-core/src/container/manager.rs +++ b/crates/loro-core/src/container/manager.rs @@ -3,7 +3,7 @@ use std::ptr::NonNull; use enum_as_inner::EnumAsInner; use fxhash::FxHashMap; -use crate::LogStore; +use crate::{log_store::LogStoreRef, LogStore}; use super::{ map::MapContainer, text::text_container::TextContainer, Container, ContainerID, ContainerType, @@ -76,9 +76,17 @@ pub struct ContainerManager { impl ContainerManager { #[inline] - pub fn create(&mut self, id: ContainerID, container_type: ContainerType) -> ContainerInstance { + pub fn create( + &mut self, + id: ContainerID, + container_type: ContainerType, + log_store: LogStoreRef, + ) -> ContainerInstance { match container_type { ContainerType::Map => ContainerInstance::Map(Box::new(MapContainer::new(id))), + ContainerType::Text => { + ContainerInstance::Text(Box::new(TextContainer::new(id, log_store))) + } _ => unimplemented!(), } } @@ -98,9 +106,13 @@ impl ContainerManager { self.containers.insert(id, container); } - pub fn get_or_create(&mut self, id: &ContainerID) -> &mut ContainerInstance { + pub fn get_or_create( + &mut self, + id: &ContainerID, + log_store: LogStoreRef, + ) -> &mut ContainerInstance { if !self.containers.contains_key(id) { - let container = self.create(id.clone(), id.container_type()); + let container = self.create(id.clone(), id.container_type(), log_store); self.insert(id.clone(), container); } diff --git a/crates/loro-core/src/container/map/tests.rs b/crates/loro-core/src/container/map/tests.rs index 51d91a7d..9d6773dd 100644 --- a/crates/loro-core/src/container/map/tests.rs +++ b/crates/loro-core/src/container/map/tests.rs @@ -15,7 +15,7 @@ use crate::{fx_map, value::InsertValue, LoroCore, LoroValue}; #[test] fn basic() { let mut loro = LoroCore::default(); - let weak = Arc::downgrade(&loro.store); + let weak = Arc::downgrade(&loro.log_store); let mut a = loro.get_map_container("map".into()); let container = a.as_mut(); container.insert("haha".into(), InsertValue::Int32(1), weak); @@ -38,7 +38,7 @@ mod map_proptest { value in prop::collection::vec(gen_insert_value(), 0..10 * PROPTEST_FACTOR_10) ) { let mut loro = LoroCore::default(); - let weak = Arc::downgrade(&loro.store); + let weak = Arc::downgrade(&loro.log_store); let mut a = loro.get_map_container("map".into()); let container = a.as_mut(); let mut map: HashMap = HashMap::new(); diff --git a/crates/loro-core/src/container/text/text_container.rs b/crates/loro-core/src/container/text/text_container.rs index ef274f57..d90e9181 100644 --- a/crates/loro-core/src/container/text/text_container.rs +++ b/crates/loro-core/src/container/text/text_container.rs @@ -7,6 +7,7 @@ use crate::{ id::ID, log_store::LogStoreRef, op::{InsertContent, Op, OpContent, OpProxy}, + smstring::SmString, span::{HasIdSpan, IdSpan}, value::LoroValue, LogStore, @@ -31,9 +32,21 @@ pub struct TextContainer { state: RleTree, raw_str: StringPool, tracker: Tracker, + state_cache: LoroValue, } impl TextContainer { + pub fn new(id: ContainerID, log_store: LogStoreRef) -> Self { + Self { + id, + log_store, + raw_str: StringPool::default(), + tracker: Tracker::new(Default::default()), + state_cache: LoroValue::Null, + state: Default::default(), + } + } + pub fn insert(&mut self, pos: usize, text: &str) -> Option { let id = if let Ok(mut store) = self.log_store.write() { let id = store.next_id(); @@ -133,7 +146,17 @@ impl Container for TextContainer { } fn get_value(&mut self) -> &LoroValue { - todo!() + let mut ans_str = SmString::new(); + for v in self.state.iter() { + let content = v.as_ref(); + match content { + ListSlice::Slice(ranges) => ans_str.push_str(&self.raw_str.get_str(ranges)), + _ => unreachable!(), + } + } + + self.state_cache = LoroValue::String(ans_str); + &self.state_cache } fn to_export(&self, op: &mut Op) { diff --git a/crates/loro-core/src/log_store.rs b/crates/loro-core/src/log_store.rs index 0d65b253..edfefa4f 100644 --- a/crates/loro-core/src/log_store.rs +++ b/crates/loro-core/src/log_store.rs @@ -61,8 +61,8 @@ pub struct LogStore { pub(crate) this_client_id: ClientID, frontier: SmallVec<[ID; 2]>, /// CRDT container manager - pub container: Arc>, - + pub(crate) container: Arc>, + to_self: Weak>, _pin: PhantomPinned, } @@ -73,17 +73,19 @@ impl LogStore { container: Arc>, ) -> Arc> { let this_client_id = client_id.unwrap_or_else(|| cfg.rand.next_u64()); - - Arc::new(RwLock::new(Self { - cfg, - this_client_id, - changes: FxHashMap::default(), - latest_lamport: 0, - latest_timestamp: 0, - frontier: Default::default(), - container, - _pin: PhantomPinned, - })) + Arc::new_cyclic(|x| { + RwLock::new(Self { + cfg, + this_client_id, + changes: FxHashMap::default(), + latest_lamport: 0, + latest_timestamp: 0, + frontier: Default::default(), + container, + to_self: x.clone(), + _pin: PhantomPinned, + }) + }) } #[inline] @@ -265,7 +267,7 @@ impl LogStore { #[inline] fn apply_remote_op(&mut self, change: &Change, op: &Op) { let mut container = self.container.write().unwrap(); - let container = container.get_or_create(op.container()); + let container = container.get_or_create(op.container(), self.to_self.upgrade().unwrap()); container.apply(&OpProxy::new(change, op, None), self); } diff --git a/crates/loro-core/src/loro.rs b/crates/loro-core/src/loro.rs index 34645d15..3327547e 100644 --- a/crates/loro-core/src/loro.rs +++ b/crates/loro-core/src/loro.rs @@ -11,6 +11,7 @@ use crate::{ container::{ manager::{ContainerInstance, ContainerManager}, map::MapContainer, + text::text_container::TextContainer, ContainerID, ContainerType, }, id::ClientID, @@ -18,7 +19,7 @@ use crate::{ }; pub struct LoroCore { - pub store: Arc>, + pub log_store: Arc>, pub container: Arc>, } @@ -35,7 +36,7 @@ impl LoroCore { store: NonNull::dangling(), })); Self { - store: LogStore::new(cfg, client_id, container.clone()), + log_store: LogStore::new(cfg, client_id, container.clone()), container, } } @@ -46,7 +47,12 @@ impl LoroCore { container: ContainerType, ) -> OwningRefMut, ContainerInstance> { let a = OwningRefMut::new(self.container.write().unwrap()); - a.map_mut(|x| x.get_or_create(&ContainerID::new_root(name, container))) + a.map_mut(|x| { + x.get_or_create( + &ContainerID::new_root(name, container), + self.log_store.clone(), + ) + }) } pub fn get_map_container( @@ -55,19 +61,37 @@ impl LoroCore { ) -> OwningRefMut, Box> { let a = OwningRefMut::new(self.container.write().unwrap()); a.map_mut(|x| { - x.get_or_create(&ContainerID::new_root(name, ContainerType::Map)) - .as_map_mut() - .unwrap() + x.get_or_create( + &ContainerID::new_root(name, ContainerType::Map), + self.log_store.clone(), + ) + .as_map_mut() + .unwrap() + }) + } + + pub fn get_text_container( + &mut self, + name: InternalString, + ) -> OwningRefMut, Box> { + let a = OwningRefMut::new(self.container.write().unwrap()); + a.map_mut(|x| { + x.get_or_create( + &ContainerID::new_root(name, ContainerType::Text), + self.log_store.clone(), + ) + .as_text_mut() + .unwrap() }) } pub fn export(&self, remote_vv: VersionVector) -> Vec { - let store = self.store.read().unwrap(); + let store = self.log_store.read().unwrap(); store.export(&remote_vv) } pub fn import(&mut self, changes: Vec) { - let mut store = self.store.write().unwrap(); + let mut store = self.log_store.write().unwrap(); store.import(changes) } } diff --git a/crates/loro-core/src/value.rs b/crates/loro-core/src/value.rs index 0a9867bf..81881098 100644 --- a/crates/loro-core/src/value.rs +++ b/crates/loro-core/src/value.rs @@ -1,9 +1,10 @@ +use enum_as_inner::EnumAsInner; use fxhash::FxHashMap; use crate::{container::ContainerID, smstring::SmString, InternalString}; /// [LoroValue] is used to represents the state of CRDT at a given version -#[derive(Debug, PartialEq, Clone, serde::Serialize)] +#[derive(Debug, PartialEq, Clone, serde::Serialize, EnumAsInner)] pub enum LoroValue { Null, Bool(bool), @@ -48,166 +49,6 @@ impl From for InsertValue { } } -// stupid getter and is_xxx -impl LoroValue { - #[inline] - pub fn is_null(&self) -> bool { - matches!(self, LoroValue::Null) - } - - #[inline] - pub fn is_bool(&self) -> bool { - matches!(self, LoroValue::Bool(_)) - } - - #[inline] - pub fn is_double(&self) -> bool { - matches!(self, LoroValue::Double(_)) - } - - #[inline] - pub fn is_integer(&self) -> bool { - matches!(self, LoroValue::Integer(_)) - } - - #[inline] - pub fn is_string(&self) -> bool { - matches!(self, LoroValue::String(_)) - } - - #[inline] - pub fn is_list(&self) -> bool { - matches!(self, LoroValue::List(_)) - } - - #[inline] - pub fn is_map(&self) -> bool { - matches!(self, LoroValue::Map(_)) - } - - #[inline] - pub fn is_unresolved(&self) -> bool { - matches!(self, LoroValue::Unresolved(_)) - } - - #[inline] - pub fn is_resolved(&self) -> bool { - !self.is_unresolved() - } - - #[inline] - pub fn as_map(&self) -> Option<&FxHashMap> { - match self { - LoroValue::Map(m) => Some(m), - _ => None, - } - } - - #[inline] - pub fn as_list(&self) -> Option<&Vec> { - match self { - LoroValue::List(l) => Some(l), - _ => None, - } - } - - #[inline] - pub fn as_string(&self) -> Option<&SmString> { - match self { - LoroValue::String(s) => Some(s), - _ => None, - } - } - - #[inline] - pub fn as_integer(&self) -> Option { - match self { - LoroValue::Integer(i) => Some(*i), - _ => None, - } - } - - #[inline] - pub fn as_double(&self) -> Option { - match self { - LoroValue::Double(d) => Some(*d), - _ => None, - } - } - - #[inline] - pub fn as_bool(&self) -> Option { - match self { - LoroValue::Bool(b) => Some(*b), - _ => None, - } - } - - #[inline] - pub fn as_container(&self) -> Option<&ContainerID> { - match self { - LoroValue::Unresolved(c) => Some(c), - _ => None, - } - } - - #[inline] - pub fn as_map_mut(&mut self) -> Option<&mut FxHashMap> { - match self { - LoroValue::Map(m) => Some(m), - _ => None, - } - } - - #[inline] - pub fn as_list_mut(&mut self) -> Option<&mut Vec> { - match self { - LoroValue::List(l) => Some(l), - _ => None, - } - } - - #[inline] - pub fn as_string_mut(&mut self) -> Option<&mut SmString> { - match self { - LoroValue::String(s) => Some(s), - _ => None, - } - } - - #[inline] - pub fn as_integer_mut(&mut self) -> Option<&mut i32> { - match self { - LoroValue::Integer(i) => Some(i), - _ => None, - } - } - - #[inline] - pub fn as_double_mut(&mut self) -> Option<&mut f64> { - match self { - LoroValue::Double(d) => Some(d), - _ => None, - } - } - - #[inline] - pub fn as_bool_mut(&mut self) -> Option<&mut bool> { - match self { - LoroValue::Bool(b) => Some(b), - _ => None, - } - } - - #[inline] - pub fn as_container_mut(&mut self) -> Option<&mut ContainerID> { - match self { - LoroValue::Unresolved(c) => Some(c), - _ => None, - } - } -} - /// [InsertValue] can be inserted to Map or List #[derive(Debug, PartialEq, Clone)] pub enum InsertValue { @@ -222,10 +63,7 @@ pub enum InsertValue { #[cfg(test)] pub(crate) mod proptest { use proptest::prelude::*; - use proptest::{prop_oneof}; - - - + use proptest::prop_oneof; use super::InsertValue; diff --git a/crates/loro-core/tests/integration.rs b/crates/loro-core/tests/integration.rs index 8b137891..fdca4fc2 100644 --- a/crates/loro-core/tests/integration.rs +++ b/crates/loro-core/tests/integration.rs @@ -1 +1,19 @@ +use ctor::ctor; +use loro_core::container::Container; +use loro_core::LoroCore; +#[test] +fn test() { + let mut store = LoroCore::new(Default::default(), None); + let mut text_container = store.get_text_container("haha".into()); + text_container.insert(0, "abc"); + text_container.insert(1, "x"); + let value = text_container.get_value(); + let value = value.as_string().unwrap(); + assert_eq!(value.as_str(), "axbc"); +} + +#[ctor] +fn init_color_backtrace() { + color_backtrace::install(); +} diff --git a/crates/rle/src/rle_vec.rs b/crates/rle/src/rle_vec.rs index 6116603d..6e93ae23 100644 --- a/crates/rle/src/rle_vec.rs +++ b/crates/rle/src/rle_vec.rs @@ -24,6 +24,7 @@ pub struct RleVec { cfg: Cfg, } +#[derive(Clone)] pub struct SearchResult<'a, T> { pub element: &'a T, pub merged_index: usize, @@ -86,7 +87,8 @@ impl + HasLength, Cfg> RleVec { }); } } - unreachable!(); + + return None; } let mut start = 0; @@ -120,13 +122,23 @@ impl + HasLength, Cfg> RleVec { /// get a slice from `from` to `to` with atom indexes pub fn slice_iter(&self, from: usize, to: usize) -> SliceIterator<'_, T> { let from_result = self.get(from).unwrap(); - let to_result = self.get(to).unwrap(); - SliceIterator { - vec: &self.vec, - cur_index: from_result.merged_index, - cur_offset: from_result.offset, - end_index: to_result.merged_index, - end_offset: to_result.offset, + let to_result = self.get(to); + if let Some(to_result) = to_result { + SliceIterator { + vec: &self.vec, + cur_index: from_result.merged_index, + cur_offset: from_result.offset, + end_index: Some(to_result.merged_index), + end_offset: Some(to_result.offset), + } + } else { + SliceIterator { + vec: &self.vec, + cur_index: from_result.merged_index, + cur_offset: from_result.offset, + end_index: None, + end_offset: None, + } } } @@ -227,25 +239,28 @@ pub struct SliceIterator<'a, T> { vec: &'a Vec, cur_index: usize, cur_offset: usize, - end_index: usize, - end_offset: usize, + end_index: Option, + end_offset: Option, } impl<'a, T: HasLength> Iterator for SliceIterator<'a, T> { type Item = Slice<'a, T>; fn next(&mut self) -> Option { - if self.cur_index == self.end_index { - if self.cur_offset == self.end_offset { + let end_index = self.end_index.unwrap_or(self.vec.len() - 1); + if self.cur_index == end_index { + let elem = &self.vec[self.cur_index]; + let end = self.end_offset.unwrap_or(elem.len()); + if self.cur_offset == end { return None; } let ans = Slice { - value: &self.vec[self.cur_index], + value: elem, start: self.cur_offset, - end: self.end_offset, + end, }; - self.cur_offset = self.end_offset; + self.cur_offset = end; return Some(ans); }