diff --git a/crates/loro-internal/src/fuzz/recursive_refactored.rs b/crates/loro-internal/src/fuzz/recursive_refactored.rs index 3483281b..b0ef242b 100644 --- a/crates/loro-internal/src/fuzz/recursive_refactored.rs +++ b/crates/loro-internal/src/fuzz/recursive_refactored.rs @@ -1,1310 +1,1313 @@ -// use std::{ -// collections::HashSet, -// fmt::Debug, -// sync::{Arc, Mutex}, -// }; +use std::{collections::HashSet, fmt::Debug, sync::Arc}; -// use arbitrary::Arbitrary; -// use enum_as_inner::EnumAsInner; -// use fxhash::FxHashMap; -// use tabled::{TableIteratorExt, Tabled}; +use arbitrary::Arbitrary; +use enum_as_inner::EnumAsInner; +use tabled::{TableIteratorExt, Tabled}; -// #[allow(unused_imports)] -// use crate::{ -// array_mut_ref, -// container::ContainerID, -// delta::DeltaItem, -// event::{Diff, Observer}, -// id::PeerID, -// ContainerType, List, LoroCore, LoroValue, Map, Text, Transact, -// }; -// use crate::{ -// container::registry::ContainerIdx, -// refactor::{loro::LoroApp, ListHandler, MapHandler, TextHandler}, -// value::{ApplyDiff, ToJson}, -// EncodeMode, -// }; +#[allow(unused_imports)] +use crate::{ + array_mut_ref, + container::ContainerID, + delta::DeltaItem, + event::{Diff, Observer}, + id::PeerID, + ContainerType, List, LoroCore, LoroValue, Map, Text, Transact, +}; +use crate::{ + container::registry::ContainerIdx, + refactor::{loro::LoroApp, ListHandler, MapHandler, TextHandler}, + value::ToJson, +}; -// #[derive(Arbitrary, EnumAsInner, Clone, PartialEq, Eq, Debug)] -// pub enum Action { -// Map { -// site: u8, -// container_idx: u8, -// key: u8, -// value: FuzzValue, -// }, -// List { -// site: u8, -// container_idx: u8, -// key: u8, -// value: FuzzValue, -// }, -// Text { -// site: u8, -// container_idx: u8, -// pos: u8, -// value: u16, -// is_del: bool, -// }, -// Sync { -// from: u8, -// to: u8, -// }, -// SyncAll, -// } +#[derive(Arbitrary, EnumAsInner, Clone, PartialEq, Eq, Debug)] +pub enum Action { + Map { + site: u8, + container_idx: u8, + key: u8, + value: FuzzValue, + }, + List { + site: u8, + container_idx: u8, + key: u8, + value: FuzzValue, + }, + Text { + site: u8, + container_idx: u8, + pos: u8, + value: u16, + is_del: bool, + }, + Sync { + from: u8, + to: u8, + }, + SyncAll, +} -// struct Actor { -// loro: LoroApp, -// // value_tracker: Arc>, -// // map_tracker: Arc>>, -// // list_tracker: Arc>>, -// // text_tracker: Arc>, -// map_containers: Vec, -// list_containers: Vec, -// text_containers: Vec, -// } +struct Actor { + loro: LoroApp, + // value_tracker: Arc>, + // map_tracker: Arc>>, + // list_tracker: Arc>>, + // text_tracker: Arc>, + map_containers: Vec, + list_containers: Vec, + text_containers: Vec, +} -// impl Actor { -// fn new(id: PeerID) -> Self { -// let mut app = LoroApp::new(); -// app.set_peer_id(id); -// let mut actor = Actor { -// loro: app, -// // value_tracker: Arc::new(Mutex::new(LoroValue::Map(Default::default()))), -// // map_tracker: Default::default(), -// // list_tracker: Default::default(), -// // text_tracker: Default::default(), -// map_containers: Default::default(), -// list_containers: Default::default(), -// text_containers: Default::default(), -// }; +impl Actor { + fn new(id: PeerID) -> Self { + let app = LoroApp::new(); + app.set_peer_id(id); + let mut actor = Actor { + loro: app, + // value_tracker: Arc::new(Mutex::new(LoroValue::Map(Default::default()))), + // map_tracker: Default::default(), + // list_tracker: Default::default(), + // text_tracker: Default::default(), + map_containers: Default::default(), + list_containers: Default::default(), + text_containers: Default::default(), + }; -// actor -// .text_containers -// .push(actor.loro.txn().unwrap().get_text("text").unwrap()); -// actor -// .map_containers -// .push(actor.loro.txn().unwrap().get_map("map").unwrap()); -// actor -// .list_containers -// .push(actor.loro.txn().unwrap().get_list("list").unwrap()); -// actor -// } -// } + actor + .text_containers + .push(actor.loro.txn().unwrap().get_text("text").unwrap()); + actor + .map_containers + .push(actor.loro.txn().unwrap().get_map("map").unwrap()); + actor + .list_containers + .push(actor.loro.txn().unwrap().get_list("list").unwrap()); + actor + } +} -// #[derive(Arbitrary, Clone, Debug, PartialEq, Eq)] -// pub enum FuzzValue { -// Null, -// I32(i32), -// Container(ContainerType), -// } +#[derive(Arbitrary, Clone, Debug, PartialEq, Eq)] +pub enum FuzzValue { + Null, + I32(i32), + Container(ContainerType), +} -// impl From for LoroValue { -// fn from(v: FuzzValue) -> Self { -// match v { -// FuzzValue::Null => LoroValue::Null, -// FuzzValue::I32(i) => LoroValue::I32(i), -// FuzzValue::Container(_) => unreachable!(), -// } -// } -// } +impl From for LoroValue { + fn from(v: FuzzValue) -> Self { + match v { + FuzzValue::Null => LoroValue::Null, + FuzzValue::I32(i) => LoroValue::I32(i), + FuzzValue::Container(_) => unreachable!(), + } + } +} -// impl Tabled for Action { -// const LENGTH: usize = 5; +impl Tabled for Action { + const LENGTH: usize = 5; -// fn fields(&self) -> Vec> { -// match self { -// Action::Sync { from, to } => vec![ -// "sync".into(), -// format!("{} to {}", from, to).into(), -// "".into(), -// "".into(), -// "".into(), -// ], -// Action::SyncAll => vec!["sync all".into(), "".into(), "".into(), "".into()], -// Action::Map { -// site, -// container_idx, -// key, -// value, -// } => vec![ -// "map".into(), -// format!("{}", site).into(), -// format!("{}", container_idx).into(), -// format!("{}", key).into(), -// format!("{:?}", value).into(), -// ], -// Action::List { -// site, -// container_idx, -// key, -// value, -// } => vec![ -// "list".into(), -// format!("{}", site).into(), -// format!("{}", container_idx).into(), -// format!("{}", key).into(), -// format!("{:?}", value).into(), -// ], -// Action::Text { -// site, -// container_idx, -// pos, -// value, -// is_del, -// } => vec![ -// "text".into(), -// format!("{}", site).into(), -// format!("{}", container_idx).into(), -// format!("{}", pos).into(), -// format!("{}{}", if *is_del { "Delete " } else { "" }, value).into(), -// ], -// } -// } + fn fields(&self) -> Vec> { + match self { + Action::Sync { from, to } => vec![ + "sync".into(), + format!("{} to {}", from, to).into(), + "".into(), + "".into(), + "".into(), + ], + Action::SyncAll => vec!["sync all".into(), "".into(), "".into(), "".into()], + Action::Map { + site, + container_idx, + key, + value, + } => vec![ + "map".into(), + format!("{}", site).into(), + format!("{}", container_idx).into(), + format!("{}", key).into(), + format!("{:?}", value).into(), + ], + Action::List { + site, + container_idx, + key, + value, + } => vec![ + "list".into(), + format!("{}", site).into(), + format!("{}", container_idx).into(), + format!("{}", key).into(), + format!("{:?}", value).into(), + ], + Action::Text { + site, + container_idx, + pos, + value, + is_del, + } => vec![ + "text".into(), + format!("{}", site).into(), + format!("{}", container_idx).into(), + format!("{}", pos).into(), + format!("{}{}", if *is_del { "Delete " } else { "" }, value).into(), + ], + } + } -// fn headers() -> Vec> { -// vec![ -// "type".into(), -// "site".into(), -// "container".into(), -// "prop".into(), -// "value".into(), -// ] -// } -// } + fn headers() -> Vec> { + vec![ + "type".into(), + "site".into(), + "container".into(), + "prop".into(), + "value".into(), + ] + } +} -// trait Actionable { -// fn apply_action(&mut self, action: &Action); -// fn preprocess(&mut self, action: &mut Action); -// } +trait Actionable { + fn apply_action(&mut self, action: &Action); + fn preprocess(&mut self, action: &mut Action); +} -// impl Actor { -// fn add_new_container(&mut self, idx: ContainerIdx, type_: ContainerType) { -// match type_ { -// ContainerType::Text => self.text_containers.push(idx.try_into().unwrap()), -// ContainerType::Map => self.map_containers.push(idx.try_into().unwrap()), -// ContainerType::List => self.list_containers.push(idx.try_into().unwrap()), -// } -// } -// } +impl Actor { + fn add_new_container(&mut self, idx: ContainerIdx, type_: ContainerType) { + match type_ { + ContainerType::Text => self + .text_containers + .push(TextHandler::new(idx, Arc::downgrade(self.loro.app_state()))), + ContainerType::Map => self + .map_containers + .push(MapHandler::new(idx, Arc::downgrade(self.loro.app_state()))), + ContainerType::List => self + .list_containers + .push(ListHandler::new(idx, Arc::downgrade(self.loro.app_state()))), + } + } +} -// impl Actionable for Vec { -// fn preprocess(&mut self, action: &mut Action) { -// let max_users = self.len() as u8; -// match action { -// Action::Sync { from, to } => { -// *from %= max_users; -// *to %= max_users; -// if to == from { -// *to = (*to + 1) % max_users; -// } -// } -// Action::SyncAll => {} -// Action::Map { -// site, -// container_idx, -// .. -// } => { -// *site %= max_users; -// *container_idx %= self[*site as usize].map_containers.len().max(1) as u8; -// } -// Action::List { -// site, -// container_idx, -// key, -// value, -// } => { -// *site %= max_users; -// *container_idx %= self[*site as usize].list_containers.len().max(1) as u8; -// let txn = self[*site as usize].loro.txn().unwrap(); -// if let Some(list) = self[*site as usize] -// .list_containers -// .get(*container_idx as usize) -// { -// *key %= (list.len(&txn) as u8).max(1); -// if *value == FuzzValue::Null && list.is_empty(&txn) { -// // no value, cannot delete -// *value = FuzzValue::I32(1); -// } -// } else { -// if *value == FuzzValue::Null { -// *value = FuzzValue::I32(1); -// } -// *key = 0; -// } -// } -// Action::Text { -// site, -// container_idx, -// pos, -// value, -// is_del, -// } => { -// *site %= max_users; -// *container_idx %= self[*site as usize].text_containers.len().max(1) as u8; -// let txn = self[*site as usize].loro.txn().unwrap(); -// if let Some(text) = self[*site as usize] -// .text_containers -// .get(*container_idx as usize) -// { -// *pos %= (text.len(&txn) as u8).max(1); -// if *is_del { -// *value &= 0x1f; -// *value = (*value).min(text.len(&txn) as u16 - (*pos) as u16); -// } -// } else { -// *is_del = false; -// *pos = 0; -// } -// } -// } -// } +impl Actionable for Vec { + fn preprocess(&mut self, action: &mut Action) { + let max_users = self.len() as u8; + match action { + Action::Sync { from, to } => { + *from %= max_users; + *to %= max_users; + if to == from { + *to = (*to + 1) % max_users; + } + } + Action::SyncAll => {} + Action::Map { + site, + container_idx, + .. + } => { + *site %= max_users; + *container_idx %= self[*site as usize].map_containers.len().max(1) as u8; + } + Action::List { + site, + container_idx, + key, + value, + } => { + *site %= max_users; + *container_idx %= self[*site as usize].list_containers.len().max(1) as u8; + if let Some(list) = self[*site as usize] + .list_containers + .get(*container_idx as usize) + { + *key %= (list.len() as u8).max(1); + if *value == FuzzValue::Null && list.is_empty() { + // no value, cannot delete + *value = FuzzValue::I32(1); + } + } else { + if *value == FuzzValue::Null { + *value = FuzzValue::I32(1); + } + *key = 0; + } + } + Action::Text { + site, + container_idx, + pos, + value, + is_del, + } => { + *site %= max_users; + *container_idx %= self[*site as usize].text_containers.len().max(1) as u8; + if let Some(text) = self[*site as usize] + .text_containers + .get(*container_idx as usize) + { + *pos %= (text.len() as u8).max(1); + if *is_del { + *value &= 0x1f; + *value = (*value).min(text.len() as u16 - (*pos) as u16); + } + } else { + *is_del = false; + *pos = 0; + } + } + } + } -// fn apply_action(&mut self, action: &Action) { -// match action { -// Action::Sync { from, to } => { -// let (a, b) = array_mut_ref!(self, [*from as usize, *to as usize]); -// let mut visited = HashSet::new(); -// a.map_containers.iter().for_each(|x| { -// visited.insert(x.id()); -// }); -// a.list_containers.iter().for_each(|x| { -// visited.insert(x.id()); -// }); -// a.text_containers.iter().for_each(|x| { -// visited.insert(x.id()); -// }); + fn apply_action(&mut self, action: &Action) { + match action { + Action::Sync { from, to } => { + let (a, b) = array_mut_ref!(self, [*from as usize, *to as usize]); + let mut visited = HashSet::new(); + a.map_containers.iter().for_each(|x| { + visited.insert(x.id()); + }); + a.list_containers.iter().for_each(|x| { + visited.insert(x.id()); + }); + a.text_containers.iter().for_each(|x| { + visited.insert(x.id()); + }); -// a.loro.import(b.loro.export(a.loro.vv_cloned())); -// b.loro.import(a.loro.export(b.loro.vv_cloned())); + a.loro + .import(&b.loro.export_from(&a.loro.vv_cloned())) + .unwrap(); + b.loro + .import(&a.loro.export_from(&b.loro.vv_cloned())) + .unwrap(); -// b.map_containers.iter().for_each(|x| { -// let id = x.id(); -// if !visited.contains(&id) { -// visited.insert(id.clone()); -// a.map_containers.push(a.loro.get_map(id)) -// } -// }); -// b.list_containers.iter().for_each(|x| { -// let id = x.id(); -// if !visited.contains(&id) { -// visited.insert(id.clone()); -// a.list_containers.push(a.loro.get_list(id)) -// } -// }); -// b.text_containers.iter().for_each(|x| { -// let id = x.id(); -// if !visited.contains(&id) { -// visited.insert(id.clone()); -// a.text_containers.push(a.loro.get_text(id)) -// } -// }); + b.map_containers.iter().for_each(|x| { + let id = x.id(); + if !visited.contains(&id) { + visited.insert(id.clone()); + a.map_containers + .push(a.loro.txn().unwrap().get_map(id).unwrap()) + } + }); + b.list_containers.iter().for_each(|x| { + let id = x.id(); + if !visited.contains(&id) { + visited.insert(id.clone()); + a.list_containers + .push(a.loro.txn().unwrap().get_list(id).unwrap()) + } + }); + b.text_containers.iter().for_each(|x| { + let id = x.id(); + if !visited.contains(&id) { + visited.insert(id.clone()); + a.text_containers + .push(a.loro.txn().unwrap().get_text(id).unwrap()) + } + }); -// b.map_containers = a -// .map_containers -// .iter() -// .map(|x| b.loro.get_map(x.id())) -// .collect(); -// b.list_containers = a -// .list_containers -// .iter() -// .map(|x| b.loro.get_list(x.id())) -// .collect(); -// b.text_containers = a -// .text_containers -// .iter() -// .map(|x| b.loro.get_text(x.id())) -// .collect(); -// } -// Action::SyncAll => { -// let mut visited = HashSet::new(); -// let a = &mut self[0]; -// a.map_containers.iter().for_each(|x| { -// visited.insert(x.id()); -// }); -// a.list_containers.iter().for_each(|x| { -// visited.insert(x.id()); -// }); -// a.text_containers.iter().for_each(|x| { -// visited.insert(x.id()); -// }); + b.map_containers = a + .map_containers + .iter() + .map(|x| b.loro.get_map(x.id()).unwrap()) + .collect(); + b.list_containers = a + .list_containers + .iter() + .map(|x| b.loro.get_list(x.id()).unwrap()) + .collect(); + b.text_containers = a + .text_containers + .iter() + .map(|x| b.loro.get_text(x.id()).unwrap()) + .collect(); + } + Action::SyncAll => { + let mut visited = HashSet::new(); + let a = &mut self[0]; + a.map_containers.iter().for_each(|x| { + visited.insert(x.id()); + }); + a.list_containers.iter().for_each(|x| { + visited.insert(x.id()); + }); + a.text_containers.iter().for_each(|x| { + visited.insert(x.id()); + }); -// for i in 1..self.len() { -// let (a, b) = array_mut_ref!(self, [0, i]); -// a.loro.import(b.loro.export(a.loro.vv_cloned())); -// b.map_containers.iter().for_each(|x| { -// let id = x.id(); -// if !visited.contains(&id) { -// visited.insert(id.clone()); -// a.map_containers.push(a.loro.get_map(id)) -// } -// }); -// b.list_containers.iter().for_each(|x| { -// let id = x.id(); -// if !visited.contains(&id) { -// visited.insert(id.clone()); -// a.list_containers.push(a.loro.get_list(id)) -// } -// }); -// b.text_containers.iter().for_each(|x| { -// let id = x.id(); -// if !visited.contains(&id) { -// visited.insert(id.clone()); -// a.text_containers.push(a.loro.get_text(id)) -// } -// }); -// } + for i in 1..self.len() { + let (a, b) = array_mut_ref!(self, [0, i]); + a.loro + .import(&b.loro.export_from(&a.loro.vv_cloned())) + .unwrap(); + b.map_containers.iter().for_each(|x| { + let id = x.id(); + if !visited.contains(&id) { + visited.insert(id.clone()); + a.map_containers.push(a.loro.get_map(id).unwrap()) + } + }); + b.list_containers.iter().for_each(|x| { + let id = x.id(); + if !visited.contains(&id) { + visited.insert(id.clone()); + a.list_containers.push(a.loro.get_list(id).unwrap()) + } + }); + b.text_containers.iter().for_each(|x| { + let id = x.id(); + if !visited.contains(&id) { + visited.insert(id.clone()); + a.text_containers.push(a.loro.get_text(id).unwrap()) + } + }); + } -// for i in 1..self.len() { -// let (a, b) = array_mut_ref!(self, [0, i]); -// b.loro.import(a.loro.export(b.loro.vv_cloned())); -// b.map_containers = a -// .map_containers -// .iter() -// .map(|x| b.loro.get_map(x.id())) -// .collect(); -// b.list_containers = a -// .list_containers -// .iter() -// .map(|x| b.loro.get_list(x.id())) -// .collect(); -// b.text_containers = a -// .text_containers -// .iter() -// .map(|x| b.loro.get_text(x.id())) -// .collect(); -// } -// } -// Action::Map { -// site, -// container_idx, -// key, -// value, -// } => { -// let actor = &mut self[*site as usize]; -// let container = actor.map_containers.get_mut(*container_idx as usize); -// let container = if let Some(container) = container { -// container -// } else { -// let map = actor.loro.get_map("map"); -// actor.map_containers.push(map); -// &mut actor.map_containers[0] -// }; -// match value { -// FuzzValue::Null => { -// container.delete(&actor.loro, &key.to_string()).unwrap(); -// } -// FuzzValue::I32(i) => { -// container.insert(&actor.loro, &key.to_string(), *i).unwrap(); -// } -// FuzzValue::Container(c) => { -// let idx = container.insert(&actor.loro, &key.to_string(), *c).unwrap(); -// if let Some(idx) = idx { -// actor.add_new_container(idx, *c); -// } -// } -// }; -// } -// Action::List { -// site, -// container_idx, -// key, -// value, -// } => { -// let actor = &mut self[*site as usize]; -// let container = actor.list_containers.get_mut(*container_idx as usize); -// let container = if container.is_none() { -// let list = actor.loro.get_list("list"); -// actor.list_containers.push(list); -// &mut actor.list_containers[0] -// } else { -// #[allow(clippy::unnecessary_unwrap)] -// container.unwrap() -// }; -// match value { -// FuzzValue::Null => { -// container.delete(&actor.loro, *key as usize, 1).unwrap(); -// } -// FuzzValue::I32(i) => { -// container.insert(&actor.loro, *key as usize, *i).unwrap(); -// } -// FuzzValue::Container(c) => { -// let idx = container.insert(&actor.loro, *key as usize, *c).unwrap(); -// if let Some(container) = idx { -// actor.add_new_container(container, *c); -// } -// } -// }; -// } -// Action::Text { -// site, -// container_idx, -// pos, -// value, -// is_del, -// } => { -// let actor = &mut self[*site as usize]; -// let container = actor.text_containers.get_mut(*container_idx as usize); -// let container = if let Some(container) = container { -// container -// } else { -// let text = actor.loro.get_text("text"); -// actor.text_containers.push(text); -// &mut actor.text_containers[0] -// }; -// let txn = actor.loro.transact(); -// if *is_del { -// container -// .delete(&txn, *pos as usize, *value as usize) -// .unwrap(); -// } else { -// container -// .insert(&txn, *pos as usize, &(format!("[{}]", value))) -// .unwrap(); -// } -// drop(txn); -// } -// } -// } -// } + for i in 1..self.len() { + let (a, b) = array_mut_ref!(self, [0, i]); + b.loro + .import(&a.loro.export_from(&b.loro.vv_cloned())) + .unwrap(); + b.map_containers = a + .map_containers + .iter() + .map(|x| b.loro.get_map(x.id()).unwrap()) + .collect(); + b.list_containers = a + .list_containers + .iter() + .map(|x| b.loro.get_list(x.id()).unwrap()) + .collect(); + b.text_containers = a + .text_containers + .iter() + .map(|x| b.loro.get_text(x.id()).unwrap()) + .collect(); + } + } + Action::Map { + site, + container_idx, + key, + value, + } => { + let actor = &mut self[*site as usize]; + let container = actor.map_containers.get_mut(*container_idx as usize); + let container = if let Some(container) = container { + container + } else { + let map = actor.loro.get_map("map").unwrap(); + actor.map_containers.push(map); + &mut actor.map_containers[0] + }; + let mut txn = actor.loro.txn().unwrap(); + match value { + FuzzValue::Null => { + container.delete(&mut txn, &key.to_string()); + } + FuzzValue::I32(i) => { + container.insert(&mut txn, &key.to_string(), LoroValue::from(*i)); + } + FuzzValue::Container(c) => { + let idx = container.insert_container(&mut txn, &key.to_string(), *c); + actor.add_new_container(idx, *c); + } + }; + } + Action::List { + site, + container_idx, + key, + value, + } => { + let actor = &mut self[*site as usize]; + let container = actor.list_containers.get_mut(*container_idx as usize); + let container = if container.is_none() { + let list = actor.loro.get_list("list").unwrap(); + actor.list_containers.push(list); + &mut actor.list_containers[0] + } else { + #[allow(clippy::unnecessary_unwrap)] + container.unwrap() + }; + let mut txn = actor.loro.txn().unwrap(); + match value { + FuzzValue::Null => { + container.delete(&mut txn, *key as usize, 1); + } + FuzzValue::I32(i) => { + container.insert(&mut txn, *key as usize, LoroValue::from(*i)); + } + FuzzValue::Container(c) => { + let idx = container.insert_container(&mut txn, *key as usize, *c); + actor.add_new_container(idx, *c); + } + }; + } + Action::Text { + site, + container_idx, + pos, + value, + is_del, + } => { + let actor = &mut self[*site as usize]; + let container = actor.text_containers.get_mut(*container_idx as usize); + let container = if let Some(container) = container { + container + } else { + let text = actor.loro.get_text("text").unwrap(); + actor.text_containers.push(text); + &mut actor.text_containers[0] + }; + let mut txn = actor.loro.txn().unwrap(); + if *is_del { + container.delete(&mut txn, *pos as usize, *value as usize); + } else { + container.insert(&mut txn, *pos as usize, &(format!("[{}]", value))); + } + drop(txn); + } + } + } +} -// fn assert_value_eq(a: &LoroValue, b: &LoroValue) { -// match (a, b) { -// (LoroValue::Map(a), LoroValue::Map(b)) => { -// for (k, v) in a.iter() { -// let is_empty = match v { -// LoroValue::String(s) => s.is_empty(), -// LoroValue::List(l) => l.is_empty(), -// LoroValue::Map(m) => m.is_empty(), -// _ => false, -// }; -// if is_empty { -// continue; -// } -// assert_value_eq(v, b.get(k).unwrap()); -// } +fn assert_value_eq(a: &LoroValue, b: &LoroValue) { + match (a, b) { + (LoroValue::Map(a), LoroValue::Map(b)) => { + for (k, v) in a.iter() { + let is_empty = match v { + LoroValue::String(s) => s.is_empty(), + LoroValue::List(l) => l.is_empty(), + LoroValue::Map(m) => m.is_empty(), + _ => false, + }; + if is_empty { + continue; + } + assert_value_eq(v, b.get(k).unwrap()); + } -// for (k, v) in b.iter() { -// let is_empty = match v { -// LoroValue::String(s) => s.is_empty(), -// LoroValue::List(l) => l.is_empty(), -// LoroValue::Map(m) => m.is_empty(), -// _ => false, -// }; -// if is_empty { -// continue; -// } + for (k, v) in b.iter() { + let is_empty = match v { + LoroValue::String(s) => s.is_empty(), + LoroValue::List(l) => l.is_empty(), + LoroValue::Map(m) => m.is_empty(), + _ => false, + }; + if is_empty { + continue; + } -// assert_value_eq(v, a.get(k).unwrap()); -// } -// } -// (a, b) => assert_eq!(a, b), -// } -// } + assert_value_eq(v, a.get(k).unwrap()); + } + } + (a, b) => assert_eq!(a, b), + } +} -// fn check_eq(a_actor: &mut Actor, b_actor: &mut Actor) { -// let a_doc = &mut a_actor.loro; -// let b_doc = &mut b_actor.loro; -// let a_result = a_doc.to_json(); -// debug_log::debug_log!("{}", a_result.to_json_pretty()); -// assert_eq!(&a_result, &b_doc.to_json()); -// assert_value_eq(&a_result, &a_actor.value_tracker.lock().unwrap()); +fn check_eq(a_actor: &mut Actor, b_actor: &mut Actor) { + let a_doc = &mut a_actor.loro; + let b_doc = &mut b_actor.loro; + let a_result = a_doc.get_state_deep_value(); + debug_log::debug_log!("{}", a_result.to_json_pretty()); + assert_eq!(&a_result, &b_doc.get_state_deep_value()); + // assert_value_eq(&a_result, &a_actor.value_tracker.lock().unwrap()); -// let a = a_doc.get_text("text"); -// let value_a = a.get_value(); -// assert_eq!( -// &**value_a.as_string().unwrap(), -// &*a_actor.text_tracker.lock().unwrap(), -// ); + // let a = a_doc.get_text("text").unwrap(); + // let value_a = a.get_value(); + // assert_eq!( + // &**value_a.as_string().unwrap(), + // &*a_actor.text_tracker.lock().unwrap(), + // ); -// let a = a_doc.get_map("map"); -// let value_a = a.get_value(); -// assert_eq!( -// &**value_a.as_map().unwrap(), -// &*a_actor.map_tracker.lock().unwrap() -// ); + // let a = a_doc.get_map("map").unwrap(); + // let value_a = a.get_value(); + // assert_eq!( + // &**value_a.as_map().unwrap(), + // &*a_actor.map_tracker.lock().unwrap() + // ); -// let a = a_doc.get_list("list"); -// let value_a = a.get_value(); -// assert_eq!( -// &**value_a.as_list().unwrap(), -// &*a_actor.list_tracker.lock().unwrap(), -// ); + // let a = a_doc.get_list("list"); + // let value_a = a.get_value(); + // assert_eq!( + // &**value_a.as_list().unwrap(), + // &*a_actor.list_tracker.lock().unwrap(), + // ); -// use itertools::Itertools; -// for key in a_doc -// .log_store -// .try_read() -// .unwrap() -// .changes() -// .keys() -// .sorted() -// { -// let as_ = a_doc.log_store.try_read().unwrap(); -// let ca = as_.changes().get(key).unwrap(); -// let bs = b_doc.log_store.try_read().unwrap(); -// let cb = bs.changes().get(key).unwrap(); -// for (la, lb) in ca.iter().zip(cb.iter()) { -// assert_eq!(la.lamport, lb.lamport); -// assert_eq!(la.id, lb.id); -// assert!(!la.deps.iter().any(|u| !lb.deps.contains(u))) -// } -// } -// } + // use itertools::Itertools; + // for key in a_doc + // .log_store + // .try_read() + // .unwrap() + // .changes() + // .keys() + // .sorted() + // { + // let as_ = a_doc.log_store.try_read().unwrap(); + // let ca = as_.changes().get(key).unwrap(); + // let bs = b_doc.log_store.try_read().unwrap(); + // let cb = bs.changes().get(key).unwrap(); + // for (la, lb) in ca.iter().zip(cb.iter()) { + // assert_eq!(la.lamport, lb.lamport); + // assert_eq!(la.id, lb.id); + // assert!(!la.deps.iter().any(|u| !lb.deps.contains(u))) + // } + // } +} -// fn check_synced(sites: &mut [Actor]) { -// for i in 0..sites.len() - 1 { -// for j in i + 1..sites.len() { -// debug_log::group!("checking {} with {}", i, j); -// let (a, b) = array_mut_ref!(sites, [i, j]); -// let a_doc = &mut a.loro; -// let b_doc = &mut b.loro; -// if i % 2 == 0 { -// a_doc -// .decode(&b_doc.encode_with_cfg(EncodeMode::RleUpdates(a_doc.vv_cloned()))) -// .unwrap(); -// b_doc -// .decode(&a_doc.encode_with_cfg(EncodeMode::Updates(b_doc.vv_cloned()))) -// .unwrap(); -// } else { -// a_doc.decode(&b_doc.encode_all()).unwrap(); -// b_doc.decode(&a_doc.encode_all()).unwrap(); -// } +fn check_synced(sites: &mut [Actor]) { + for i in 0..sites.len() - 1 { + for j in i + 1..sites.len() { + debug_log::group!("checking {} with {}", i, j); + let (a, b) = array_mut_ref!(sites, [i, j]); + let a_doc = &mut a.loro; + let b_doc = &mut b.loro; + if i % 2 == 0 { + a_doc + .import(&b_doc.export_from(&a_doc.vv_cloned())) + .unwrap(); + b_doc + .import(&a_doc.export_from(&b_doc.vv_cloned())) + .unwrap(); + } else { + a_doc.import(&b_doc.export_snapshot()).unwrap(); + b_doc.import(&a_doc.export_snapshot()).unwrap(); + } -// check_eq(a, b); -// debug_log::group_end!(); -// } -// } -// } + check_eq(a, b); + debug_log::group_end!(); + } + } +} -// pub fn normalize(site_num: u8, actions: &mut [Action]) -> Vec { -// let mut sites = Vec::new(); -// for i in 0..site_num { -// sites.push(Actor::new(i as u64)); -// } +pub fn normalize(site_num: u8, actions: &mut [Action]) -> Vec { + let mut sites = Vec::new(); + for i in 0..site_num { + sites.push(Actor::new(i as u64)); + } -// let mut applied = Vec::new(); -// for action in actions.iter_mut() { -// sites.preprocess(action); -// applied.push(action.clone()); -// let sites_ptr: usize = &mut sites as *mut _ as usize; -// #[allow(clippy::blocks_in_if_conditions)] -// if std::panic::catch_unwind(|| { -// // SAFETY: Test -// let sites = unsafe { &mut *(sites_ptr as *mut Vec<_>) }; -// sites.apply_action(&action.clone()); -// }) -// .is_err() -// { -// break; -// } -// } + let mut applied = Vec::new(); + for action in actions.iter_mut() { + sites.preprocess(action); + applied.push(action.clone()); + let sites_ptr: usize = &mut sites as *mut _ as usize; + #[allow(clippy::blocks_in_if_conditions)] + if std::panic::catch_unwind(|| { + // SAFETY: Test + let sites = unsafe { &mut *(sites_ptr as *mut Vec<_>) }; + sites.apply_action(&action.clone()); + }) + .is_err() + { + break; + } + } -// println!("{}", applied.clone().table()); -// applied -// } + println!("{}", applied.clone().table()); + applied +} -// pub fn test_multi_sites(site_num: u8, actions: &mut [Action]) { -// let mut sites = Vec::new(); -// for i in 0..site_num { -// sites.push(Actor::new(i as u64)); -// } +pub fn test_multi_sites(site_num: u8, actions: &mut [Action]) { + let mut sites = Vec::new(); + for i in 0..site_num { + sites.push(Actor::new(i as u64)); + } -// let mut applied = Vec::new(); -// for action in actions.iter_mut() { -// sites.preprocess(action); -// applied.push(action.clone()); -// debug_log::debug_log!("\n{}", (&applied).table()); -// sites.apply_action(action); -// } + let mut applied = Vec::new(); + for action in actions.iter_mut() { + sites.preprocess(action); + applied.push(action.clone()); + debug_log::debug_log!("\n{}", (&applied).table()); + sites.apply_action(action); + } -// debug_log::group!("check synced"); -// check_synced(&mut sites); -// debug_log::group_end!(); -// } + debug_log::group!("check synced"); + check_synced(&mut sites); + debug_log::group_end!(); +} -// pub fn test_multi_sites_refactored(site_num: u8, actions: &mut [Action]) { -// let mut sites = Vec::new(); -// for i in 0..site_num { -// sites.push(Actor::new(i as u64)); -// } +pub fn test_multi_sites_refactored(site_num: u8, actions: &mut [Action]) { + let mut sites = Vec::new(); + for i in 0..site_num { + sites.push(Actor::new(i as u64)); + } -// let mut applied = Vec::new(); -// for action in actions.iter_mut() { -// sites.preprocess(action); -// applied.push(action.clone()); -// debug_log::debug_log!("\n{}", (&applied).table()); -// sites.apply_action(action); -// } + let mut applied = Vec::new(); + for action in actions.iter_mut() { + sites.preprocess(action); + applied.push(action.clone()); + debug_log::debug_log!("\n{}", (&applied).table()); + sites.apply_action(action); + } -// debug_log::group!("check synced"); -// check_synced(&mut sites); -// debug_log::group_end!(); -// } + debug_log::group!("check synced"); + check_synced(&mut sites); + debug_log::group_end!(); +} -// #[cfg(test)] -// mod failed_tests { -// use crate::fuzz::minify_error; -// use crate::tests::PROPTEST_FACTOR_10; +#[cfg(test)] +mod failed_tests { + use crate::fuzz::minify_error; + use crate::tests::PROPTEST_FACTOR_10; -// use super::normalize; -// use super::test_multi_sites; -// use super::Action; -// use super::Action::*; -// use super::FuzzValue::*; -// use arbtest::arbitrary::{self, Unstructured}; + use super::normalize; + use super::test_multi_sites; + use super::Action; + use super::Action::*; + use super::FuzzValue::*; + use arbtest::arbitrary::{self, Unstructured}; -// fn prop(u: &mut Unstructured<'_>, site_num: u8) -> arbitrary::Result<()> { -// let xs = u.arbitrary::>()?; -// if let Err(e) = std::panic::catch_unwind(|| { -// test_multi_sites(site_num, &mut xs.clone()); -// }) { -// dbg!(xs); -// println!("{:?}", e); -// panic!() -// } else { -// Ok(()) -// } -// } + fn prop(u: &mut Unstructured<'_>, site_num: u8) -> arbitrary::Result<()> { + let xs = u.arbitrary::>()?; + if let Err(e) = std::panic::catch_unwind(|| { + test_multi_sites(site_num, &mut xs.clone()); + }) { + dbg!(xs); + println!("{:?}", e); + panic!() + } else { + Ok(()) + } + } -// #[test] -// fn notify_causal_order_check() { -// test_multi_sites( -// 5, -// &mut [ -// Text { -// site: 1, -// container_idx: 0, -// pos: 0, -// value: 38912, -// is_del: false, -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 138, -// value: Container(C::List), -// }, -// List { -// site: 4, -// container_idx: 0, -// key: 0, -// value: I32(1), -// }, -// List { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// SyncAll, -// ], -// ) -// } + #[test] + fn notify_causal_order_check() { + test_multi_sites( + 5, + &mut [ + Text { + site: 1, + container_idx: 0, + pos: 0, + value: 38912, + is_del: false, + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + Map { + site: 0, + container_idx: 0, + key: 138, + value: Container(C::List), + }, + List { + site: 4, + container_idx: 0, + key: 0, + value: I32(1), + }, + List { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + SyncAll, + ], + ) + } -// #[test] -// fn test() { -// arbtest::builder() -// .budget_ms((100 * PROPTEST_FACTOR_10 * PROPTEST_FACTOR_10) as u64) -// .run(|u| prop(u, 2)) -// } + #[test] + fn test() { + arbtest::builder() + .budget_ms((100 * PROPTEST_FACTOR_10 * PROPTEST_FACTOR_10) as u64) + .run(|u| prop(u, 2)) + } -// #[test] -// fn test_3sites() { -// arbtest::builder() -// .budget_ms((100 * PROPTEST_FACTOR_10 * PROPTEST_FACTOR_10) as u64) -// .run(|u| prop(u, 3)) -// } -// #[test] -// fn deleted_container() { -// test_multi_sites( -// 5, -// &mut [ -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// SyncAll, -// List { -// site: 4, -// container_idx: 0, -// key: 0, -// value: I32(-1734829928), -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// ], -// ) -// } + #[test] + fn test_3sites() { + arbtest::builder() + .budget_ms((100 * PROPTEST_FACTOR_10 * PROPTEST_FACTOR_10) as u64) + .run(|u| prop(u, 3)) + } + #[test] + fn deleted_container() { + test_multi_sites( + 5, + &mut [ + Map { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + SyncAll, + List { + site: 4, + container_idx: 0, + key: 0, + value: I32(-1734829928), + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + ], + ) + } -// #[test] -// fn should_notify() { -// test_multi_sites( -// 5, -// &mut [ -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::Text), -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// SyncAll, -// Text { -// site: 4, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// ], -// ); -// } + #[test] + fn should_notify() { + test_multi_sites( + 5, + &mut [ + Map { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::Text), + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + SyncAll, + Text { + site: 4, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + ], + ); + } -// #[test] -// fn hierarchy() { -// test_multi_sites( -// 5, -// &mut [ -// Map { -// site: 0, -// container_idx: 0, -// key: 255, -// value: Container(C::Text), -// }, -// Map { -// site: 3, -// container_idx: 0, -// key: 255, -// value: Container(C::Text), -// }, -// SyncAll, -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 4, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// ], -// ) -// } + #[test] + fn hierarchy() { + test_multi_sites( + 5, + &mut [ + Map { + site: 0, + container_idx: 0, + key: 255, + value: Container(C::Text), + }, + Map { + site: 3, + container_idx: 0, + key: 255, + value: Container(C::Text), + }, + SyncAll, + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + Text { + site: 4, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + ], + ) + } -// #[test] -// fn apply_directly() { -// test_multi_sites( -// 5, -// &mut [ -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::Text), -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// SyncAll, -// Text { -// site: 4, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// SyncAll, -// Text { -// site: 2, -// container_idx: 0, -// pos: 5, -// value: 39064, -// is_del: false, -// }, -// ], -// ) -// } + #[test] + fn apply_directly() { + test_multi_sites( + 5, + &mut [ + Map { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::Text), + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + SyncAll, + Text { + site: 4, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + SyncAll, + Text { + site: 2, + container_idx: 0, + pos: 5, + value: 39064, + is_del: false, + }, + ], + ) + } -// #[test] -// fn find_path_for_deleted_container() { -// test_multi_sites( -// 5, -// &mut [ -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::Map), -// }, -// SyncAll, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// Map { -// site: 1, -// container_idx: 1, -// key: 255, -// value: Container(C::List), -// }, -// Map { -// site: 4, -// container_idx: 1, -// key: 9, -// value: Null, -// }, -// ], -// ) -// } + #[test] + fn find_path_for_deleted_container() { + test_multi_sites( + 5, + &mut [ + Map { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::Map), + }, + SyncAll, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + Map { + site: 1, + container_idx: 1, + key: 255, + value: Container(C::List), + }, + Map { + site: 4, + container_idx: 1, + key: 9, + value: Null, + }, + ], + ) + } -// #[test] -// fn list_unknown() { -// test_multi_sites( -// 5, -// &mut [ -// List { -// site: 139, -// container_idx: 133, -// key: 32, -// value: Container(C::Text), -// }, -// List { -// site: 166, -// container_idx: 127, -// key: 207, -// value: Null, -// }, -// Text { -// site: 203, -// container_idx: 105, -// pos: 87, -// value: 52649, -// is_del: false, -// }, -// List { -// site: 122, -// container_idx: 137, -// key: 41, -// value: Container(C::List), -// }, -// ], -// ) -// } + #[test] + fn list_unknown() { + test_multi_sites( + 5, + &mut [ + List { + site: 139, + container_idx: 133, + key: 32, + value: Container(C::Text), + }, + List { + site: 166, + container_idx: 127, + key: 207, + value: Null, + }, + Text { + site: 203, + container_idx: 105, + pos: 87, + value: 52649, + is_del: false, + }, + List { + site: 122, + container_idx: 137, + key: 41, + value: Container(C::List), + }, + ], + ) + } -// #[test] -// fn path_issue() { -// test_multi_sites( -// 5, -// &mut [ -// List { -// site: 1, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// List { -// site: 1, -// container_idx: 1, -// key: 0, -// value: Container(C::List), -// }, -// List { -// site: 1, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// ], -// ) -// } + #[test] + fn path_issue() { + test_multi_sites( + 5, + &mut [ + List { + site: 1, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + List { + site: 1, + container_idx: 1, + key: 0, + value: Container(C::List), + }, + List { + site: 1, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + ], + ) + } -// #[test] -// fn cannot_skip_ops_from_deleted_container_due_to_this_case() { -// test_multi_sites( -// 5, -// &mut [ -// List { -// site: 1, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 2, -// value: Container(C::List), -// }, -// SyncAll, -// Map { -// site: 0, -// container_idx: 0, -// key: 255, -// value: Container(C::List), -// }, -// SyncAll, -// Map { -// site: 0, -// container_idx: 0, -// key: 255, -// value: Container(C::List), -// }, -// List { -// site: 3, -// container_idx: 3, -// key: 0, -// value: Container(C::List), -// }, -// List { -// site: 1, -// container_idx: 3, -// key: 0, -// value: Container(C::List), -// }, -// SyncAll, -// List { -// site: 0, -// container_idx: 3, -// key: 0, -// value: Container(C::Map), -// }, -// List { -// site: 1, -// container_idx: 3, -// key: 1, -// value: Container(C::Map), -// }, -// ], -// ) -// } + #[test] + fn cannot_skip_ops_from_deleted_container_due_to_this_case() { + test_multi_sites( + 5, + &mut [ + List { + site: 1, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + Map { + site: 0, + container_idx: 0, + key: 2, + value: Container(C::List), + }, + SyncAll, + Map { + site: 0, + container_idx: 0, + key: 255, + value: Container(C::List), + }, + SyncAll, + Map { + site: 0, + container_idx: 0, + key: 255, + value: Container(C::List), + }, + List { + site: 3, + container_idx: 3, + key: 0, + value: Container(C::List), + }, + List { + site: 1, + container_idx: 3, + key: 0, + value: Container(C::List), + }, + SyncAll, + List { + site: 0, + container_idx: 3, + key: 0, + value: Container(C::Map), + }, + List { + site: 1, + container_idx: 3, + key: 1, + value: Container(C::Map), + }, + ], + ) + } -// #[test] -// fn map_apply() { -// test_multi_sites( -// 5, -// &mut [ -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// List { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::Map), -// }, -// Map { -// site: 0, -// container_idx: 1, -// key: 255, -// value: Container(C::Map), -// }, -// ], -// ) -// } + #[test] + fn map_apply() { + test_multi_sites( + 5, + &mut [ + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + List { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::Map), + }, + Map { + site: 0, + container_idx: 1, + key: 255, + value: Container(C::Map), + }, + ], + ) + } -// #[test] -// fn maybe_because_of_hierarchy() { -// test_multi_sites( -// 5, -// &mut [ -// List { -// site: 1, -// container_idx: 0, -// key: 0, -// value: Container(C::Text), -// }, -// List { -// site: 1, -// container_idx: 0, -// key: 0, -// value: Container(C::Text), -// }, -// Sync { from: 1, to: 2 }, -// List { -// site: 2, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// Sync { from: 1, to: 2 }, -// Text { -// site: 1, -// container_idx: 2, -// pos: 0, -// value: 45232, -// is_del: false, -// }, -// ], -// ) -// } + #[test] + fn maybe_because_of_hierarchy() { + test_multi_sites( + 5, + &mut [ + List { + site: 1, + container_idx: 0, + key: 0, + value: Container(C::Text), + }, + List { + site: 1, + container_idx: 0, + key: 0, + value: Container(C::Text), + }, + Sync { from: 1, to: 2 }, + List { + site: 2, + container_idx: 0, + key: 0, + value: Null, + }, + Sync { from: 1, to: 2 }, + Text { + site: 1, + container_idx: 2, + pos: 0, + value: 45232, + is_del: false, + }, + ], + ) + } -// #[test] -// fn checkout_error() { -// test_multi_sites( -// 2, -// &mut [ -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Null, -// }, -// List { -// site: 1, -// container_idx: 0, -// key: 0, -// value: I32(1), -// }, -// List { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// ], -// ) -// } + #[test] + fn checkout_error() { + test_multi_sites( + 2, + &mut [ + Map { + site: 0, + container_idx: 0, + key: 0, + value: Null, + }, + List { + site: 1, + container_idx: 0, + key: 0, + value: I32(1), + }, + List { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + ], + ) + } -// #[test] -// fn unknown() { -// test_multi_sites( -// 5, -// &mut [ -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 5, -// value: 152, -// is_del: false, -// }, -// Sync { from: 2, to: 3 }, -// Text { -// site: 3, -// container_idx: 0, -// pos: 10, -// value: 2, -// is_del: true, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// Sync { from: 2, to: 3 }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 16, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 8, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 28, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 41, -// value: 45232, -// is_del: false, -// }, -// Sync { from: 1, to: 2 }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 48, -// value: 39064, -// is_del: false, -// }, -// List { -// site: 1, -// container_idx: 0, -// key: 0, -// value: I32(-1734829928), -// }, -// ], -// ) -// } + #[test] + fn unknown() { + test_multi_sites( + 5, + &mut [ + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + Text { + site: 2, + container_idx: 0, + pos: 5, + value: 152, + is_del: false, + }, + Sync { from: 2, to: 3 }, + Text { + site: 3, + container_idx: 0, + pos: 10, + value: 2, + is_del: true, + }, + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + Sync { from: 2, to: 3 }, + Text { + site: 2, + container_idx: 0, + pos: 16, + value: 39064, + is_del: false, + }, + Text { + site: 2, + container_idx: 0, + pos: 8, + value: 39064, + is_del: false, + }, + Text { + site: 2, + container_idx: 0, + pos: 28, + value: 39064, + is_del: false, + }, + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + Text { + site: 2, + container_idx: 0, + pos: 41, + value: 45232, + is_del: false, + }, + Sync { from: 1, to: 2 }, + Text { + site: 2, + container_idx: 0, + pos: 48, + value: 39064, + is_del: false, + }, + List { + site: 1, + container_idx: 0, + key: 0, + value: I32(-1734829928), + }, + ], + ) + } -// #[test] -// fn list_slice_err() { -// test_multi_sites( -// 5, -// &mut [ -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::List), -// }, -// Map { -// site: 0, -// container_idx: 0, -// key: 0, -// value: Container(C::Map), -// }, -// SyncAll, -// Map { -// site: 1, -// container_idx: 1, -// key: 37, -// value: Null, -// }, -// ], -// ) -// } + #[test] + fn list_slice_err() { + test_multi_sites( + 5, + &mut [ + Map { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::List), + }, + Map { + site: 0, + container_idx: 0, + key: 0, + value: Container(C::Map), + }, + SyncAll, + Map { + site: 1, + container_idx: 1, + key: 37, + value: Null, + }, + ], + ) + } -// #[test] -// fn utf16_err() { -// test_multi_sites( -// 5, -// &mut [ -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// Text { -// site: 2, -// container_idx: 0, -// pos: 1, -// value: 2, -// is_del: true, -// }, -// ], -// ) -// } + #[test] + fn utf16_err() { + test_multi_sites( + 5, + &mut [ + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + Text { + site: 2, + container_idx: 0, + pos: 1, + value: 2, + is_del: true, + }, + ], + ) + } -// #[test] -// fn merge_err() { -// test_multi_sites( -// 5, -// &mut [ -// Text { -// site: 2, -// container_idx: 0, -// pos: 0, -// value: 39064, -// is_del: false, -// }, -// SyncAll, -// Text { -// site: 1, -// container_idx: 0, -// pos: 5, -// value: 2, -// is_del: true, -// }, -// ], -// ) -// } + #[test] + fn merge_err() { + test_multi_sites( + 5, + &mut [ + Text { + site: 2, + container_idx: 0, + pos: 0, + value: 39064, + is_del: false, + }, + SyncAll, + Text { + site: 1, + container_idx: 0, + pos: 5, + value: 2, + is_del: true, + }, + ], + ) + } -// use super::ContainerType as C; -// #[test] -// fn to_minify() { -// minify_error(5, vec![], test_multi_sites, normalize) -// } + use super::ContainerType as C; + #[test] + fn to_minify() { + minify_error(5, vec![], test_multi_sites, normalize) + } -// #[ctor::ctor] -// fn init_color_backtrace() { -// color_backtrace::install(); -// } -// } + #[ctor::ctor] + fn init_color_backtrace() { + color_backtrace::install(); + } +} diff --git a/crates/loro-internal/src/refactor/arena.rs b/crates/loro-internal/src/refactor/arena.rs index 00bf28b0..f03b2d96 100644 --- a/crates/loro-internal/src/refactor/arena.rs +++ b/crates/loro-internal/src/refactor/arena.rs @@ -33,6 +33,7 @@ pub struct SharedArena { text: Arc>, text_utf16_len: Arc, values: Arc>>, + root_c_idx: Arc>>, } 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 { + self.root_c_idx.lock().unwrap().clone() + } } diff --git a/crates/loro-internal/src/refactor/handler.rs b/crates/loro-internal/src/refactor/handler.rs index bced241c..a50c2ecd 100644 --- a/crates/loro-internal/src/refactor/handler.rs +++ b/crates/loro-internal/src/refactor/handler.rs @@ -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, diff --git a/crates/loro-internal/src/refactor/loro.rs b/crates/loro-internal/src/refactor/loro.rs index e2ffea0e..00168001 100644 --- a/crates/loro-internal/src/refactor/loro.rs +++ b/crates/loro-internal/src/refactor/loro.rs @@ -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> { &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>(&self, id: I) -> Option { + 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>(&self, id: I) -> Option { + 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>(&self, id: I) -> Option { + let idx = self.get_container_idx(id); + idx.map(|x| MapHandler::new(x, Arc::downgrade(&self.state))) + } + + fn get_container_idx>(&self, id: I) -> Option { + 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 { diff --git a/crates/loro-internal/src/refactor/snapshot_encode.rs b/crates/loro-internal/src/refactor/snapshot_encode.rs index 0378278c..8e991f34 100644 --- a/crates/loro-internal/src/refactor/snapshot_encode.rs +++ b/crates/loro-internal/src/refactor/snapshot_encode.rs @@ -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)?; diff --git a/crates/loro-internal/src/refactor/state.rs b/crates/loro-internal/src/refactor/state.rs index 26ed6a00..bdbc1ce7 100644 --- a/crates/loro-internal/src/refactor/state.rs +++ b/crates/loro-internal/src/refactor/state.rs @@ -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 { diff --git a/crates/loro-internal/src/refactor/txn.rs b/crates/loro-internal/src/refactor/txn.rs index d4183d7c..9ecdaecf 100644 --- a/crates/loro-internal/src/refactor/txn.rs +++ b/crates/loro-internal/src/refactor/txn.rs @@ -30,7 +30,7 @@ pub struct Transaction { oplog: Arc>, 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 {