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:
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user