use super::*;
use crate::opslog::Origin;
use tempfile::tempdir;
fn root(writable: &[&str], cwd: &str) -> Root {
Root {
writable: writable.iter().map(PathBuf::from).collect(),
cwd: PathBuf::from(cwd),
home: PathBuf::from("/home/op"),
}
}
fn claim_row(project: &str, id: &str, claimant: &str) -> OpEntry {
OpEntry {
ts: "TS".to_owned(),
argv: ["bl", "claim", id, "--as", claimant]
.iter()
.map(|s| (*s).to_owned())
.collect(),
cwd: project.to_owned(),
exit: 0,
stdout: String::new(),
stderr: String::new(),
origin: Origin::Balls,
}
}
#[test]
fn an_operand_resolves_against_the_cwd_with_home_and_dots_folded() {
let r = root(&["/w/agent"], "/w/agent/src");
assert_eq!(r.resolve("mod.rs"), PathBuf::from("/w/agent/src/mod.rs"));
assert_eq!(
r.resolve("../Cargo.toml"),
PathBuf::from("/w/agent/Cargo.toml")
);
assert_eq!(r.resolve("./x/./y"), PathBuf::from("/w/agent/src/x/y"));
assert_eq!(r.resolve("/etc/hosts"), PathBuf::from("/etc/hosts"));
assert_eq!(r.resolve("~"), PathBuf::from("/home/op"));
assert_eq!(r.resolve("~/.ssh"), PathBuf::from("/home/op/.ssh"));
assert_eq!(r.resolve("~root/x"), PathBuf::from("/w/agent/src/~root/x"));
assert_eq!(r.resolve("/../../x"), PathBuf::from("/x"));
}
#[test]
fn containment_covers_the_root_itself_and_every_writable_dir() {
let r = root(&["/w/agent", "/state/work/bl-1a2b"], "/w/agent");
assert!(r.holds(Path::new("/w/agent")));
assert!(r.holds(Path::new("/w/agent/src/mod.rs")));
assert!(r.holds(Path::new("/state/work/bl-1a2b/README.md")));
assert!(!r.holds(Path::new("/w/other/x")));
assert!(r.holds_all(&[]), "no operands is inside the root");
assert!(r.holds_all(&["src/a".to_owned(), "/w/agent/b".to_owned()]));
assert!(!r.holds_all(&["src/a".to_owned(), "/etc/b".to_owned()]));
}
#[test]
fn the_agent_worktree_is_the_workspace_s_agents_dir() {
assert_eq!(
agent_worktree(Path::new("/w/ws"), "amber-1"),
PathBuf::from("/w/ws/agents/amber-1")
);
}
#[test]
fn the_bound_worktree_is_the_last_claim_this_workspace_made() {
let balls = Path::new("/state/balls");
let rows = [
claim_row("/dev/proj", "bl-0000", "someone-else"),
claim_row("/dev/proj", "bl-1111", "cobalt-gecko"),
claim_row("/dev/other", "bl-2222", "cobalt-gecko"),
];
assert_eq!(
bound_worktrees(&rows, balls, "cobalt-gecko"),
[
crate::binding::work_worktree_path(balls, Path::new("/dev/other"), "bl-2222", None),
crate::binding::work_worktree_path(
balls,
Path::new("/dev/other"),
"bl-2222",
Some("cobalt-gecko")
),
]
);
assert!(bound_worktrees(&rows, balls, "nobody").is_empty());
assert!(bound_worktrees(&[], balls, "cobalt-gecko").is_empty());
}
#[test]
fn only_a_bl_claim_row_stamped_with_this_claimant_joins() {
let balls = Path::new("/state/balls");
let mut close = claim_row("/dev/proj", "bl-1111", "cobalt-gecko");
close.argv[1] = "close".to_owned();
let mut foreign = claim_row("/dev/proj", "bl-1111", "cobalt-gecko");
foreign.argv[0] = "litany".to_owned();
let mut unstamped = claim_row("/dev/proj", "bl-1111", "cobalt-gecko");
unstamped.argv.truncate(3);
let mut short = claim_row("/dev/proj", "bl-1111", "cobalt-gecko");
short.argv.truncate(2);
for row in [close, foreign, unstamped, short] {
assert!(
bound_worktrees(std::slice::from_ref(&row), balls, "cobalt-gecko").is_empty(),
"{:?} is not a claim this workspace made",
row.argv
);
}
}
#[test]
fn a_fanned_candidates_own_worktree_is_writable_and_a_strangers_is_not() {
let xdg = balls::layout::Xdg::with(Path::new("/home/u"), None, Some("/home/u/.local/state"));
let ws = Path::new("/w/workspaces/cobalt-gecko");
let mine = balls::delivery_path::attempt_path(&xdg, "/dev/proj", "at-0badcafe");
let fire = |binding: &Path, cwd: &str| OpEntry {
ts: "TS".to_owned(),
argv: [
"litany",
"prompt",
"--name",
"amber-1",
"--cwd",
&binding.to_string_lossy(),
"/w/workspaces/cobalt-gecko",
"goal",
]
.iter()
.map(|s| (*s).to_owned())
.collect(),
cwd: cwd.to_owned(),
exit: 0,
stdout: String::new(),
stderr: String::new(),
origin: Origin::Balls,
};
let trail = vec![
claim_row("/dev/proj", "bl-1111", "cobalt-gecko"),
fire(&mine, "/w/workspaces/cobalt-gecko"),
fire(
&balls::delivery_path::attempt_path(&xdg, "/dev/proj", "at-99999999"),
"/w/workspaces/other",
),
];
assert_eq!(
candidate_worktrees(&trail, &xdg, ws, "cobalt-gecko"),
vec![mine],
);
assert!(candidate_worktrees(&trail, &xdg, ws, "somebody-else").is_empty());
}
#[test]
fn the_cwd_mark_is_read_from_litany_s_own_ref() {
let dir = tempdir().unwrap();
let ws = dir.path().join("ws");
let repo = ws.join("repo.git");
std::fs::create_dir_all(&repo).unwrap();
assert_eq!(agent_cwd(&ws, "amber-1"), None);
let git = |args: &[&str]| {
crate::git_env::output(crate::git_env::git().arg("--git-dir").arg(&repo).args(args))
.unwrap()
};
assert!(
git(&["init", "--bare", "-b", "config/default"])
.status
.success()
);
assert_eq!(agent_cwd(&ws, "amber-1"), None);
let blob = dir.path().join("cwd");
std::fs::write(&blob, "/w/ws/agents/amber-1/sub\n").unwrap();
let out = git(&["hash-object", "-w", "--", &blob.display().to_string()]);
let oid = String::from_utf8(out.stdout).unwrap().trim().to_owned();
assert!(
git(&["update-ref", "refs/litany/cwd/amber-1", &oid])
.status
.success()
);
assert_eq!(
agent_cwd(&ws, "amber-1"),
Some(PathBuf::from("/w/ws/agents/amber-1/sub")),
);
std::fs::write(&blob, "").unwrap();
let out = git(&["hash-object", "-w", "--", &blob.display().to_string()]);
let oid = String::from_utf8(out.stdout).unwrap().trim().to_owned();
assert!(
git(&["update-ref", "refs/litany/cwd/amber-1", &oid])
.status
.success()
);
assert_eq!(agent_cwd(&ws, "amber-1"), None);
}