swarm-controller: test how a subagent's status becomes its list entry
The running, stale and finished rule, the status deciding the entry over the newest row, and the fallback to the row when a subagent has no status.
This commit is contained in:
parent
e9bc28fe2f
commit
7122084850
1 changed files with 94 additions and 1 deletions
|
|
@ -472,9 +472,102 @@ fn read_failed(agent: &str, e: &dyn std::error::Error) -> problem_details::Probl
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::header;
|
use super::{NewestRow, SubagentEntry, SubagentState, entry_of, header};
|
||||||
|
use swarm_wire::subagent_status::{STALE_AFTER_S, State, Status};
|
||||||
use swarm_wire::subagent_term::{HEADER_EFFORT, HEADER_MODEL, subagent_names};
|
use swarm_wire::subagent_term::{HEADER_EFFORT, HEADER_MODEL, subagent_names};
|
||||||
|
|
||||||
|
const NOW: i64 = 1_000_000;
|
||||||
|
|
||||||
|
fn status(state: State, since: i64, ts: i64) -> Status {
|
||||||
|
Status {
|
||||||
|
state,
|
||||||
|
since,
|
||||||
|
exit: None,
|
||||||
|
model: Some("opus".to_owned()),
|
||||||
|
effort: Some("high".to_owned()),
|
||||||
|
ts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row() -> NewestRow {
|
||||||
|
NewestRow {
|
||||||
|
unix: NOW - 30,
|
||||||
|
model: Some("haiku".to_owned()),
|
||||||
|
effort: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A running status counts as running until three heartbeats are missed,
|
||||||
|
/// then stale; a finished one is finished however fresh it is.
|
||||||
|
#[test]
|
||||||
|
fn a_state_is_running_until_its_heartbeats_stop() {
|
||||||
|
let fresh = status(State::Running, NOW - 500, NOW - 10);
|
||||||
|
assert_eq!(SubagentState::of(&fresh, NOW), SubagentState::Running);
|
||||||
|
let edge = status(State::Running, NOW - 500, NOW - STALE_AFTER_S);
|
||||||
|
assert_eq!(SubagentState::of(&edge, NOW), SubagentState::Running);
|
||||||
|
let silent = status(State::Running, NOW - 500, NOW - STALE_AFTER_S - 1);
|
||||||
|
assert_eq!(SubagentState::of(&silent, NOW), SubagentState::Stale);
|
||||||
|
let done = status(State::Finished, NOW - 500, NOW - 1);
|
||||||
|
assert_eq!(SubagentState::of(&done, NOW), SubagentState::Finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With a status the entry takes the state, the start, the exit status
|
||||||
|
/// and the model and effort from it, and ignores any newest row.
|
||||||
|
#[test]
|
||||||
|
fn a_status_decides_the_entry_over_the_newest_row() {
|
||||||
|
let mut done = status(State::Finished, NOW - 500, NOW - 1);
|
||||||
|
done.exit = Some(2);
|
||||||
|
let entry = entry_of("scout".to_owned(), Some(done), Some(Ok(row())), NOW);
|
||||||
|
assert_eq!(
|
||||||
|
entry,
|
||||||
|
SubagentEntry {
|
||||||
|
name: "scout".to_owned(),
|
||||||
|
last_row_unix: None,
|
||||||
|
model: Some("opus".to_owned()),
|
||||||
|
effort: Some("high".to_owned()),
|
||||||
|
state: Some(SubagentState::Finished),
|
||||||
|
since_unix: Some(NOW - 500),
|
||||||
|
exit: Some(2),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Without a status, as from an older daemon, the entry is what the newest
|
||||||
|
/// row says and has no state, so the swarm UI judges it from the row's age.
|
||||||
|
#[test]
|
||||||
|
fn without_a_status_the_entry_is_the_newest_row() {
|
||||||
|
let entry = entry_of("scout".to_owned(), None, Some(Ok(row())), NOW);
|
||||||
|
assert_eq!(
|
||||||
|
entry,
|
||||||
|
SubagentEntry {
|
||||||
|
name: "scout".to_owned(),
|
||||||
|
last_row_unix: Some(NOW - 30),
|
||||||
|
model: Some("haiku".to_owned()),
|
||||||
|
effort: None,
|
||||||
|
state: None,
|
||||||
|
since_unix: None,
|
||||||
|
exit: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A row that could not be read leaves the time and headers out and
|
||||||
|
/// still lists the subagent.
|
||||||
|
#[test]
|
||||||
|
fn a_failed_row_lookup_still_lists_the_subagent() {
|
||||||
|
let failed = entry_of(
|
||||||
|
"scout".to_owned(),
|
||||||
|
None,
|
||||||
|
Some(Err("timeout".to_owned())),
|
||||||
|
NOW,
|
||||||
|
);
|
||||||
|
assert_eq!(failed.name, "scout");
|
||||||
|
assert_eq!(failed.last_row_unix, None);
|
||||||
|
assert_eq!(failed.state, None);
|
||||||
|
let skipped = entry_of("scout".to_owned(), None, None, NOW);
|
||||||
|
assert_eq!(skipped, failed);
|
||||||
|
}
|
||||||
|
|
||||||
/// The list is what `STREAM.INFO` reports for the stream's subjects, by
|
/// The list is what `STREAM.INFO` reports for the stream's subjects, by
|
||||||
/// name: a subagent that published once and one that published many rows
|
/// name: a subagent that published once and one that published many rows
|
||||||
/// list the same way, and the order is the name's, not the stream's.
|
/// list the same way, and the order is the name's, not the stream's.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue