diff --git a/crates/kebab-app/src/ingest.rs b/crates/kebab-app/src/ingest.rs index f7d72b4..e4211ed 100644 --- a/crates/kebab-app/src/ingest.rs +++ b/crates/kebab-app/src/ingest.rs @@ -84,6 +84,10 @@ pub fn ingest(scope: SourceScope, opts: IngestOpts) -> anyhow::Result(); + ingest_with_config( + env.config.clone(), + env.scope(), + IngestOpts { + progress: Some(tx), + cancel: Some(cancel), + ..Default::default() + }, + ) + .expect("a cancelled ingest still returns a report"); + let events: Vec = rx.into_iter().collect(); + + assert!( + matches!( + events + .iter() + .find(|e| matches!(e, IngestEvent::ScanCompleted { .. })), + Some(IngestEvent::ScanCompleted { total: 0 }) + ), + "the premise: this run must find nothing to index, or the asset \ + loop runs an iteration and records the cancel by itself: {events:?}" + ); + assert!( + matches!(events.last(), Some(IngestEvent::Aborted { .. })), + "a cancelled run must end in Aborted even when the asset loop \ + never ran a single iteration: {:?}", + events.last() + ); +} diff --git a/crates/kebab-cli/src/progress.rs b/crates/kebab-cli/src/progress.rs index aff03bb..ef07f6e 100644 --- a/crates/kebab-cli/src/progress.rs +++ b/crates/kebab-cli/src/progress.rs @@ -547,68 +547,64 @@ pub(crate) fn now_rfc3339() -> anyhow::Result { mod tests { use super::*; - fn feed(events: &[IngestEvent]) -> ProgressDisplay { - // quiet + non-tty: the bar exists with a hidden draw target, so - // its state is observable without a terminal and nothing is - // printed to the test's stderr. + /// The sweep phase (issue #228) borrows the asset bar and puts its + /// own, smaller total on it. If it does not hand the bar back, every + /// asset drawn afterwards carries the sweep's denominator — that + /// shipped once already, so it is pinned here. + /// + /// Scope: length and position only. The same bug also dropped the + /// `{asset_elapsed}` heartbeat key along with the style, and + /// indicatif exposes no way to read a bar's style back, so that half + /// rests on `dress_bar_for_assets` being the single place either + /// phase dresses the bar. + #[test] + fn sweep_hands_the_bar_back_to_the_asset_loop() { let mut d = ProgressDisplay::new(ProgressMode::Human { tty: false, quiet: true, }); - for e in events { - d.handle(e).expect("handle"); - } - d - } - - /// The sweep phase (issue #228) borrows the asset bar and puts its - /// own, smaller total on it. If it does not hand the bar back, every - /// asset drawn afterwards carries the sweep's label and denominator — - /// and the style swap also drops the `{asset_elapsed}` heartbeat that - /// is the only sign a slow asset is alive. That shipped once already; - /// this pins it so the next phase added here cannot repeat it. - #[test] - fn sweep_hands_the_bar_back_to_the_asset_loop() { - let d = feed(&[ + for e in [ IngestEvent::ScanStarted { root: "/ws".to_string(), }, IngestEvent::ScanCompleted { total: 17 }, IngestEvent::SweepStarted { total: 3 }, IngestEvent::SweepProgress { - idx: 1, + idx: 3, total: 3, path: "gone.md".to_string(), removed: true, }, - ]); + ] { + d.handle(&e).expect("handle"); + } assert_eq!( d.bar.as_ref().and_then(indicatif::ProgressBar::length), Some(3), "during the sweep the bar counts sweep candidates" ); + assert_eq!( + d.bar.as_ref().map(indicatif::ProgressBar::position), + Some(3), + "and tracks them" + ); - let d = feed(&[ - IngestEvent::ScanStarted { - root: "/ws".to_string(), - }, - IngestEvent::ScanCompleted { total: 17 }, - IngestEvent::SweepStarted { total: 3 }, - IngestEvent::SweepCompleted { - checked: 3, - purged: 3, - ms: 12, - }, - ]); + d.handle(&IngestEvent::SweepCompleted { + checked: 3, + purged: 3, + ms: 12, + }) + .expect("handle"); assert_eq!( d.bar.as_ref().and_then(indicatif::ProgressBar::length), Some(17), - "and once it ends the bar counts assets again" + "once the sweep ends the bar counts assets again" ); assert_eq!( d.bar.as_ref().map(indicatif::ProgressBar::position), Some(0), - "from the start, not from wherever the sweep left off" + "from the start — leaving the sweep's position would make the \ + asset loop look part-done before it began" ); } diff --git a/docs/DOGFOOD.md b/docs/DOGFOOD.md index 9e6fe5f..7771968 100644 --- a/docs/DOGFOOD.md +++ b/docs/DOGFOOD.md @@ -274,7 +274,7 @@ echo "# stdin content" | "$RELEASE_BIN" ingest-stdin --title "from stdin" --conf - ordering invariant (design §2.4a). - per-asset `idx/total/path/media/result/chunks`. - aggregate `counts` on `completed` / `aborted`. -- sweep 구간: `sweep_started.total` 이 후보 수와 맞고, `sweep_progress.idx` 가 1..=total 로 연속이며, `sweep_completed.checked == total`. +- sweep 구간: `sweep_started.total` 이 후보 수와 맞고, `sweep_progress.idx` 가 1..=total 로 연속이며, 취소 없이 완주하면 `sweep_completed.checked == total`. - sweep 이 끝난 뒤 진행바가 asset 분모·라벨로 돌아오는가 (TTY). sweep 이 같은 바를 빌려 쓰므로 복구가 빠지면 색인 구간 내내 `sweep [..] 4213/21` 로 그려진다. - ndjson 로그에 `purge` 줄과 `sweep_summary` 가 남는가 (이슈 #228 이전에는 이 구간이 0바이트였다). purge 실패 시 `purge_failed`. - sweep 중 Ctrl-C 한 번에 실제로 멈추는가. `sweep_completed.checked` 가 예고한 `total` 이 아니라 실제 검사한 수로 나와야 한다.