test: start integration testing

This commit is contained in:
Zixuan Chen 2022-10-20 17:34:51 +08:00
parent 5c47f2e04e
commit 796317097d
10 changed files with 145 additions and 209 deletions

2
Cargo.lock generated
View file

@ -366,7 +366,9 @@ dependencies = [
name = "loro-core"
version = "0.1.0"
dependencies = [
"color-backtrace",
"crdt-list",
"ctor",
"enum-as-inner",
"fxhash",
"im",

View file

@ -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]

View file

@ -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);
}

View file

@ -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();

View file

@ -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) {

View file

@ -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);
}

View file

@ -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)
}
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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);
}