aboutsummaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-31 19:03:15 +0100
committertv <tv@krebsco.de>2026-10-31 19:03:15 +0100
commite4f801db5d150fb1bf5540d9ba00f491a6129a5c (patch)
tree38a8b5ffd8233df7d24f9bb5effac58cbd92187e /src/document
parent4a6403d0242cf730a3f864c8d26e0ba15a98fcf0 (diff)
tests write through document::testing helpers
Diffstat (limited to 'src/document')
-rw-r--r--src/document/changes.rs11
-rw-r--r--src/document/file.rs7
-rw-r--r--src/document/mod.rs2
-rw-r--r--src/document/store.rs70
-rw-r--r--src/document/sync.rs19
-rw-r--r--src/document/testing.rs29
6 files changed, 103 insertions, 35 deletions
diff --git a/src/document/changes.rs b/src/document/changes.rs
index 655063b..33029fc 100644
--- a/src/document/changes.rs
+++ b/src/document/changes.rs
@@ -101,12 +101,13 @@ pub(crate) fn load_delta(doc: &mut Document, bytes: &[u8]) -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::document::testing::change;
use automerge::{ActorId, ROOT, transaction::Transactable};
fn random_write(rng: &mut crate::testrand::Rng, doc: &mut Document) {
let key = format!("k{}", rng.below(6));
- doc.put(ROOT, key, rng.below(100) as i64).unwrap();
- doc.commit();
+ let value = rng.below(100) as i64;
+ change(doc, |doc| doc.put(ROOT, key, value).unwrap());
}
fn sorted(mut heads: Vec<ChangeHash>) -> Vec<ChangeHash> {
@@ -158,8 +159,7 @@ mod tests {
#[test]
fn no_delta_without_its_heads() {
let mut doc = Document::new();
- doc.put(ROOT, "k", 1_i64).unwrap();
- doc.commit();
+ change(&mut doc, |doc| doc.put(ROOT, "k", 1_i64).unwrap());
assert!(delta_bytes(&mut doc, &[], &[ChangeHash([7; 32])]).is_none());
}
@@ -167,8 +167,7 @@ mod tests {
#[test]
fn a_change_is_checked_against_its_hash() {
let mut doc = Document::new();
- doc.put(ROOT, "k", 1_i64).unwrap();
- doc.commit();
+ change(&mut doc, |doc| doc.put(ROOT, "k", 1_i64).unwrap());
let hash = doc.get_heads()[0];
let bytes = change_bytes(&mut doc, &hash).unwrap();
assert_eq!(change_from_bytes(bytes.clone(), hash).unwrap().hash(), hash);
diff --git a/src/document/file.rs b/src/document/file.rs
index e03c2e6..6439e27 100644
--- a/src/document/file.rs
+++ b/src/document/file.rs
@@ -322,19 +322,18 @@ impl DocumentFile {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::document::testing::change;
use automerge::{ROOT, ReadDoc, transaction::Transactable};
#[test]
fn whole_then_increments() {
let mut doc = AutoCommit::new();
- doc.put(ROOT, "a", 1_i64).unwrap();
- doc.commit();
+ change(&mut doc, |doc| doc.put(ROOT, "a", 1_i64).unwrap());
let actor = doc.get_actor().clone();
let heads = doc.get_heads();
let mut bytes = whole(std::slice::from_ref(&actor), &doc.save()).unwrap();
- doc.put(ROOT, "b", 2_i64).unwrap();
- doc.commit();
+ change(&mut doc, |doc| doc.put(ROOT, "b", 2_i64).unwrap());
bytes.extend(record(&doc.save_after(&heads)));
let (loaded, dropped) = parse(&bytes).unwrap();
diff --git a/src/document/mod.rs b/src/document/mod.rs
index f11fef0..a348774 100644
--- a/src/document/mod.rs
+++ b/src/document/mod.rs
@@ -37,6 +37,8 @@ pub(crate) mod meta;
pub(crate) mod replica;
pub(crate) mod store;
pub(crate) mod sync;
+#[cfg(test)]
+pub(crate) mod testing;
#[cfg(test)]
mod tests {
diff --git a/src/document/store.rs b/src/document/store.rs
index dff0cfb..c6c428c 100644
--- a/src/document/store.rs
+++ b/src/document/store.rs
@@ -1071,6 +1071,7 @@ fn nested<T>(
#[cfg(test)]
mod tests {
use super::*;
+ use crate::document::testing::try_change;
use serde_json::json;
fn path(at: &str) -> Vec<String> {
@@ -1080,7 +1081,10 @@ mod tests {
#[test]
fn paths_go_through_lists() {
let mut doc = AutoCommit::new();
- write_path(&mut doc, &path("list"), &json!(["b", {"k": 1}, "d"])).unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("list"), &json!(["b", {"k": 1}, "d"]))
+ })
+ .unwrap();
assert_eq!(read_path(&doc, &path("list/0")).unwrap(), Some(json!("b")));
assert_eq!(read_path(&doc, &path("list/1/k")).unwrap(), Some(json!(1)));
// Not an index, or past the end: nothing.
@@ -1088,18 +1092,27 @@ mod tests {
assert_eq!(read_path(&doc, &path(missing)).unwrap(), None, "{missing}");
}
// Replaced, and appended at the end; past it, nothing.
- write_path(&mut doc, &path("list/0"), &json!("a")).unwrap();
- write_path(&mut doc, &path("list/3"), &json!("e")).unwrap();
- write_path(&mut doc, &path("list/1/k"), &json!(2)).unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("list/0"), &json!("a"))
+ })
+ .unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("list/3"), &json!("e"))
+ })
+ .unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("list/1/k"), &json!(2))
+ })
+ .unwrap();
assert!(matches!(
- write_path(&mut doc, &path("list/9"), &json!(0)),
+ try_change(&mut doc, |doc| write_path(doc, &path("list/9"), &json!(0))),
Err(StoreError::NotFound)
));
assert_eq!(
read_path(&doc, &path("list")).unwrap(),
Some(json!(["a", {"k": 2}, "d", "e"]))
);
- delete_path(&mut doc, &path("list/2"), false).unwrap();
+ try_change(&mut doc, |doc| delete_path(doc, &path("list/2"), false)).unwrap();
assert_eq!(
read_path(&doc, &path("list")).unwrap(),
Some(json!(["a", {"k": 2}, "e"]))
@@ -1113,8 +1126,14 @@ mod tests {
#[test]
fn negative_indexes_count_from_the_end() {
let mut doc = AutoCommit::new();
- write_path(&mut doc, &path("list"), &json!(["a", "b"])).unwrap();
- write_path(&mut doc, &path("map"), &json!({"-1": "a key"})).unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("list"), &json!(["a", "b"]))
+ })
+ .unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("map"), &json!({"-1": "a key"}))
+ })
+ .unwrap();
assert_eq!(
positive_path(&doc, &path("list/-1"), false).unwrap(),
Some(path("list/1"))
@@ -1135,28 +1154,47 @@ mod tests {
#[test]
fn inserts_into_lists() {
let mut doc = AutoCommit::new();
- write_path(&mut doc, &path("list"), &json!(["b", "d"])).unwrap();
- insert_path(&mut doc, &path("list/0"), &json!("a")).unwrap();
- insert_path(&mut doc, &path("list/2"), &json!("c")).unwrap();
- insert_path(&mut doc, &path("list/4"), &json!({"e": 5})).unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("list"), &json!(["b", "d"]))
+ })
+ .unwrap();
+ try_change(&mut doc, |doc| {
+ insert_path(doc, &path("list/0"), &json!("a"))
+ })
+ .unwrap();
+ try_change(&mut doc, |doc| {
+ insert_path(doc, &path("list/2"), &json!("c"))
+ })
+ .unwrap();
+ try_change(&mut doc, |doc| {
+ insert_path(doc, &path("list/4"), &json!({"e": 5}))
+ })
+ .unwrap();
assert_eq!(
read_path(&doc, &path("list")).unwrap(),
Some(json!(["a", "b", "c", "d", {"e": 5}]))
);
assert!(matches!(
- insert_path(&mut doc, &path("list/6"), &json!(1)),
+ try_change(&mut doc, |doc| insert_path(doc, &path("list/6"), &json!(1))),
Err(StoreError::PastTheEnd {
index: 6,
length: 5
})
));
- write_path(&mut doc, &path("map"), &json!({"k": 1})).unwrap();
+ try_change(&mut doc, |doc| {
+ write_path(doc, &path("map"), &json!({"k": 1}))
+ })
+ .unwrap();
assert!(matches!(
- insert_path(&mut doc, &path("map/0"), &json!(1)),
+ try_change(&mut doc, |doc| insert_path(doc, &path("map/0"), &json!(1))),
Err(StoreError::NotAList)
));
assert!(matches!(
- insert_path(&mut doc, &path("nothing/0"), &json!(1)),
+ try_change(&mut doc, |doc| insert_path(
+ doc,
+ &path("nothing/0"),
+ &json!(1)
+ )),
Err(StoreError::NotFound)
));
}
diff --git a/src/document/sync.rs b/src/document/sync.rs
index dc286fa..625d805 100644
--- a/src/document/sync.rs
+++ b/src/document/sync.rs
@@ -384,11 +384,12 @@ mod tests {
/// trip (`answer_for_everything_since_shared`).
#[test]
fn missing_predecessors_go_at_once() {
+ use crate::document::testing::change;
use automerge::transaction::Transactable;
let mut a = Document::new();
let mut b = Document::new();
let (mut at_a, mut at_b) = (sync::State::new(), sync::State::new());
- a.put(automerge::ROOT, "early", 1).unwrap();
+ change(&mut a, |a| a.put(automerge::ROOT, "early", 1).unwrap());
// Until in step.
for _ in 0..10 {
let to_b = a.sync().generate_sync_message(&mut at_a);
@@ -406,12 +407,12 @@ mod tests {
assert_eq!(a.get_heads(), b.get_heads());
// Five changes, whose message is lost; then a sixth, which arrives alone.
for i in 0..5 {
- a.put(automerge::ROOT, format!("lost{i}"), i).unwrap();
- a.commit();
+ change(&mut a, |a| {
+ a.put(automerge::ROOT, format!("lost{i}"), i).unwrap()
+ });
}
let _lost = a.sync().generate_sync_message(&mut at_a).unwrap();
- a.put(automerge::ROOT, "last", 6).unwrap();
- a.commit();
+ change(&mut a, |a| a.put(automerge::ROOT, "last", 6).unwrap());
at_a.in_flight = false; // As after our reply timeout.
let last = a.sync().generate_sync_message(&mut at_a).unwrap();
assert_eq!(last.changes.len(), 1);
@@ -467,6 +468,7 @@ mod tests {
/// overdue answer, gets the same changes in the next message.
#[test]
fn withdrawing_for_a_delta_keeps_the_changes_for_later() {
+ use crate::document::testing::change;
use automerge::{ROOT, transaction::Transactable};
// 16 KiB of hex, about 8 KiB of information: Automerge compresses what it sends
@@ -476,8 +478,7 @@ mod tests {
.map(|_| format!("{:016x}", rng.next_u64()))
.collect();
let mut ours = Document::new();
- ours.put(ROOT, "big", big).unwrap();
- ours.commit();
+ change(&mut ours, |ours| ours.put(ROOT, "big", big).unwrap());
let mut theirs = Document::new();
let (mut a, mut b) = (session(), session());
let now = Instant::now();
@@ -515,6 +516,7 @@ mod tests {
/// has everything (`have_all_theirs`).
#[test]
fn after_a_delta_the_peer_learns_our_heads() {
+ use crate::document::testing::change;
use automerge::{ROOT, transaction::Transactable};
let mut rng = crate::testrand::Rng::new(2);
@@ -522,8 +524,7 @@ mod tests {
.map(|_| format!("{:016x}", rng.next_u64()))
.collect();
let mut ours = Document::new();
- ours.put(ROOT, "big", big).unwrap();
- ours.commit();
+ change(&mut ours, |ours| ours.put(ROOT, "big", big).unwrap());
let mut theirs = Document::new();
let (mut a, mut b) = (session(), session());
let now = Instant::now();
diff --git a/src/document/testing.rs b/src/document/testing.rs
new file mode 100644
index 0000000..b34d85f
--- /dev/null
+++ b/src/document/testing.rs
@@ -0,0 +1,29 @@
+//! For tests: writing to documents the way the node does, one change at a time, so
+//! that tests don't depend on how the engine groups writes into changes.
+
+use super::Document;
+
+/// The writes `f` makes to `doc`, as one change; what `f` returns.
+pub(crate) fn change<T>(doc: &mut Document, f: impl FnOnce(&mut Document) -> T) -> T {
+ let out = f(doc);
+ doc.commit();
+ out
+}
+
+/// The writes `f` makes to `doc`, as one change if it succeeds, dropped if not; what
+/// `f` returns. Committed at time 0: the same writes make the same change.
+pub(crate) fn try_change<T, E>(
+ doc: &mut Document,
+ f: impl FnOnce(&mut Document) -> Result<T, E>,
+) -> Result<T, E> {
+ let out = f(doc);
+ match &out {
+ Ok(_) => {
+ doc.commit_with(automerge::transaction::CommitOptions::default().with_time(0));
+ }
+ Err(_) => {
+ doc.rollback();
+ }
+ }
+ out
+}