Add a failing test for detecting a file move

Nathan Sobo created

Change summary

zed/src/test.rs              | 16 ++++++++++
zed/src/worktree/worktree.rs | 58 ++++++++++++++++++++++++++++++++++++++
2 files changed, 74 insertions(+)

Detailed changes

zed/src/test.rs 🔗

@@ -2,6 +2,7 @@ use rand::Rng;
 use std::{
     collections::BTreeMap,
     path::{Path, PathBuf},
+    time::{Duration, Instant},
 };
 use tempdir::TempDir;
 
@@ -136,3 +137,18 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
         panic!("You must pass a JSON object to this helper")
     }
 }
+
+pub async fn assert_condition(poll_interval: u64, timeout: u64, mut f: impl FnMut() -> bool) {
+    let poll_interval = Duration::from_millis(poll_interval);
+    let timeout = Duration::from_millis(timeout);
+    let start = Instant::now();
+    loop {
+        if f() {
+            return;
+        } else if Instant::now().duration_since(start) < timeout {
+            smol::Timer::after(poll_interval).await;
+        } else {
+            panic!("timed out waiting on condition");
+        }
+    }
+}

zed/src/worktree/worktree.rs 🔗

@@ -279,6 +279,28 @@ impl Worktree {
         Ok(path.join(self.entry_path(entry_id)?))
     }
 
+    #[cfg(test)]
+    fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
+        let path = path.as_ref();
+        let state = self.0.read();
+        state.root_ino.and_then(|mut ino| {
+            'components: for component in path {
+                if let Entry::Dir { children, .. } = &state.entries[&ino] {
+                    for child in children {
+                        if state.entries[child].name() == component {
+                            ino = *child;
+                            continue 'components;
+                        }
+                    }
+                    return None;
+                } else {
+                    return None;
+                }
+            }
+            Some(ino)
+        })
+    }
+
     fn fmt_entry(&self, f: &mut fmt::Formatter<'_>, entry_id: u64, indent: usize) -> fmt::Result {
         match &self.0.read().entries[&entry_id] {
             Entry::Dir { name, children, .. } => {
@@ -740,4 +762,40 @@ mod test {
             assert_eq!(history.base_text.as_ref(), buffer.text());
         });
     }
+
+    #[test]
+    fn test_rescan() {
+        App::test_async((), |mut app| async move {
+            let dir = temp_tree(json!({
+                "dir1": {
+                    "file": "contents"
+                },
+                "dir2": {
+                }
+            }));
+
+            let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), ctx));
+            app.finish_pending_tasks().await;
+
+            let file_entry = app.read(|ctx| tree.read(ctx).entry_for_path("dir1/file").unwrap());
+
+            app.read(|ctx| {
+                let tree = tree.read(ctx);
+                assert_eq!(
+                    tree.abs_entry_path(file_entry).unwrap(),
+                    tree.path().join("dir1/file")
+                );
+            });
+
+            std::fs::rename(dir.path().join("dir1/file"), dir.path().join("dir2/file")).unwrap();
+
+            assert_condition(1, 300, || {
+                app.read(|ctx| {
+                    let tree = tree.read(ctx);
+                    tree.abs_entry_path(file_entry).unwrap() == tree.path().join("dir2/file")
+                })
+            })
+            .await
+        });
+    }
 }