use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, Instant};
use scryer_db::{ProjectRegistry, ScryerDb, SourceFile};
use scryer_engine::EngineService;
async fn indexed(db: &ScryerDb, project_id: u64) -> Vec<String> {
let mut guard = db.lock().await;
let mut paths: Vec<String> = SourceFile::filter_by_project_id(project_id)
.exec(&mut *guard)
.await
.unwrap()
.into_iter()
.map(|f| f.path)
.collect();
paths.sort();
paths
}
async fn wait_for_index(db: &ScryerDb, project_id: u64, expected: &[&str]) {
let deadline = Instant::now() + Duration::from_secs(5);
loop {
let paths = indexed(db, project_id).await;
if paths == expected {
return;
}
assert!(
Instant::now() < deadline,
"project {project_id}: indexed {paths:?}, expected {expected:?}"
);
tokio::time::sleep(Duration::from_millis(50)).await;
}
}
fn make_repo(dir: &Path, name: &str) -> PathBuf {
let root = dir.join(name);
fs::create_dir_all(root.join("src")).unwrap();
fs::write(root.join(".gitignore"), "target/\n").unwrap();
fs::write(root.join("src/lib.rs"), "pub fn a() {}\n").unwrap();
fs::create_dir_all(root.join("target/debug")).unwrap();
dunce::canonicalize(root).unwrap()
}
#[tokio::test(flavor = "multi_thread")]
async fn one_watcher_tracks_several_projects() -> anyhow::Result<()> {
let temp = tempfile::tempdir()?;
let alpha = make_repo(temp.path(), "alpha");
let beta = make_repo(temp.path(), "beta");
let db = ScryerDb::new_in_memory().await?;
let registry = Arc::new(ProjectRegistry::new(db.inner()).await?);
let engine = EngineService::new(db.clone());
let a = registry.register_or_update(&alpha, None).await?;
let b = registry.register_or_update(&beta, None).await?;
engine.index_project(a.id, &alpha).await?;
engine.index_project(b.id, &beta).await?;
let watcher = engine.watcher()?;
watcher.add_project(a.id, &alpha)?;
watcher.add_project(b.id, &beta)?;
let watched = watcher.projects();
assert_eq!(watched.len(), 2);
assert!(watched.iter().all(|w| w.directories == 2), "{watched:?}");
fs::write(alpha.join("src/new.rs"), "pub fn n() {}\n")?;
fs::write(beta.join("src/other.rs"), "pub fn o() {}\n")?;
fs::write(alpha.join("target/debug/gen.rs"), "pub fn g() {}\n")?;
wait_for_index(&db, a.id, &["src/lib.rs", "src/new.rs"]).await;
wait_for_index(&db, b.id, &["src/lib.rs", "src/other.rs"]).await;
fs::create_dir_all(alpha.join("src/nested"))?;
fs::write(alpha.join("src/nested/deep.rs"), "pub fn d() {}\n")?;
wait_for_index(
&db,
a.id,
&["src/lib.rs", "src/nested/deep.rs", "src/new.rs"],
)
.await;
fs::rename(alpha.join("src/nested"), alpha.join("src/moved"))?;
wait_for_index(
&db,
a.id,
&["src/lib.rs", "src/moved/deep.rs", "src/new.rs"],
)
.await;
fs::remove_file(beta.join("src/other.rs"))?;
wait_for_index(&db, b.id, &["src/lib.rs"]).await;
watcher.remove_project(b.id);
fs::write(beta.join("src/late.rs"), "pub fn l() {}\n")?;
tokio::time::sleep(Duration::from_millis(600)).await;
assert_eq!(indexed(&db, b.id).await, ["src/lib.rs"]);
watcher.stop().await;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn watcher_edits_are_visible_to_definition_resolution() -> anyhow::Result<()> {
let temp = tempfile::tempdir()?;
let root = make_repo(temp.path(), "resolve");
fs::write(root.join("src/main.rs"), "fn main() {}\n")?;
let db = ScryerDb::new_in_memory().await?;
let registry = Arc::new(ProjectRegistry::new(db.inner()).await?);
let engine = EngineService::new(db.clone());
let project = registry.register_or_update(&root, None).await?;
engine.index_project(project.id, &root).await?;
let watcher = engine.watcher()?;
watcher.add_project(project.id, &root)?;
fs::write(root.join("src/util.rs"), "pub fn helper() {}\n")?;
fs::write(
root.join("src/main.rs"),
"mod util;\nfn main() {\n helper();\n}\n",
)?;
wait_for_index(
&db,
project.id,
&["src/lib.rs", "src/main.rs", "src/util.rs"],
)
.await;
let deadline = Instant::now() + Duration::from_secs(5);
let def = loop {
if let Some(def) = engine
.resolve_definition(project.id, &root, Path::new("src/main.rs"), 3, 6)
.await?
{
break def;
}
assert!(Instant::now() < deadline, "helper never resolved");
tokio::time::sleep(Duration::from_millis(50)).await;
};
assert_eq!(def.symbol_name, "helper");
assert_eq!(def.file_path, Path::new("src/util.rs"));
watcher.stop().await;
Ok(())
}