use super::*;
use std::cell::RefCell;
use std::io;
use std::path::PathBuf;
#[derive(Default)]
struct StubGit {
runs: RefCell<Vec<(PathBuf, Vec<String>)>>,
fail_at: Option<usize>,
}
impl StubGit {
fn ok() -> Self {
Self::default()
}
fn failing_at(idx: usize) -> Self {
Self {
fail_at: Some(idx),
..Self::default()
}
}
}
impl GitRunner for StubGit {
fn run(&self, dest: &Path, args: &[&str]) -> io::Result<()> {
let mut runs = self.runs.borrow_mut();
let idx = runs.len();
runs.push((
dest.to_path_buf(),
args.iter().map(|s| (*s).to_owned()).collect(),
));
if self.fail_at == Some(idx) {
Err(io::Error::other(format!("stub git fail at {idx}")))
} else {
Ok(())
}
}
fn run_capture(&self, _dest: &Path, _args: &[&str]) -> io::Result<String> {
unreachable!("spawn_subagent_branch never issues capturing git ops")
}
}
fn tmpdir() -> tempfile::TempDir {
tempfile::TempDir::new().unwrap()
}
fn req<'a>(parent_wt: &'a Path, sub_wt: &'a Path, soul: Option<&'a str>) -> SpawnRequest<'a> {
SpawnRequest {
parent_worktree: parent_wt,
parent_branch: "p1",
sub_branch: "p1-ct-2-deadbeef",
sub_worktree: sub_wt,
fork_point: None,
goal_text: "do the thing\n",
soul_text: soul,
granted: &[],
commit_subject: "dispatch: worker [p1-ct-2-deadbeef]",
}
}
#[test]
fn writes_goal_and_soul_when_soul_present() {
let parent_dir = tmpdir();
let sub_dir = tmpdir();
let parent_wt = parent_dir.path();
let sub_wt = sub_dir.path();
let git = StubGit::ok();
spawn_subagent_branch(&req(parent_wt, sub_wt, Some("you are worker\n")), &git).unwrap();
let runs = git.runs.borrow();
assert_eq!(runs[0].0, parent_wt);
assert_eq!(
runs[0].1[..4],
["worktree", "add", "-b", "agents/p1-ct-2-deadbeef"]
);
assert_eq!(runs[0].1[4], sub_wt.to_string_lossy().to_string());
assert_eq!(runs[0].1[5], "agents/p1");
assert_eq!(runs[1].0, sub_wt);
assert_eq!(runs[1].1[..5], ["rm", "-r", "-q", "--ignore-unmatch", "--"]);
assert_eq!(runs[2].0, sub_wt);
assert_eq!(runs[2].1, vec!["add", "goal.md", "soul.md"]);
assert_eq!(runs[3].0, sub_wt);
assert_eq!(runs[3].1[0], "commit");
assert_eq!(runs[3].1[2], "dispatch: worker [p1-ct-2-deadbeef]");
assert_eq!(
std::fs::read_to_string(sub_wt.join("goal.md")).unwrap(),
"do the thing\n"
);
assert_eq!(
std::fs::read_to_string(sub_wt.join("soul.md")).unwrap(),
"you are worker\n"
);
}
#[test]
fn writes_only_goal_when_soul_is_none() {
let parent_dir = tmpdir();
let sub_dir = tmpdir();
let git = StubGit::ok();
spawn_subagent_branch(&req(parent_dir.path(), sub_dir.path(), None), &git).unwrap();
let runs = git.runs.borrow();
assert_eq!(runs[2].1, vec!["add", "goal.md"]);
assert!(
!sub_dir.path().join("soul.md").exists(),
"soul.md should not be written"
);
}
#[test]
fn surfaces_worktree_add_failure() {
let parent_dir = tmpdir();
let sub_dir = tmpdir();
let git = StubGit::failing_at(0);
let err =
spawn_subagent_branch(&req(parent_dir.path(), sub_dir.path(), None), &git).unwrap_err();
assert!(
matches!(
err,
Error::Git {
op: "worktree add",
..
}
),
"got {err:?}"
);
}
#[test]
fn surfaces_control_rm_failure() {
let parent_dir = tmpdir();
let sub_dir = tmpdir();
let git = StubGit::failing_at(1);
let err = spawn_subagent_branch(
&req(parent_dir.path(), sub_dir.path(), Some("soul\n")),
&git,
)
.unwrap_err();
assert!(
matches!(
err,
Error::Git {
op: "rm control files",
..
}
),
"got {err:?}"
);
}
#[test]
fn surfaces_add_failure() {
let parent_dir = tmpdir();
let sub_dir = tmpdir();
let git = StubGit::failing_at(2);
let err = spawn_subagent_branch(
&req(parent_dir.path(), sub_dir.path(), Some("soul\n")),
&git,
)
.unwrap_err();
assert!(matches!(err, Error::Git { op: "add", .. }), "got {err:?}");
}
#[test]
fn surfaces_commit_failure() {
let parent_dir = tmpdir();
let sub_dir = tmpdir();
let git = StubGit::failing_at(3);
let err =
spawn_subagent_branch(&req(parent_dir.path(), sub_dir.path(), None), &git).unwrap_err();
assert!(
matches!(err, Error::Git { op: "commit", .. }),
"got {err:?}"
);
}
#[test]
fn surfaces_io_failure_when_sub_worktree_is_a_file() {
let parent_dir = tmpdir();
let sub_wt = parent_dir.path().join("collision");
std::fs::write(&sub_wt, b"existing file").unwrap();
let git = StubGit::ok();
let r = SpawnRequest {
parent_worktree: parent_dir.path(),
parent_branch: "p1",
sub_branch: "p1-x",
sub_worktree: &sub_wt,
fork_point: None,
goal_text: "g",
soul_text: None,
granted: &[],
commit_subject: "x",
};
let err = spawn_subagent_branch(&r, &git).unwrap_err();
assert!(matches!(err, Error::Io(_)), "got {err:?}");
}