Assert that buffers' file state matches in randomized collab test

Max Brunsfeld and Nathan Sobo created

Co-authored-by: Nathan Sobo <nathan@zed.dev>

Change summary

crates/collab/src/tests/randomized_integration_tests.rs | 13 +++++++++
crates/editor/src/items.rs                              |  2 
crates/editor/src/multi_buffer.rs                       |  2 
crates/fs/src/fs.rs                                     | 15 ++++++++--
crates/language/src/buffer.rs                           |  8 ++--
crates/project/src/worktree.rs                          |  2 
crates/rpc/proto/zed.proto                              |  1 
7 files changed, 31 insertions(+), 12 deletions(-)

Detailed changes

crates/collab/src/tests/randomized_integration_tests.rs 🔗

@@ -356,6 +356,19 @@ async fn test_random_collaboration(
                     buffer_id,
                     path
                 );
+
+                let host_file = host_buffer.read_with(host_cx, |b, _| b.file().cloned());
+                let guest_file = guest_buffer.read_with(client_cx, |b, _| b.file().cloned());
+                match (host_file, guest_file) {
+                    (Some(host_file), Some(guest_file)) => {
+                        assert_eq!(host_file.mtime(), guest_file.mtime());
+                        assert_eq!(host_file.path(), guest_file.path());
+                        assert_eq!(host_file.is_deleted(), guest_file.is_deleted());
+                    }
+                    (None, None) => {}
+                    (None, _) => panic!("host's file is None, guest's isn't "),
+                    (_, None) => panic!("guest's file is None, hosts's isn't "),
+                }
             }
         }
     }

crates/editor/src/items.rs 🔗

@@ -1114,7 +1114,7 @@ fn path_for_buffer<'a>(
     cx: &'a AppContext,
 ) -> Option<Cow<'a, Path>> {
     let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
-    path_for_file(file, height, include_filename, cx)
+    path_for_file(file.as_ref(), height, include_filename, cx)
 }
 
 fn path_for_file<'a>(

crates/editor/src/multi_buffer.rs 🔗

@@ -1311,7 +1311,7 @@ impl MultiBuffer {
             .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
     }
 
-    pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a dyn File; 2]> {
+    pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a Arc<dyn File>; 2]> {
         let buffers = self.buffers.borrow();
         buffers
             .values()

crates/fs/src/fs.rs 🔗

@@ -23,7 +23,7 @@ use std::{
     time::{Duration, SystemTime},
 };
 use tempfile::NamedTempFile;
-use util::ResultExt;
+use util::{post_inc, ResultExt};
 
 #[cfg(any(test, feature = "test-support"))]
 use collections::{btree_map, BTreeMap};
@@ -389,6 +389,7 @@ pub struct FakeFs {
 struct FakeFsState {
     root: Arc<Mutex<FakeFsEntry>>,
     next_inode: u64,
+    next_mtime: SystemTime,
     event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
 }
 
@@ -521,6 +522,7 @@ impl FakeFs {
                     entries: Default::default(),
                     git_repo_state: None,
                 })),
+                next_mtime: SystemTime::UNIX_EPOCH,
                 next_inode: 1,
                 event_txs: Default::default(),
             }),
@@ -531,10 +533,12 @@ impl FakeFs {
         let mut state = self.state.lock().await;
         let path = path.as_ref();
         let inode = state.next_inode;
+        let mtime = state.next_mtime;
         state.next_inode += 1;
+        state.next_mtime += Duration::from_millis(1);
         let file = Arc::new(Mutex::new(FakeFsEntry::File {
             inode,
-            mtime: SystemTime::now(),
+            mtime,
             content,
         }));
         state
@@ -816,6 +820,9 @@ impl Fs for FakeFs {
         let source = normalize_path(source);
         let target = normalize_path(target);
         let mut state = self.state.lock().await;
+        let mtime = state.next_mtime;
+        let inode = post_inc(&mut state.next_inode);
+        state.next_mtime += Duration::from_millis(1);
         let source_entry = state.read_path(&source).await?;
         let content = source_entry.lock().await.file_content(&source)?.clone();
         let entry = state
@@ -831,8 +838,8 @@ impl Fs for FakeFs {
                 }
                 btree_map::Entry::Vacant(e) => Ok(Some(
                     e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
-                        inode: 0,
-                        mtime: SystemTime::now(),
+                        inode,
+                        mtime,
                         content: String::new(),
                     })))
                     .clone(),

crates/language/src/buffer.rs 🔗

@@ -514,8 +514,8 @@ impl Buffer {
         self.text.snapshot()
     }
 
-    pub fn file(&self) -> Option<&dyn File> {
-        self.file.as_deref()
+    pub fn file(&self) -> Option<&Arc<dyn File>> {
+        self.file.as_ref()
     }
 
     pub fn save(
@@ -2373,8 +2373,8 @@ impl BufferSnapshot {
         self.selections_update_count
     }
 
-    pub fn file(&self) -> Option<&dyn File> {
-        self.file.as_deref()
+    pub fn file(&self) -> Option<&Arc<dyn File>> {
+        self.file.as_ref()
     }
 
     pub fn resolve_file_path(&self, cx: &AppContext, include_root: bool) -> Option<PathBuf> {

crates/project/src/worktree.rs 🔗

@@ -1981,7 +1981,7 @@ impl File {
         })
     }
 
-    pub fn from_dyn(file: Option<&dyn language::File>) -> Option<&Self> {
+    pub fn from_dyn(file: Option<&Arc<dyn language::File>>) -> Option<&Self> {
         file.and_then(|f| f.as_any().downcast_ref())
     }
 

crates/rpc/proto/zed.proto 🔗

@@ -170,7 +170,6 @@ message RejoinRoom {
     uint64 id = 1;
     repeated UpdateProject reshared_projects = 2;
     repeated RejoinProject rejoined_projects = 3;
-    // relay open buffers and their vector clock
 }
 
 message RejoinProject {