Cargo.lock 🔗
@@ -6498,6 +6498,7 @@ dependencies = [
"async-trait",
"cocoa 0.26.0",
"collections",
+ "fs",
"fsevent",
"futures 0.3.31",
"git",
Piotr Osiewicz created
This reduces time needed to build tests for fs from 1.2s to 0.6s.
Release Notes:
- N/A
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(-)
@@ -6498,6 +6498,7 @@ dependencies = [
"async-trait",
"cocoa 0.26.0",
"collections",
+ "fs",
"fsevent",
"futures 0.3.31",
"git",
@@ -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"] }
@@ -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())
- ]
- );
- }
-}
@@ -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?
- }
-}
@@ -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())
+ ]
+ );
+}
@@ -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?
+}
@@ -0,0 +1,2 @@
+mod fake_git_repo;
+mod fs;