Skip to content

Commit 2b165a8

Browse files
committed
test(core): cover the Recovering emission throttle (first + final ticks pass)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7q3CvJzL4zZmDA9CbXyQQ
1 parent a117f86 commit 2b165a8

1 file changed

Lines changed: 70 additions & 1 deletion

File tree

crates/driven-core/src/orchestrator.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3272,6 +3272,11 @@ mod tests {
32723272
/// "reconcile invalid_grant -> needs_reauth + suspend" test can assert
32733273
/// the orchestrator runs the V-F transition off the reconcile path.
32743274
reconcile_auth_fail: std::sync::atomic::AtomicBool,
3275+
/// 2026-08-14 follow-up: when `> 0`, `reconcile` fires the recover
3276+
/// sink this many times (bytes climbing 0 -> total) with the clock
3277+
/// STATIONARY - drives the "throttle emits only the first and final
3278+
/// Recovering states" test.
3279+
recover_ticks: AtomicU64,
32753280
/// R2-P2-1: when `> 0`, `execute` STREAMS this many op outcomes through
32763281
/// the per-op `on_outcome` sink (so their activity is persisted) and
32773282
/// THEN returns `Err` - simulating a crash/shutdown PART-WAY through a
@@ -3448,12 +3453,26 @@ mod tests {
34483453
async fn reconcile(
34493454
&self,
34503455
source: &SourceRow,
3451-
_on_recover: &crate::executor::RecoverProgressSink<'_>,
3456+
on_recover: &crate::executor::RecoverProgressSink<'_>,
34523457
) -> anyhow::Result<()> {
34533458
// Count every attempt. A configured transient failure returns Err
34543459
// WITHOUT recording the source as adopted, so the P1-1 test can
34553460
// assert the failed source is retried on the next cycle.
34563461
self.reconciles.fetch_add(1, Ordering::SeqCst);
3462+
// 2026-08-14 follow-up: emulate a resume's progress stream (start
3463+
// tick at 0, evenly spaced acks, final tick at total).
3464+
let ticks = self.recover_ticks.load(Ordering::SeqCst);
3465+
if ticks > 1 {
3466+
let total = 1_000u64;
3467+
for i in 0..ticks {
3468+
on_recover(crate::executor::RecoverProgress {
3469+
path: "big/file.bin".to_string(),
3470+
bytes_done: i * total / (ticks - 1),
3471+
bytes_total: total,
3472+
})
3473+
.await;
3474+
}
3475+
}
34573476
// R-P2-2: a revoked token during the reconcile corrupt-trash retry
34583477
// surfaces as a CLASSIFIED error the orchestrator must turn into
34593478
// needs_reauth + suspend (not a plain transient retry).
@@ -6630,6 +6649,56 @@ mod tests {
66306649
);
66316650
}
66326651

6652+
/// 2026-08-14 follow-up: the reconcile recovery sink's orchestrator-side
6653+
/// throttle. Five rapid ticks with a STATIONARY clock must surface exactly
6654+
/// TWO `Recovering` states: the first tick (the immediate flip out of the
6655+
/// indeterminate sweep) and the final tick (bytes_done == bytes_total,
6656+
/// exempt from the throttle). The middle ticks are within the 1s window
6657+
/// and suppressed.
6658+
#[tokio::test]
6659+
async fn reconcile_recovery_ticks_throttle_to_first_and_final() {
6660+
let account = AccountId::new_v4();
6661+
let tmp = tempfile::tempdir().unwrap();
6662+
let src = source_in(account, tmp.path());
6663+
let exec = Arc::new(RecordingExecutor::default());
6664+
exec.recover_ticks.store(5, Ordering::SeqCst);
6665+
let state = Arc::new(FakeState::with_sources(vec![src]));
6666+
let clock = Arc::new(FakeClock::new());
6667+
clock.advance(std::time::Duration::from_millis(5_000));
6668+
let orch = SyncOrchestrator::new(
6669+
account,
6670+
state.clone(),
6671+
exec.clone(),
6672+
Arc::new(FakePowerSource::new(power_on_ac())),
6673+
Arc::new(FakeNet::online()),
6674+
clock.clone(),
6675+
OrchestratorConfig::default(),
6676+
);
6677+
6678+
let mut events = orch.subscribe();
6679+
orch.run_cycle(TickSource::Scheduled).await.unwrap();
6680+
6681+
let mut recovering: Vec<(u64, u64)> = Vec::new();
6682+
while let Ok(ev) = events.try_recv() {
6683+
if let OrchestratorEvent::StateChanged {
6684+
state:
6685+
OrchestratorState::Recovering {
6686+
bytes_done,
6687+
bytes_total,
6688+
..
6689+
},
6690+
} = ev
6691+
{
6692+
recovering.push((bytes_done, bytes_total));
6693+
}
6694+
}
6695+
assert_eq!(
6696+
recovering,
6697+
vec![(0, 1_000), (1_000, 1_000)],
6698+
"first and final ticks pass; the stationary-clock middle ticks are throttled"
6699+
);
6700+
}
6701+
66336702
#[tokio::test]
66346703
async fn auth_invalid_grant_marks_needs_reauth_emits_event_and_stops_cycle() {
66356704
// V-F (DESIGN s5.4): an op failing with `auth.invalid_grant` must move

0 commit comments

Comments
 (0)