fs: Use public-api-only tests (#47149)

Piotr Osiewicz created

This reduces time needed to build tests for fs from 1.2s to 0.6s.
Release Notes:

- N/A

Change summary

Cargo.lock                                   |   1 
crates/fs/Cargo.toml                         |   7 
crates/fs/src/fake_git_repo.rs               |  62 -
crates/fs/src/fs.rs                          | 762 ++-------------------
crates/fs/tests/integration/fake_git_repo.rs |  58 +
crates/fs/tests/integration/fs.rs            | 564 ++++++++++++++++
crates/fs/tests/integration/main.rs          |   2 
7 files changed, 726 insertions(+), 730 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6498,6 +6498,7 @@ dependencies = [
  "async-trait",
  "cocoa 0.26.0",
  "collections",
+ "fs",
  "fsevent",
  "futures 0.3.31",
  "git",

crates/fs/Cargo.toml 🔗

@@ -10,6 +10,12 @@ workspace = true
 
 [lib]
 path = "src/fs.rs"
+test = false
+
+[[test]]
+name = "integration"
+required-features = ["test-support"]
+path = "tests/integration/main.rs"
 
 [dependencies]
 anyhow.workspace = true
@@ -50,6 +56,7 @@ windows.workspace = true
 ashpd.workspace = true
 
 [dev-dependencies]
+fs = { workspace = true, features = ["test-support"] }
 gpui = { workspace = true, features = ["test-support"] }
 git = { workspace = true, features = ["test-support"] }
 

crates/fs/src/fake_git_repo.rs 🔗

@@ -738,65 +738,3 @@ impl GitRepository for FakeGitRepository {
         })
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use crate::{FakeFs, Fs};
-    use gpui::BackgroundExecutor;
-    use serde_json::json;
-    use std::path::Path;
-    use util::path;
-
-    #[gpui::test]
-    async fn test_checkpoints(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor);
-        fs.insert_tree(
-            path!("/"),
-            json!({
-                "bar": {
-                    "baz": "qux"
-                },
-                "foo": {
-                    ".git": {},
-                    "a": "lorem",
-                    "b": "ipsum",
-                },
-            }),
-        )
-        .await;
-        fs.with_git_state(Path::new("/foo/.git"), true, |_git| {})
-            .unwrap();
-        let repository = fs
-            .open_repo(Path::new("/foo/.git"), Some("git".as_ref()))
-            .unwrap();
-
-        let checkpoint_1 = repository.checkpoint().await.unwrap();
-        fs.write(Path::new("/foo/b"), b"IPSUM").await.unwrap();
-        fs.write(Path::new("/foo/c"), b"dolor").await.unwrap();
-        let checkpoint_2 = repository.checkpoint().await.unwrap();
-        let checkpoint_3 = repository.checkpoint().await.unwrap();
-
-        assert!(
-            repository
-                .compare_checkpoints(checkpoint_2.clone(), checkpoint_3.clone())
-                .await
-                .unwrap()
-        );
-        assert!(
-            !repository
-                .compare_checkpoints(checkpoint_1.clone(), checkpoint_2.clone())
-                .await
-                .unwrap()
-        );
-
-        repository.restore_checkpoint(checkpoint_1).await.unwrap();
-        assert_eq!(
-            fs.files_with_contents(Path::new("")),
-            [
-                (Path::new(path!("/bar/baz")).into(), b"qux".into()),
-                (Path::new(path!("/foo/a")).into(), b"lorem".into()),
-                (Path::new(path!("/foo/b")).into(), b"ipsum".into())
-            ]
-        );
-    }
-}

crates/fs/src/fs.rs 🔗

@@ -46,21 +46,21 @@ use std::{
 use tempfile::TempDir;
 use text::LineEnding;
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 mod fake_git_repo;
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 use collections::{BTreeMap, btree_map};
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 use fake_git_repo::FakeGitRepositoryState;
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 use git::{
     repository::{RepoPath, repo_path},
     status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus},
 };
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 use smol::io::AsyncReadExt;
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 use std::ffi::OsStr;
 
 pub trait Watcher: Send + Sync {
@@ -152,7 +152,7 @@ pub trait Fs: Send + Sync {
     async fn is_case_sensitive(&self) -> Result<bool>;
     fn subscribe_to_jobs(&self) -> JobEventReceiver;
 
-    #[cfg(any(test, feature = "test-support"))]
+    #[cfg(feature = "test-support")]
     fn as_fake(&self) -> Arc<FakeFs> {
         panic!("called as_fake on a real fs");
     }
@@ -1223,7 +1223,7 @@ impl Watcher for RealWatcher {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 pub struct FakeFs {
     this: std::sync::Weak<Self>,
     // Use an unfair lock to ensure tests are deterministic.
@@ -1231,7 +1231,7 @@ pub struct FakeFs {
     executor: gpui::BackgroundExecutor,
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 struct FakeFsState {
     root: FakeFsEntry,
     next_inode: u64,
@@ -1247,7 +1247,7 @@ struct FakeFsState {
     job_event_subscribers: Arc<Mutex<Vec<JobEventSender>>>,
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 #[derive(Clone, Debug)]
 enum FakeFsEntry {
     File {
@@ -1270,7 +1270,7 @@ enum FakeFsEntry {
     },
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 impl PartialEq for FakeFsEntry {
     fn eq(&self, other: &Self) -> bool {
         match (self, other) {
@@ -1331,7 +1331,7 @@ impl PartialEq for FakeFsEntry {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 impl FakeFsState {
     fn get_and_increment_mtime(&mut self) -> MTime {
         let mtime = self.next_mtime;
@@ -1495,11 +1495,11 @@ impl FakeFsState {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 pub static FS_DOT_GIT: std::sync::LazyLock<&'static OsStr> =
     std::sync::LazyLock::new(|| OsStr::new(".git"));
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 impl FakeFs {
     /// We need to use something large enough for Windows and Unix to consider this a new file.
     /// https://doc.rust-lang.org/nightly/std/time/struct.SystemTime.html#platform-specific-behavior
@@ -1617,50 +1617,58 @@ impl FakeFs {
         new_content: Vec<u8>,
         recreate_inode: bool,
     ) -> Result<()> {
-        let mut state = self.state.lock();
-        let path_buf = path.as_ref().to_path_buf();
-        *state.path_write_counts.entry(path_buf).or_insert(0) += 1;
-        let new_inode = state.get_and_increment_inode();
-        let new_mtime = state.get_and_increment_mtime();
-        let new_len = new_content.len() as u64;
-        let mut kind = None;
-        state.write_path(path.as_ref(), |entry| {
-            match entry {
-                btree_map::Entry::Vacant(e) => {
-                    kind = Some(PathEventKind::Created);
-                    e.insert(FakeFsEntry::File {
-                        inode: new_inode,
-                        mtime: new_mtime,
-                        len: new_len,
-                        content: new_content,
-                        git_dir_path: None,
-                    });
-                }
-                btree_map::Entry::Occupied(mut e) => {
-                    kind = Some(PathEventKind::Changed);
-                    if let FakeFsEntry::File {
-                        inode,
-                        mtime,
-                        len,
-                        content,
-                        ..
-                    } = e.get_mut()
-                    {
-                        *mtime = new_mtime;
-                        *content = new_content;
-                        *len = new_len;
-                        if recreate_inode {
-                            *inode = new_inode;
+        fn inner(
+            this: &FakeFs,
+            path: &Path,
+            new_content: Vec<u8>,
+            recreate_inode: bool,
+        ) -> Result<()> {
+            let mut state = this.state.lock();
+            let path_buf = path.to_path_buf();
+            *state.path_write_counts.entry(path_buf).or_insert(0) += 1;
+            let new_inode = state.get_and_increment_inode();
+            let new_mtime = state.get_and_increment_mtime();
+            let new_len = new_content.len() as u64;
+            let mut kind = None;
+            state.write_path(path, |entry| {
+                match entry {
+                    btree_map::Entry::Vacant(e) => {
+                        kind = Some(PathEventKind::Created);
+                        e.insert(FakeFsEntry::File {
+                            inode: new_inode,
+                            mtime: new_mtime,
+                            len: new_len,
+                            content: new_content,
+                            git_dir_path: None,
+                        });
+                    }
+                    btree_map::Entry::Occupied(mut e) => {
+                        kind = Some(PathEventKind::Changed);
+                        if let FakeFsEntry::File {
+                            inode,
+                            mtime,
+                            len,
+                            content,
+                            ..
+                        } = e.get_mut()
+                        {
+                            *mtime = new_mtime;
+                            *content = new_content;
+                            *len = new_len;
+                            if recreate_inode {
+                                *inode = new_inode;
+                            }
+                        } else {
+                            anyhow::bail!("not a file")
                         }
-                    } else {
-                        anyhow::bail!("not a file")
                     }
                 }
-            }
+                Ok(())
+            })?;
+            state.emit_event([(path, kind)]);
             Ok(())
-        })?;
-        state.emit_event([(path.as_ref(), kind)]);
-        Ok(())
+        }
+        inner(self, path.as_ref(), new_content, recreate_inode)
     }
 
     pub fn read_file_sync(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
@@ -1725,30 +1733,35 @@ impl FakeFs {
         use futures::FutureExt as _;
         use serde_json::Value::*;
 
-        async move {
-            let path = path.as_ref();
-
-            match tree {
-                Object(map) => {
-                    self.create_dir(path).await.unwrap();
-                    for (name, contents) in map {
-                        let mut path = PathBuf::from(path);
-                        path.push(name);
-                        self.insert_tree(&path, contents).await;
+        fn inner<'a>(
+            this: &'a FakeFs,
+            path: Arc<Path>,
+            tree: serde_json::Value,
+        ) -> futures::future::BoxFuture<'a, ()> {
+            async move {
+                match tree {
+                    Object(map) => {
+                        this.create_dir(&path).await.unwrap();
+                        for (name, contents) in map {
+                            let mut path = PathBuf::from(path.as_ref());
+                            path.push(name);
+                            this.insert_tree(&path, contents).await;
+                        }
+                    }
+                    Null => {
+                        this.create_dir(&path).await.unwrap();
+                    }
+                    String(contents) => {
+                        this.insert_file(&path, contents.into_bytes()).await;
+                    }
+                    _ => {
+                        panic!("JSON object must contain only objects, strings, or null");
                     }
-                }
-                Null => {
-                    self.create_dir(path).await.unwrap();
-                }
-                String(contents) => {
-                    self.insert_file(&path, contents.into_bytes()).await;
-                }
-                _ => {
-                    panic!("JSON object must contain only objects, strings, or null");
                 }
             }
+            .boxed()
         }
-        .boxed()
+        inner(self, Arc::from(path.as_ref()), tree)
     }
 
     pub fn insert_tree_from_real_fs<'a>(
@@ -2205,7 +2218,7 @@ impl FakeFs {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 impl FakeFsEntry {
     fn is_file(&self) -> bool {
         matches!(self, Self::File { .. })
@@ -2232,7 +2245,7 @@ impl FakeFsEntry {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 struct FakeWatcher {
     tx: smol::channel::Sender<Vec<PathEvent>>,
     original_path: PathBuf,
@@ -2240,7 +2253,7 @@ struct FakeWatcher {
     prefixes: Mutex<Vec<PathBuf>>,
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 impl Watcher for FakeWatcher {
     fn add(&self, path: &Path) -> Result<()> {
         if path.starts_with(&self.original_path) {
@@ -2260,13 +2273,13 @@ impl Watcher for FakeWatcher {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 #[derive(Debug)]
 struct FakeHandle {
     inode: u64,
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 impl FileHandle for FakeHandle {
     fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf> {
         let fs = fs.as_fake();
@@ -2282,7 +2295,7 @@ impl FileHandle for FakeHandle {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
+#[cfg(feature = "test-support")]
 #[async_trait::async_trait]
 impl Fs for FakeFs {
     async fn create_dir(&self, path: &Path) -> Result<()> {
@@ -2810,7 +2823,7 @@ impl Fs for FakeFs {
         receiver
     }
 
-    #[cfg(any(test, feature = "test-support"))]
+    #[cfg(feature = "test-support")]
     fn as_fake(&self) -> Arc<FakeFs> {
         self.this.upgrade().unwrap()
     }
@@ -2977,590 +2990,3 @@ fn atomic_replace<P: AsRef<Path>>(
         )
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::BackgroundExecutor;
-    use serde_json::json;
-    use util::path;
-
-    #[gpui::test]
-    async fn test_fake_fs(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            path!("/root"),
-            json!({
-                "dir1": {
-                    "a": "A",
-                    "b": "B"
-                },
-                "dir2": {
-                    "c": "C",
-                    "dir3": {
-                        "d": "D"
-                    }
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/root/dir1/a")),
-                PathBuf::from(path!("/root/dir1/b")),
-                PathBuf::from(path!("/root/dir2/c")),
-                PathBuf::from(path!("/root/dir2/dir3/d")),
-            ]
-        );
-
-        fs.create_symlink(path!("/root/dir2/link-to-dir3").as_ref(), "./dir3".into())
-            .await
-            .unwrap();
-
-        assert_eq!(
-            fs.canonicalize(path!("/root/dir2/link-to-dir3").as_ref())
-                .await
-                .unwrap(),
-            PathBuf::from(path!("/root/dir2/dir3")),
-        );
-        assert_eq!(
-            fs.canonicalize(path!("/root/dir2/link-to-dir3/d").as_ref())
-                .await
-                .unwrap(),
-            PathBuf::from(path!("/root/dir2/dir3/d")),
-        );
-        assert_eq!(
-            fs.load(path!("/root/dir2/link-to-dir3/d").as_ref())
-                .await
-                .unwrap(),
-            "D",
-        );
-    }
-
-    #[gpui::test]
-    async fn test_copy_recursive_with_single_file(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            path!("/outer"),
-            json!({
-                "a": "A",
-                "b": "B",
-                "inner": {}
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/a")),
-                PathBuf::from(path!("/outer/b")),
-            ]
-        );
-
-        let source = Path::new(path!("/outer/a"));
-        let target = Path::new(path!("/outer/a copy"));
-        copy_recursive(fs.as_ref(), source, target, Default::default())
-            .await
-            .unwrap();
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/a")),
-                PathBuf::from(path!("/outer/a copy")),
-                PathBuf::from(path!("/outer/b")),
-            ]
-        );
-
-        let source = Path::new(path!("/outer/a"));
-        let target = Path::new(path!("/outer/inner/a copy"));
-        copy_recursive(fs.as_ref(), source, target, Default::default())
-            .await
-            .unwrap();
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/a")),
-                PathBuf::from(path!("/outer/a copy")),
-                PathBuf::from(path!("/outer/b")),
-                PathBuf::from(path!("/outer/inner/a copy")),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_copy_recursive_with_single_dir(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            path!("/outer"),
-            json!({
-                "a": "A",
-                "empty": {},
-                "non-empty": {
-                    "b": "B",
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/a")),
-                PathBuf::from(path!("/outer/non-empty/b")),
-            ]
-        );
-        assert_eq!(
-            fs.directories(false),
-            vec![
-                PathBuf::from(path!("/")),
-                PathBuf::from(path!("/outer")),
-                PathBuf::from(path!("/outer/empty")),
-                PathBuf::from(path!("/outer/non-empty")),
-            ]
-        );
-
-        let source = Path::new(path!("/outer/empty"));
-        let target = Path::new(path!("/outer/empty copy"));
-        copy_recursive(fs.as_ref(), source, target, Default::default())
-            .await
-            .unwrap();
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/a")),
-                PathBuf::from(path!("/outer/non-empty/b")),
-            ]
-        );
-        assert_eq!(
-            fs.directories(false),
-            vec![
-                PathBuf::from(path!("/")),
-                PathBuf::from(path!("/outer")),
-                PathBuf::from(path!("/outer/empty")),
-                PathBuf::from(path!("/outer/empty copy")),
-                PathBuf::from(path!("/outer/non-empty")),
-            ]
-        );
-
-        let source = Path::new(path!("/outer/non-empty"));
-        let target = Path::new(path!("/outer/non-empty copy"));
-        copy_recursive(fs.as_ref(), source, target, Default::default())
-            .await
-            .unwrap();
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/a")),
-                PathBuf::from(path!("/outer/non-empty/b")),
-                PathBuf::from(path!("/outer/non-empty copy/b")),
-            ]
-        );
-        assert_eq!(
-            fs.directories(false),
-            vec![
-                PathBuf::from(path!("/")),
-                PathBuf::from(path!("/outer")),
-                PathBuf::from(path!("/outer/empty")),
-                PathBuf::from(path!("/outer/empty copy")),
-                PathBuf::from(path!("/outer/non-empty")),
-                PathBuf::from(path!("/outer/non-empty copy")),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_copy_recursive(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            path!("/outer"),
-            json!({
-                "inner1": {
-                    "a": "A",
-                    "b": "B",
-                    "inner3": {
-                        "d": "D",
-                    },
-                    "inner4": {}
-                },
-                "inner2": {
-                    "c": "C",
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/inner3/d")),
-            ]
-        );
-        assert_eq!(
-            fs.directories(false),
-            vec![
-                PathBuf::from(path!("/")),
-                PathBuf::from(path!("/outer")),
-                PathBuf::from(path!("/outer/inner1")),
-                PathBuf::from(path!("/outer/inner2")),
-                PathBuf::from(path!("/outer/inner1/inner3")),
-                PathBuf::from(path!("/outer/inner1/inner4")),
-            ]
-        );
-
-        let source = Path::new(path!("/outer"));
-        let target = Path::new(path!("/outer/inner1/outer"));
-        copy_recursive(fs.as_ref(), source, target, Default::default())
-            .await
-            .unwrap();
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/inner3/d")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/inner3/d")),
-            ]
-        );
-        assert_eq!(
-            fs.directories(false),
-            vec![
-                PathBuf::from(path!("/")),
-                PathBuf::from(path!("/outer")),
-                PathBuf::from(path!("/outer/inner1")),
-                PathBuf::from(path!("/outer/inner2")),
-                PathBuf::from(path!("/outer/inner1/inner3")),
-                PathBuf::from(path!("/outer/inner1/inner4")),
-                PathBuf::from(path!("/outer/inner1/outer")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1")),
-                PathBuf::from(path!("/outer/inner1/outer/inner2")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/inner3")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/inner4")),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_copy_recursive_with_overwriting(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            path!("/outer"),
-            json!({
-                "inner1": {
-                    "a": "A",
-                    "b": "B",
-                    "outer": {
-                        "inner1": {
-                            "a": "B"
-                        }
-                    }
-                },
-                "inner2": {
-                    "c": "C",
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
-            ]
-        );
-        assert_eq!(
-            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
-                .await
-                .unwrap(),
-            "B",
-        );
-
-        let source = Path::new(path!("/outer"));
-        let target = Path::new(path!("/outer/inner1/outer"));
-        copy_recursive(
-            fs.as_ref(),
-            source,
-            target,
-            CopyOptions {
-                overwrite: true,
-                ..Default::default()
-            },
-        )
-        .await
-        .unwrap();
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
-            ]
-        );
-        assert_eq!(
-            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
-                .await
-                .unwrap(),
-            "A"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_copy_recursive_with_ignoring(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            path!("/outer"),
-            json!({
-                "inner1": {
-                    "a": "A",
-                    "b": "B",
-                    "outer": {
-                        "inner1": {
-                            "a": "B"
-                        }
-                    }
-                },
-                "inner2": {
-                    "c": "C",
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
-            ]
-        );
-        assert_eq!(
-            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
-                .await
-                .unwrap(),
-            "B",
-        );
-
-        let source = Path::new(path!("/outer"));
-        let target = Path::new(path!("/outer/inner1/outer"));
-        copy_recursive(
-            fs.as_ref(),
-            source,
-            target,
-            CopyOptions {
-                ignore_if_exists: true,
-                ..Default::default()
-            },
-        )
-        .await
-        .unwrap();
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
-                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
-                PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
-            ]
-        );
-        assert_eq!(
-            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
-                .await
-                .unwrap(),
-            "B"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_realfs_atomic_write(executor: BackgroundExecutor) {
-        // With the file handle still open, the file should be replaced
-        // https://github.com/zed-industries/zed/issues/30054
-        let fs = RealFs {
-            bundled_git_binary_path: None,
-            executor,
-            next_job_id: Arc::new(AtomicUsize::new(0)),
-            job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
-        };
-        let temp_dir = TempDir::new().unwrap();
-        let file_to_be_replaced = temp_dir.path().join("file.txt");
-        let mut file = std::fs::File::create_new(&file_to_be_replaced).unwrap();
-        file.write_all(b"Hello").unwrap();
-        // drop(file);  // We still hold the file handle here
-        let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
-        assert_eq!(content, "Hello");
-        smol::block_on(fs.atomic_write(file_to_be_replaced.clone(), "World".into())).unwrap();
-        let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
-        assert_eq!(content, "World");
-    }
-
-    #[gpui::test]
-    async fn test_realfs_atomic_write_non_existing_file(executor: BackgroundExecutor) {
-        let fs = RealFs {
-            bundled_git_binary_path: None,
-            executor,
-            next_job_id: Arc::new(AtomicUsize::new(0)),
-            job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
-        };
-        let temp_dir = TempDir::new().unwrap();
-        let file_to_be_replaced = temp_dir.path().join("file.txt");
-        smol::block_on(fs.atomic_write(file_to_be_replaced.clone(), "Hello".into())).unwrap();
-        let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
-        assert_eq!(content, "Hello");
-    }
-
-    #[gpui::test]
-    #[cfg(target_os = "windows")]
-    async fn test_realfs_canonicalize(executor: BackgroundExecutor) {
-        use util::paths::SanitizedPath;
-
-        let fs = RealFs {
-            bundled_git_binary_path: None,
-            executor,
-            next_job_id: Arc::new(AtomicUsize::new(0)),
-            job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
-        };
-        let temp_dir = TempDir::new().unwrap();
-        let file = temp_dir.path().join("test (1).txt");
-        let file = SanitizedPath::new(&file);
-        std::fs::write(&file, "test").unwrap();
-
-        let canonicalized = fs.canonicalize(file.as_path()).await;
-        assert!(canonicalized.is_ok());
-    }
-
-    #[gpui::test]
-    async fn test_rename(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            path!("/root"),
-            json!({
-                "src": {
-                    "file_a.txt": "content a",
-                    "file_b.txt": "content b"
-                }
-            }),
-        )
-        .await;
-
-        fs.rename(
-            Path::new(path!("/root/src/file_a.txt")),
-            Path::new(path!("/root/src/new/renamed_a.txt")),
-            RenameOptions {
-                create_parents: true,
-                ..Default::default()
-            },
-        )
-        .await
-        .unwrap();
-
-        // Assert that the `file_a.txt` file was being renamed and moved to a
-        // different directory that did not exist before.
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/root/src/file_b.txt")),
-                PathBuf::from(path!("/root/src/new/renamed_a.txt")),
-            ]
-        );
-
-        let result = fs
-            .rename(
-                Path::new(path!("/root/src/file_b.txt")),
-                Path::new(path!("/root/src/old/renamed_b.txt")),
-                RenameOptions {
-                    create_parents: false,
-                    ..Default::default()
-                },
-            )
-            .await;
-
-        // Assert that the `file_b.txt` file was not renamed nor moved, as
-        // `create_parents` was set to `false`.
-        // different directory that did not exist before.
-        assert!(result.is_err());
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from(path!("/root/src/file_b.txt")),
-                PathBuf::from(path!("/root/src/new/renamed_a.txt")),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    #[cfg(unix)]
-    async fn test_realfs_broken_symlink_metadata(executor: BackgroundExecutor) {
-        let tempdir = TempDir::new().unwrap();
-        let path = tempdir.path();
-        let fs = RealFs {
-            bundled_git_binary_path: None,
-            executor,
-            next_job_id: Arc::new(AtomicUsize::new(0)),
-            job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
-        };
-        let symlink_path = path.join("symlink");
-        smol::block_on(fs.create_symlink(&symlink_path, PathBuf::from("file_a.txt"))).unwrap();
-        let metadata = fs
-            .metadata(&symlink_path)
-            .await
-            .expect("metadata call succeeds")
-            .expect("metadata returned");
-        assert!(metadata.is_symlink);
-        assert!(!metadata.is_dir);
-        assert!(!metadata.is_fifo);
-        assert!(!metadata.is_executable);
-        // don't care about len or mtime on symlinks?
-    }
-
-    #[gpui::test]
-    #[cfg(unix)]
-    async fn test_realfs_symlink_loop_metadata(executor: BackgroundExecutor) {
-        let tempdir = TempDir::new().unwrap();
-        let path = tempdir.path();
-        let fs = RealFs {
-            bundled_git_binary_path: None,
-            executor,
-            next_job_id: Arc::new(AtomicUsize::new(0)),
-            job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
-        };
-        let symlink_path = path.join("symlink");
-        smol::block_on(fs.create_symlink(&symlink_path, PathBuf::from("symlink"))).unwrap();
-        let metadata = fs
-            .metadata(&symlink_path)
-            .await
-            .expect("metadata call succeeds")
-            .expect("metadata returned");
-        assert!(metadata.is_symlink);
-        assert!(!metadata.is_dir);
-        assert!(!metadata.is_fifo);
-        assert!(!metadata.is_executable);
-        // don't care about len or mtime on symlinks?
-    }
-}

crates/fs/tests/integration/fake_git_repo.rs 🔗

@@ -0,0 +1,58 @@
+use fs::{FakeFs, Fs};
+use gpui::BackgroundExecutor;
+use serde_json::json;
+use std::path::Path;
+use util::path;
+
+#[gpui::test]
+async fn test_checkpoints(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor);
+    fs.insert_tree(
+        path!("/"),
+        json!({
+            "bar": {
+                "baz": "qux"
+            },
+            "foo": {
+                ".git": {},
+                "a": "lorem",
+                "b": "ipsum",
+            },
+        }),
+    )
+    .await;
+    fs.with_git_state(Path::new("/foo/.git"), true, |_git| {})
+        .unwrap();
+    let repository = fs
+        .open_repo(Path::new("/foo/.git"), Some("git".as_ref()))
+        .unwrap();
+
+    let checkpoint_1 = repository.checkpoint().await.unwrap();
+    fs.write(Path::new("/foo/b"), b"IPSUM").await.unwrap();
+    fs.write(Path::new("/foo/c"), b"dolor").await.unwrap();
+    let checkpoint_2 = repository.checkpoint().await.unwrap();
+    let checkpoint_3 = repository.checkpoint().await.unwrap();
+
+    assert!(
+        repository
+            .compare_checkpoints(checkpoint_2.clone(), checkpoint_3.clone())
+            .await
+            .unwrap()
+    );
+    assert!(
+        !repository
+            .compare_checkpoints(checkpoint_1.clone(), checkpoint_2.clone())
+            .await
+            .unwrap()
+    );
+
+    repository.restore_checkpoint(checkpoint_1).await.unwrap();
+    assert_eq!(
+        fs.files_with_contents(Path::new("")),
+        [
+            (Path::new(path!("/bar/baz")).into(), b"qux".into()),
+            (Path::new(path!("/foo/a")).into(), b"lorem".into()),
+            (Path::new(path!("/foo/b")).into(), b"ipsum".into())
+        ]
+    );
+}

crates/fs/tests/integration/fs.rs 🔗

@@ -0,0 +1,564 @@
+use std::{
+    io::Write,
+    path::{Path, PathBuf},
+};
+
+use fs::*;
+use gpui::BackgroundExecutor;
+use serde_json::json;
+use tempfile::TempDir;
+use util::path;
+
+#[gpui::test]
+async fn test_fake_fs(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor.clone());
+    fs.insert_tree(
+        path!("/root"),
+        json!({
+            "dir1": {
+                "a": "A",
+                "b": "B"
+            },
+            "dir2": {
+                "c": "C",
+                "dir3": {
+                    "d": "D"
+                }
+            }
+        }),
+    )
+    .await;
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/root/dir1/a")),
+            PathBuf::from(path!("/root/dir1/b")),
+            PathBuf::from(path!("/root/dir2/c")),
+            PathBuf::from(path!("/root/dir2/dir3/d")),
+        ]
+    );
+
+    fs.create_symlink(path!("/root/dir2/link-to-dir3").as_ref(), "./dir3".into())
+        .await
+        .unwrap();
+
+    assert_eq!(
+        fs.canonicalize(path!("/root/dir2/link-to-dir3").as_ref())
+            .await
+            .unwrap(),
+        PathBuf::from(path!("/root/dir2/dir3")),
+    );
+    assert_eq!(
+        fs.canonicalize(path!("/root/dir2/link-to-dir3/d").as_ref())
+            .await
+            .unwrap(),
+        PathBuf::from(path!("/root/dir2/dir3/d")),
+    );
+    assert_eq!(
+        fs.load(path!("/root/dir2/link-to-dir3/d").as_ref())
+            .await
+            .unwrap(),
+        "D",
+    );
+}
+
+#[gpui::test]
+async fn test_copy_recursive_with_single_file(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor.clone());
+    fs.insert_tree(
+        path!("/outer"),
+        json!({
+            "a": "A",
+            "b": "B",
+            "inner": {}
+        }),
+    )
+    .await;
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/a")),
+            PathBuf::from(path!("/outer/b")),
+        ]
+    );
+
+    let source = Path::new(path!("/outer/a"));
+    let target = Path::new(path!("/outer/a copy"));
+    copy_recursive(fs.as_ref(), source, target, Default::default())
+        .await
+        .unwrap();
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/a")),
+            PathBuf::from(path!("/outer/a copy")),
+            PathBuf::from(path!("/outer/b")),
+        ]
+    );
+
+    let source = Path::new(path!("/outer/a"));
+    let target = Path::new(path!("/outer/inner/a copy"));
+    copy_recursive(fs.as_ref(), source, target, Default::default())
+        .await
+        .unwrap();
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/a")),
+            PathBuf::from(path!("/outer/a copy")),
+            PathBuf::from(path!("/outer/b")),
+            PathBuf::from(path!("/outer/inner/a copy")),
+        ]
+    );
+}
+
+#[gpui::test]
+async fn test_copy_recursive_with_single_dir(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor.clone());
+    fs.insert_tree(
+        path!("/outer"),
+        json!({
+            "a": "A",
+            "empty": {},
+            "non-empty": {
+                "b": "B",
+            }
+        }),
+    )
+    .await;
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/a")),
+            PathBuf::from(path!("/outer/non-empty/b")),
+        ]
+    );
+    assert_eq!(
+        fs.directories(false),
+        vec![
+            PathBuf::from(path!("/")),
+            PathBuf::from(path!("/outer")),
+            PathBuf::from(path!("/outer/empty")),
+            PathBuf::from(path!("/outer/non-empty")),
+        ]
+    );
+
+    let source = Path::new(path!("/outer/empty"));
+    let target = Path::new(path!("/outer/empty copy"));
+    copy_recursive(fs.as_ref(), source, target, Default::default())
+        .await
+        .unwrap();
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/a")),
+            PathBuf::from(path!("/outer/non-empty/b")),
+        ]
+    );
+    assert_eq!(
+        fs.directories(false),
+        vec![
+            PathBuf::from(path!("/")),
+            PathBuf::from(path!("/outer")),
+            PathBuf::from(path!("/outer/empty")),
+            PathBuf::from(path!("/outer/empty copy")),
+            PathBuf::from(path!("/outer/non-empty")),
+        ]
+    );
+
+    let source = Path::new(path!("/outer/non-empty"));
+    let target = Path::new(path!("/outer/non-empty copy"));
+    copy_recursive(fs.as_ref(), source, target, Default::default())
+        .await
+        .unwrap();
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/a")),
+            PathBuf::from(path!("/outer/non-empty/b")),
+            PathBuf::from(path!("/outer/non-empty copy/b")),
+        ]
+    );
+    assert_eq!(
+        fs.directories(false),
+        vec![
+            PathBuf::from(path!("/")),
+            PathBuf::from(path!("/outer")),
+            PathBuf::from(path!("/outer/empty")),
+            PathBuf::from(path!("/outer/empty copy")),
+            PathBuf::from(path!("/outer/non-empty")),
+            PathBuf::from(path!("/outer/non-empty copy")),
+        ]
+    );
+}
+
+#[gpui::test]
+async fn test_copy_recursive(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor.clone());
+    fs.insert_tree(
+        path!("/outer"),
+        json!({
+            "inner1": {
+                "a": "A",
+                "b": "B",
+                "inner3": {
+                    "d": "D",
+                },
+                "inner4": {}
+            },
+            "inner2": {
+                "c": "C",
+            }
+        }),
+    )
+    .await;
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/inner3/d")),
+        ]
+    );
+    assert_eq!(
+        fs.directories(false),
+        vec![
+            PathBuf::from(path!("/")),
+            PathBuf::from(path!("/outer")),
+            PathBuf::from(path!("/outer/inner1")),
+            PathBuf::from(path!("/outer/inner2")),
+            PathBuf::from(path!("/outer/inner1/inner3")),
+            PathBuf::from(path!("/outer/inner1/inner4")),
+        ]
+    );
+
+    let source = Path::new(path!("/outer"));
+    let target = Path::new(path!("/outer/inner1/outer"));
+    copy_recursive(fs.as_ref(), source, target, Default::default())
+        .await
+        .unwrap();
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/inner3/d")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/inner3/d")),
+        ]
+    );
+    assert_eq!(
+        fs.directories(false),
+        vec![
+            PathBuf::from(path!("/")),
+            PathBuf::from(path!("/outer")),
+            PathBuf::from(path!("/outer/inner1")),
+            PathBuf::from(path!("/outer/inner2")),
+            PathBuf::from(path!("/outer/inner1/inner3")),
+            PathBuf::from(path!("/outer/inner1/inner4")),
+            PathBuf::from(path!("/outer/inner1/outer")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1")),
+            PathBuf::from(path!("/outer/inner1/outer/inner2")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/inner3")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/inner4")),
+        ]
+    );
+}
+
+#[gpui::test]
+async fn test_copy_recursive_with_overwriting(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor.clone());
+    fs.insert_tree(
+        path!("/outer"),
+        json!({
+            "inner1": {
+                "a": "A",
+                "b": "B",
+                "outer": {
+                    "inner1": {
+                        "a": "B"
+                    }
+                }
+            },
+            "inner2": {
+                "c": "C",
+            }
+        }),
+    )
+    .await;
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
+        ]
+    );
+    assert_eq!(
+        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
+            .await
+            .unwrap(),
+        "B",
+    );
+
+    let source = Path::new(path!("/outer"));
+    let target = Path::new(path!("/outer/inner1/outer"));
+    copy_recursive(
+        fs.as_ref(),
+        source,
+        target,
+        CopyOptions {
+            overwrite: true,
+            ..Default::default()
+        },
+    )
+    .await
+    .unwrap();
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
+        ]
+    );
+    assert_eq!(
+        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
+            .await
+            .unwrap(),
+        "A"
+    );
+}
+
+#[gpui::test]
+async fn test_copy_recursive_with_ignoring(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor.clone());
+    fs.insert_tree(
+        path!("/outer"),
+        json!({
+            "inner1": {
+                "a": "A",
+                "b": "B",
+                "outer": {
+                    "inner1": {
+                        "a": "B"
+                    }
+                }
+            },
+            "inner2": {
+                "c": "C",
+            }
+        }),
+    )
+    .await;
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
+        ]
+    );
+    assert_eq!(
+        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
+            .await
+            .unwrap(),
+        "B",
+    );
+
+    let source = Path::new(path!("/outer"));
+    let target = Path::new(path!("/outer/inner1/outer"));
+    copy_recursive(
+        fs.as_ref(),
+        source,
+        target,
+        CopyOptions {
+            ignore_if_exists: true,
+            ..Default::default()
+        },
+    )
+    .await
+    .unwrap();
+
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
+            PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
+            PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
+        ]
+    );
+    assert_eq!(
+        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
+            .await
+            .unwrap(),
+        "B"
+    );
+}
+
+#[gpui::test]
+async fn test_realfs_atomic_write(executor: BackgroundExecutor) {
+    // With the file handle still open, the file should be replaced
+    // https://github.com/zed-industries/zed/issues/30054
+    let fs = RealFs::new(None, executor);
+    let temp_dir = TempDir::new().unwrap();
+    let file_to_be_replaced = temp_dir.path().join("file.txt");
+    let mut file = std::fs::File::create_new(&file_to_be_replaced).unwrap();
+    file.write_all(b"Hello").unwrap();
+    // drop(file);  // We still hold the file handle here
+    let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
+    assert_eq!(content, "Hello");
+    smol::block_on(fs.atomic_write(file_to_be_replaced.clone(), "World".into())).unwrap();
+    let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
+    assert_eq!(content, "World");
+}
+
+#[gpui::test]
+async fn test_realfs_atomic_write_non_existing_file(executor: BackgroundExecutor) {
+    let fs = RealFs::new(None, executor);
+    let temp_dir = TempDir::new().unwrap();
+    let file_to_be_replaced = temp_dir.path().join("file.txt");
+    smol::block_on(fs.atomic_write(file_to_be_replaced.clone(), "Hello".into())).unwrap();
+    let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
+    assert_eq!(content, "Hello");
+}
+
+#[gpui::test]
+#[cfg(target_os = "windows")]
+async fn test_realfs_canonicalize(executor: BackgroundExecutor) {
+    use util::paths::SanitizedPath;
+
+    let fs = RealFs::new(None, executor);
+    let temp_dir = TempDir::new().unwrap();
+    let file = temp_dir.path().join("test (1).txt");
+    let file = SanitizedPath::new(&file);
+    std::fs::write(&file, "test").unwrap();
+
+    let canonicalized = fs.canonicalize(file.as_path()).await;
+    assert!(canonicalized.is_ok());
+}
+
+#[gpui::test]
+async fn test_rename(executor: BackgroundExecutor) {
+    let fs = FakeFs::new(executor.clone());
+    fs.insert_tree(
+        path!("/root"),
+        json!({
+            "src": {
+                "file_a.txt": "content a",
+                "file_b.txt": "content b"
+            }
+        }),
+    )
+    .await;
+
+    fs.rename(
+        Path::new(path!("/root/src/file_a.txt")),
+        Path::new(path!("/root/src/new/renamed_a.txt")),
+        RenameOptions {
+            create_parents: true,
+            ..Default::default()
+        },
+    )
+    .await
+    .unwrap();
+
+    // Assert that the `file_a.txt` file was being renamed and moved to a
+    // different directory that did not exist before.
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/root/src/file_b.txt")),
+            PathBuf::from(path!("/root/src/new/renamed_a.txt")),
+        ]
+    );
+
+    let result = fs
+        .rename(
+            Path::new(path!("/root/src/file_b.txt")),
+            Path::new(path!("/root/src/old/renamed_b.txt")),
+            RenameOptions {
+                create_parents: false,
+                ..Default::default()
+            },
+        )
+        .await;
+
+    // Assert that the `file_b.txt` file was not renamed nor moved, as
+    // `create_parents` was set to `false`.
+    // different directory that did not exist before.
+    assert!(result.is_err());
+    assert_eq!(
+        fs.files(),
+        vec![
+            PathBuf::from(path!("/root/src/file_b.txt")),
+            PathBuf::from(path!("/root/src/new/renamed_a.txt")),
+        ]
+    );
+}
+
+#[gpui::test]
+#[cfg(unix)]
+async fn test_realfs_broken_symlink_metadata(executor: BackgroundExecutor) {
+    let tempdir = TempDir::new().unwrap();
+    let path = tempdir.path();
+    let fs = RealFs::new(None, executor);
+    let symlink_path = path.join("symlink");
+    smol::block_on(fs.create_symlink(&symlink_path, PathBuf::from("file_a.txt"))).unwrap();
+    let metadata = fs
+        .metadata(&symlink_path)
+        .await
+        .expect("metadata call succeeds")
+        .expect("metadata returned");
+    assert!(metadata.is_symlink);
+    assert!(!metadata.is_dir);
+    assert!(!metadata.is_fifo);
+    assert!(!metadata.is_executable);
+    // don't care about len or mtime on symlinks?
+}
+
+#[gpui::test]
+#[cfg(unix)]
+async fn test_realfs_symlink_loop_metadata(executor: BackgroundExecutor) {
+    let tempdir = TempDir::new().unwrap();
+    let path = tempdir.path();
+    let fs = RealFs::new(None, executor);
+    let symlink_path = path.join("symlink");
+    smol::block_on(fs.create_symlink(&symlink_path, PathBuf::from("symlink"))).unwrap();
+    let metadata = fs
+        .metadata(&symlink_path)
+        .await
+        .expect("metadata call succeeds")
+        .expect("metadata returned");
+    assert!(metadata.is_symlink);
+    assert!(!metadata.is_dir);
+    assert!(!metadata.is_fifo);
+    assert!(!metadata.is_executable);
+    // don't care about len or mtime on symlinks?
+}