use std::time::Duration;
use tokio::time::timeout;
use vcs_core::Repo;
use vcs_testkit::{GitSandbox, JjSandbox, TempDir};
use vcs_watch::{RepoEvent, RepoWatcher};
async fn wait_for(
watcher: &mut RepoWatcher,
overall: Duration,
pred: impl Fn(&RepoEvent) -> bool,
) -> bool {
let deadline = tokio::time::Instant::now() + overall;
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return false;
}
match timeout(remaining, watcher.recv()).await {
Ok(Some(change)) => {
if change.events.iter().any(&pred) {
return true;
}
}
Ok(None) | Err(_) => return false,
}
}
}
fn fast(repo: Repo) -> impl std::future::Future<Output = vcs_watch::Result<RepoWatcher>> {
RepoWatcher::builder(repo)
.debounce(Duration::from_millis(50))
.build()
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "requires the git binary"]
async fn git_branch_create_emits_branch_created() {
let sandbox = GitSandbox::init("watch-git-branch");
sandbox.commit_file("seed.txt", "seed\n", "initial");
let repo = Repo::open(sandbox.path()).expect("open");
let mut watcher = fast(repo).await.expect("watcher");
sandbox.git(&["branch", "feature"]);
assert!(
wait_for(&mut watcher, Duration::from_secs(10), |e| {
matches!(e, RepoEvent::BranchCreated { name } if name == "feature")
})
.await,
"expected a BranchCreated(feature) event"
);
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "requires the git binary"]
async fn git_worktree_sees_branch_created_from_main() {
let sandbox = GitSandbox::init("watch-git-wt");
sandbox.commit_file("seed.txt", "seed\n", "initial");
let wt_parent = TempDir::new("watch-git-wt-linked");
let wt_path = wt_parent.path().join("wt");
sandbox.git(&[
"worktree",
"add",
"-q",
"-b",
"wt-branch",
wt_path.to_str().expect("utf8 worktree path"),
]);
let repo = Repo::open(&wt_path).expect("open worktree");
let mut watcher = fast(repo).await.expect("watcher");
sandbox.git(&["branch", "feature"]);
assert!(
wait_for(&mut watcher, Duration::from_secs(10), |e| {
matches!(e, RepoEvent::BranchCreated { name } if name == "feature")
})
.await,
"worktree watcher must see a branch created in the shared git dir"
);
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "requires the git binary"]
async fn git_working_tree_edit_emits_working_copy_changed() {
let sandbox = GitSandbox::init("watch-git-wc");
sandbox.commit_file("seed.txt", "seed\n", "initial");
let repo = Repo::open(sandbox.path()).expect("open");
let mut watcher = RepoWatcher::builder(repo)
.working_tree(true)
.debounce(Duration::from_millis(50))
.build()
.await
.expect("watcher");
sandbox.write("dirty.txt", "x\n");
assert!(
wait_for(&mut watcher, Duration::from_secs(10), |e| {
matches!(e, RepoEvent::WorkingCopyChanged { dirty: true, .. })
})
.await,
"expected a WorkingCopyChanged(dirty) event"
);
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "requires the jj binary"]
async fn jj_bookmark_create_emits_branch_created() {
let sandbox = JjSandbox::init("watch-jj-bm");
sandbox.write("seed.txt", "seed\n");
sandbox.describe("initial");
let repo = Repo::open(sandbox.path()).expect("open");
let mut watcher = fast(repo).await.expect("watcher");
sandbox.bookmark("feature");
assert!(
wait_for(&mut watcher, Duration::from_secs(10), |e| {
matches!(e, RepoEvent::BranchCreated { name } if name == "feature")
})
.await,
"expected a BranchCreated(feature) event on jj"
);
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "requires the git binary"]
async fn drop_stops_the_watch() {
let sandbox = GitSandbox::init("watch-drop");
sandbox.commit_file("seed.txt", "seed\n", "initial");
let repo = Repo::open(sandbox.path()).expect("open");
let mut watcher = fast(repo).await.expect("watcher");
let quiet = timeout(Duration::from_millis(300), watcher.recv()).await;
assert!(quiet.is_err(), "no events expected on a quiescent repo");
drop(watcher); }