dwarves-gui/tests/populate_steps_red.rs
//! **dwarves' populate step bar, proven RED first.**
//!
//! The flat bar this replaces was not wrong about the clone — it was wrong about the
//! other two thirds of the work, which it drew as 100 %. So the assertions that matter
//! here are the ones a bar which only tracked the clone would fail: that the run is NOT
//! complete when the members have landed, and that the phases after the clone are
//! reported as unmeasured rather than as full.
//!
//! Everything is driven through the real producer ([`observe`]) from real
//! [`PopulateProgress`] values. Nothing writes a `StepRun` into place, because a fixture
//! staged past the producer would prove only that the widget can draw a struct.
use dwarves_gui::populate_steps::{
self, board, job_id, observe, phase_of, progress_fixture, Phase, CLONE, CODE_GRAPH, WAREHOUSE,
};
use facett_jobview::{ProgressMeter, StepPhase};
use nornir_warehouse_embed::Counter;
use step_board::OrderFault;
/// nordisk: 19 members. Every fixture below uses the real number.
const N: usize = 19;
/// A workspace id used by exactly one test.
///
/// The board is deliberately process-wide — one registry, every producer thread and
/// every pane — so two tests sharing an id are literally two producers racing on one
/// run. That is the condition `OrderFault` exists to catch, and it was catching it: the
/// first cut of this file used "nordisk" everywhere and six tests failed on each
/// other's writes. Sharing an id here would be staging the bug, not testing around it.
fn clean(ws: &str) -> String {
board().forget(&job_id(ws));
ws.to_string()
}
fn known(v: u64) -> Counter {
Counter::Known { value: v }
}
// ── 1. THE PHASE DECISION — pure, and it must distinguish all three ───────────
/// **A populate is three phases and the reader must tell them apart.**
///
/// The old bar collapsed all three into `repos_done / repos_total`. If any two of these
/// come back equal, the step bar is a flat bar with extra segments.
#[test]
fn the_three_phases_are_distinguishable_from_one_poll() {
// Members still coming in.
let cloning = progress_fixture("nordisk", N, 12, 3, 0, Counter::Pending, Counter::Pending);
assert_eq!(phase_of(&cloning), Phase::Clone);
// Every member landed — the OLD bar reads 100% here and stops. The warehouse has
// not been built and the code graph has not been consumed.
let built = progress_fixture("nordisk", N, N, 0, 0, Counter::Pending, Counter::Pending);
assert_eq!(
phase_of(&built),
Phase::Warehouse,
"19 of 19 members cloned is the START of phase 2, not the end of the work"
);
// The dep graph exists (the build's product); the consumer's stamp does not.
let scanning = progress_fixture("nordisk", N, N, 0, 0, known(1_284), Counter::Pending);
assert_eq!(phase_of(&scanning), Phase::CodeGraph);
// Both products present.
let done = progress_fixture("nordisk", N, N, 0, 0, known(1_284), known(1));
assert_eq!(phase_of(&done), Phase::Complete);
// And a warehouse nobody is touching is not a run at all.
let idle = progress_fixture("nordisk", 0, 0, 0, 0, Counter::Pending, Counter::Pending);
assert_eq!(
phase_of(&idle),
Phase::Idle,
"no members ⇒ no run, not 'phase 1 at 0%'"
);
// The four working phases are four distinct answers.
let all = [
phase_of(&cloning),
phase_of(&built),
phase_of(&scanning),
phase_of(&done),
];
for (i, a) in all.iter().enumerate() {
for b in all.iter().skip(i + 1) {
assert_ne!(a, b, "two phases collapsed onto one another: {a:?}");
}
}
}
// ── 2. THE GENUINE Determinate ────────────────────────────────────────────────
/// **The clone step shows a REAL fraction, and the phases after it show none.**
///
/// This is the whole reason dwarves wanted the bar: korp's four steps are all
/// `count: None` by design, so `Determinate` never renders there. Here the server reports
/// one row per member on every poll, and 12 of 19 must reach the segment as 12 of 19 —
/// not as `1.0`, not as `0.0`, and not as a constant that would look the same whatever
/// the server said.
#[test]
fn the_clone_step_is_genuinely_determinate_and_the_rest_are_not() {
let ws = clean("ws-determinate");
let p = progress_fixture(&ws, N, 12, 3, 0, Counter::Pending, Counter::Pending);
observe(&ws, &p, "Re-sync clicked").expect("a fresh run is in order");
let run = board()
.get(&job_id(&ws))
.expect("the producer started a run");
// ── the MEASURED step ──
assert_eq!(
run.steps[CLONE].count,
Some((12, N as u64)),
"the count is the members, verbatim"
);
match run.steps[CLONE].meter() {
ProgressMeter::Determinate(f) => {
let want = 12.0f32 / N as f32;
assert!((f - want).abs() < 1e-6, "12/19 and nothing else, got {f}");
// Not the identity value at either end — a test sitting on 0.0 or 1.0 cannot
// tell a working counter from a dead one.
assert!(f > 0.6 && f < 0.64, "a genuine partial fill: {f}");
}
other => panic!("the clone step must be Determinate, got {other:?}"),
}
// ── and it MOVES with the server, which a constant could not ──
let later = progress_fixture(&ws, N, 17, 2, 0, Counter::Pending, Counter::Pending);
observe(&ws, &later, "poll").expect("still in order");
let run2 = board().get(&job_id(&ws)).unwrap();
assert_eq!(run2.steps[CLONE].count, Some((17, N as u64)));
let (f1, f2) = (
match run.steps[CLONE].meter() {
ProgressMeter::Determinate(f) => f,
_ => unreachable!(),
},
match run2.steps[CLONE].meter() {
ProgressMeter::Determinate(f) => f,
_ => unreachable!(),
},
);
assert!(f2 > f1, "the fraction tracks the server: {f1} → {f2}");
// ── the UNMEASURED steps ──
assert_eq!(
run.steps[WAREHOUSE].count, None,
"nobody counts the warehouse build"
);
assert_eq!(
run.steps[CODE_GRAPH].count, None,
"nobody counts the consume"
);
}
/// **An active unmeasured step renders `Indeterminate` and publishes NO `fraction`.**
///
/// A zero in that slot reads as "0 % done" when the truth is "nobody counted", and one
/// invented percentage beside a measured one makes the measured one worthless.
#[test]
fn an_unmeasured_active_step_is_indeterminate_with_no_fraction_key() {
let ws = clean("ws-indeterminate");
// Every member landed → the warehouse build is ACTIVE and nothing counts it.
let p = progress_fixture(&ws, N, N, 0, 0, Counter::Pending, Counter::Pending);
observe(&ws, &p, "Re-sync clicked").expect("in order");
let run = board().get(&job_id(&ws)).unwrap();
assert_eq!(run.steps[WAREHOUSE].phase, StepPhase::Active);
assert_eq!(
run.steps[WAREHOUSE].meter(),
ProgressMeter::Indeterminate,
"in flight and unmeasured is a POSITIVE statement, never Determinate(0.0)"
);
let j = run.state_json();
let steps = j["steps"].as_array().expect("steps");
assert_eq!(steps[WAREHOUSE]["meter"]["kind"], "indeterminate");
assert!(
steps[WAREHOUSE]["meter"].get("fraction").is_none(),
"an unmeasured step must publish NO fraction key — a zero there reads as a \
measurement. got: {}",
steps[WAREHOUSE]["meter"]
);
// …while the measured one does carry its numbers.
assert_eq!(steps[CLONE]["meter"]["kind"], "determinate");
assert_eq!(steps[CLONE]["meter"]["current"], N);
assert_eq!(steps[CLONE]["meter"]["total"], N);
}
/// **The run is NOT complete when the members have landed.** The exact claim the old flat
/// bar made, and the reason this work exists.
#[test]
fn a_finished_clone_is_not_a_finished_populate() {
let ws = clean("ws-not-done");
let p = progress_fixture(&ws, N, N, 0, 0, Counter::Pending, Counter::Pending);
observe(&ws, &p, "Re-sync clicked").expect("in order");
let run = board().get(&job_id(&ws)).unwrap();
assert!(
!run.is_complete(),
"19 of 19 members is one of three phases, not the whole populate"
);
assert_eq!(run.done(), 1, "one phase finished, two to go");
assert_eq!(run.human_index(), 2, "the human reads 'step 2 of 3'");
let cap = run.caption();
assert_eq!(cap, "run #1 · step 2 of 3 · build the warehouse");
assert!(
!cap.contains("3 of 3"),
"the caption must not claim a finished populate: {cap}"
);
// And when the consumer DOES stamp the workspace, it finishes — otherwise this test passes for a
// bar that can never read complete, which is the opposite hollow guard.
let done = progress_fixture(&ws, N, N, 0, 0, known(1_284), known(1));
observe(&ws, &done, "poll").expect("in order");
let run = board().get(&job_id(&ws)).unwrap();
assert!(
run.is_complete(),
"both products on disk ⇒ the populate really is done"
);
assert_eq!(run.caption(), "run #1 · 3 of 3 steps complete");
}
// ── 3. THE ORDER FAULT — two owners on one run ────────────────────────────────
/// **A second producer announcing the build over a running clone is refused, and the
/// fault reaches the caller.**
///
/// dwarves is where this stops being theoretical: the poll worker owns the clone phase
/// while a `Workspaces.Fetch` started by the button, by nornir's poll loop or by the CLI
/// owns the build.
#[test]
fn a_second_producer_overtaking_the_clone_is_an_order_fault() {
let ws = clean("ws-fault");
let p = progress_fixture(&ws, N, 12, 3, 0, Counter::Pending, Counter::Pending);
observe(&ws, &p, "Re-sync clicked").expect("the clone phase is in order");
// The overtaking publish, straight at the board — what a second owner does.
let err = board()
.enter(&job_id(&ws), WAREHOUSE)
.expect_err("the order check MUST refuse a build announced over a running clone");
match &err {
OrderFault::PredecessorUnfinished {
entered_key,
blocker_key,
blocker_phase,
..
} => {
assert_eq!(entered_key, "warehouse");
assert_eq!(blocker_key, "clone");
assert_eq!(*blocker_phase, StepPhase::Active);
}
other => panic!("wrong fault: {other:?}"),
}
// The bar is red, and the clone was NOT silently stamped Done.
let run = board().get(&job_id(&ws)).unwrap();
assert_eq!(run.steps[WAREHOUSE].phase, StepPhase::Failed);
assert_eq!(
run.steps[CLONE].phase,
StepPhase::Active,
"the forward-fill would have hidden this"
);
assert!(
run.error
.as_deref()
.is_some_and(|e| e.contains("OUT OF ORDER")),
"{:?}",
run.error
);
}
/// **A fast tick must not walk a healthy run backwards into a red bar.**
///
/// This is the regression that nearly shipped. The pane polls every 1.5 s but only runs
/// the two table scans on every 4th tick, so on the three ticks in between `crates` and
/// `code_graph` both read `Pending` — and `phase_of`, which is pure and honest, reports
/// `Warehouse` because it can see no evidence of the build's product. Publishing that
/// over a run already on `Lens` is entering an earlier step behind a later active one:
/// `OrderFault::Backwards`, a RED bar, three ticks out of four, on a populate that is
/// perfectly healthy.
///
/// The assertion is deliberately on the DRAWN result — the step's phase and the caption —
/// and not on `observe`'s return value, because the board marks the step Failed itself.
/// A test that only checked for `Ok` would have passed through the whole bug.
#[test]
fn a_fast_tick_with_nothing_measured_does_not_walk_the_run_backwards() {
let ws = clean("ws-fasttick");
// A refine tick: the dep graph is on disk, so the run reaches the CODE_GRAPH phase.
let refined = progress_fixture(&ws, N, N, 0, 0, known(1_284), Counter::Pending);
observe(&ws, &refined, "Re-sync clicked").expect("in order");
let run = board().get(&job_id(&ws)).unwrap();
assert_eq!(
run.active(),
Some(CODE_GRAPH),
"the refine tick got us to the code-graph phase"
);
// Now three fast ticks. Identical server state, but the scans were skipped, so both
// counters come back Pending — which reads as "the warehouse build has no product".
let fast = progress_fixture(&ws, N, N, 0, 0, Counter::Pending, Counter::Pending);
assert_eq!(
phase_of(&fast),
Phase::Warehouse,
"the pure reader honestly says Warehouse — it has no evidence of anything later"
);
for _ in 0..3 {
observe(&ws, &fast, "poll").expect("a fast tick is not a fault");
}
let run = board().get(&job_id(&ws)).unwrap();
assert_eq!(
run.active(),
Some(CODE_GRAPH),
"the run held its place instead of falling back"
);
assert_eq!(run.failed(), None, "and NOTHING went red: {:?}", run.error);
assert_eq!(run.steps[CODE_GRAPH].phase, StepPhase::Active);
assert_eq!(
run.steps[WAREHOUSE].phase,
StepPhase::Done,
"a finished phase stays finished"
);
assert_eq!(
run.caption(),
"run #1 · step 3 of 3 · consume the code graph"
);
assert_eq!(
run.generation, 1,
"and it is still the SAME run — no phantom restart"
);
// …and the next refine tick still finishes it. Without this the test passes for an
// `observe` that ignores every poll after the first.
let done = progress_fixture(&ws, N, N, 0, 0, known(1_284), known(1));
observe(&ws, &done, "poll").expect("in order");
assert!(
board().get(&job_id(&ws)).unwrap().is_complete(),
"the run can still finish"
);
}
/// **`observe` hands the fault back rather than swallowing it.**
///
/// The board goes red on its own — that is fix-by-construction and it is tested in
/// step-board — but the sentence naming the two racing steps has to reach the pane, or
/// nobody learns WHICH producers collided. The signature is the contract.
#[test]
fn observe_returns_the_fault_rather_than_swallowing_it() {
let ws = clean("ws-observe");
let cloning = progress_fixture(&ws, N, 12, 3, 0, Counter::Pending, Counter::Pending);
// The return type is a Result the caller must look at — asserted so a refactor to
// `-> ()` fails a test rather than a review.
let out: Result<(), OrderFault> = observe(&ws, &cloning, "Re-sync clicked");
assert!(out.is_ok(), "an orderly first poll: {out:?}");
// A second OWNER announces the build over the running clone — the real shape, since
// `observe` claims its own predecessor and so cannot fault against itself.
let err = board()
.enter(&job_id(&ws), WAREHOUSE)
.expect_err("the board refuses a build announced over a running clone");
assert!(err.message().contains("OUT OF ORDER"), "{}", err.message());
// The bar is already red. `observe`'s next poll must not paper over it by quietly
// re-entering a step: a run that faulted stays faulted until it restarts.
let run = board().get(&job_id(&ws)).unwrap();
assert_eq!(run.steps[WAREHOUSE].phase, StepPhase::Failed);
assert!(run.caption().contains("FAILED"), "{}", run.caption());
}
// ── 4. FAILURE, RESTART, IDLE ─────────────────────────────────────────────────
/// **A member that did not clone fails the CLONE step and names it.** The chain must not
/// walk past a phase that failed.
#[test]
fn a_failed_member_reds_the_clone_step_and_names_the_member() {
let ws = clean("ws-failed");
let p = progress_fixture(&ws, N, 16, 0, 3, Counter::Pending, Counter::Pending);
observe(&ws, &p, "Re-sync clicked").expect("in order");
let run = board().get(&job_id(&ws)).unwrap();
assert_eq!(run.steps[CLONE].phase, StepPhase::Failed);
assert_eq!(
run.steps[WAREHOUSE].phase,
StepPhase::Pending,
"the chain did not walk past it"
);
assert!(!run.is_complete());
let err = run.error.clone().unwrap_or_default();
assert!(
err.contains("3 of 19"),
"the ratio, not a bare count: {err}"
);
assert!(
err.contains("znippy"),
"the first failing member, by name: {err}"
);
assert!(
err.contains("Connection timed out"),
"the server's own error chain: {err}"
);
assert!(run.caption().contains("FAILED"), "{}", run.caption());
}
/// **A populate that starts again reads as a new run, not as lost ground.**
#[test]
fn a_restart_bumps_the_run_number_and_clears_the_old_count() {
let ws = clean("ws-restart");
let done = progress_fixture(&ws, N, N, 0, 0, known(1_284), known(1));
observe(&ws, &done, "Re-sync clicked").expect("in order");
assert_eq!(board().get(&job_id(&ws)).unwrap().generation, 1);
// A day later, new commits: members start fetching again.
let again = progress_fixture(&ws, N, 2, 5, 0, Counter::Pending, Counter::Pending);
observe(&ws, &again, "nornir's poll loop found new commits").expect("in order");
let run = board().get(&job_id(&ws)).unwrap();
assert_eq!(
run.generation, 2,
"'step 1 of 3' after '3 of 3' must read as run #2"
);
assert_eq!(
run.steps[CLONE].count,
Some((2, N as u64)),
"measuring the NEW run"
);
assert!(
run.reason_line().contains("new commits"),
"{}",
run.reason_line()
);
assert_eq!(
run.steps[CODE_GRAPH].phase,
StepPhase::Pending,
"the old completion is gone"
);
}
/// **An idle warehouse gets no run at all** — a bar drawn for work that is not happening
/// is the thing this module removes.
#[test]
fn an_idle_warehouse_starts_no_run() {
let ws = clean("quiet");
let idle = progress_fixture(&ws, 0, 0, 0, 0, Counter::Pending, Counter::Pending);
observe(&ws, &idle, "poll").expect("no run, no fault");
assert!(board().get(&job_id(&ws)).is_none(), "no members ⇒ no bar");
}
/// **Repeated identical polls do not churn the board.** `observe` runs every 1.5 s; if it
/// rewrote the run each time, `updated_unix` would be a clock rather than a transition
/// and the bar would flicker.
#[test]
fn an_unchanged_poll_writes_nothing() {
let ws = clean("ws-steady");
let p = progress_fixture(&ws, N, N, 0, 0, Counter::Pending, Counter::Pending);
observe(&ws, &p, "Re-sync clicked").expect("in order");
let first = board().get(&job_id(&ws)).unwrap();
observe(&ws, &p, "poll").expect("in order");
observe(&ws, &p, "poll").expect("in order");
let after = board().get(&job_id(&ws)).unwrap();
assert_eq!(after.generation, first.generation, "no phantom restart");
assert_eq!(
after.steps, first.steps,
"no phase churn on an unchanged poll"
);
}
/// The Jobs-pane row carries the producer's own sentences, verbatim.
#[test]
fn the_job_row_carries_the_producers_sentences() {
let ws = clean("ws-jobrow");
let p = progress_fixture(&ws, N, 12, 3, 0, Counter::Pending, Counter::Pending);
observe(&ws, &p, "Re-sync clicked").expect("in order");
let run = board().get(&job_id(&ws)).unwrap();
let rows = populate_steps::job_entries();
let row = rows
.iter()
.find(|r| r.id == run.id)
.expect("one row per populate");
let meter = row.steps_meter().expect("the row carries a step meter");
assert_eq!(
meter.caption_text(),
run.caption(),
"drawn sentence == producer's sentence"
);
assert_eq!(
meter.steps[CLONE].count,
Some((12, N as u64)),
"the count survives the trip"
);
assert_eq!(row.kind, "populate");
}