use std::fs;
use std::path::PathBuf;
use std::time::Duration;
use reference_query::index;
use reference_query::search::{self, ActiveFiles};
use reference_query::store::Store;
fn scratch_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("rq-budget-{tag}-{}", std::process::id()));
fs::remove_dir_all(&dir).ok();
fs::create_dir_all(&dir).unwrap();
dir
}
fn finds(store: &Store, query: &str) -> bool {
!search::search(store, query, None, None, &ActiveFiles::default(), 5)
.unwrap()
.is_empty()
}
#[test]
fn a_zero_budget_still_indexes_the_active_files() {
let dir = scratch_dir("active");
fs::write(dir.join("a.rb"), "class Widget\nend\n").unwrap();
fs::write(dir.join("b.rb"), "class Gadget\nend\n").unwrap();
let mut store = Store::open_in_memory().unwrap();
index::index_budgeted(
&mut store,
&dir,
&["a.rb".to_string()],
Duration::ZERO,
None,
)
.unwrap();
assert!(finds(&store, "Widget"), "active file is indexed regardless");
assert!(!finds(&store, "Gadget"), "the walk hasn't run yet");
assert_eq!(store.coverage_overview().unwrap()[0].status, "warming");
fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_full_sweep_completes_and_tracks_added_and_deleted_files() {
let dir = scratch_dir("sweep");
fs::write(dir.join("a.rb"), "class Widget\nend\n").unwrap();
fs::write(dir.join("b.rb"), "class Gadget\nend\n").unwrap();
let mut store = Store::open_in_memory().unwrap();
let ample = Duration::from_secs(5);
index::index_budgeted(&mut store, &dir, &[], ample, None).unwrap();
assert!(finds(&store, "Widget"));
assert!(finds(&store, "Gadget"));
assert_eq!(store.coverage_overview().unwrap()[0].status, "complete");
fs::write(dir.join("c.rb"), "class Sprocket\nend\n").unwrap();
index::index_budgeted(&mut store, &dir, &[], ample, None).unwrap();
assert!(finds(&store, "Sprocket"));
fs::remove_file(dir.join("b.rb")).unwrap();
index::index_budgeted(&mut store, &dir, &[], ample, None).unwrap();
assert!(
!finds(&store, "Gadget"),
"deleted file's symbols are forgotten"
);
assert!(finds(&store, "Widget"), "surviving files remain");
fs::remove_dir_all(&dir).ok();
}
#[test]
fn indexing_prunes_a_stale_checkout_but_keeps_the_live_one() {
let dir = scratch_dir("prune-checkout");
fs::write(dir.join("a.rb"), "class Widget\nend\n").unwrap();
let mut store = Store::open_in_memory().unwrap();
index::index_budgeted(&mut store, &dir, &[], Duration::from_secs(5), None).unwrap();
let identity = index::detect_identity(&dir).to_string();
let repo = store.repository_id(&identity).unwrap().unwrap();
store.upsert_checkout(repo, "/gone/old/path", None).unwrap();
assert!(
store
.checkout_roots(repo)
.unwrap()
.iter()
.any(|p| p == "/gone/old/path")
);
index::index_budgeted(&mut store, &dir, &[], Duration::from_secs(5), None).unwrap();
let roots = store.checkout_roots(repo).unwrap();
assert!(
!roots.iter().any(|p| p == "/gone/old/path"),
"stale checkout pruned: {roots:?}"
);
assert!(!roots.is_empty(), "the live checkout remains");
assert!(finds(&store, "Widget"), "symbols are untouched by pruning");
fs::remove_dir_all(&dir).ok();
}
#[test]
fn an_empty_source_tree_never_reports_complete() {
let dir = scratch_dir("empty-src");
fs::write(dir.join("notes.txt"), "not source\n").unwrap();
let mut store = Store::open_in_memory().unwrap();
index::index_budgeted(&mut store, &dir, &[], Duration::from_secs(5), None).unwrap();
assert_eq!(store.coverage_overview().unwrap()[0].status, "warming");
assert!(!finds(&store, "anything"));
fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_cancelled_pass_stops_early_and_stays_warming() {
let dir = scratch_dir("cancel");
for i in 0..20 {
fs::write(
dir.join(format!("m{i:02}.rb")),
format!("class C{i}\nend\n"),
)
.unwrap();
}
let mut store = Store::open_in_memory().unwrap();
let cancel = std::sync::atomic::AtomicBool::new(true); let ample = Duration::from_secs(5); let stats =
index::index_budgeted_cancellable(&mut store, &dir, &[], ample, None, &cancel).unwrap();
assert!(
stats.files_indexed < 20,
"a cancelled pass didn't index the whole repo: {stats:?}"
);
assert_eq!(
store.coverage_overview().unwrap()[0].status,
"warming",
"an aborted sweep is never finalized as complete"
);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_content_scan_returns_only_matching_files_to_persist() {
use std::collections::HashSet;
let dir = scratch_dir("scanq");
fs::write(dir.join("a.rb"), "class Widget\nend\n").unwrap();
fs::write(dir.join("b.rb"), "class Gadget\nend\n").unwrap();
let scanned = index::scan(&dir, &HashSet::new(), None, Some(b"widget"));
assert_eq!(scanned.len(), 1, "only the matching file: {scanned:?}");
assert_eq!(scanned[0].path, "a.rb");
assert!(scanned[0].symbols.iter().any(|s| s.name == "Widget"));
}