`redraw` asks a dirty widget at its offer, and then again in the final box its parent chose from that answer. The second ask handed that box over as if it were an offer, so `draw_inner` ran `placed_box` on it and applied the widget's own alignment to a box that had already been placed -- a second placement on every local redraw of a widget that is not near-aligned. It only showed where the widget's alignment was its own to apply: a container override makes `draw_inner` take the box as given, and `Stack`, `Pad` and `Scroll` override every child they hand a box to. It is the fix for both of the handoff's standing warm-against-cold failures. Shrinker seed 288 on `region-node` was an 8.8px inset at each end of a `Text` under a `Span(Y-)` under two `Stack`s; oracle seed 326 at depth 6 was 88px on a `Text` under two `Branch`es. Neither reduced below 11 and 43 widgets, and both are this. Checked: fmt, clippy, 80 suite tests, 17 core unit tests, the release oracle at 100 seeds, **all fifteen shrinker cases at 400 seeds of depth 5**, and **1000 seeds of depth 6** -- the last two for the first time. `tabs`, `text`, `random` and the tab replay render byte-identical at 1920x1200 against `08c9d5a`, since nothing about a cold layout changes. Generated seed 20 at depth 4 catches it and joins the ordinary set, so `cargo test` fails without this rather than only the ignored long run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
127 lines
3.7 KiB
Rust
127 lines
3.7 KiB
Rust
//! Laying a tree out again has to land where growing it that way would.
|
|
//!
|
|
//! Every case is one of `scenario`'s, over the trees `iris::random` grows
|
|
//! from a seed. The fast test takes a handful of seeds and the ignored one
|
|
//! takes as many as it is asked for; both run the same cases the shrinker
|
|
//! does over the same trees, so a seed that fails here is reduced by
|
|
//!
|
|
//! SHRINK_SEED=<seed> SHRINK_DEPTH=<depth> SHRINK_CASE=<case> \
|
|
//! cargo test --release --test shrink -- --ignored --nocapture
|
|
//!
|
|
//! `IRIS_GENERATED_SEED`, `IRIS_GENERATED_SEEDS` and `IRIS_GENERATED_DEPTH`
|
|
//! select what the long run covers.
|
|
|
|
#[path = "scenario/mod.rs"]
|
|
mod scenario;
|
|
|
|
use iris::random::{Edits, plan};
|
|
use scenario::{ALL, Case, diverges, env, over_seeds};
|
|
|
|
/// How deep the generator branches. The generator widens two to four ways per
|
|
/// level, so depth is exponential in width and a deep narrow tree is not
|
|
/// reachable by raising this -- it buys more overlap between dependency
|
|
/// paths, not more ancestry.
|
|
fn depth() -> usize {
|
|
env("IRIS_GENERATED_DEPTH", 4)
|
|
}
|
|
|
|
/// The seeds the ordinary tests take. Seven that have never failed; 86,
|
|
/// which a `Scroll` fixed point once settled differently on; and 20, which
|
|
/// caught a locally redrawn widget being placed twice in the box its parent
|
|
/// had already placed it in.
|
|
const SEEDS: [u64; 10] = [1, 2, 3, 5, 8, 10, 13, 20, 86, 98];
|
|
|
|
fn check(seed: u64, depth: usize, case: Case) {
|
|
let grown = plan(seed, depth, &Edits::default());
|
|
if let Some(how) = diverges(&grown, case, seed) {
|
|
panic!(
|
|
"seed {seed} at depth {depth} differs after {}: {how}\n\
|
|
reduce it with SHRINK_SEED={seed} SHRINK_DEPTH={depth} \
|
|
SHRINK_CASE={} cargo test --release --test shrink -- --ignored --nocapture",
|
|
case.name(),
|
|
case.name(),
|
|
);
|
|
}
|
|
}
|
|
|
|
macro_rules! case {
|
|
($name:ident, $case:expr) => {
|
|
#[test]
|
|
fn $name() {
|
|
for seed in SEEDS {
|
|
check(seed, depth(), $case);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
case!(
|
|
many_widgets_redrawing_at_once_leaves_every_box_where_it_was,
|
|
Case::RepaintSome
|
|
);
|
|
case!(
|
|
everything_redrawing_at_once_leaves_every_box_where_it_was,
|
|
Case::Repaint
|
|
);
|
|
case!(
|
|
a_resize_lands_where_starting_at_that_size_would,
|
|
Case::Resize
|
|
);
|
|
case!(
|
|
a_resize_and_a_repaint_land_where_starting_that_way_would,
|
|
Case::ResizeRepaint
|
|
);
|
|
case!(
|
|
a_size_change_after_a_resize_lands_the_same_way,
|
|
Case::ResizeSize
|
|
);
|
|
case!(
|
|
a_size_change_lands_where_growing_it_that_way_would,
|
|
Case::Size
|
|
);
|
|
case!(
|
|
every_size_changing_at_once_lands_where_growing_it_that_way_would,
|
|
Case::EverySize
|
|
);
|
|
case!(
|
|
an_alignment_change_lands_where_growing_it_that_way_would,
|
|
Case::Align
|
|
);
|
|
case!(
|
|
giving_and_taking_a_movable_region_rebuilds_what_resolves_it,
|
|
Case::RegionNode
|
|
);
|
|
case!(
|
|
reordering_a_span_lands_where_growing_it_that_way_would,
|
|
Case::Reorder
|
|
);
|
|
|
|
#[test]
|
|
fn adding_and_removing_span_children_lands_where_growing_it_that_way_would() {
|
|
for case in ALL {
|
|
if matches!(case, Case::Shuffle(_)) {
|
|
for seed in SEEDS {
|
|
check(seed, depth(), case);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "as many seeds as it is asked for, rather than the nine the others check"]
|
|
fn a_long_run_of_seeds_agrees() {
|
|
let depth = depth();
|
|
let seeds: Vec<u64> = match std::env::var("IRIS_GENERATED_SEED")
|
|
.ok()
|
|
.and_then(|v| v.parse().ok())
|
|
{
|
|
Some(seed) => vec![seed],
|
|
None => (1..=env("IRIS_GENERATED_SEEDS", 100_u64)).collect(),
|
|
};
|
|
over_seeds(seeds, |seed| {
|
|
for case in ALL {
|
|
check(seed, depth, case);
|
|
}
|
|
});
|
|
}
|