test(gp): pin the FitOutcome::warnings order the doc comment and gp.md both state (improvise-ipqu)

`fit_measure` concatenates three warning tiers with two `splice(0..0, ..)`
calls. The resulting order — the budget's notices, then the feature table's,
then preprocess's — is stated in the field's doc comment and in
context/code/gp.md section 14, joined into a user-facing status line, and was
checked nowhere: swapping the two statements left all 74 improvise-gp tests
green.

No pre-existing fixture produced more than one tier, which is why nothing caught
it. `all_three_warning_tiers_at_once` adds two things to the wide-category
fixture: a `_Measure` item with no cell anywhere (a header column entirely
missing in every row, which preprocess warns about) and a `Cost` cell at an
unused coordinate (one visible record with no `Score`, which feature_table
skips). Neither disturbs the budget arithmetic.

The assertion is on the three positions, not on the concatenation containing
them, and the helper panics on an absent tier so a tier that stops speaking
fails loudly rather than making the ordering trivially true. Two rivals, each
moving a different boundary, each caught only by this test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
Edward Langley
2026-09-22 18:29:35 -07:00
parent 2ccf2e5e2c
commit 17a582ae4d
2 changed files with 85 additions and 0 deletions
+40
View File
@@ -1183,3 +1183,43 @@ The assertion that the ranking is **non-empty** is there so the test cannot pass
by reporting nothing at all — which is precisely what the non-finite guard makes
`importance_ranking` do, one layer down, if the overflow is ever reintroduced
upstream of it.
### fit::tests::a_budget_notice_precedes_a_table_warning_which_precedes_a_preprocess_one
improvise-ipqu. `FitOutcome::warnings` is three tiers concatenated by two
`splice(0..0, ..)` calls in `fit_measure`, and the resulting order — the
budget's notices, then the feature table's, then `preprocess`'s — is stated in
the field's own doc comment **and** in `context/code/gp.md` §14, and is joined
into a user-facing status line. It was checked nowhere. Swapping the two
statements left all 74 `improvise-gp` tests green, measured before and after.
This is the shape gotcha 43 names — a rule "held up only by the order of two
statements" — and the repair is the same one
`a_published_basis_follows_the_grid_updates_it_describes` made: inject the swap
and watch a test fail.
**No pre-existing fixture produced more than one tier**, which is why nothing
caught it, and building one is the whole of the work. `all_three_warning_tiers_at_once`
extends `two_categories_of_different_widths` — 60 `Wide` items against a
64-feature budget, so `Wide` is dropped and a notice is emitted — with two
further things, each aimed at exactly one of the other tiers. A `Blank` item on
`_Measure` carrying **no cell anywhere** is a header column (`raw_measures`
reads item names, not data) that is empty in every row, which is what
`preprocess` warns about. And a `Cost` cell at `(w0, n1)`, a coordinate the base
fixture does not use, is one visible record with no `Score` value, which
`feature_table` skips and warns about. Neither addition disturbs the budget
arithmetic: the skipped record contributes no row, so `Wide` is still 60 wide,
and `Blank` costs 2, leaving `60 + 8 + 1 + 2 = 71` over budget by `Wide` alone.
Asserting that the concatenation *contains* all three would be vacuous
(`doctrine(fold_tests_must_test_order_not_the_axiom)`), so the assertion is on
the three positions. The helper resolving a tier to its index **panics** when a
tier is absent rather than returning a sentinel, so a future change that stops
one tier speaking fails here loudly instead of making the ordering assertion
trivially true over two elements.
Two rivals, each moving a different boundary, and each caught only by this test
(74 passed, 1 failed, both times). Swapping the two `splice` statements gives
table → notice → preprocess and fails at `notice < table`. Replacing the first
`splice(0..0, table.warnings)` with `extend(table.warnings)` gives notice →
preprocess → table and fails at `table < preprocessed` — which is the rival that
earns the third tier, since the swap alone never moves `preprocess`'s.
+45
View File
@@ -785,6 +785,51 @@ mod tests {
);
}
/// improvise-ipqu: the three warning tiers are concatenated, and the order
/// is user-facing. `context/test-evidence/gp.md`.
#[test]
fn a_budget_notice_precedes_a_table_warning_which_precedes_a_preprocess_one() {
let (model, view) = all_three_warning_tiers_at_once();
let outcome = fit_measure(&model, &view, "Score", FIT_INTO, &request())
.expect("dropping the wide column is enough");
let at = |needle: &str| {
outcome
.warnings
.iter()
.position(|w| w.contains(needle))
.unwrap_or_else(|| panic!("no {needle:?} warning in {:?}", outcome.warnings))
};
let (notice, table, preprocessed) = (at("budget"), at("no Score value"), at("Blank"));
assert!(
notice < table && table < preprocessed,
"budget notice, then the table's, then preprocess's: {:?}",
outcome.warnings
);
}
/// [`two_categories_of_different_widths`] plus the two things that make the
/// other two tiers speak. Reasoning: `context/test-evidence/gp.md`.
fn all_three_warning_tiers_at_once() -> (Model, View) {
let (mut model, view) = two_categories_of_different_widths();
// A measure with no cell anywhere is a header column that is entirely
// missing in every row, which is what `preprocess` warns about.
model
.category_mut(MEASURE)
.expect("_Measure always exists")
.add_item("Blank");
// A coordinate the base fixture does not use, carrying `Cost` and no
// `Score`: one visible record `feature_table` must skip.
model.set_cell(
CellKey::new(vec![
("Wide".to_string(), "w0".to_string()),
("Narrow".to_string(), "n1".to_string()),
(MEASURE.to_string(), "Cost".to_string()),
]),
CellValue::Number(1.0),
);
(model, view)
}
/// 60 `Wide` items and 8 `Narrow` ones over 60 records, so the two columns
/// are 60 and 8 and only one of them has to go.
fn two_categories_of_different_widths() -> (Model, View) {