mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 21:07:43 +00:00
test: start integration testing
This commit is contained in:
parent
5c47f2e04e
commit
796317097d
10 changed files with 145 additions and 209 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -366,7 +366,9 @@ dependencies = [
|
|||
name = "loro-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"color-backtrace",
|
||||
"crdt-list",
|
||||
"ctor",
|
||||
"enum-as-inner",
|
||||
"fxhash",
|
||||
"im",
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String, InsertValue> = HashMap::new();
|
||||
|
|
|
@ -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<ListSlice, ListSliceTreeTrait>,
|
||||
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<ID> {
|
||||
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) {
|
||||
|
|
|
@ -61,8 +61,8 @@ pub struct LogStore {
|
|||
pub(crate) this_client_id: ClientID,
|
||||
frontier: SmallVec<[ID; 2]>,
|
||||
/// CRDT container manager
|
||||
pub container: Arc<RwLock<ContainerManager>>,
|
||||
|
||||
pub(crate) container: Arc<RwLock<ContainerManager>>,
|
||||
to_self: Weak<RwLock<LogStore>>,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
|
||||
|
@ -73,17 +73,19 @@ impl LogStore {
|
|||
container: Arc<RwLock<ContainerManager>>,
|
||||
) -> Arc<RwLock<Self>> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<RwLock<LogStore>>,
|
||||
pub log_store: Arc<RwLock<LogStore>>,
|
||||
pub container: Arc<RwLock<ContainerManager>>,
|
||||
}
|
||||
|
||||
|
@ -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<RwLockWriteGuard<ContainerManager>, 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<RwLockWriteGuard<ContainerManager>, Box<MapContainer>> {
|
||||
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<RwLockWriteGuard<ContainerManager>, Box<TextContainer>> {
|
||||
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<Change> {
|
||||
let store = self.store.read().unwrap();
|
||||
let store = self.log_store.read().unwrap();
|
||||
store.export(&remote_vv)
|
||||
}
|
||||
|
||||
pub fn import(&mut self, changes: Vec<Change>) {
|
||||
let mut store = self.store.write().unwrap();
|
||||
let mut store = self.log_store.write().unwrap();
|
||||
store.import(changes)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<LoroValue> 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<InternalString, LoroValue>> {
|
||||
match self {
|
||||
LoroValue::Map(m) => Some(m),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_list(&self) -> Option<&Vec<LoroValue>> {
|
||||
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<i32> {
|
||||
match self {
|
||||
LoroValue::Integer(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_double(&self) -> Option<f64> {
|
||||
match self {
|
||||
LoroValue::Double(d) => Some(*d),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
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<InternalString, LoroValue>> {
|
||||
match self {
|
||||
LoroValue::Map(m) => Some(m),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_list_mut(&mut self) -> Option<&mut Vec<LoroValue>> {
|
||||
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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ pub struct RleVec<T, Cfg = ()> {
|
|||
cfg: Cfg,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SearchResult<'a, T> {
|
||||
pub element: &'a T,
|
||||
pub merged_index: usize,
|
||||
|
@ -86,7 +87,8 @@ impl<T: Mergable<Cfg> + HasLength, Cfg> RleVec<T, Cfg> {
|
|||
});
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut start = 0;
|
||||
|
@ -120,13 +122,23 @@ impl<T: Mergable<Cfg> + HasLength, Cfg> RleVec<T, Cfg> {
|
|||
/// 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<T>,
|
||||
cur_index: usize,
|
||||
cur_offset: usize,
|
||||
end_index: usize,
|
||||
end_offset: usize,
|
||||
end_index: Option<usize>,
|
||||
end_offset: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a, T: HasLength> Iterator for SliceIterator<'a, T> {
|
||||
type Item = Slice<'a, T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue