use super::super::*;
use crate::app::Snapshot;
use crate::boundary::dispatch::Deps;
use crate::boundary::tests::agent;
use crate::cli_outbound::Cli;
use crate::projects::join::JoinState;
use crate::ui_state::SystemClock;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use tempfile::tempdir;
const NOW: i64 = 1_000_000;
const WS: &str = "/names/otter";
const PROJECT: &str = "/dev/yog";
fn ctx(state_root: &Path, snapshot: Snapshot) -> PilotCtx {
let cell = crate::state::new_snapshot_cell(Arc::new(snapshot));
PilotCtx {
deps: Deps {
lernie: Cli::new("/no/such/lernie"),
bl: Cli::new("/no/such/bl"),
state_root: state_root.to_path_buf(),
yog_binary: PathBuf::from("/no/such/yog"),
world: crate::test_support::no_world(),
home: state_root.join("home"),
yog_data_root: state_root.join("data"),
balls_state_root: state_root.join("balls"),
snapshot: Arc::new(Snapshot::empty(0)),
caller: crate::boundary::dispatch::Caller::default(),
},
cell,
clock: Arc::new(SystemClock),
ui_path: state_root.join("ui.json"),
}
}
#[test]
fn an_unarmed_tick_acts_on_nothing_and_writes_nothing() {
let root = tempdir().expect("tempdir");
let ctx = ctx(root.path(), Snapshot::empty(0));
assert!(!ctx.pass());
assert!(
!root.path().join("ops.jsonl").exists(),
"an unarmed loop leaves no trace at all"
);
assert_eq!(ctx.period(), crate::app::Cadence::default().full_sweep);
}
#[test]
fn an_armed_tick_with_no_work_does_nothing() {
let root = tempdir().expect("tempdir");
let mut snapshot = Snapshot::empty(0);
snapshot.fleet.insert(
WS.to_owned(),
crate::fleet::Policy {
project: PathBuf::from(PROJECT),
cap: 2,
lease: None,
},
);
let ctx = ctx(root.path(), snapshot);
assert!(!ctx.pass(), "no ready ball, no claim: nothing to do");
assert!(!root.path().join("ops.jsonl").exists());
}
#[test]
fn the_thread_ticks_and_stops_on_drop() {
let root = tempdir().expect("tempdir");
let pilot = Pilot::spawn(ctx(root.path(), Snapshot::empty(0)));
drop(pilot); }
fn armed_world(ws: &Path, project: &Path, claimed: bool, lease: Option<Duration>) -> Snapshot {
let name = "otter";
let mut agents = Vec::new();
if claimed {
let mut drone = agent("root-1", crate::git_tree::AgentState::Stopped, NOW - 3600);
drone.goal_ball = Some("bl-1".to_owned());
agents.push(drone);
}
let join = crate::projects::join::JoinRow {
project: crate::naming::leaf(project),
ball_id: "bl-1".to_owned(),
state: if claimed {
JoinState::Bound
} else {
JoinState::ReadyStartable
},
workspace: claimed.then(|| crate::naming::leaf(ws)),
claimant: claimed.then(|| name.to_owned()),
title: Some("the one ball".to_owned()),
};
let ball = crate::projects::balls::Ball {
id: "bl-1".to_owned(),
title: "the one ball".to_owned(),
body: "do the thing".to_owned(),
claimant: claimed.then(|| name.to_owned()),
blockers: vec![],
parent: None,
priority: 2,
tags: vec![],
created: Some(0),
updated: Some(0),
root_commit: None,
};
let mut snap = crate::boundary::tests::snapshot(ws, name, agents, vec![join]);
snap.projects = vec![project.to_path_buf()];
snap.balls_by_project
.insert(project.to_path_buf(), vec![ball]);
snap.fleet.insert(
crate::nav::ws_key(ws),
crate::fleet::Policy {
project: project.to_path_buf(),
cap: 1,
lease,
},
);
snap
}
fn fake(dir: &Path, name: &str, body: &str) -> Cli {
use std::os::unix::fs::PermissionsExt;
let path = dir.join(name);
std::fs::write(&path, body).expect("write");
let mut perms = std::fs::metadata(&path).expect("stat").permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&path, perms).expect("chmod");
Cli::new(path)
}
#[test]
fn a_landed_reap_releases_the_claim_and_leaves_one_row() {
let root = tempdir().expect("tempdir");
let project = root.path().join("proj");
let ws = root.path().join("ws");
std::fs::create_dir_all(&project).expect("mkdir");
let _guard = crate::test_support::spawn_guard();
let bl = fake(root.path(), "bl", "#!/bin/sh\nexit 0\n");
let mut ctx = ctx(
root.path(),
armed_world(&ws, &project, true, Some(Duration::from_mins(30))),
);
ctx.deps.bl = bl;
assert!(ctx.pass(), "the ball is an hour quiet against a 30m lease");
let trail = std::fs::read_to_string(root.path().join("ops.jsonl")).expect("trail");
assert!(trail.contains("yog-fleet"), "{trail}");
assert!(trail.contains("reap"), "{trail}");
assert!(
trail.contains("lease expired"),
"the reason is the comparison: {trail}"
);
}
#[test]
fn a_refused_reap_writes_no_loop_row() {
let root = tempdir().expect("tempdir");
let project = root.path().join("proj");
let ws = root.path().join("ws");
std::fs::create_dir_all(&project).expect("mkdir");
let _guard = crate::test_support::spawn_guard();
let bl = fake(
root.path(),
"bl",
"#!/bin/sh\nprintf 'boom\\n' 1>&2\nexit 3\n",
);
let mut ctx = ctx(
root.path(),
armed_world(&ws, &project, true, Some(Duration::from_mins(30))),
);
ctx.deps.bl = bl;
assert!(!ctx.pass());
let trail = std::fs::read_to_string(root.path().join("ops.jsonl")).unwrap_or_default();
assert!(
!trail.contains("yog-fleet"),
"the executor's own failure row is the record: {trail}"
);
}
#[test]
fn a_landed_spawn_starts_a_drone_and_leaves_one_row() {
let root = tempdir().expect("tempdir");
let project = root.path().join("proj");
let ws = root.path().join("ws");
std::fs::create_dir_all(&project).expect("mkdir");
let mut ctx = ctx(root.path(), armed_world(&ws, &project, false, None));
let _guard = crate::test_support::spawn_guard();
let worktree = ctx
.deps
.balls_state_root
.join("plugins/bl-delivery")
.join(project.strip_prefix("/").expect("absolute"))
.join("bl-1");
std::fs::create_dir_all(&worktree).expect("mkdir");
ctx.deps.bl = fake(
root.path(),
"bl",
&format!(
"#!/bin/sh\ncase \"$1\" in\n claim) printf '%s\\n' '{}' ;;\nesac\nexit 0\n",
worktree.display()
),
);
ctx.deps.lernie = fake(
root.path(),
"lernie",
&format!(
"#!/bin/sh\ncase \"$1\" in\n{}esac\nexit 0\n",
crate::test_support::authoring_new_arm()
),
);
let acted = ctx.pass();
let trail0 = std::fs::read_to_string(root.path().join("ops.jsonl")).unwrap_or_default();
assert!(acted, "a ready ball under the cap is taken: {trail0}");
let trail = std::fs::read_to_string(root.path().join("ops.jsonl")).expect("trail");
assert!(trail.contains("yog-fleet"), "{trail}");
assert!(trail.contains("spawn"), "{trail}");
assert!(trail.contains("bl-1"), "{trail}");
}
#[test]
fn a_refused_spawn_writes_no_loop_row() {
let root = tempdir().expect("tempdir");
let project = root.path().join("proj");
let ws = root.path().join("ws");
std::fs::create_dir_all(&project).expect("mkdir");
let _guard = crate::test_support::spawn_guard();
let mut ctx = ctx(root.path(), armed_world(&ws, &project, false, None));
ctx.deps.bl = fake(
root.path(),
"bl",
"#!/bin/sh\nprintf 'no\\n' 1>&2\nexit 3\n",
);
assert!(!ctx.pass());
let trail = std::fs::read_to_string(root.path().join("ops.jsonl")).unwrap_or_default();
assert!(!trail.contains("yog-fleet"), "{trail}");
}