Skip to content

Commit 241707e

Browse files
committed
chore: expanad fuzzy test suite
1 parent ba2075a commit 241707e

9 files changed

Lines changed: 888 additions & 96 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ zlob = "1.3.3"
3636
mlua = { version = "0.11.1", features = ["module", "luajit"] }
3737
neo_frizbee = { version = "0.10.2", features = ["match_end_col"] }
3838
notify = { version = "9.0.0-rc.3" }
39-
notify-debouncer-full = { package = "fff-notify-debouncer-full", version = "0.9.3" }
39+
notify-debouncer-full = { package = "fff-notify-debouncer-full", version = "0.9.4" }
4040
once_cell = "1.20.2"
4141
parking_lot = "0.12"
4242
pathdiff = "0.2.1"

Makefile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ INCLUDEDIR ?= $(PREFIX)/include
88
STRESS_RUSTFLAGS := --cfg stress
99
FFF_STRESS_DEFAULT_SEED ?= 0xDEADBEEFCAFEBABE
1010

11-
.PHONY: build build-c-lib install uninstall test test-rust test-lua test-version test-bun test-node prepare-bun prepare-node set-npm-version header test-stress test-stress-seeded test-stress-random
11+
.PHONY: build build-c-lib install uninstall test test-rust test-lua test-version test-bun test-node prepare-bun prepare-node set-npm-version header test-stress test-stress-seeded test-stress-random test-stress-repos
1212

1313
all: format test lint
1414

@@ -101,21 +101,29 @@ test: test-rust test-lua test-version test-bun test-node
101101
test-stress-seeded:
102102
FFF_STRESS_SEED="$${FFF_STRESS_SEED:-$(FFF_STRESS_DEFAULT_SEED)}" \
103103
RUSTFLAGS="$(STRESS_RUSTFLAGS)" \
104-
cargo test \
104+
cargo test --release \
105105
-p fff-search \
106106
--test fuzz_git_watcher_stress \
107107
--features zlob \
108108
-- --nocapture stress_seeded
109109

110110
test-stress-random:
111111
RUSTFLAGS="$(STRESS_RUSTFLAGS)" \
112-
cargo test \
112+
cargo test --release \
113113
-p fff-search \
114114
--test fuzz_git_watcher_stress \
115115
--features zlob \
116116
-- --nocapture stress_random
117117

118-
test-stress: test-stress-seeded test-stress-random
118+
test-stress-repos:
119+
RUSTFLAGS="$(STRESS_RUSTFLAGS)" \
120+
cargo test --release \
121+
-p fff-search \
122+
--test fuzz_real_repos \
123+
--features zlob \
124+
-- --nocapture
125+
126+
test-stress: test-stress-seeded test-stress-random test-stress-repos
119127

120128
# Update version in a package.json, including optionalDependencies.
121129
# Usage: make set-npm-version PKG=packages/fff-bun VERSION=1.0.0-nightly.abc1234

crates/fff-core/src/background_watcher.rs

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,13 @@ fn handle_debounced_events(
579579

580580
files_to_update_git_status.reserve(paths_to_add_or_modify.len());
581581
for path in &paths_to_add_or_modify {
582+
// Double-check existence: the debouncer may deliver a Modify event
583+
// for a path that was deleted between event emission and processing
584+
// (Create+Remove merged into just Create by the debouncer).
585+
if !path.exists() {
586+
picker.remove_file_by_path(path);
587+
continue;
588+
}
582589
if picker.handle_create_or_modify(path).is_some() {
583590
files_to_update_git_status.push(path.to_path_buf());
584591
} else {
@@ -587,13 +594,40 @@ fn handle_debounced_events(
587594
}
588595

589596
overflow_count = picker.get_overflow_files().len();
597+
598+
// Reconcile overflow files: stat-check each live overflow file and
599+
// tombstone any that no longer exist on disk. This handles deletions
600+
// where the debouncer swallowed the Remove event.
601+
if overflow_count > 0 {
602+
let base_path = picker.base_path().to_path_buf();
603+
let stale: Vec<PathBuf> = picker
604+
.get_overflow_files()
605+
.iter()
606+
.filter(|f| !f.is_deleted())
607+
.filter_map(|f| {
608+
let abs = f.absolute_path(&*picker, &base_path);
609+
(!abs.exists()).then_some(abs)
610+
})
611+
.collect();
612+
613+
for path in &stale {
614+
picker.remove_file_by_path(path);
615+
}
616+
if !stale.is_empty() {
617+
overflow_count = picker.get_overflow_files().len();
618+
}
619+
}
590620
}
591621

592622
info!(
593623
files_updated = files_to_update_git_status.len(),
594624
overflow_count, "File index changes applied",
595625
);
596626

627+
// Reconcile overflow files unconditionally on every batch: stat-check
628+
// each live overflow file and tombstone any that no longer exist on disk.
629+
// This is the only reliable way to detect deletions when the debouncer
630+
// coalesces Create+Remove for the same path into nothing.
597631
if need_full_rescan || overflow_count > MAX_OVERFLOW_FILES {
598632
info!("Watcher faced limit of index overflow. Triggering rescan");
599633
if let Err(e) = shared_picker.trigger_full_rescan_async(shared_frecency) {
@@ -645,46 +679,57 @@ fn handle_debounced_events(
645679
}
646680
}
647681

648-
// Git status updates require a repository.
649-
let Some(repo) = repo.as_ref() else {
650-
debug!("No git repo available, skipping git status updates");
651-
return new_dirs_to_watch;
652-
};
653-
654-
if need_full_git_rescan && !need_full_rescan {
655-
info!("Triggering full git rescan");
656-
657-
if let Err(e) = shared_picker.refresh_git_status(shared_frecency) {
658-
error!("Failed to refresh git status: {:?}", e);
659-
}
660-
}
661-
662-
// do not update the git status if the
663-
if !files_to_update_git_status.is_empty() && !need_full_git_rescan {
664-
info!(
665-
"Fetching git status for {} files",
666-
files_to_update_git_status.len()
667-
);
682+
// Git status updates are IO-heavy (reads .git/index, stats files) and
683+
// not on the critical path for search correctness. Spawn them on the
684+
// background pool so the debouncer handler returns immediately and can
685+
// process the next event batch without waiting for git.
686+
if need_full_git_rescan || !files_to_update_git_status.is_empty() {
687+
let sp = shared_picker.clone();
688+
let sf = shared_frecency.clone();
689+
let git_workdir = repo.as_ref().map(|r| {
690+
r.workdir()
691+
.unwrap_or_else(|| r.path())
692+
.to_path_buf()
693+
});
694+
let full_rescan = need_full_git_rescan;
695+
let need_picker_rescan = need_full_rescan;
696+
let files = files_to_update_git_status;
697+
698+
crate::file_picker::BACKGROUND_THREAD_POOL.spawn(move || {
699+
let Some(git_path) = git_workdir else { return };
700+
let Ok(repo) = Repository::open(&git_path) else {
701+
error!("Failed to open git repo for async status update");
702+
return;
703+
};
668704

669-
let status = match GitStatusCache::git_status_for_paths(repo, &files_to_update_git_status) {
670-
Ok(status) => status,
671-
Err(e) => {
672-
tracing::error!(?e, "Failed to query git status");
673-
return new_dirs_to_watch;
705+
if full_rescan && !need_picker_rescan {
706+
info!("Async: triggering full git rescan");
707+
if let Err(e) = sp.refresh_git_status(&sf) {
708+
error!("Failed to refresh git status: {:?}", e);
709+
}
674710
}
675-
};
676711

677-
if let Ok(mut guard) = shared_picker.write()
678-
&& let Some(ref mut picker) = *guard
679-
{
680-
if let Err(e) = picker.update_git_statuses(status, shared_frecency) {
681-
error!("Failed to update git statuses: {:?}", e);
682-
} else {
683-
info!("Successfully updated git statuses in picker");
712+
if !files.is_empty() && !full_rescan {
713+
info!("Async: fetching git status for {} files", files.len());
714+
let status = match GitStatusCache::git_status_for_paths(&repo, &files) {
715+
Ok(s) => s,
716+
Err(e) => {
717+
error!("Failed to query git status: {:?}", e);
718+
return;
719+
}
720+
};
721+
722+
if let Ok(mut guard) = sp.write()
723+
&& let Some(ref mut picker) = *guard
724+
{
725+
if let Err(e) = picker.update_git_statuses(status, &sf) {
726+
error!("Failed to update git statuses: {:?}", e);
727+
} else {
728+
info!("Async: git statuses updated");
729+
}
730+
}
684731
}
685-
} else {
686-
error!("Failed to acquire picker lock for git status update");
687-
}
732+
});
688733
}
689734

690735
new_dirs_to_watch

0 commit comments

Comments
 (0)