Files
iris/tests/unsettled.rs
T
iris-aiandClaude Opus 5 b7caab3b9e Grow trees that can be taken apart, and find that a first frame is wrong
Reconstructing a generated failure by hand had failed three times: a seed
reproduces a tree of hundreds of widgets, and the printed chain is not
enough to see which part matters. `tests/shrink.rs` grows trees from a
description it can simplify -- drop a child, unwrap a wrapper, shorten a
text, drop a declared length -- and takes the first simplification that
still fails until none does. It lives in the tests; nothing in the library
knows about it.

It works: with the box-length check in `try_reuse` deliberately disabled
it reduced a 96-widget tree to 2. That check is worth keeping, because a
fuzzer that cannot fail is a fuzzer that agrees with everything.

What it found is not what any of this was looking for. Six widgets, shrunk
from 402:

    Span[ Stack[ Text("Wrapping"), Aligned(pos,pos,
          SetSize(x: 76px, Text("Wrapping shapes", wrap))) ] ]

The wrapping text is one line on the first frame and two after a repaint,
and two is right for a 76px box -- so the *cold* tree is the one that has
not settled. `generated.rs` has been comparing a warm frame against a cold
one and calling the difference a retained-layout defect, while at least
some of it is the first frame shaping a text at a width it was measured in
rather than the one it was given. Retained state is not involved.

`tests/unsettled.rs` is that case by hand, in 0.06s. Both of its tests
fail, so both are ignored with the reason rather than left to break the
build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:49:00 -04:00

100 lines
3.3 KiB
Rust

//! The smallest tree that lays out differently on a second frame, shrunk from
//! a 402-widget one `tests/shrink.rs` grew. Both of these fail: a cold frame
//! leaves a wrapping text shaped at a width it was measured in rather than the
//! one it was given, and a repaint is what puts it right. So the warm-against-
//! cold oracle in `generated.rs` has been comparing against a tree that had
//! not settled, and some of what it called a warm defect is the cold side
//! being wrong.
use iris::harness::Harness;
use iris::prelude::*;
/// Six widgets, shrunk from a 402-widget tree the fuzzer found. Nothing about
/// the tree changes -- every widget is marked for redraw and the frame is
/// taken again -- so no box may move, and a warm frame has to land where a
/// cold one does.
fn plant(h: &mut Harness) -> Vec<WidgetId> {
let plain = wtext("Wrapping").size(16).wrap(false).add(&mut h.rsc);
let wrapped = wtext("Wrapping shapes").size(16).wrap(true).add(&mut h.rsc);
let sized = SetSize {
inner: wrapped.add_strong(&mut h.rsc),
x: Some(Len::px(76.0)),
y: None,
}
.add(&mut h.rsc);
let aligned = Aligned {
inner: sized.add_strong(&mut h.rsc),
align: Align {
x: Some(AxisAlign::Pos),
y: Some(AxisAlign::Pos),
},
}
.add(&mut h.rsc);
let stack = Stack {
children: vec![plain.add_strong(&mut h.rsc), aligned.add_strong(&mut h.rsc)],
size: StackSize::Child(0),
}
.add(&mut h.rsc);
let root = (stack,).span(Dir::RIGHT).add(&mut h.rsc);
h.set_root(root);
vec![
plain.id(),
wrapped.id(),
sized.id(),
aligned.id(),
stack.id(),
root.id(),
]
}
/// The first frame does not reach the layout a second one does, so "cold" is
/// not a fixed point and comparing against it compares against a tree that
/// has not settled.
#[test]
#[ignore = "fails: the first frame shapes the text at a width it does not have"]
fn one_frame_is_enough() {
let mut h = Harness::new((640, 900));
let ids = plant(&mut h);
let first = h.region(&ids[1]).unwrap();
for _ in 0..3 {
for &id in &ids {
h.rsc.widgets_mut().get_dyn_mut(id);
}
h.frame();
}
let settled = h.region(&ids[1]).unwrap();
println!(
"first frame {} tall, settled {} tall",
first.bot_right.y - first.top_left.y,
settled.bot_right.y - settled.top_left.y
);
assert_eq!(
first.bot_right.y - first.top_left.y,
settled.bot_right.y - settled.top_left.y,
"the first frame had not finished laying out"
);
}
#[test]
#[ignore = "fails for the same reason: the cold side has not settled either"]
fn repainting_everything_moves_nothing() {
let mut warm = Harness::new((640, 900));
let ids = plant(&mut warm);
for &id in &ids {
warm.rsc.widgets_mut().get_dyn_mut(id);
}
warm.frame();
let mut cold = Harness::new((640, 900));
let cold_ids = plant(&mut cold);
let mut wrong = Vec::new();
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
let (got, want) = (warm.region(&w), cold.region(&c));
if got != want {
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
}
}
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
}