//! 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= SHRINK_DEPTH= SHRINK_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, 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. Eight 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) { check_plan(&plan(seed, depth, &Edits::default()), seed, depth, case); } fn check_plan(grown: &Plan, seed: u64, depth: usize, case: Case) { 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(), ); } } /// A test per case, and the list of which cases have one, from the same /// place. A case the ordinary suite leaves out runs only in the long scan, /// which nobody runs by hand. macro_rules! cases { ($($name:ident = $case:expr,)*) => { $( #[test] fn $name() { for seed in SEEDS { check(seed, depth(), $case); } } )* const NAMED: [Case; [$($case,)*].len()] = [$($case,)*]; }; } cases! { many_widgets_redrawing_at_once_leaves_every_box_where_it_was = Case::RepaintSome, everything_redrawing_at_once_leaves_every_box_where_it_was = Case::Repaint, a_resize_lands_where_starting_at_that_size_would = Case::Resize, a_resize_and_a_repaint_land_where_starting_that_way_would = Case::ResizeRepaint, a_size_change_after_a_resize_lands_the_same_way = Case::ResizeSize, a_resize_after_a_size_change_lands_the_same_way = Case::SizeResize, a_size_change_lands_where_growing_it_that_way_would = Case::Size, every_size_changing_at_once_lands_where_growing_it_that_way_would = Case::EverySize, an_alignment_change_lands_where_growing_it_that_way_would = Case::Align, giving_and_taking_a_movable_region_rebuilds_what_resolves_it = Case::RegionNode, reordering_a_span_lands_where_growing_it_that_way_would = Case::Reorder, } /// The shuffles are one test between them, so they are the only cases `ALL` /// may hold without a test of their own. #[test] fn every_case_runs_without_the_long_scan() { for case in ALL { assert!( NAMED.contains(&case) || matches!(case, Case::Shuffle(_)), "{} runs only in the long seed scan; give it a case here", case.name() ); } } #[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 ten the others check"] fn a_long_run_of_seeds_agrees() { let depth = depth(); let seeds: Vec = 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| { let grown = plan(seed, depth, &Edits::default()); for case in ALL { check_plan(&grown, seed, depth, case); } }); }