loro/crates/loro-internal/tests/test.rs

413 lines
13 KiB
Rust
Raw Normal View History

use std::sync::{Arc, Mutex};
feat: movable tree support (#120) * feat: tree state * feat: tree value * feat: tree handler * fix: tree diff * test: fuzz tree * feat: tree snapshot * fix: tree default value * fix: test new node * fix: tree diff * fix: tree unresolved value * fix: tree fuzz * fix: tree fuzz move * fix: sort by tree id * fix: tree diff sorted by lamport * fix: sort roots before tree converted to string * fix: rebase main * fix: tree fuzz * fix: delete undo * fix: tree to json children sorted * fix: diff calculate * fix: diff cycle move * fix: tree old parent cache * feat: cache * fix: local op add tree cache * fix: don't add same tree move to cache * fix: need update cache * feat: new cache * bench: add checkout bench * chore: clean * fix: apply node uncheck * perf: lamport bound * fix: calc old parent * feat: tree wasm * fix: change tree diff * fix: tree diff retreat * fix: tree diff should not apply when add node * feat: new tree loro value * chore: typo * fix: tree deep value * fix: snapshot tree index -1 * fix: decode tree snapshot use state * fix: release state lock when emit event * fix: tree node meta container * fix: need set map container when covert to local tree op * fix: tree value add deleted * fix: more then one op in a change * fix: tree fuzz deleted equal * fix: tree calc min lamport * feat: tree encoding v2 * doc: movable tree * fix: test tree meta * test: remove import bytes check * refactor: diff of text and map * refactor: del span * perf: tree state use deleted cache * fix: some details * fix: loro js tree create * feat: add un exist tree node * bench: tree depth * fix: check out should emit event * refactor: event * fix: fuzz err * fix: pass all tests * fix: fuzz err * fix: list child cache err * chore: rm debug code * fix: encode enhanced err * fix: encode enchanced * fix: fix several richtext issue * fix: richtext anchor err * chore: rm debug code * fix: richtext fuzz err * feat: speedup text snapshot decode * perf: optimize snapshot encoding * perf: speed up decode & insert * fix: fugue span merge err * perf: speedup delete & id cursor map * fix: fugue merge err * chore: update utils * fix: fix merge * fix: return err apply op * fix: fix merge * fix: get map container as tree meta
2023-10-30 03:13:52 +00:00
use loro_common::{ContainerID, ContainerType, LoroValue, ID};
use loro_internal::{version::Frontiers, ApplyDiff, LoroDoc, ToJson};
use serde_json::json;
#[test]
fn import_after_init_handlers() {
let a = LoroDoc::new_auto_commit();
a.subscribe(
&ContainerID::new_root("text", ContainerType::Text),
Arc::new(|event| {
assert!(matches!(
event.container.diff,
loro_internal::event::Diff::Text(_)
))
}),
);
a.subscribe(
&ContainerID::new_root("map", ContainerType::Map),
Arc::new(|event| {
assert!(matches!(
event.container.diff,
loro_internal::event::Diff::NewMap(_)
))
}),
);
a.subscribe(
&ContainerID::new_root("list", ContainerType::List),
Arc::new(|event| {
assert!(matches!(
event.container.diff,
loro_internal::event::Diff::List(_)
))
}),
);
let b = LoroDoc::new_auto_commit();
b.get_list("list").insert_(0, "list".into()).unwrap();
b.get_list("list_a").insert_(0, "list_a".into()).unwrap();
b.get_text("text").insert_(0, "text").unwrap();
b.get_map("map").insert_("m", "map".into()).unwrap();
a.import(&b.export_snapshot()).unwrap();
a.commit();
}
#[test]
fn test_pending() {
let a = LoroDoc::new_auto_commit();
a.get_text("text").insert_(0, "0").unwrap();
let b = LoroDoc::new_auto_commit();
b.import(&a.export_from(&Default::default())).unwrap();
b.get_text("text").insert_(0, "1").unwrap();
let c = LoroDoc::new_auto_commit();
c.import(&b.export_from(&Default::default())).unwrap();
c.get_text("text").insert_(0, "2").unwrap();
// c creates a pending change for a, insert "2" cannot be merged into a yet
a.import(&c.export_from(&b.oplog_vv())).unwrap();
assert_eq!(a.get_deep_value().to_json_value(), json!({"text": "0"}));
// b does not has c's change
a.import(&b.export_from(&a.oplog_vv())).unwrap();
assert_eq!(a.get_deep_value().to_json_value(), json!({"text": "210"}));
}
#[test]
fn test_checkout() {
let mut doc_0 = LoroDoc::new();
doc_0.set_peer_id(0).unwrap();
let doc_1 = LoroDoc::new();
doc_1.set_peer_id(1).unwrap();
let value: Arc<Mutex<LoroValue>> = Arc::new(Mutex::new(LoroValue::Map(Default::default())));
let root_value = value.clone();
doc_0.subscribe_deep(Arc::new(move |event| {
let mut root_value = root_value.lock().unwrap();
root_value.apply(
&event.container.path.iter().map(|x| x.1.clone()).collect(),
&[event.container.diff.clone()],
);
}));
let map = doc_0.get_map("map");
doc_0
.with_txn(|txn| {
let handler = map.insert_container(txn, "text", ContainerType::Text)?;
let text = handler.into_text().unwrap();
text.insert(txn, 0, "123")
})
.unwrap();
let map = doc_1.get_map("map");
doc_1
.with_txn(|txn| map.insert(txn, "text", LoroValue::Double(1.0)))
.unwrap();
doc_0
.import(&doc_1.export_from(&Default::default()))
.unwrap();
doc_0
.checkout(&Frontiers::from(vec![ID::new(0, 2)]))
.unwrap();
assert_eq!(&doc_0.get_deep_value(), &*value.lock().unwrap());
assert_eq!(
value.lock().unwrap().to_json_value(),
json!({
"map": {
"text": "12"
}
})
);
}
2022-11-11 03:13:35 +00:00
2023-08-30 08:41:04 +00:00
#[test]
fn import() {
let doc = LoroDoc::new();
doc.import(&[
108, 111, 114, 111, 0, 0, 10, 10, 255, 255, 68, 255, 255, 4, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 255, 255, 108, 111, 114, 111, 255, 255, 0, 255, 207, 207, 255, 255, 255, 255,
255,
])
.unwrap_or_default();
}
#[test]
fn test_timestamp() {
let doc = LoroDoc::new();
let text = doc.get_text("text");
let mut txn = doc.txn().unwrap();
text.insert(&mut txn, 0, "123").unwrap();
txn.commit().unwrap();
2023-08-29 02:38:48 +00:00
let op_log = &doc.oplog().lock().unwrap();
let change = op_log.get_change_at(ID::new(doc.peer_id(), 0)).unwrap();
assert!(change.timestamp() > 1690966970);
}
#[test]
fn test_text_checkout() {
let mut doc = LoroDoc::new();
let text = doc.get_text("text");
let mut txn = doc.txn().unwrap();
text.insert(&mut txn, 0, "你界").unwrap();
text.insert(&mut txn, 1, "好世").unwrap();
txn.commit().unwrap();
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 0)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "");
}
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 1)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你界");
}
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 2)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好界");
}
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 3)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好世界");
}
assert_eq!(text.len_unicode(), 4);
assert_eq!(text.len_utf8(), 12);
assert_eq!(text.len_unicode(), 4);
doc.checkout_to_latest();
doc.with_txn(|txn| text.delete(txn, 3, 1)).unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好世");
doc.with_txn(|txn| text.delete(txn, 2, 1)).unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好");
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 3)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好世界");
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 4)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好世");
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 5)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好");
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 0)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "");
}
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 1)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你界");
}
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 2)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好界");
}
{
doc.checkout(&Frontiers::from([ID::new(doc.peer_id(), 3)].as_slice()))
.unwrap();
assert_eq!(text.get_value().as_string().unwrap().as_str(), "你好世界");
}
}
2023-08-04 14:41:02 +00:00
#[test]
fn map_checkout() {
let mut doc = LoroDoc::new();
let meta = doc.get_map("meta");
let v_empty = doc.oplog_frontiers();
doc.with_txn(|txn| {
meta.insert(txn, "key", 0.into()).unwrap();
Ok(())
2023-08-04 14:41:02 +00:00
})
.unwrap();
let v0 = doc.oplog_frontiers();
doc.with_txn(|txn| {
meta.insert(txn, "key", 1.into()).unwrap();
Ok(())
2023-08-04 14:41:02 +00:00
})
.unwrap();
let v1 = doc.oplog_frontiers();
assert_eq!(meta.get_deep_value().to_json(), r#"{"key":1}"#);
doc.checkout(&v0).unwrap();
2023-08-04 14:41:02 +00:00
assert_eq!(meta.get_deep_value().to_json(), r#"{"key":0}"#);
doc.checkout(&v_empty).unwrap();
2023-08-04 14:41:02 +00:00
assert_eq!(meta.get_deep_value().to_json(), r#"{}"#);
doc.checkout(&v1).unwrap();
2023-08-04 14:41:02 +00:00
assert_eq!(meta.get_deep_value().to_json(), r#"{"key":1}"#);
}
#[test]
fn a_list_of_map_checkout() {
let mut doc = LoroDoc::new();
let entry = doc.get_map("entry");
let (list, sub) = doc
.with_txn(|txn| {
let list = entry
.insert_container(txn, "list", loro_common::ContainerType::List)?
.into_list()
.unwrap();
let sub_map = list
.insert_container(txn, 0, loro_common::ContainerType::Map)?
.into_map()
.unwrap();
sub_map.insert(txn, "x", 100.into())?;
sub_map.insert(txn, "y", 1000.into())?;
Ok((list, sub_map))
})
.unwrap();
let v0 = doc.oplog_frontiers();
let d0 = doc.get_deep_value().to_json();
doc.with_txn(|txn| {
list.insert(txn, 0, 3.into())?;
list.push(txn, 4.into())?;
list.insert_container(txn, 2, loro_common::ContainerType::Map)?;
list.insert_container(txn, 3, loro_common::ContainerType::Map)?;
Ok(())
})
.unwrap();
doc.with_txn(|txn| {
list.delete(txn, 2, 1)?;
Ok(())
})
.unwrap();
doc.with_txn(|txn| {
sub.insert(txn, "x", 9.into())?;
sub.insert(txn, "y", 9.into())?;
Ok(())
})
.unwrap();
doc.with_txn(|txn| {
sub.insert(txn, "z", 9.into())?;
Ok(())
})
.unwrap();
let v1 = doc.oplog_frontiers();
let d1 = doc.get_deep_value().to_json();
doc.with_txn(|txn| {
sub.insert(txn, "x", 77.into())?;
Ok(())
})
.unwrap();
doc.with_txn(|txn| {
sub.insert(txn, "y", 88.into())?;
Ok(())
})
.unwrap();
doc.with_txn(|txn| {
list.delete(txn, 0, 1)?;
list.insert(txn, 0, 123.into())?;
list.push(txn, 99.into())?;
Ok(())
})
.unwrap();
let v2 = doc.oplog_frontiers();
let d2 = doc.get_deep_value().to_json();
doc.checkout(&v0).unwrap();
assert_eq!(doc.get_deep_value().to_json(), d0);
doc.checkout(&v1).unwrap();
assert_eq!(doc.get_deep_value().to_json(), d1);
doc.checkout(&v2).unwrap();
println!("{}", doc.get_deep_value_with_id().to_json_pretty());
assert_eq!(doc.get_deep_value().to_json(), d2);
debug_log::group!("checking out v1");
doc.checkout(&v1).unwrap();
debug_log::group_end!();
println!("{}", doc.get_deep_value_with_id().to_json_pretty());
assert_eq!(doc.get_deep_value().to_json(), d1);
doc.checkout(&v0).unwrap();
assert_eq!(doc.get_deep_value().to_json(), d0);
}
2023-08-04 14:41:02 +00:00
#[test]
fn map_concurrent_checkout() {
let mut doc_a = LoroDoc::new();
let meta_a = doc_a.get_map("meta");
let doc_b = LoroDoc::new();
let meta_b = doc_b.get_map("meta");
doc_a
.with_txn(|txn| {
meta_a.insert(txn, "key", 0.into()).unwrap();
Ok(())
2023-08-04 14:41:02 +00:00
})
.unwrap();
let va = doc_a.oplog_frontiers();
doc_b
.with_txn(|txn| {
meta_b.insert(txn, "s", 1.into()).unwrap();
Ok(())
2023-08-04 14:41:02 +00:00
})
.unwrap();
let vb_0 = doc_b.oplog_frontiers();
doc_b
.with_txn(|txn| {
meta_b.insert(txn, "key", 1.into()).unwrap();
Ok(())
2023-08-04 14:41:02 +00:00
})
.unwrap();
let vb_1 = doc_b.oplog_frontiers();
doc_a.import(&doc_b.export_snapshot()).unwrap();
doc_a
.with_txn(|txn| {
meta_a.insert(txn, "key", 2.into()).unwrap();
Ok(())
2023-08-04 14:41:02 +00:00
})
.unwrap();
let v_merged = doc_a.oplog_frontiers();
doc_a.checkout(&va).unwrap();
2023-08-04 14:41:02 +00:00
assert_eq!(meta_a.get_deep_value().to_json(), r#"{"key":0}"#);
doc_a.checkout(&vb_0).unwrap();
2023-08-04 14:41:02 +00:00
assert_eq!(meta_a.get_deep_value().to_json(), r#"{"s":1}"#);
doc_a.checkout(&vb_1).unwrap();
2023-08-04 14:41:02 +00:00
assert_eq!(meta_a.get_deep_value().to_json(), r#"{"s":1,"key":1}"#);
doc_a.checkout(&v_merged).unwrap();
2023-08-04 14:41:02 +00:00
assert_eq!(meta_a.get_deep_value().to_json(), r#"{"s":1,"key":2}"#);
}
feat: movable tree support (#120) * feat: tree state * feat: tree value * feat: tree handler * fix: tree diff * test: fuzz tree * feat: tree snapshot * fix: tree default value * fix: test new node * fix: tree diff * fix: tree unresolved value * fix: tree fuzz * fix: tree fuzz move * fix: sort by tree id * fix: tree diff sorted by lamport * fix: sort roots before tree converted to string * fix: rebase main * fix: tree fuzz * fix: delete undo * fix: tree to json children sorted * fix: diff calculate * fix: diff cycle move * fix: tree old parent cache * feat: cache * fix: local op add tree cache * fix: don't add same tree move to cache * fix: need update cache * feat: new cache * bench: add checkout bench * chore: clean * fix: apply node uncheck * perf: lamport bound * fix: calc old parent * feat: tree wasm * fix: change tree diff * fix: tree diff retreat * fix: tree diff should not apply when add node * feat: new tree loro value * chore: typo * fix: tree deep value * fix: snapshot tree index -1 * fix: decode tree snapshot use state * fix: release state lock when emit event * fix: tree node meta container * fix: need set map container when covert to local tree op * fix: tree value add deleted * fix: more then one op in a change * fix: tree fuzz deleted equal * fix: tree calc min lamport * feat: tree encoding v2 * doc: movable tree * fix: test tree meta * test: remove import bytes check * refactor: diff of text and map * refactor: del span * perf: tree state use deleted cache * fix: some details * fix: loro js tree create * feat: add un exist tree node * bench: tree depth * fix: check out should emit event * refactor: event * fix: fuzz err * fix: pass all tests * fix: fuzz err * fix: list child cache err * chore: rm debug code * fix: encode enhanced err * fix: encode enchanced * fix: fix several richtext issue * fix: richtext anchor err * chore: rm debug code * fix: richtext fuzz err * feat: speedup text snapshot decode * perf: optimize snapshot encoding * perf: speed up decode & insert * fix: fugue span merge err * perf: speedup delete & id cursor map * fix: fugue merge err * chore: update utils * fix: fix merge * fix: return err apply op * fix: fix merge * fix: get map container as tree meta
2023-10-30 03:13:52 +00:00
#[test]
fn tree_checkout() {
let mut doc_a = LoroDoc::new();
doc_a.subscribe_deep(Arc::new(|_e| {}));
doc_a.set_peer_id(1).unwrap();
feat: movable tree support (#120) * feat: tree state * feat: tree value * feat: tree handler * fix: tree diff * test: fuzz tree * feat: tree snapshot * fix: tree default value * fix: test new node * fix: tree diff * fix: tree unresolved value * fix: tree fuzz * fix: tree fuzz move * fix: sort by tree id * fix: tree diff sorted by lamport * fix: sort roots before tree converted to string * fix: rebase main * fix: tree fuzz * fix: delete undo * fix: tree to json children sorted * fix: diff calculate * fix: diff cycle move * fix: tree old parent cache * feat: cache * fix: local op add tree cache * fix: don't add same tree move to cache * fix: need update cache * feat: new cache * bench: add checkout bench * chore: clean * fix: apply node uncheck * perf: lamport bound * fix: calc old parent * feat: tree wasm * fix: change tree diff * fix: tree diff retreat * fix: tree diff should not apply when add node * feat: new tree loro value * chore: typo * fix: tree deep value * fix: snapshot tree index -1 * fix: decode tree snapshot use state * fix: release state lock when emit event * fix: tree node meta container * fix: need set map container when covert to local tree op * fix: tree value add deleted * fix: more then one op in a change * fix: tree fuzz deleted equal * fix: tree calc min lamport * feat: tree encoding v2 * doc: movable tree * fix: test tree meta * test: remove import bytes check * refactor: diff of text and map * refactor: del span * perf: tree state use deleted cache * fix: some details * fix: loro js tree create * feat: add un exist tree node * bench: tree depth * fix: check out should emit event * refactor: event * fix: fuzz err * fix: pass all tests * fix: fuzz err * fix: list child cache err * chore: rm debug code * fix: encode enhanced err * fix: encode enchanced * fix: fix several richtext issue * fix: richtext anchor err * chore: rm debug code * fix: richtext fuzz err * feat: speedup text snapshot decode * perf: optimize snapshot encoding * perf: speed up decode & insert * fix: fugue span merge err * perf: speedup delete & id cursor map * fix: fugue merge err * chore: update utils * fix: fix merge * fix: return err apply op * fix: fix merge * fix: get map container as tree meta
2023-10-30 03:13:52 +00:00
let tree = doc_a.get_tree("root");
let id1 = doc_a.with_txn(|txn| tree.create(txn)).unwrap();
let id2 = doc_a.with_txn(|txn| tree.create_and_mov(txn, id1)).unwrap();
let v1_state = tree.get_deep_value();
let v1 = doc_a.oplog_frontiers();
let _id3 = doc_a.with_txn(|txn| tree.create_and_mov(txn, id2)).unwrap();
let v2_state = tree.get_deep_value();
let v2 = doc_a.oplog_frontiers();
doc_a.with_txn(|txn| tree.delete(txn, id2)).unwrap();
let v3_state = tree.get_deep_value();
let v3 = doc_a.oplog_frontiers();
doc_a.checkout(&v1).unwrap();
assert_eq!(
serde_json::to_value(tree.get_deep_value())
.unwrap()
.get("roots"),
serde_json::to_value(v1_state).unwrap().get("roots")
);
doc_a.checkout(&v2).unwrap();
assert_eq!(
serde_json::to_value(tree.get_deep_value())
.unwrap()
.get("roots"),
serde_json::to_value(v2_state).unwrap().get("roots")
);
doc_a.checkout(&v3).unwrap();
assert_eq!(
serde_json::to_value(tree.get_deep_value())
.unwrap()
.get("roots"),
serde_json::to_value(v3_state).unwrap().get("roots")
);
doc_a.attach();
doc_a
.with_txn(|txn| {
tree.create(txn)
//tree.insert_meta(txn, id1, "a", 1.into())
})
.unwrap();
}