use super::*;
fn fail<'a>(tool: &'a str, fp: &'a str, err: &'a str) -> ToolAttempt<'a> {
ToolAttempt {
tool,
arg_fingerprint: fp,
error: Some(err),
hard_reject: false,
recoverable_miss: false,
}
}
fn ok<'a>(tool: &'a str, fp: &'a str) -> ToolAttempt<'a> {
ToolAttempt {
tool,
arg_fingerprint: fp,
error: None,
hard_reject: false,
recoverable_miss: false,
}
}
#[test]
fn identical_failure_nudges_then_halts() {
let t = NoProgressTracker::new(DEFAULT_IDENTICAL_HALT_THRESHOLD);
assert_eq!(
t.record(1, &fail("read_file", "a", "not found")),
NoProgress::Continue
);
match t.record(2, &fail("read_file", "a", "not found")) {
NoProgress::Nudge(msg) => {
assert!(msg.contains("no progress since step 2"));
assert!(msg.contains("read_file"));
assert!(msg.contains("Change strategy"));
}
other => panic!("expected a nudge on the second identical failure, got {other:?}"),
}
match t.record(3, &fail("read_file", "a", "not found")) {
NoProgress::Halt(msg) => assert!(msg.contains("retried 3 times")),
other => panic!("expected a halt on the third identical failure, got {other:?}"),
}
}
#[test]
fn a_success_resets_the_ladder() {
let t = NoProgressTracker::new(DEFAULT_IDENTICAL_HALT_THRESHOLD);
let _ = t.record(1, &fail("t", "a", "boom"));
let _ = t.record(2, &fail("t", "a", "boom")); assert_eq!(t.record(3, &ok("t", "a")), NoProgress::Continue);
assert_eq!(t.record(4, &fail("t", "a", "boom")), NoProgress::Continue);
assert!(matches!(
t.record(5, &fail("t", "a", "boom")),
NoProgress::Nudge(_)
));
}
#[test]
fn changed_arguments_clear_the_identical_streak() {
let t = NoProgressTracker::new(DEFAULT_IDENTICAL_HALT_THRESHOLD);
let _ = t.record(1, &fail("read_file", "a", "not found"));
assert_eq!(
t.record(2, &fail("read_file", "b", "not found")),
NoProgress::Continue
);
assert_eq!(
t.record(3, &fail("list_dir", "c", "denied")),
NoProgress::Continue
);
}
#[test]
fn a_hard_rejection_halts_on_the_second_identical_repeat() {
let t = NoProgressTracker::new(DEFAULT_IDENTICAL_HALT_THRESHOLD);
let mut a = fail("send_email", "a", "[policy-blocked] denied");
a.hard_reject = true;
assert_eq!(t.record(1, &a), NoProgress::Continue);
let mut b = fail("send_email", "a", "[policy-blocked] denied");
b.hard_reject = true;
assert!(matches!(t.record(2, &b), NoProgress::Halt(msg) if msg.contains("security policy")));
}
#[test]
fn varied_failures_nudge_then_hit_the_backstop() {
let t = NoProgressTracker::new(DEFAULT_IDENTICAL_HALT_THRESHOLD);
for i in 1..=3 {
let (fp, err) = (format!("fp{i}"), format!("err{i}"));
assert_eq!(t.record(i, &fail("t", &fp, &err)), NoProgress::Continue);
}
assert!(matches!(
t.record(4, &fail("t", "fp4", "err4")),
NoProgress::Nudge(_)
));
assert_eq!(t.record(5, &fail("t", "fp5", "err5")), NoProgress::Continue);
assert!(matches!(
t.record(6, &fail("t", "fp6", "err6")),
NoProgress::Halt(msg) if msg.contains("in a row failed")
));
}
#[test]
fn unknown_tool_recovery_does_not_feed_the_backstop() {
let t = NoProgressTracker::new(DEFAULT_IDENTICAL_HALT_THRESHOLD);
for i in 0..6 {
let (fp, err) = (format!("fp{i}"), format!("unknown tool foo{i}"));
let mut a = fail("__unknown_tool__", &fp, &err);
a.recoverable_miss = true;
assert_eq!(t.record(i, &a), NoProgress::Continue);
}
}
#[test]
fn identical_halt_threshold_is_clamped_above_the_nudge() {
let t = NoProgressTracker::new(1);
assert_eq!(t.record(1, &fail("t", "a", "boom")), NoProgress::Continue);
assert!(matches!(
t.record(2, &fail("t", "a", "boom")),
NoProgress::Nudge(_)
));
assert!(matches!(
t.record(3, &fail("t", "a", "boom")),
NoProgress::Halt(_)
));
}