From ad1abf6ec84f0b6a40df4c0ce47c615150fec6ea Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 27 Jan 2025 15:58:28 +0100 Subject: [PATCH] add proptests to some layout ops --- Cargo.lock | 104 +++++++++++++++++++ output/Cargo.toml | 1 + output/proptest-regressions/op_transform.txt | 10 ++ output/src/area.rs | 12 +-- output/src/coordinate.rs | 11 +- output/src/op_transform.rs | 60 ++++++++--- tek/src/view_scene.rs | 65 ++++++------ 7 files changed, 206 insertions(+), 57 deletions(-) create mode 100644 output/proptest-regressions/op_transform.txt diff --git a/Cargo.lock b/Cargo.lock index 5188647a..e4b84df3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,21 @@ dependencies = [ "console", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -403,6 +418,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fnv" version = "1.0.7" @@ -911,6 +932,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.8.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "quanta" version = "0.12.5" @@ -926,6 +967,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.38" @@ -965,6 +1012,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -1033,6 +1089,12 @@ dependencies = [ "bitflags 2.8.0", ] +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "ringbuf" version = "0.3.3" @@ -1067,6 +1129,18 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.18" @@ -1455,6 +1529,7 @@ dependencies = [ name = "tek_output" version = "0.2.0" dependencies = [ + "proptest", "tek_edn", "tek_tui", ] @@ -1509,6 +1584,20 @@ dependencies = [ "tek_time", ] +[[package]] +name = "tempfile" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1578,6 +1667,12 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.14" @@ -1628,6 +1723,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/output/Cargo.toml b/output/Cargo.toml index 11da9a3a..147a1c23 100644 --- a/output/Cargo.toml +++ b/output/Cargo.toml @@ -8,3 +8,4 @@ tek_edn = { path = "../edn" } [dev-dependencies] tek_tui = { path = "../tui" } +proptest = "^1" diff --git a/output/proptest-regressions/op_transform.txt b/output/proptest-regressions/op_transform.txt new file mode 100644 index 00000000..59815360 --- /dev/null +++ b/output/proptest-regressions/op_transform.txt @@ -0,0 +1,10 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc b05b448ca4eb29304cae506927639494cae99a9e1ab40c58ac9dcb70d1ea1298 # shrinks to op_x = Some(0), op_y = None, content = "", x = 0, y = 46377, w = 0, h = 38318 +cc efdb7136c68396fa7c632cc6d3b304545ada1ba134269278f890639559a17575 # shrinks to op_x = Some(0), op_y = Some(32768), content = "", x = 0, y = 0, w = 0, h = 0 +cc f6d43c39db04f4c0112fe998ef68cff0a4454cd9791775a3014cc81997fbadf4 # shrinks to op_x = Some(10076), op_y = None, content = "", x = 60498, y = 0, w = 0, h = 0 +cc 3cabc97f3fa3a83fd5f8cf2c619ed213c2be5e9b1cb13e5178bde87dd838e2f4 # shrinks to op_x = Some(3924), op_y = None, content = "", x = 63574, y = 0, w = 0, h = 0 diff --git a/output/src/area.rs b/output/src/area.rs index 8607bc7a..f4867949 100644 --- a/output/src/area.rs +++ b/output/src/area.rs @@ -38,29 +38,29 @@ pub trait Area: From<[N;4]> + Debug + Copy { [self.x(), self.y(), self.w(), h] } #[inline] fn x2 (&self) -> N { - self.x() + self.w() + self.x().plus(self.w()) } #[inline] fn y2 (&self) -> N { - self.y() + self.h() + self.y().plus(self.h()) } #[inline] fn lrtb (&self) -> [N;4] { [self.x(), self.x2(), self.y(), self.y2()] } #[inline] fn center (&self) -> [N;2] { - [self.x() + self.w()/2.into(), self.y() + self.h()/2.into()] + [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] } #[inline] fn center_x (&self, n: N) -> [N;4] { let [x, y, w, h] = self.xywh(); - [(x + w / 2.into()).minus(n / 2.into()), y + h / 2.into(), n, 1.into()] + [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] } #[inline] fn center_y (&self, n: N) -> [N;4] { let [x, y, w, h] = self.xywh(); - [x + w / 2.into(), (y + h / 2.into()).minus(n / 2.into()), 1.into(), n] + [x.plus(w / 2.into()), (y + h / 2.into()).minus(n / 2.into()), 1.into(), n] } #[inline] fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { let [x, y, w, h] = self.xywh(); - [(x + w / 2.into()).minus(n / 2.into()), (y + h / 2.into()).minus(m / 2.into()), n, m] + [(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m] } #[inline] fn centered (&self) -> [N;2] { diff --git a/output/src/coordinate.rs b/output/src/coordinate.rs index 4526a621..9523fbc7 100644 --- a/output/src/coordinate.rs +++ b/output/src/coordinate.rs @@ -1,7 +1,11 @@ use std::fmt::{Debug, Display}; use std::ops::{Add, Sub, Mul, Div}; -impl Coordinate for u16 {} +impl Coordinate for u16 { + #[inline] fn plus (self, other: Self) -> Self { + self.saturating_add(other) + } +} /// A linear coordinate. pub trait Coordinate: Send + Sync + Copy @@ -15,6 +19,7 @@ pub trait Coordinate: Send + Sync + Copy + Into + Into { + #[inline] fn zero () -> Self { 0.into() } #[inline] fn minus (self, other: Self) -> Self { if self >= other { self - other @@ -22,7 +27,5 @@ pub trait Coordinate: Send + Sync + Copy 0.into() } } - #[inline] fn zero () -> Self { - 0.into() - } + fn plus (self, other: Self) -> Self; } diff --git a/output/src/op_transform.rs b/output/src/op_transform.rs index 31bae805..17a1831d 100644 --- a/output/src/op_transform.rs +++ b/output/src/op_transform.rs @@ -139,8 +139,7 @@ transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{ X(_) => [x0, y, wmax, h], Y(_) => [x, y0, w, hmax], XY(_) => [x0, y0, wmax, hmax], - }.into() -}); + }.into() }); transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ let [x, y, w, h] = area.xywh(); let fixed_area = match self { @@ -154,8 +153,7 @@ transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ Self::Y(fh, _) => [x, y, w, *fh], Self::XY(fw, fh, _) => [x, y, *fw, *fh], }; - fixed_area -}); + fixed_area }); transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{ let area = Render::layout(&self.content(), area); match self { @@ -175,24 +173,60 @@ transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render:: [area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into())); transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout( &self.content(), - [area.x(), area.y(), area.w() + self.dx(), area.h() + self.dy()].into())); + [area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into())); transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{ let area = Render::layout(&self.content(), area); - [area.x() + self.dx(), area.y() + self.dy(), area.w(), area.h()] -}); + [area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()] }); transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{ let area = Render::layout(&self.content(), area); - [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] -}); + [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] }); transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{ let area = Render::layout(&self.content(), area); let dx = self.dx(); let dy = self.dy(); - [area.x().minus(dx), area.y().minus(dy), area.w() + dy + dy, area.h() + dy + dy] -}); + [area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))] }); transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ let area = Render::layout(&self.content(), area); let dx = self.dx(); let dy = self.dy(); - [area.x() + dx, area.y() + dy, area.w().minus(dy + dy), area.h().minus(dy + dy), ] -}); + [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy)), ] }); + +#[cfg(test)] mod test_op_transform { + use super::*; + use proptest::prelude::*; + use proptest::option::of; + macro_rules! test_op_transform { + ($fn:ident, $Op:ident) => { + proptest! { + #[test] fn $fn ( + op_x in of(u16::MIN..u16::MAX), + op_y in of(u16::MIN..u16::MAX), + content in "\\PC*", + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + ) { + if let Some(op) = match (op_x, op_y) { + (Some(x), Some(y)) => Some($Op::xy(x, y, content)), + (Some(x), None) => Some($Op::x(x, content)), + (Some(y), None) => Some($Op::y(y, content)), + _ => None + } { + assert_eq!(Content::layout(&op, [x, y, w, h]), + Render::layout(&op, [x, y, w, h])); + } + } + } + } + } + test_op_transform!(test_op_fixed, Fixed); + test_op_transform!(test_op_min, Min); + test_op_transform!(test_op_max, Max); + test_op_transform!(test_op_push, Push); + test_op_transform!(test_op_pull, Pull); + test_op_transform!(test_op_shrink, Shrink); + test_op_transform!(test_op_expand, Expand); + test_op_transform!(test_op_margin, Margin); + test_op_transform!(test_op_padding, Padding); +} diff --git a/tek/src/view_scene.rs b/tek/src/view_scene.rs index 0d55c8cd..dc043db8 100644 --- a/tek/src/view_scene.rs +++ b/tek/src/view_scene.rs @@ -1,23 +1,37 @@ use crate::*; impl Tek { + pub(crate) fn h_scenes (&self, editing: bool, height: usize, larger: usize) -> u16 { + self.scenes_sizes(editing, height, larger).last().map(|(_, _, _, y)|y as u16).unwrap_or(0)} + pub(crate) fn scenes_sizes (&self, editing: bool, height: usize, larger: usize) -> impl ScenesSizes<'_> { + let mut y = 0; + let (selected_track, selected_scene) = match self.selected() { + Selection::Track(t) => (Some(*t), None), + Selection::Scene(s) => (None, Some(*s)), + Selection::Clip(t, s) => (Some(*t), Some(*s)), + _ => (None, None) + }; + self.scenes().iter().enumerate().map(move|(s, scene)|{ + let active = editing && selected_track.is_some() && selected_scene == Some(s); + let height = if active { larger } else { height }; + let data = (s, scene, y, y + height); + y += height; + data})} pub fn view_scenes (&self) -> impl Content + use<'_> { let w_full = self.w(); let h = self.h_tracks_area(); let editing = self.is_editing(); let selected_track = self.selected().track(); let selected_scene = self.selected().scene(); - Tui::bg(Black, self.row_top( - self.w_tracks_area(), - h, - Map::new( - move||self.scenes_with_colors(editing, h), - move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_name( - w_full, (1 + y2 - y1) as u16, y1 as u16, s, scene, prev)), - self.per_track(move|t, track|Map::new( - move||self.scenes_with_track_colors(editing, h, t), - move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_clip( - (1 + y2 - y1) as u16, y1 as u16, - scene, prev, s, t, editing, selected_track == Some(t+1), selected_scene))), ())) } + let scene_names = Map::new( + move||self.scenes_with_colors(editing, h), + move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_name( + w_full, (1 + y2 - y1) as u16, y1 as u16, s, scene, prev)); + let scene_clips = self.per_track(move|t, track|Map::new( + move||self.scenes_with_track_colors(editing, h, t), + move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_clip( + (1 + y2 - y1) as u16, y1 as u16, + scene, prev, s, t, editing, selected_track == Some(t+1), selected_scene))); + Tui::bg(Black, self.row_top(self.w_tracks_area(), h, scene_names, scene_clips, ())) } fn scenes_with_colors (&self, editing: bool, h: u16) -> impl ScenesColors<'_> { self.scenes_sizes(editing, 2, 15).map_while( move|(s, scene, y1, y2)|if y2 as u16 > h { @@ -79,6 +93,11 @@ impl Tek { let colors = Self::colors(color, prev, selected, neighbor, is_last); let content = Fill::x(Align::w(Tui::fg(fg, Tui::bold(true, Bsp::e(icon, name))))); Phat { width: 0, height: 0, selected, content, colors, } } + const TAB: &str = " Tab"; + pub fn view_scene_add (&self) -> impl Content + use<'_> { + let data = (self.selected().scene().unwrap_or(0), self.scenes().len()); + self.fmtd.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); + self.button3("S", "add scene", self.fmtd.read().unwrap().scns.view.clone()) } fn colors ( theme: &ItemPalette, prev: Option, selected: bool, neighbor: bool, is_last: bool, @@ -92,26 +111,4 @@ impl Tek { prev.map(|prev|if neighbor { prev.light.rgb } else { prev.base.rgb }).unwrap_or(Reset) } fn color_lo (theme: &ItemPalette, is_last: bool, selected: bool) -> Color { if is_last { Reset } else if selected { theme.light.rgb } else { theme.base.rgb } } - const TAB: &str = " Tab"; - pub fn view_scene_add (&self) -> impl Content + use<'_> { - let data = (self.selected().scene().unwrap_or(0), self.scenes().len()); - self.fmtd.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); - self.button3("S", "add scene", self.fmtd.read().unwrap().scns.view.clone()) } - pub(crate) fn h_scenes (&self, editing: bool, height: usize, larger: usize) -> u16 { - self.scenes_sizes(editing, height, larger).last().map(|(_, _, _, y)|y as u16).unwrap_or(0) - } - pub(crate) fn scenes_sizes (&self, editing: bool, height: usize, larger: usize) -> impl ScenesSizes<'_> { - let mut y = 0; - let (selected_track, selected_scene) = match self.selected() { - Selection::Clip(t, s) => (Some(t.saturating_sub(1)), Some(s.saturating_sub(1))), - _ => (None, None) - }; - self.scenes().iter().enumerate().map(move|(s, scene)|{ - let active = editing && selected_track.is_some() && selected_scene == Some(s); - let height = if active { larger } else { height }; - let data = (s, scene, y, y + height); - y += height; - data - }) - } }