Files
zed/crates/fs/tests/integration/fs.rs
T
Piotr Osiewicz 2be94ce893 fs: Use public-api-only tests (#47149)
This reduces time needed to build tests for fs from 1.2s to 0.6s.
Release Notes:

- N/A
2026-01-22 10:22:29 +01:00

565 lines
16 KiB
Rust

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?
}