use super::*;
use crate::app::Snapshot;
use crate::board::Column;
use crate::boundary::tests::agent;
use std::path::Path;
mod fixture;
use fixture::{NOW, WS, facts, row, snap};
#[test]
fn a_workspace_under_its_cap_takes_the_top_ready_ball() {
let rows = vec![
row("bl-2", Column::Ready, vec![]),
row("bl-9", Column::Ready, vec![]),
];
let one = plan(
&snap(vec![]),
&facts(2, 1, None),
Path::new(fixture::WS),
&rows,
NOW,
);
assert_eq!(
one,
Some(Move::Spawn {
row: rows[0].clone()
}),
"board order is the pick — there is no second ranking"
);
}
#[test]
fn a_full_workspace_a_gated_ball_and_a_bound_ceiling_all_spawn_nothing() {
let ready = vec![row("bl-2", Column::Ready, vec![])];
assert_eq!(
plan(
&snap(vec![]),
&facts(1, 1, None),
Path::new(fixture::WS),
&ready,
NOW
),
None,
"at the cap"
);
let gated = vec![row("bl-2", Column::Gated, vec![])];
assert_eq!(
plan(
&snap(vec![]),
&facts(3, 0, None),
Path::new(fixture::WS),
&gated,
NOW
),
None,
"a gated ball can be started but not delivered, so the loop leaves it"
);
let mut bound = facts(3, 0, None);
bound.ceiling = Some("spend ceiling reached".to_owned());
assert_eq!(
plan(&snap(vec![]), &bound, Path::new(fixture::WS), &ready, NOW),
None,
"the ceiling binds on the next spawn, so there is no next spawn"
);
}
#[test]
fn a_ready_ball_in_another_project_is_not_this_loops_work() {
let mut elsewhere = row("bl-2", Column::Ready, vec![]);
elsewhere.project = "litany".to_owned();
assert_eq!(
plan(
&snap(vec![]),
&facts(3, 0, None),
Path::new(fixture::WS),
&[elsewhere],
NOW
),
None
);
}
#[test]
fn a_quiet_ball_past_its_lease_is_reaped_with_the_comparison_as_its_reason() {
let quiet = agent("root-1", crate::git_tree::AgentState::Quiescent, NOW - 2820);
let elsewhere = agent("root-9", crate::git_tree::AgentState::Live, NOW);
let rows = vec![row("bl-1", Column::Claimed, vec!["root-1"])];
let one = plan(
&snap(vec![quiet, elsewhere]),
&facts(3, 1, Some(Duration::from_mins(30))),
Path::new(fixture::WS),
&rows,
NOW,
);
let Some(Move::Reap {
row,
claimant,
since,
}) = one
else {
panic!("a reap");
};
assert_eq!(row.id, "bl-1");
assert_eq!(claimant, "otter", "released from whoever the row names");
assert_eq!(
since, "lease expired 17m ago",
"the reason is the comparison itself, never a diagnosis"
);
}
#[test]
fn a_running_drone_is_never_reaped_however_old_its_claim() {
for state in [
crate::git_tree::AgentState::Live,
crate::git_tree::AgentState::InFlight,
] {
let busy = agent("root-1", state, NOW - 999_999);
let rows = vec![row("bl-1", Column::Claimed, vec!["root-1"])];
assert_eq!(
plan(
&snap(vec![busy]),
&facts(1, 1, Some(Duration::from_mins(1))),
Path::new(fixture::WS),
&rows,
NOW
),
None,
"{state:?}: killing mid-ball destroys uncommitted work"
);
}
}
#[test]
fn no_lease_reaps_nothing_and_a_ball_still_inside_its_lease_is_left_alone() {
let quiet = agent("root-1", crate::git_tree::AgentState::Stopped, NOW - 60);
let rows = vec![row("bl-1", Column::Claimed, vec!["root-1"])];
assert_eq!(
plan(
&snap(vec![quiet.clone()]),
&facts(1, 1, None),
Path::new(fixture::WS),
&rows,
NOW
),
None,
"an absent lease reaps nothing rather than reaping on a default"
);
assert_eq!(
plan(
&snap(vec![quiet]),
&facts(1, 1, Some(Duration::from_mins(30))),
Path::new(fixture::WS),
&rows,
NOW
),
None,
"still inside the lease"
);
}
#[test]
fn a_row_that_names_no_claimant_is_not_reapable() {
let quiet = agent("root-1", crate::git_tree::AgentState::Stopped, NOW - 3600);
let anonymous = BoardRow {
claimant: None,
..row("bl-1", Column::Claimed, vec!["root-1"])
};
assert_eq!(
plan(
&snap(vec![quiet]),
&facts(3, 1, Some(Duration::from_mins(1))),
Path::new(fixture::WS),
&[anonymous],
NOW
),
None
);
}
#[test]
fn a_claim_with_no_conversation_and_no_birth_row_is_not_reapable_and_reaps_go_first() {
let droneless = vec![row("bl-1", Column::Claimed, vec![])];
assert_eq!(
plan(
&snap(vec![]),
&facts(3, 1, Some(Duration::from_secs(1))),
Path::new(fixture::WS),
&droneless,
NOW
),
None,
"no conversation and no birth of the loop's own: nothing to compare, \
and yog does not guess whose claim it was"
);
let quiet = agent("root-1", crate::git_tree::AgentState::Stopped, NOW - 600);
let both = vec![
row("bl-1", Column::Claimed, vec!["root-1"]),
row("bl-2", Column::Ready, vec![]),
];
let one = plan(
&snap(vec![quiet]),
&facts(9, 1, Some(Duration::from_mins(1))),
Path::new(fixture::WS),
&both,
NOW,
);
assert!(
matches!(one, Some(Move::Reap { .. })),
"freeing a slot goes before filling one"
);
}
#[test]
fn a_workspace_with_no_derived_tree_reaps_nothing() {
let mut none = Snapshot::empty(0);
none.trees.clear();
let rows = vec![row("bl-1", Column::Claimed, vec!["root-1"])];
assert_eq!(
plan(
&none,
&facts(1, 1, Some(Duration::from_secs(1))),
Path::new(fixture::WS),
&rows,
NOW
),
None
);
}
mod fire;
mod retaking;
mod stillbirth;