summaryrefslogtreecommitdiffstats
path: root/src/remake/delta.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/delta.rs')
-rw-r--r--src/remake/delta.rs69
1 files changed, 69 insertions, 0 deletions
diff --git a/src/remake/delta.rs b/src/remake/delta.rs
index ef6a60a..80e5443 100644
--- a/src/remake/delta.rs
+++ b/src/remake/delta.rs
@@ -344,6 +344,75 @@ mod tests {
});
}
+ /// Every non-empty-object value of `json` with its path.
+ fn leaves(
+ json: &serde_json::Value,
+ path: &mut Vec<String>,
+ out: &mut Vec<(Vec<String>, String)>,
+ ) {
+ match json {
+ serde_json::Value::Object(map) => {
+ for (key, value) in map {
+ path.push(key.clone());
+ leaves(value, path, out);
+ path.pop();
+ }
+ }
+ other => out.push((path.clone(), other.to_string())),
+ }
+ }
+
+ /// Over random histories (writes and deletes, some on concurrent forks): the paths
+ /// are sorted, none is a prefix of another, and together they cover every value that
+ /// differs between before and after.
+ #[test]
+ fn changed_paths_cover_every_change_minimally() {
+ crate::testrand::for_seeds(300, |rng| {
+ let actor = |i: u8| ActorId::from(&[i; 16][..]);
+ let mut doc = AutoCommit::new().with_actor(actor(0));
+ for _ in 0..rng.between(0, 8) {
+ random_write(rng, &mut doc);
+ }
+ let before = doc.get_heads();
+ let mut forks: Vec<AutoCommit> = (1..=rng.between(1, 3))
+ .map(|i| doc.fork().with_actor(actor(i as u8)))
+ .collect();
+ for _ in 0..rng.between(0, 12) {
+ let fork = rng.below(forks.len());
+ random_write(rng, &mut forks[fork]);
+ }
+ for fork in &mut forks {
+ doc.merge(fork).unwrap();
+ }
+ let after = doc.get_heads();
+
+ let paths = changed_paths(&mut doc, &before, &after);
+ for pair in paths.windows(2) {
+ assert!(pair[0] < pair[1], "not sorted: {paths:?}");
+ assert!(!pair[1].starts_with(&pair[0]), "not minimal: {paths:?}");
+ }
+
+ let mut old = Vec::new();
+ leaves(
+ &view(&doc.fork_at(&before).unwrap()),
+ &mut Vec::new(),
+ &mut old,
+ );
+ let mut new = Vec::new();
+ leaves(&view(&doc), &mut Vec::new(), &mut new);
+ let differing = old
+ .iter()
+ .filter(|leaf| !new.contains(leaf))
+ .chain(new.iter().filter(|leaf| !old.contains(leaf)));
+ for (path, _) in differing {
+ assert!(
+ paths.iter().any(|changed| path.starts_with(changed)),
+ "{path:?} changed, but {paths:?} don't cover it"
+ );
+ }
+ });
+ }
+
#[test]
fn minimal_paths() {
let mut doc = AutoCommit::new();