test: recursive fuzz test for refactored

This commit is contained in:
Zixuan Chen 2023-07-17 17:28:07 +08:00
parent a3488c7088
commit 93252c9522
7 changed files with 1431 additions and 1254 deletions

File diff suppressed because it is too large Load diff

View file

@ -33,6 +33,7 @@ pub struct SharedArena {
text: Arc<Mutex<AppendOnlyBytes>>,
text_utf16_len: Arc<AtomicUsize>,
values: Arc<Mutex<Vec<LoroValue>>>,
root_c_idx: Arc<Mutex<Vec<ContainerIdx>>>,
}
pub struct StrAllocResult {
@ -53,6 +54,9 @@ impl SharedArena {
container_idx_to_id.push(id.clone());
let ans = ContainerIdx::from_index_and_type(idx as u32, id.container_type());
container_id_to_idx.insert(id.clone(), ans);
if id.is_root() {
self.root_c_idx.lock().unwrap().push(ans);
}
ans
}
@ -240,4 +244,8 @@ impl SharedArena {
})
.collect()
}
pub fn root_containers(&self) -> Vec<ContainerIdx> {
self.root_c_idx.lock().unwrap().clone()
}
}

View file

@ -107,20 +107,42 @@ impl ListHandler {
}
}
pub fn insert(&self, txn: &mut Transaction, pos: usize, s: &str) {
if s.is_empty() {
pub fn insert(&self, txn: &mut Transaction, pos: usize, v: LoroValue) {
if let Some(container) = v.as_container() {
self.insert_container(txn, pos, container.container_type());
return;
}
txn.apply_local_op(
self.container_idx,
crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Insert {
slice: ListSlice::RawStr(Cow::Borrowed(s)),
slice: ListSlice::RawData(Cow::Owned(vec![v])),
pos,
}),
);
}
pub fn insert_container(
&self,
txn: &mut Transaction,
pos: usize,
c_type: ContainerType,
) -> ContainerIdx {
let id = txn.next_id();
let container_id = ContainerID::new_normal(id, c_type);
let child_idx = txn.arena.id_to_idx(&container_id).unwrap();
txn.arena.set_parent(child_idx, Some(self.container_idx));
let v = LoroValue::Container(container_id);
txn.apply_local_op(
self.container_idx,
crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Insert {
slice: ListSlice::RawData(Cow::Owned(vec![v])),
pos,
}),
);
child_idx
}
pub fn delete(&self, txn: &mut Transaction, pos: usize, len: usize) {
if len == 0 {
return;
@ -181,6 +203,11 @@ impl MapHandler {
}
pub fn insert(&self, txn: &mut Transaction, key: &str, value: LoroValue) {
if let Some(value) = value.as_container() {
self.insert_container(txn, key, value.container_type());
return;
}
txn.apply_local_op(
self.container_idx,
crate::op::RawOpContent::Map(crate::container::map::MapSet {
@ -190,6 +217,26 @@ impl MapHandler {
);
}
pub fn insert_container(
&self,
txn: &mut Transaction,
key: &str,
c_type: ContainerType,
) -> ContainerIdx {
let id = txn.next_id();
let container_id = ContainerID::new_normal(id, c_type);
let child_idx = txn.arena.id_to_idx(&container_id).unwrap();
txn.arena.set_parent(child_idx, Some(self.container_idx));
txn.apply_local_op(
self.container_idx,
crate::op::RawOpContent::Map(crate::container::map::MapSet {
key: key.into(),
value: LoroValue::Container(container_id),
}),
);
child_idx
}
pub fn delete(&self, txn: &mut Transaction, key: &str) {
txn.apply_local_op(
self.container_idx,

View file

@ -3,7 +3,10 @@ use std::{
sync::{Arc, Mutex},
};
use loro_common::LoroValue;
use crate::{
container::{registry::ContainerIdx, ContainerIdRaw},
id::PeerID,
log_store::encoding::{ConcreteEncodeMode, ENCODE_SCHEMA_VERSION, MAGIC_BYTES},
EncodeMode, LoroError, VersionVector,
@ -15,6 +18,7 @@ use super::{
snapshot_encode::{decode_app_snapshot, encode_app_snapshot},
state::{AppState, AppStateDiff, ContainerStateDiff},
txn::Transaction,
ListHandler, MapHandler, TextHandler,
};
/// `LoroApp` serves as the library's primary entry point.
@ -103,6 +107,10 @@ impl LoroApp {
&self.state
}
pub fn get_state_deep_value(&self) -> LoroValue {
self.state.lock().unwrap().get_deep_value()
}
pub fn oplog(&self) -> &Arc<Mutex<OpLog>> {
&self.oplog
}
@ -179,6 +187,47 @@ impl LoroApp {
pub(crate) fn vv_cloned(&self) -> VersionVector {
self.oplog.lock().unwrap().vv().clone()
}
/// id can be a str, ContainerID, or ContainerIdRaw.
/// if it's str it will use Root container, which will not be None
pub fn get_text<I: Into<ContainerIdRaw>>(&self, id: I) -> Option<TextHandler> {
let idx = self.get_container_idx(id);
idx.map(|x| TextHandler::new(x, Arc::downgrade(&self.state)))
}
/// id can be a str, ContainerID, or ContainerIdRaw.
/// if it's str it will use Root container, which will not be None
pub fn get_list<I: Into<ContainerIdRaw>>(&self, id: I) -> Option<ListHandler> {
let idx = self.get_container_idx(id);
idx.map(|x| ListHandler::new(x, Arc::downgrade(&self.state)))
}
/// id can be a str, ContainerID, or ContainerIdRaw.
/// if it's str it will use Root container, which will not be None
pub fn get_map<I: Into<ContainerIdRaw>>(&self, id: I) -> Option<MapHandler> {
let idx = self.get_container_idx(id);
idx.map(|x| MapHandler::new(x, Arc::downgrade(&self.state)))
}
fn get_container_idx<I: Into<ContainerIdRaw>>(&self, id: I) -> Option<ContainerIdx> {
let id: ContainerIdRaw = id.into();
match id {
ContainerIdRaw::Root { name } => {
Some(self.oplog().lock().unwrap().arena.register_container(
&crate::container::ContainerID::Root {
name,
container_type: crate::ContainerType::Text,
},
))
}
ContainerIdRaw::Normal { id: _ } => self
.oplog()
.lock()
.unwrap()
.arena
.id_to_idx(&id.with_type(crate::ContainerType::Text)),
}
}
}
impl Default for LoroApp {

View file

@ -204,6 +204,7 @@ struct PreEncodedState {
}
fn preprocess_app_state(app_state: &AppState) -> PreEncodedState {
assert!(!app_state.is_in_txn());
let mut peers = Vec::new();
let mut peer_lookup = FxHashMap::default();
let mut bytes = Vec::new();
@ -625,6 +626,7 @@ pub fn decode_oplog(
pub fn decode_state(app_state: &mut AppState, data: &FinalPhase) -> Result<(), LoroError> {
assert!(app_state.is_empty());
assert!(!app_state.is_in_txn());
let arena = app_state.arena.clone();
let common = CommonArena::decode(&data)?;
let state_arena = TempArena::decode_state_arena(&data)?;

View file

@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::{borrow::Cow, sync::Arc};
use enum_as_inner::EnumAsInner;
use enum_dispatch::enum_dispatch;
@ -234,6 +234,67 @@ impl AppState {
pub fn is_empty(&self) -> bool {
!self.in_txn && self.states.is_empty() && self.arena.is_empty()
}
pub fn get_deep_value(&self) -> LoroValue {
let roots = self.arena.root_containers();
let mut ans = FxHashMap::with_capacity_and_hasher(roots.len(), Default::default());
for root_idx in roots {
let id = self.arena.idx_to_id(root_idx).unwrap();
match id {
loro_common::ContainerID::Root { name, .. } => {
ans.insert(name.to_string(), self.get_container_deep_value(root_idx));
}
loro_common::ContainerID::Normal { .. } => {
unreachable!()
}
}
}
LoroValue::Map(Arc::new(ans))
}
pub fn get_container_deep_value(&self, container: ContainerIdx) -> LoroValue {
let state = self.states.get(&container).unwrap();
let value = state.get_value();
match value {
LoroValue::Container(_) => unreachable!(),
LoroValue::List(mut list) => {
if list.iter().all(|x| !x.is_container()) {
return LoroValue::List(list);
}
let list_mut = Arc::make_mut(&mut list);
for item in list_mut.iter_mut() {
if item.is_container() {
let container = item.as_container().unwrap();
let container_idx = self.arena.id_to_idx(container).unwrap();
let value = self.get_container_deep_value(container_idx);
*item = value;
}
}
LoroValue::List(list)
}
LoroValue::Map(mut map) => {
if map.iter().all(|x| !x.1.is_container()) {
return LoroValue::Map(map);
}
let map_mut = Arc::make_mut(&mut map);
for (_key, value) in map_mut.iter_mut() {
if value.is_container() {
let container = value.as_container().unwrap();
let container_idx = self.arena.id_to_idx(container).unwrap();
let new_value = self.get_container_deep_value(container_idx);
*value = new_value;
}
}
LoroValue::Map(map)
}
_ => value,
}
}
}
pub fn create_state(kind: ContainerType) -> State {

View file

@ -30,7 +30,7 @@ pub struct Transaction {
oplog: Arc<Mutex<OpLog>>,
frontiers: Frontiers,
local_ops: RleVec<[Op; 1]>,
arena: SharedArena,
pub(super) arena: SharedArena,
finished: bool,
}
@ -183,6 +183,13 @@ impl Transaction {
let state = self.state.lock().unwrap();
f(state.get_state(idx).unwrap())
}
pub fn next_id(&self) -> ID {
ID {
peer: self.peer,
counter: self.next_counter,
}
}
}
impl Drop for Transaction {