In random collaboration test, add failing assertion for worktree convergence

Max Brunsfeld created

Change summary

crates/fuzzy/src/char_bag.rs    |  2 
crates/gpui/src/executor.rs     |  7 ++++
crates/project/src/worktree.rs  | 34 ++++++++++++++++------
crates/server/src/rpc.rs        | 52 +++++++++++++++++++++++++++++++---
crates/sum_tree/src/sum_tree.rs |  8 +++++
5 files changed, 88 insertions(+), 15 deletions(-)

Detailed changes

crates/fuzzy/src/char_bag.rs 🔗

@@ -1,6 +1,6 @@
 use std::iter::FromIterator;
 
-#[derive(Copy, Clone, Debug, Default)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
 pub struct CharBag(u64);
 
 impl CharBag {

crates/gpui/src/executor.rs 🔗

@@ -370,6 +370,13 @@ impl Foreground {
         *any_value.downcast().unwrap()
     }
 
+    pub fn run_until_parked(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.run_until_parked(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
     pub fn parking_forbidden(&self) -> bool {
         match self {
             Self::Deterministic { executor, .. } => executor.state.lock().forbid_parking,

crates/project/src/worktree.rs 🔗

@@ -87,7 +87,7 @@ pub struct RemoteWorktree {
     pending_updates: VecDeque<proto::UpdateWorktree>,
 }
 
-#[derive(Clone)]
+#[derive(Clone, PartialEq, Eq)]
 pub struct Snapshot {
     id: WorktreeId,
     root_name: String,
@@ -1315,13 +1315,29 @@ impl fmt::Debug for LocalWorktree {
 
 impl fmt::Debug for Snapshot {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        for entry in self.entries_by_path.cursor::<()>() {
-            for _ in entry.path.ancestors().skip(1) {
-                write!(f, " ")?;
+        struct EntriesById<'a>(&'a SumTree<PathEntry>);
+        struct EntriesByPath<'a>(&'a SumTree<Entry>);
+
+        impl<'a> fmt::Debug for EntriesByPath<'a> {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                f.debug_map()
+                    .entries(self.0.iter().map(|entry| (&entry.path, entry.id)))
+                    .finish()
             }
-            writeln!(f, "{:?} (inode: {})", entry.path, entry.inode)?;
         }
-        Ok(())
+
+        impl<'a> fmt::Debug for EntriesById<'a> {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                f.debug_list().entries(self.0.iter()).finish()
+            }
+        }
+
+        f.debug_struct("Snapshot")
+            .field("id", &self.id)
+            .field("root_name", &self.root_name)
+            .field("entries_by_path", &EntriesByPath(&self.entries_by_path))
+            .field("entries_by_id", &EntriesById(&self.entries_by_id))
+            .finish()
     }
 }
 
@@ -1528,7 +1544,7 @@ impl File {
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Entry {
     pub id: usize,
     pub kind: EntryKind,
@@ -1539,7 +1555,7 @@ pub struct Entry {
     pub is_ignored: bool,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub enum EntryKind {
     PendingDir,
     Dir,
@@ -1642,7 +1658,7 @@ impl sum_tree::Summary for EntrySummary {
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 struct PathEntry {
     id: usize,
     path: Arc<Path>,

crates/server/src/rpc.rs 🔗

@@ -1085,6 +1085,7 @@ mod tests {
         github, AppState, Config,
     };
     use ::rpc::Peer;
+    use collections::BTreeMap;
     use gpui::{executor, ModelHandle, TestAppContext};
     use parking_lot::Mutex;
     use postage::{mpsc, watch};
@@ -3597,11 +3598,11 @@ mod tests {
             .unwrap();
 
         clients.push(cx.foreground().spawn(host.simulate_host(
-            host_project,
+            host_project.clone(),
             operations.clone(),
             max_operations,
             rng.clone(),
-            host_cx,
+            host_cx.clone(),
         )));
 
         while operations.get() < max_operations {
@@ -3647,10 +3648,51 @@ mod tests {
         }
 
         let clients = futures::future::join_all(clients).await;
+        cx.foreground().run_until_parked();
+
+        let host_worktree_snapshots = host_project.read_with(&host_cx, |project, cx| {
+            project
+                .worktrees(cx)
+                .map(|worktree| {
+                    let snapshot = worktree.read(cx).snapshot();
+                    (snapshot.id(), snapshot)
+                })
+                .collect::<BTreeMap<_, _>>()
+        });
+
         for (ix, (client_a, cx_a)) in clients.iter().enumerate() {
-            for buffer_a in &client_a.buffers {
-                let buffer_id = buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
-                for (client_b, cx_b) in &clients[ix + 1..] {
+            let worktree_snapshots =
+                client_a
+                    .project
+                    .as_ref()
+                    .unwrap()
+                    .read_with(cx_a, |project, cx| {
+                        project
+                            .worktrees(cx)
+                            .map(|worktree| {
+                                let snapshot = worktree.read(cx).snapshot();
+                                (snapshot.id(), snapshot)
+                            })
+                            .collect::<BTreeMap<_, _>>()
+                    });
+
+            assert_eq!(
+                worktree_snapshots.keys().collect::<Vec<_>>(),
+                host_worktree_snapshots.keys().collect::<Vec<_>>(),
+                "guest {} has different worktrees than the host",
+                ix
+            );
+            for (id, snapshot) in &host_worktree_snapshots {
+                assert_eq!(
+                    worktree_snapshots[id], *snapshot,
+                    "guest {} has different snapshot than the host for worktree {}",
+                    ix, id
+                );
+            }
+
+            for (client_b, cx_b) in &clients[ix + 1..] {
+                for buffer_a in &client_a.buffers {
+                    let buffer_id = buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
                     if let Some(buffer_b) = client_b.buffers.iter().find(|buffer| {
                         buffer.read_with(cx_b, |buffer, _| buffer.remote_id() == buffer_id)
                     }) {

crates/sum_tree/src/sum_tree.rs 🔗

@@ -478,6 +478,14 @@ impl<T: Item> SumTree<T> {
     }
 }
 
+impl<T: Item + PartialEq> PartialEq for SumTree<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.iter().eq(other.iter())
+    }
+}
+
+impl<T: Item + Eq> Eq for SumTree<T> {}
+
 impl<T: KeyedItem> SumTree<T> {
     pub fn insert_or_replace(&mut self, item: T, cx: &<T::Summary as Summary>::Context) -> bool {
         let mut replaced = false;