logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled

Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree
with a 3-file patch applied (no upstream history).

Patch (crates/gpui_linux/src/linux/wayland/):
  - serial.rs: add SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; add latest_serial_of()
  - window.rs: activate() uses keyboard-enter serial (Mutter focus gate)

Mutter honors window activation only when the token carries the keyboard-
focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial.
See docs/tray-window-focus-wayland.md in logiguard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 02:22:17 +03:30
commit b72a46db68
3984 changed files with 1583326 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
use fs::{FakeFs, Fs};
use gpui::{BackgroundExecutor, TestAppContext};
use serde_json::json;
use std::path::{Path, PathBuf};
use util::path;
#[gpui::test]
async fn test_fake_worktree_lifecycle(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/project", json!({".git": {}, "file.txt": "content"}))
.await;
let repo = fs
.open_repo(Path::new("/project/.git"), None)
.expect("should open fake repo");
// Initially only the main worktree exists
let worktrees = repo.worktrees().await.unwrap();
assert_eq!(worktrees.len(), 1);
assert_eq!(worktrees[0].path, PathBuf::from("/project"));
fs.create_dir("/my-worktrees".as_ref()).await.unwrap();
let worktrees_dir = Path::new("/my-worktrees");
// Create a worktree
let worktree_1_dir = worktrees_dir.join("feature-branch");
repo.create_worktree(
git::repository::CreateWorktreeTarget::NewBranch {
branch_name: "feature-branch".to_string(),
base_sha: Some("abc123".to_string()),
},
worktree_1_dir.clone(),
)
.await
.unwrap();
// List worktrees — should have main + one created
let worktrees = repo.worktrees().await.unwrap();
assert_eq!(worktrees.len(), 2);
assert_eq!(worktrees[0].path, PathBuf::from("/project"));
assert_eq!(worktrees[1].path, worktree_1_dir);
assert_eq!(
worktrees[1].ref_name,
Some("refs/heads/feature-branch".into())
);
assert_eq!(worktrees[1].sha.as_ref(), "abc123");
// Directory should exist in FakeFs after create
assert!(fs.is_dir(&worktrees_dir.join("feature-branch")).await);
// Create a second worktree (without explicit commit)
let worktree_2_dir = worktrees_dir.join("bugfix-branch");
repo.create_worktree(
git::repository::CreateWorktreeTarget::NewBranch {
branch_name: "bugfix-branch".to_string(),
base_sha: None,
},
worktree_2_dir.clone(),
)
.await
.unwrap();
let worktrees = repo.worktrees().await.unwrap();
assert_eq!(worktrees.len(), 3);
assert!(fs.is_dir(&worktree_2_dir).await);
// Rename the first worktree
repo.rename_worktree(worktree_1_dir, worktrees_dir.join("renamed-branch"))
.await
.unwrap();
let worktrees = repo.worktrees().await.unwrap();
assert_eq!(worktrees.len(), 3);
assert!(
worktrees
.iter()
.any(|w| w.path == worktrees_dir.join("renamed-branch")),
);
assert!(
worktrees
.iter()
.all(|w| w.path != worktrees_dir.join("feature-branch")),
);
// Directory should be moved in FakeFs after rename
assert!(!fs.is_dir(&worktrees_dir.join("feature-branch")).await);
assert!(fs.is_dir(&worktrees_dir.join("renamed-branch")).await);
// Rename a nonexistent worktree should fail
let result = repo
.rename_worktree(PathBuf::from("/nonexistent"), PathBuf::from("/somewhere"))
.await;
assert!(result.is_err());
// Remove a worktree
repo.remove_worktree(worktrees_dir.join("renamed-branch"), false)
.await
.unwrap();
let worktrees = repo.worktrees().await.unwrap();
assert_eq!(worktrees.len(), 2);
assert_eq!(worktrees[0].path, PathBuf::from("/project"));
assert_eq!(worktrees[1].path, worktree_2_dir);
// Directory should be removed from FakeFs after remove
assert!(!fs.is_dir(&worktrees_dir.join("renamed-branch")).await);
// Remove a nonexistent worktree should fail
let result = repo
.remove_worktree(PathBuf::from("/nonexistent"), false)
.await;
assert!(result.is_err());
// Remove the last worktree
repo.remove_worktree(worktree_2_dir.clone(), false)
.await
.unwrap();
let worktrees = repo.worktrees().await.unwrap();
assert_eq!(worktrees.len(), 1);
assert_eq!(worktrees[0].path, PathBuf::from("/project"));
assert!(!fs.is_dir(&worktree_2_dir).await);
}
#[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.clone())
.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())
]
);
// diff_checkpoints: identical checkpoints produce empty diff
let diff = repository
.diff_checkpoints(checkpoint_2.clone(), checkpoint_3.clone())
.await
.unwrap();
assert!(
diff.is_empty(),
"identical checkpoints should produce empty diff"
);
// diff_checkpoints: different checkpoints produce non-empty diff
let diff = repository
.diff_checkpoints(checkpoint_1.clone(), checkpoint_2.clone())
.await
.unwrap();
assert!(diff.contains("b"), "diff should mention changed file 'b'");
assert!(diff.contains("c"), "diff should mention added file 'c'");
}

View File

@@ -0,0 +1,914 @@
use std::{
collections::BTreeSet,
ffi::OsString,
io::Write,
path::{Path, PathBuf},
time::Duration,
};
use futures::{FutureExt, StreamExt};
use fs::*;
use gpui::{BackgroundExecutor, TestAppContext};
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");
gpui::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");
gpui::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(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
async fn test_realfs_parallel_rename_without_overwrite_preserves_losing_source(
executor: BackgroundExecutor,
) {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
let source_a = root.join("dir_a/shared.txt");
let source_b = root.join("dir_b/shared.txt");
let target = root.join("shared.txt");
std::fs::create_dir_all(source_a.parent().unwrap()).unwrap();
std::fs::create_dir_all(source_b.parent().unwrap()).unwrap();
std::fs::write(&source_a, "from a").unwrap();
std::fs::write(&source_b, "from b").unwrap();
let fs = RealFs::new(None, executor);
let (first_result, second_result) = futures::future::join(
fs.rename(&source_a, &target, RenameOptions::default()),
fs.rename(&source_b, &target, RenameOptions::default()),
)
.await;
assert_ne!(first_result.is_ok(), second_result.is_ok());
assert!(target.exists());
assert_eq!(source_a.exists() as u8 + source_b.exists() as u8, 1);
}
#[gpui::test]
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
async fn test_realfs_rename_ignore_if_exists_leaves_source_and_target_unchanged(
executor: BackgroundExecutor,
) {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
let source = root.join("source.txt");
let target = root.join("target.txt");
std::fs::write(&source, "from source").unwrap();
std::fs::write(&target, "from target").unwrap();
let fs = RealFs::new(None, executor);
let result = fs
.rename(
&source,
&target,
RenameOptions {
ignore_if_exists: true,
..Default::default()
},
)
.await;
assert!(result.is_ok());
assert_eq!(std::fs::read_to_string(&source).unwrap(), "from source");
assert_eq!(std::fs::read_to_string(&target).unwrap(), "from target");
}
#[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");
gpui::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");
gpui::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?
}
#[gpui::test]
async fn test_fake_fs_trash(executor: BackgroundExecutor) {
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
path!("/root"),
json!({
"src": {
"file_c.txt": "File C",
"file_d.txt": "File D"
},
"file_a.txt": "File A",
"file_b.txt": "File B",
}),
)
.await;
// Trashing a file.
let root_path = PathBuf::from(path!("/root"));
let path = path!("/root/file_a.txt").as_ref();
let trashed_entry = fs
.trash(path, Default::default())
.await
.expect("should be able to trash {path:?}");
assert_eq!(trashed_entry.name, "file_a.txt");
assert_eq!(trashed_entry.original_parent, root_path);
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/file_b.txt")),
PathBuf::from(path!("/root/src/file_c.txt")),
PathBuf::from(path!("/root/src/file_d.txt"))
]
);
let trash_entries = fs.trash_entries();
assert_eq!(trash_entries.len(), 1);
assert_eq!(trash_entries[0].name, "file_a.txt");
assert_eq!(trash_entries[0].original_parent, root_path);
// Trashing a directory.
let path = path!("/root/src").as_ref();
let trashed_entry = fs
.trash(
path,
RemoveOptions {
recursive: true,
..Default::default()
},
)
.await
.expect("should be able to trash {path:?}");
assert_eq!(trashed_entry.name, "src");
assert_eq!(trashed_entry.original_parent, root_path);
assert_eq!(fs.files(), vec![PathBuf::from(path!("/root/file_b.txt"))]);
let trash_entries = fs.trash_entries();
assert_eq!(trash_entries.len(), 2);
assert_eq!(trash_entries[1].name, "src");
assert_eq!(trash_entries[1].original_parent, root_path);
}
#[gpui::test]
async fn test_fake_fs_restore(executor: BackgroundExecutor) {
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
path!("/root"),
json!({
"src": {
"file_a.txt": "File A",
"file_b.txt": "File B",
},
"file_c.txt": "File C",
}),
)
.await;
// Providing a non-existent `TrashedEntry` should result in an error.
let id = OsString::from("/trash/file_c.txt");
let name = OsString::from("file_c.txt");
let original_parent = PathBuf::from(path!("/root"));
let trashed_entry = TrashedEntry {
id,
name,
original_parent,
};
let result = fs.restore(trashed_entry).await;
assert!(matches!(result, Err(TrashRestoreError::NotFound { .. })));
// Attempt deleting a file, asserting that the filesystem no longer reports
// it as part of its list of files, restore it and verify that the list of
// files and trash has been updated accordingly.
let path = path!("/root/src/file_a.txt").as_ref();
let trashed_entry = fs.trash(path, Default::default()).await.unwrap();
assert_eq!(fs.trash_entries().len(), 1);
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/file_c.txt")),
PathBuf::from(path!("/root/src/file_b.txt"))
]
);
fs.restore(trashed_entry).await.unwrap();
assert_eq!(fs.trash_entries().len(), 0);
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/file_c.txt")),
PathBuf::from(path!("/root/src/file_a.txt")),
PathBuf::from(path!("/root/src/file_b.txt"))
]
);
// Deleting and restoring a directory should also remove all of its files
// but create a single trashed entry, which should be removed after
// restoration.
let options = RemoveOptions {
recursive: true,
..Default::default()
};
let path = path!("/root/src/").as_ref();
let trashed_entry = fs.trash(path, options).await.unwrap();
assert_eq!(fs.trash_entries().len(), 1);
assert_eq!(fs.files(), vec![PathBuf::from(path!("/root/file_c.txt"))]);
fs.restore(trashed_entry).await.unwrap();
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/file_c.txt")),
PathBuf::from(path!("/root/src/file_a.txt")),
PathBuf::from(path!("/root/src/file_b.txt"))
]
);
assert_eq!(fs.trash_entries().len(), 0);
// A collision error should be returned in case a file is being restored to
// a path where a file already exists.
let path = path!("/root/src/file_a.txt").as_ref();
let trashed_entry = fs.trash(path, Default::default()).await.unwrap();
assert_eq!(fs.trash_entries().len(), 1);
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/file_c.txt")),
PathBuf::from(path!("/root/src/file_b.txt"))
]
);
fs.write(path, "New File A".as_bytes()).await.unwrap();
assert_eq!(fs.trash_entries().len(), 1);
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/file_c.txt")),
PathBuf::from(path!("/root/src/file_a.txt")),
PathBuf::from(path!("/root/src/file_b.txt"))
]
);
let file_contents = fs.files_with_contents(path);
assert!(fs.restore(trashed_entry).await.is_err());
assert_eq!(
file_contents,
vec![(PathBuf::from(path), b"New File A".to_vec())]
);
// A collision error should be returned in case a directory is being
// restored to a path where a directory already exists.
let options = RemoveOptions {
recursive: true,
..Default::default()
};
let path = path!("/root/src/").as_ref();
let trashed_entry = fs.trash(path, options).await.unwrap();
assert_eq!(fs.trash_entries().len(), 2);
assert_eq!(fs.files(), vec![PathBuf::from(path!("/root/file_c.txt"))]);
fs.create_dir(path).await.unwrap();
assert_eq!(fs.files(), vec![PathBuf::from(path!("/root/file_c.txt"))]);
assert_eq!(fs.trash_entries().len(), 2);
let result = fs.restore(trashed_entry).await;
assert!(result.is_err());
assert_eq!(fs.files(), vec![PathBuf::from(path!("/root/file_c.txt"))]);
assert_eq!(fs.trash_entries().len(), 2);
}
#[gpui::test]
#[ignore = "stress test; run explicitly when needed"]
async fn test_realfs_watch_stress_reports_missed_paths(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
const FILE_COUNT: usize = 32000;
cx.executor().allow_parking();
let fs = RealFs::new(None, executor.clone());
let temp_dir = TempDir::new().expect("create temp dir");
let root = temp_dir.path();
let mut file_paths = Vec::with_capacity(FILE_COUNT);
let mut expected_paths = BTreeSet::new();
for index in 0..FILE_COUNT {
let dir_path = root.join(format!("dir-{index:04}"));
let file_path = dir_path.join("file.txt");
fs.create_dir(&dir_path).await.expect("create watched dir");
fs.write(&file_path, b"before")
.await
.expect("create initial file");
expected_paths.insert(file_path.clone());
file_paths.push(file_path);
}
let (mut events, watcher) = fs.watch(root, Duration::from_millis(10)).await;
let _watcher = watcher;
for file_path in &expected_paths {
_watcher
.add(file_path.parent().expect("file has parent"))
.expect("add explicit directory watch");
}
for (index, file_path) in file_paths.iter().enumerate() {
let content = format!("after-{index}");
fs.write(file_path, content.as_bytes())
.await
.expect("modify watched file");
}
let mut changed_paths = BTreeSet::new();
let mut rescan_count: u32 = 0;
let timeout = executor.timer(Duration::from_secs(10)).fuse();
futures::pin_mut!(timeout);
let mut ticks = 0;
while ticks < 1000 {
if let Some(batch) = events.next().fuse().now_or_never().flatten() {
for event in batch {
if event.kind == Some(PathEventKind::Rescan) {
rescan_count += 1;
}
if expected_paths.contains(&event.path) {
changed_paths.insert(event.path);
}
}
if changed_paths.len() == expected_paths.len() {
break;
}
ticks = 0;
} else {
ticks += 1;
executor.timer(Duration::from_millis(10)).await;
}
}
let missed_paths: BTreeSet<_> = expected_paths.difference(&changed_paths).cloned().collect();
eprintln!(
"realfs watch stress: expected={}, observed={}, missed={}, rescan={}",
expected_paths.len(),
changed_paths.len(),
missed_paths.len(),
rescan_count
);
assert!(
missed_paths.is_empty() || rescan_count > 0,
"missed {} paths without rescan being reported",
missed_paths.len()
);
}

View File

@@ -0,0 +1,2 @@
mod fake_git_repo;
mod fs;