From e4cd61f39f7a9fcb2df2d7162de3d64921537b24 Mon Sep 17 00:00:00 2001 From: Wojciech Kozyra Date: Tue, 5 Nov 2024 09:03:06 +0100 Subject: [PATCH] wip --- Cargo.toml | 1 + build_tools/nix/package.nix | 1 - .../simple_input_pass_through.scene.json | 0 snapshot_tests/snapshots | 2 +- src/bin/update_snapshots/main.rs | 85 - src/lib.rs | 3 + src/main.rs | 3 - src/snapshot_tests.rs | 173 +- src/snapshot_tests/image_tests.rs | 76 + src/snapshot_tests/input.rs | 111 + src/snapshot_tests/rescaler_tests.rs | 250 +++ src/snapshot_tests/shader_tests.rs | 262 +++ src/snapshot_tests/simple_tests.rs | 17 + src/snapshot_tests/snapshot.rs | 90 + src/snapshot_tests/test_case.rs | 325 +-- src/snapshot_tests/tests.rs | 1807 ----------------- src/snapshot_tests/text_tests.rs | 118 ++ src/snapshot_tests/tiles_tests.rs | 318 +++ src/snapshot_tests/tiles_transitions_tests.rs | 174 ++ src/snapshot_tests/transition_tests.rs | 95 + src/snapshot_tests/utils.rs | 67 +- src/snapshot_tests/view_tests.rs | 219 ++ 22 files changed, 1920 insertions(+), 2277 deletions(-) rename snapshot_tests/{ => simple}/simple_input_pass_through.scene.json (100%) delete mode 100644 src/bin/update_snapshots/main.rs create mode 100644 src/snapshot_tests/image_tests.rs create mode 100644 src/snapshot_tests/input.rs create mode 100644 src/snapshot_tests/rescaler_tests.rs create mode 100644 src/snapshot_tests/shader_tests.rs create mode 100644 src/snapshot_tests/simple_tests.rs create mode 100644 src/snapshot_tests/snapshot.rs delete mode 100644 src/snapshot_tests/tests.rs create mode 100644 src/snapshot_tests/text_tests.rs create mode 100644 src/snapshot_tests/tiles_tests.rs create mode 100644 src/snapshot_tests/tiles_transitions_tests.rs create mode 100644 src/snapshot_tests/transition_tests.rs create mode 100644 src/snapshot_tests/view_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 5ff8a49ab..c0261eb4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ resolver = "2" [features] default = ["web_renderer"] +update_snapshots = [] decklink = ["compositor_api/decklink"] web_renderer = ["dep:compositor_chromium", "compositor_api/web_renderer"] diff --git a/build_tools/nix/package.nix b/build_tools/nix/package.nix index 2c1d9e512..f28ce969f 100644 --- a/build_tools/nix/package.nix +++ b/build_tools/nix/package.nix @@ -51,7 +51,6 @@ rustPlatform.buildRustPackage { '' rm -f $out/bin/live_compositor rm -f $out/bin/package_for_release - rm -f $out/bin/update_snapshots mv $out/bin/main_process $out/bin/live_compositor '' + ( diff --git a/snapshot_tests/simple_input_pass_through.scene.json b/snapshot_tests/simple/simple_input_pass_through.scene.json similarity index 100% rename from snapshot_tests/simple_input_pass_through.scene.json rename to snapshot_tests/simple/simple_input_pass_through.scene.json diff --git a/snapshot_tests/snapshots b/snapshot_tests/snapshots index d8becda39..0d8cc569b 160000 --- a/snapshot_tests/snapshots +++ b/snapshot_tests/snapshots @@ -1 +1 @@ -Subproject commit d8becda39b0c167a7f4ddee67878d97f75ffad8e +Subproject commit 0d8cc569b9f616de79c62dbe1e10d3aa3db0cd39 diff --git a/src/bin/update_snapshots/main.rs b/src/bin/update_snapshots/main.rs deleted file mode 100644 index 7d47bd8d5..000000000 --- a/src/bin/update_snapshots/main.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::{collections::HashSet, fs, io}; - -#[path = "../../snapshot_tests/tests.rs"] -mod tests; - -#[allow(dead_code)] -#[path = "../../snapshot_tests/utils.rs"] -mod utils; - -#[path = "../../snapshot_tests/test_case.rs"] -mod test_case; - -use tests::snapshot_tests; - -use crate::{ - test_case::TestCaseInstance, - utils::{find_unused_snapshots, snapshots_path}, -}; - -fn main() { - println!("Updating snapshots:"); - let log_filter = tracing_subscriber::EnvFilter::new("info,wgpu_core=warn,wgpu_hal=warn"); - tracing_subscriber::fmt().with_env_filter(log_filter).init(); - - let tests: Vec<_> = snapshot_tests(); - let has_only_flag = tests.iter().any(|t| t.only); - let tests: Vec<_> = if has_only_flag { - tests - .into_iter() - .filter(|t| t.only) - .map(TestCaseInstance::new) - .collect() - } else { - tests.into_iter().map(TestCaseInstance::new).collect() - }; - for test in tests.iter() { - for pts in &test.case.timestamps { - let (snapshot, Err(_)) = test.test_snapshots_for_pts(*pts) else { - println!("PASS: \"{}\" (pts: {}ms)", test.case.name, pts.as_millis()); - continue; - }; - - println!( - "UPDATE: \"{}\" (pts: {}ms)", - test.case.name, - pts.as_millis() - ); - - let snapshot_path = snapshot.save_path(); - - if let Err(err) = fs::remove_file(&snapshot_path) { - if err.kind() != io::ErrorKind::NotFound { - panic!("Failed to remove old snapshots: {err}"); - } - } - let parent_folder = snapshot_path.parent().unwrap(); - if !parent_folder.exists() { - fs::create_dir_all(parent_folder).unwrap(); - } - - let width = snapshot.resolution.width - (snapshot.resolution.width % 2); - let height = snapshot.resolution.height - (snapshot.resolution.height % 2); - image::save_buffer( - snapshot_path, - &snapshot.data, - width as u32, - height as u32, - image::ColorType::Rgba8, - ) - .unwrap(); - } - } - if !has_only_flag { - // Check for unused snapshots - let snapshot_paths = tests - .iter() - .flat_map(TestCaseInstance::snapshot_paths) - .collect::>(); - for path in find_unused_snapshots(&snapshot_paths, snapshots_path()) { - println!("Removed unused snapshot {path:?}"); - fs::remove_file(path).unwrap(); - } - } - println!("Update finished"); -} diff --git a/src/lib.rs b/src/lib.rs index f24d5211f..d0a1c5f43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,6 @@ pub mod middleware; pub mod routes; pub mod server; pub mod state; + +#[cfg(test)] +mod snapshot_tests; diff --git a/src/main.rs b/src/main.rs index c463d9312..dbe1771a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,6 @@ mod routes; mod server; mod state; -#[cfg(test)] -mod snapshot_tests; - fn main() { #[cfg(feature = "web_renderer")] { diff --git a/src/snapshot_tests.rs b/src/snapshot_tests.rs index 6f5fb8639..54122e53e 100644 --- a/src/snapshot_tests.rs +++ b/src/snapshot_tests.rs @@ -1,89 +1,134 @@ -use std::{collections::HashSet, env, fs, path::PathBuf}; - -use self::{ - test_case::{TestCaseError, TestCaseInstance}, - tests::snapshot_tests, - utils::{find_unused_snapshots, snapshots_path}, +use std::{ + collections::HashSet, + fs, + path::{Path, PathBuf}, + time::Duration, }; +use compositor_api::types::UpdateOutputRequest; +use compositor_render::{scene::Component, Resolution}; +use test_case::{TestCase, TestResult, OUTPUT_ID}; +use utils::SNAPSHOTS_DIR_NAME; + +mod input; +mod snapshot; mod test_case; -mod tests; mod utils; -#[test] -fn test_snapshots() { - let tests: Vec = snapshot_tests() - .into_iter() - .map(TestCaseInstance::new) - .collect(); +mod image_tests; +mod rescaler_tests; +mod shader_tests; +mod simple_tests; +mod text_tests; +mod tiles_tests; +mod tiles_transitions_tests; +mod transition_tests; +mod view_tests; - check_test_names_uniqueness(&tests); +const DEFAULT_RESOLUTION: Resolution = Resolution { + width: 640, + height: 360, +}; - for test in tests.iter() { - println!("Test \"{}\"", test.case.name); - if let Err(err) = test.run() { - handle_error(err); +struct TestRunner { + cases: Vec, + snapshot_dir: PathBuf, +} + +impl TestRunner { + fn new(snapshot_dir: PathBuf) -> Self { + Self { + cases: Vec::new(), + snapshot_dir, } } - // Check for unused snapshots - let snapshot_paths = tests - .iter() - .flat_map(TestCaseInstance::snapshot_paths) - .collect::>(); - let unused_snapshots = find_unused_snapshots(&snapshot_paths, snapshots_path()); - if !unused_snapshots.is_empty() { - panic!("Some snapshots were not used: {unused_snapshots:#?}") + fn add(&mut self, case: TestCase) { + self.cases.push(case) } -} -fn handle_error(err: TestCaseError) { - let TestCaseError::Mismatch { - ref snapshot_from_disk, - ref produced_snapshot, - .. - } = err - else { - panic!("{err}"); - }; - - let failed_snapshot_path = failed_snapshot_path(); - if !failed_snapshot_path.exists() { - fs::create_dir_all(&failed_snapshot_path).unwrap(); + fn run(self) { + check_test_names_uniqueness(&self.cases); + check_unused_snapshots(&self.cases, &self.snapshot_dir); + let has_only = self.cases.iter().any(|test| test.only); + + let mut failed = false; + for test in self.cases.iter() { + if has_only && !test.only { + continue; + } + println!("Test \"{}\"", test.name); + if let TestResult::Failure = test.run() { + failed = true; + } + } + if failed { + panic!("Test failed") + } } - let snapshot_save_path = produced_snapshot.save_path(); - let snapshot_name = snapshot_save_path.file_name().unwrap().to_string_lossy(); - - let width = produced_snapshot.resolution.width - (produced_snapshot.resolution.width % 2); - let height = produced_snapshot.resolution.height - (produced_snapshot.resolution.height % 2); - image::save_buffer( - failed_snapshot_path.join(format!("mismatched_{snapshot_name}")), - &produced_snapshot.data, - width as u32, - height as u32, - image::ColorType::Rgba8, - ) - .unwrap(); - - snapshot_from_disk - .save(failed_snapshot_path.join(format!("original_{snapshot_name}"))) - .unwrap(); - - panic!("{err}"); } -fn failed_snapshot_path() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("failed_snapshot_tests") +fn scene_from_json(scene: &'static str) -> Vec { + let scene: UpdateOutputRequest = serde_json::from_str(scene).unwrap(); + vec![scene.video.unwrap().try_into().unwrap()] +} + +fn scenes_from_json(scenes: &[&'static str]) -> Vec { + scenes + .iter() + .map(|scene| { + let scene: UpdateOutputRequest = serde_json::from_str(scene).unwrap(); + scene.video.unwrap().try_into().unwrap() + }) + .collect() } -fn check_test_names_uniqueness(tests: &[TestCaseInstance]) { +fn check_test_names_uniqueness(tests: &[TestCase]) { let mut test_names = HashSet::new(); for test in tests.iter() { - if !test_names.insert(test.case.name) { + if !test_names.insert(test.name) { panic!( "Multiple snapshots tests with the same name: \"{}\".", - test.case.name + test.name ); } } } + +fn snapshots_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SNAPSHOTS_DIR_NAME) +} + +fn snapshot_save_path(test_name: &str, pts: &Duration) -> PathBuf { + let out_file_name = format!("{}_{}_{}.png", test_name, pts.as_millis(), OUTPUT_ID); + snapshots_path().join(out_file_name) +} + +fn check_unused_snapshots(tests: &[TestCase], snapshot_dir: &Path) { + let existing_snapshots = tests + .iter() + .flat_map(TestCase::snapshot_paths) + .collect::>(); + let mut unused_snapshots = Vec::new(); + for entry in fs::read_dir(snapshot_dir).unwrap() { + let entry = entry.unwrap(); + if !entry.file_name().to_string_lossy().ends_with(".png") { + continue; + } + + if !existing_snapshots.contains(&entry.path()) { + unused_snapshots.push(entry.path()) + } + } + + if !unused_snapshots.is_empty() { + if cfg!(feature = "update_snapshots") { + for snapshot_path in unused_snapshots { + println!("DELETE: Unused snapshot {snapshot_path:?}"); + fs::remove_file(snapshot_path).unwrap(); + } + } else { + panic!("Some snapshots were not used: {unused_snapshots:#?}") + } + } +} diff --git a/src/snapshot_tests/image_tests.rs b/src/snapshot_tests/image_tests.rs new file mode 100644 index 000000000..60360413e --- /dev/null +++ b/src/snapshot_tests/image_tests.rs @@ -0,0 +1,76 @@ +use compositor_render::{ + image::{ImageSource, ImageSpec, ImageType}, + RendererId, RendererSpec, +}; + +use super::{ + input::TestInput, scene_from_json, scenes_from_json, snapshots_path, test_case::TestCase, + TestRunner, +}; + +#[test] +fn image_tests() { + let mut runner = TestRunner::new(snapshots_path().join("image")); + + let image_renderer = ( + RendererId("image_jpeg".into()), + RendererSpec::Image(ImageSpec { + src: ImageSource::Url { + url: "https://www.rust-lang.org/static/images/rust-social.jpg".to_string(), + }, + image_type: ImageType::Jpeg, + }), + ); + + runner.add(TestCase { + name: "image/jpeg_as_root", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/image/jpeg_as_root.scene.json" + )), + renderers: vec![image_renderer.clone()], + inputs: vec![TestInput::new(1)], + ..Default::default() + }); + runner.add(TestCase { + name: "image/jpeg_in_view", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/image/jpeg_in_view.scene.json" + )), + renderers: vec![image_renderer.clone()], + inputs: vec![TestInput::new(1)], + ..Default::default() + }); + runner.add(TestCase { + name: "image/jpeg_in_view_overflow_fit", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/image/jpeg_in_view_overflow_fit.scene.json" + )), + renderers: vec![image_renderer.clone()], + inputs: vec![TestInput::new(1)], + ..Default::default() + }); + runner.add(TestCase { + // Test if removing image from scene works + name: "image/remove_jpeg_as_root", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/image/jpeg_as_root.scene.json"), + include_str!("../../snapshot_tests/view/empty_view.scene.json"), + ]), + renderers: vec![image_renderer.clone()], + inputs: vec![TestInput::new(1)], + ..Default::default() + }); + runner.add(TestCase { + // Test if removing image from scene works + name: "image/remove_jpeg_in_view", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/image/jpeg_in_view.scene.json"), + include_str!("../../snapshot_tests/view/empty_view.scene.json"), + ]), + renderers: vec![image_renderer.clone()], + inputs: vec![TestInput::new(1)], + ..Default::default() + }); + + runner.run() +} diff --git a/src/snapshot_tests/input.rs b/src/snapshot_tests/input.rs new file mode 100644 index 000000000..b01567772 --- /dev/null +++ b/src/snapshot_tests/input.rs @@ -0,0 +1,111 @@ +use compositor_render::{scene::RGBColor, FrameData, Resolution, YuvPlanes}; + +#[derive(Debug, Clone)] +pub(super) struct TestInput { + pub name: String, + pub resolution: Resolution, + pub data: FrameData, +} + +impl TestInput { + const COLOR_VARIANTS: [RGBColor; 17] = [ + // RED, input_0 + RGBColor(255, 0, 0), + // GREEN, input_1 + RGBColor(0, 255, 0), + // YELLOW, input_2 + RGBColor(255, 255, 0), + // MAGENTA, input_3 + RGBColor(255, 0, 255), + // BLUE, input_4 + RGBColor(0, 0, 255), + // CYAN, input_5 + RGBColor(0, 255, 255), + // ORANGE, input_6 + RGBColor(255, 165, 0), + // WHITE, input_7 + RGBColor(255, 255, 255), + // GRAY, input_8 + RGBColor(128, 128, 128), + // LIGHT_RED, input_9 + RGBColor(255, 128, 128), + // LIGHT_BLUE, input_10 + RGBColor(128, 128, 255), + // LIGHT_GREEN, input_11 + RGBColor(128, 255, 128), + // PINK, input_12 + RGBColor(255, 192, 203), + // PURPLE, input_13 + RGBColor(128, 0, 128), + // BROWN, input_14 + RGBColor(165, 42, 42), + // YELLOW_GREEN, input_15 + RGBColor(154, 205, 50), + // LIGHT_YELLOW, input_16 + RGBColor(255, 255, 224), + ]; + + pub fn new(index: usize) -> Self { + Self::new_with_resolution( + index, + Resolution { + width: 640, + height: 360, + }, + ) + } + + pub fn new_with_resolution(index: usize, resolution: Resolution) -> Self { + let color = Self::COLOR_VARIANTS[index].to_yuv(); + let mut y_plane = vec![0; resolution.width * resolution.height]; + let mut u_plane = vec![0; (resolution.width * resolution.height) / 4]; + let mut v_plane = vec![0; (resolution.width * resolution.height) / 4]; + + let yuv_color = |x: usize, y: usize| { + const BORDER_SIZE: usize = 18; + const GRID_SIZE: usize = 72; + + let is_border_in_x = + x <= BORDER_SIZE || (x <= resolution.width && x >= resolution.width - BORDER_SIZE); + let is_border_in_y: bool = y <= BORDER_SIZE + || (y <= resolution.height && y >= resolution.height - BORDER_SIZE); + let is_on_grid = (x / GRID_SIZE + y / GRID_SIZE) % 2 == 0; + + let mut y = color.0; + if is_border_in_x || is_border_in_y || is_on_grid { + y -= 0.2; + } + + (y.clamp(0.0, 1.0), color.1, color.2) + }; + + for x_coord in 0..resolution.width { + for y_coord in 0..resolution.height { + let (y, u, v) = yuv_color(x_coord, y_coord); + if x_coord % 2 == 0 && y_coord % 2 == 0 { + let (_, u2, v2) = yuv_color(x_coord + 1, y_coord); + let (_, u3, v3) = yuv_color(x_coord, y_coord + 1); + let (_, u4, v4) = yuv_color(x_coord + 1, y_coord + 1); + + let coord = (y_coord / 2) * (resolution.width / 2) + (x_coord / 2); + u_plane[coord] = ((u + u2 + u3 + u4) * 64.0) as u8; + v_plane[coord] = ((v + v2 + v3 + v4) * 64.0) as u8; + } + + y_plane[y_coord * resolution.width + x_coord] = (y * 255.0) as u8; + } + } + + let data = FrameData::PlanarYuv420(YuvPlanes { + y_plane: y_plane.into(), + u_plane: u_plane.into(), + v_plane: v_plane.into(), + }); + + Self { + name: format!("input_{index}"), + resolution, + data, + } + } +} diff --git a/src/snapshot_tests/rescaler_tests.rs b/src/snapshot_tests/rescaler_tests.rs new file mode 100644 index 000000000..a14b7d348 --- /dev/null +++ b/src/snapshot_tests/rescaler_tests.rs @@ -0,0 +1,250 @@ +use compositor_render::Resolution; + +use super::{ + input::TestInput, scene_from_json, snapshots_path, test_case::TestCase, TestRunner, + DEFAULT_RESOLUTION, +}; + +#[test] +fn rescaler_tests() { + let mut runner = TestRunner::new(snapshots_path().join("rescaler")); + let default = TestCase { + inputs: vec![TestInput::new(1)], + ..Default::default() + }; + + let higher_than_default_resolution = Resolution { + width: DEFAULT_RESOLUTION.width, + height: DEFAULT_RESOLUTION.height + 100, + }; + let lower_than_default_resolution = Resolution { + width: DEFAULT_RESOLUTION.width, + height: DEFAULT_RESOLUTION.height - 100, + }; + let portrait_resolution = Resolution { + width: 360, + height: 640, + }; + let higher_than_default = TestInput::new_with_resolution(1, higher_than_default_resolution); + let lower_than_default = TestInput::new_with_resolution(1, lower_than_default_resolution); + let portrait = TestInput::new_with_resolution(1, portrait_resolution); + + runner.add(TestCase { + name: "rescaler/fit_view_with_known_height", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_view_with_known_height.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_view_with_known_width", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_view_with_known_width.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_view_with_unknown_width_and_height", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_view_with_unknown_width_and_height.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fill_input_stream_inverted_aspect_ratio_align_top_left", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fill_input_stream_align_top_left.scene.json" + )), + inputs: vec![portrait.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fill_input_stream_inverted_aspect_ratio_align_bottom_right", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fill_input_stream_align_bottom_right.scene.json" + )), + inputs: vec![portrait.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fill_input_stream_lower_aspect_ratio_align_bottom_right", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fill_input_stream_align_bottom_right.scene.json" + )), + inputs: vec![lower_than_default.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fill_input_stream_lower_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fill_input_stream.scene.json" + )), + inputs: vec![lower_than_default.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fill_input_stream_higher_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fill_input_stream.scene.json" + )), + inputs: vec![higher_than_default.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fill_input_stream_inverted_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fill_input_stream.scene.json" + )), + inputs: vec![portrait.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fill_input_stream_matching_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fill_input_stream.scene.json" + )), + inputs: vec![TestInput::new(1)], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_lower_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream.scene.json" + )), + inputs: vec![lower_than_default.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_higher_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream.scene.json" + )), + inputs: vec![higher_than_default.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_higher_aspect_ratio_small_resolution", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream.scene.json" + )), + inputs: vec![TestInput::new_with_resolution( + 1, + Resolution { + width: higher_than_default_resolution.width / 10, + height: higher_than_default_resolution.height / 10, + }, + )], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_inverted_aspect_ratio_align_top_left", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream_align_top_left.scene.json" + )), + inputs: vec![portrait.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_inverted_aspect_ratio_align_bottom_right", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream_align_bottom_right.scene.json" + )), + inputs: vec![portrait.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_lower_aspect_ratio_align_bottom_right", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream_align_bottom_right.scene.json" + )), + inputs: vec![lower_than_default.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_inverted_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream.scene.json" + )), + inputs: vec![portrait.clone()], + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/fit_input_stream_matching_aspect_ratio", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/fit_input_stream.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/border_radius", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/border_radius.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/border_width", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/border_width.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/box_shadow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/box_shadow.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/border_radius_border_box_shadow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/border_radius_border_box_shadow.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/border_radius_box_shadow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/border_radius_box_shadow.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/border_radius_box_shadow_fit_input_stream", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/border_radius_box_shadow_fit_input_stream.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/border_radius_box_shadow_fill_input_stream", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/border_radius_box_shadow_fill_input_stream.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/nested_border_width_radius", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/nested_border_width_radius.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "rescaler/nested_border_width_radius_aligned", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/nested_border_width_radius_aligned.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + // it is supposed to be cut off because of the rescaler that wraps it + name: "rescaler/border_radius_border_box_shadow_rescaled", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/rescaler/border_radius_border_box_shadow_rescaled.scene.json" + )), + ..default.clone() + }); + runner.run() +} diff --git a/src/snapshot_tests/shader_tests.rs b/src/snapshot_tests/shader_tests.rs new file mode 100644 index 000000000..126bce552 --- /dev/null +++ b/src/snapshot_tests/shader_tests.rs @@ -0,0 +1,262 @@ +use std::time::Duration; + +use compositor_render::{ + scene::{ + Component, InputStreamComponent, ShaderComponent, ShaderParam, ShaderParamStructField, + }, + shader::ShaderSpec, + InputId, RendererId, RendererSpec, +}; + +use super::DEFAULT_RESOLUTION; + +use super::{input::TestInput, scene_from_json, snapshots_path, test_case::TestCase, TestRunner}; + +#[test] +fn shader_tests() { + let mut runner = TestRunner::new(snapshots_path().join("shader")); + + let input1 = TestInput::new(1); + let input2 = TestInput::new(2); + let input3 = TestInput::new(3); + let input4 = TestInput::new(4); + let input5 = TestInput::new(5); + + let plane_id_shader = ( + RendererId("base_params_plane_id".into()), + RendererSpec::Shader(ShaderSpec { + source: include_str!("../../snapshot_tests/shader/layout_planes.wgsl").into(), + }), + ); + + let time_shader = ( + RendererId("base_params_time".into()), + RendererSpec::Shader(ShaderSpec { + source: include_str!("../../snapshot_tests/shader/fade_to_ball.wgsl").into(), + }), + ); + + let texture_count_shader = ( + RendererId("base_params_texture_count".into()), + RendererSpec::Shader(ShaderSpec { + source: include_str!( + "../../snapshot_tests/shader/color_output_with_texture_count.wgsl" + ) + .into(), + }), + ); + + let output_resolution_shader = ( + RendererId("base_params_output_resolution".into()), + RendererSpec::Shader(ShaderSpec { + source: include_str!("../../snapshot_tests/shader/red_border.wgsl").into(), + }), + ); + + runner.add(TestCase { + name: "shader/base_params_plane_id_no_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/shader/base_params_plane_id_no_inputs.scene.json" + )), + renderers: vec![plane_id_shader.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "shader/base_params_plane_id_5_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/shader/base_params_plane_id_5_inputs.scene.json" + )), + renderers: vec![plane_id_shader.clone()], + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + input5.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "shader/base_params_time", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/shader/base_params_time.scene.json" + )), + renderers: vec![time_shader.clone()], + inputs: vec![input1.clone()], + timestamps: vec![ + Duration::from_secs(0), + Duration::from_secs(1), + Duration::from_secs(2), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "shader/base_params_output_resolution", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/shader/base_params_output_resolution.scene.json" + )), + renderers: vec![output_resolution_shader.clone()], + inputs: vec![input1.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "shader/base_params_texture_count_no_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/shader/base_params_texture_count_no_inputs.scene.json" + )), + renderers: vec![texture_count_shader.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "shader/base_params_texture_count_1_input", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/shader/base_params_texture_count_1_input.scene.json" + )), + renderers: vec![texture_count_shader.clone()], + inputs: vec![input1.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "shader/base_params_texture_count_2_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/shader/base_params_texture_count_2_inputs.scene.json" + )), + renderers: vec![texture_count_shader.clone()], + inputs: vec![input1.clone(), input2.clone()], + ..Default::default() + }); + + user_params_snapshot_tests(&mut runner); + + runner.run() +} + +fn user_params_snapshot_tests(runner: &mut TestRunner) { + struct CircleLayout { + pub left_px: u32, + pub top_px: u32, + pub width_px: u32, + pub height_px: u32, + /// RGBA 0.0 - 1.0 range + pub background_color: [f32; 4], + } + + impl CircleLayout { + pub fn shader_param(&self) -> ShaderParam { + ShaderParam::Struct(vec![ + ShaderParamStructField { + field_name: "left_px".to_string(), + value: ShaderParam::U32(self.left_px), + }, + ShaderParamStructField { + field_name: "top_px".to_string(), + value: ShaderParam::U32(self.top_px), + }, + ShaderParamStructField { + field_name: "width_px".to_string(), + value: ShaderParam::U32(self.width_px), + }, + ShaderParamStructField { + field_name: "height_px".to_string(), + value: ShaderParam::U32(self.height_px), + }, + ShaderParamStructField { + field_name: "background_color".to_string(), + value: ShaderParam::List(vec![ + ShaderParam::F32(self.background_color[0]), + ShaderParam::F32(self.background_color[1]), + ShaderParam::F32(self.background_color[2]), + ShaderParam::F32(self.background_color[3]), + ]), + }, + ]) + } + } + + let input1 = TestInput::new(1); + let input2 = TestInput::new(2); + let input3 = TestInput::new(3); + let input4 = TestInput::new(4); + + const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; + const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0]; + const BLUE: [f32; 4] = [0.0, 0.0, 1.0, 1.0]; + const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; + + let shader_id = RendererId("user_params_circle_layout".into()); + + let layout1 = CircleLayout { + left_px: 0, + top_px: 0, + width_px: (DEFAULT_RESOLUTION.width / 2) as u32, + height_px: (DEFAULT_RESOLUTION.height / 2) as u32, + background_color: RED, + }; + + let layout2 = CircleLayout { + left_px: (DEFAULT_RESOLUTION.width / 2) as u32, + top_px: 0, + width_px: (DEFAULT_RESOLUTION.width / 2) as u32, + height_px: (DEFAULT_RESOLUTION.height / 2) as u32, + background_color: GREEN, + }; + + let layout3 = CircleLayout { + left_px: 0, + top_px: (DEFAULT_RESOLUTION.height / 2) as u32, + width_px: (DEFAULT_RESOLUTION.width / 2) as u32, + height_px: (DEFAULT_RESOLUTION.height / 2) as u32, + background_color: BLUE, + }; + + let layout4 = CircleLayout { + left_px: (DEFAULT_RESOLUTION.width / 2) as u32, + top_px: (DEFAULT_RESOLUTION.height / 2) as u32, + width_px: (DEFAULT_RESOLUTION.width / 2) as u32, + height_px: (DEFAULT_RESOLUTION.height / 2) as u32, + background_color: WHITE, + }; + + let circle_layout_scene = Component::Shader(ShaderComponent { + id: None, + shader_id: shader_id.clone(), + shader_param: Some(ShaderParam::List(vec![ + layout1.shader_param(), + layout2.shader_param(), + layout3.shader_param(), + layout4.shader_param(), + ])), + size: DEFAULT_RESOLUTION.into(), + children: vec![ + Component::InputStream(InputStreamComponent { + id: None, + input_id: InputId(input1.name.clone().into()), + }), + Component::InputStream(InputStreamComponent { + id: None, + input_id: InputId(input2.name.clone().into()), + }), + Component::InputStream(InputStreamComponent { + id: None, + input_id: InputId(input3.name.clone().into()), + }), + Component::InputStream(InputStreamComponent { + id: None, + input_id: InputId(input4.name.clone().into()), + }), + ], + }); + + runner.add(TestCase { + name: "shader/user_params_circle_layout", + scene_updates: vec![circle_layout_scene], + renderers: vec![( + shader_id.clone(), + RendererSpec::Shader(ShaderSpec { + source: include_str!("../../snapshot_tests/shader/circle_layout.wgsl").into(), + }), + )], + inputs: vec![input1, input2, input3, input4], + ..Default::default() + }); +} diff --git a/src/snapshot_tests/simple_tests.rs b/src/snapshot_tests/simple_tests.rs new file mode 100644 index 000000000..9cbd63103 --- /dev/null +++ b/src/snapshot_tests/simple_tests.rs @@ -0,0 +1,17 @@ +use super::{input::TestInput, scene_from_json, snapshots_path, test_case::TestCase, TestRunner}; + +#[test] +fn simple_tests() { + let mut runner = TestRunner::new(snapshots_path().join("simple")); + + runner.add(TestCase { + name: "simple/simple_input_pass_through", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/simple/simple_input_pass_through.scene.json" + )), + inputs: vec![TestInput::new(1)], + ..Default::default() + }); + + runner.run() +} diff --git a/src/snapshot_tests/snapshot.rs b/src/snapshot_tests/snapshot.rs new file mode 100644 index 000000000..395e63617 --- /dev/null +++ b/src/snapshot_tests/snapshot.rs @@ -0,0 +1,90 @@ +use std::{ + fs::{self, create_dir_all}, + path::PathBuf, + time::Duration, +}; + +use compositor_render::Resolution; + +use super::snapshot_save_path; + +#[derive(Debug, Clone)] +pub(super) struct Snapshot { + pub test_name: String, + pub pts: Duration, + pub resolution: Resolution, + pub data: Vec, +} + +impl Snapshot { + pub(super) fn save_path(&self) -> PathBuf { + snapshot_save_path(&self.test_name, &self.pts) + } + + pub(super) fn diff_with_saved(&self) -> f32 { + let save_path = self.save_path(); + if !save_path.exists() { + return 1000.0; + } + let old_snapshot = image::open(save_path).unwrap().to_rgba8(); + snapshots_diff(&old_snapshot, &self.data) + } + + pub(super) fn update_on_disk(&self) { + let width = self.resolution.width - (self.resolution.width % 2); + let height = self.resolution.height - (self.resolution.height % 2); + let save_path = self.save_path(); + create_dir_all(save_path.parent().unwrap()).unwrap(); + image::save_buffer( + save_path, + &self.data, + width as u32, + height as u32, + image::ColorType::Rgba8, + ) + .unwrap(); + } + + pub(super) fn write_as_failed_snapshot(&self) { + let failed_snapshot_path = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("failed_snapshot_tests"); + create_dir_all(&failed_snapshot_path).unwrap(); + + let snapshot_name = self + .save_path() + .file_name() + .unwrap() + .to_string_lossy() + .to_string(); + + let width = self.resolution.width - (self.resolution.width % 2); + let height = self.resolution.height - (self.resolution.height % 2); + image::save_buffer( + failed_snapshot_path.join(format!("actual_{snapshot_name}")), + &self.data, + width as u32, + height as u32, + image::ColorType::Rgba8, + ) + .unwrap(); + + fs::copy( + self.save_path(), + failed_snapshot_path.join(format!("expected_{snapshot_name}")), + ) + .unwrap(); + } +} + +fn snapshots_diff(old_snapshot: &[u8], new_snapshot: &[u8]) -> f32 { + if old_snapshot.len() != new_snapshot.len() { + return 10000.0; + } + let square_error: f32 = old_snapshot + .iter() + .zip(new_snapshot) + .map(|(a, b)| (*a as i32 - *b as i32).pow(2) as f32) + .sum(); + + square_error / old_snapshot.len() as f32 +} diff --git a/src/snapshot_tests/test_case.rs b/src/snapshot_tests/test_case.rs index 3cd2ce00b..48d46dd58 100644 --- a/src/snapshot_tests/test_case.rs +++ b/src/snapshot_tests/test_case.rs @@ -1,31 +1,30 @@ -use std::{fmt::Display, path::PathBuf, sync::Arc, time::Duration}; +use std::{path::PathBuf, sync::Arc, time::Duration}; -use super::utils::{create_renderer, frame_to_rgba, snaphot_save_path, snapshots_diff}; +use super::{ + input::TestInput, + snapshot::Snapshot, + snapshot_save_path, + utils::{create_renderer, frame_to_rgba}, +}; use anyhow::Result; -use compositor_api::types::{self}; use compositor_render::{ - scene::RGBColor, Frame, FrameData, FrameSet, InputId, OutputFrameFormat, OutputId, Renderer, - RendererId, RendererSpec, Resolution, YuvPlanes, + scene::Component, Frame, FrameSet, InputId, OutputFrameFormat, OutputId, Renderer, RendererId, + RendererSpec, Resolution, }; -use image::ImageBuffer; pub(super) const OUTPUT_ID: &str = "output_1"; -pub struct TestCase { +#[derive(Debug, Clone)] +pub(super) struct TestCase { pub name: &'static str, pub inputs: Vec, pub renderers: Vec<(RendererId, RendererSpec)>, pub timestamps: Vec, - pub scene_updates: Updates, - #[allow(dead_code)] + pub scene_updates: Vec, pub only: bool, pub allowed_error: f32, -} - -pub enum Updates { - Scene(&'static str, Resolution), - Scenes(Vec<(&'static str, Resolution)>), + pub resolution: Resolution, } impl Default for TestCase { @@ -35,116 +34,97 @@ impl Default for TestCase { inputs: Vec::new(), renderers: Vec::new(), timestamps: vec![Duration::from_secs(0)], - scene_updates: Updates::Scenes(vec![]), + scene_updates: vec![], only: false, allowed_error: 1.0, + resolution: Resolution { + width: 640, + height: 360, + }, } } } -pub struct TestCaseInstance { - pub case: TestCase, - pub renderer: Renderer, +pub(super) enum TestResult { + Success, + Failure, } -impl TestCaseInstance { - pub fn new(test_case: TestCase) -> TestCaseInstance { - if test_case.name.is_empty() { - panic!("Snapshot test name has to be provided"); - } - - let mut renderer = create_renderer(); - for (id, spec) in test_case.renderers.iter() { +impl TestCase { + fn renderer(&self) -> Renderer { + let renderer = create_renderer(); + for (id, spec) in self.renderers.iter() { renderer .register_renderer(id.clone(), spec.clone()) .unwrap(); } - for (index, _) in test_case.inputs.iter().enumerate() { + for (index, _) in self.inputs.iter().enumerate() { renderer.register_input(InputId(format!("input_{}", index + 1).into())) } - let outputs = match test_case.scene_updates { - Updates::Scene(scene, resolution) => vec![(scene, resolution)], - Updates::Scenes(ref scenes) => scenes.clone(), - }; + renderer + } - for (update_str, resolution) in outputs { - let scene: types::UpdateOutputRequest = serde_json::from_str(update_str).unwrap(); - if let Some(root) = scene.video { - renderer - .update_scene( - OutputId(OUTPUT_ID.into()), - resolution, - OutputFrameFormat::PlanarYuv420Bytes, - root.try_into().unwrap(), - ) - .unwrap(); - } + pub(super) fn run(&self) -> TestResult { + if self.name.is_empty() { + panic!("Snapshot test name has to be provided"); } + let mut renderer = self.renderer(); + let mut result = TestResult::Success; - TestCaseInstance { - case: test_case, - renderer, + for update in &self.scene_updates { + renderer + .update_scene( + OutputId(OUTPUT_ID.into()), + self.resolution, + OutputFrameFormat::PlanarYuv420Bytes, + update.clone(), + ) + .unwrap(); } - } - #[allow(dead_code)] - pub fn run(&self) -> Result<(), TestCaseError> { - for pts in self.case.timestamps.iter() { - let (_, test_result) = self.test_snapshots_for_pts(*pts); - test_result?; + for pts in self.timestamps.iter().copied() { + if let TestResult::Failure = self.test_snapshots_for_pts(&mut renderer, pts) { + result = TestResult::Failure; + } } - Ok(()) + result } - pub fn test_snapshots_for_pts(&self, pts: Duration) -> (Snapshot, Result<(), TestCaseError>) { - let snapshot = self.snapshot_for_pts(pts).unwrap(); - - let save_path = snapshot.save_path(); - if !save_path.exists() { - return ( - snapshot.clone(), - Err(TestCaseError::SnapshotNotFound(snapshot.clone())), - ); - } - - let snapshot_from_disk = image::open(&save_path).unwrap().to_rgba8(); - let snapshots_diff = snapshots_diff(&snapshot_from_disk, &snapshot.data); - if snapshots_diff > self.case.allowed_error { - return ( - snapshot.clone(), - Err(TestCaseError::Mismatch { - snapshot_from_disk: snapshot_from_disk.into(), - produced_snapshot: snapshot.clone(), - diff: snapshots_diff, - }), - ); - } + fn test_snapshots_for_pts(&self, renderer: &mut Renderer, pts: Duration) -> TestResult { + let snapshot = self.snapshot_for_pts(renderer, pts).unwrap(); + let snapshots_diff = snapshot.diff_with_saved(); if snapshots_diff > 0.0 { println!( "Snapshot error in range (allowed: {}, current: {})", - self.case.allowed_error, snapshots_diff + self.allowed_error, snapshots_diff ); } - - (snapshot, Ok(())) - } - - #[allow(dead_code)] - pub fn snapshot_paths(&self) -> Vec { - let mut paths = Vec::new(); - for pts in self.case.timestamps.iter() { - paths.push(snaphot_save_path(self.case.name, pts)); + if snapshots_diff > self.allowed_error { + if cfg!(feature = "update_snapshots") { + println!("UPDATE: \"{}\" (pts: {}ms)", self.name, pts.as_millis(),); + snapshot.update_on_disk(); + } else { + println!("FAILED: \"{}\" (pts: {}ms)", self.name, pts.as_millis(),); + snapshot.write_as_failed_snapshot(); + return TestResult::Failure; + } } + TestResult::Success + } - paths + pub(super) fn snapshot_paths(&self) -> Vec { + self.timestamps + .iter() + .map(|pts| snapshot_save_path(self.name, pts)) + .collect() } - pub fn snapshot_for_pts(&self, pts: Duration) -> Result { + fn snapshot_for_pts(&self, renderer: &mut Renderer, pts: Duration) -> Result { let mut frame_set = FrameSet::new(pts); - for input in self.case.inputs.iter() { + for input in self.inputs.iter() { let input_id = InputId::from(Arc::from(input.name.clone())); let frame = Frame { data: input.data.clone(), @@ -154,178 +134,15 @@ impl TestCaseInstance { frame_set.frames.insert(input_id, frame); } - let outputs = self.renderer.render(frame_set)?; + let outputs = renderer.render(frame_set)?; let output_frame = outputs.frames.get(&OutputId(OUTPUT_ID.into())).unwrap(); let new_snapshot = frame_to_rgba(output_frame); Ok(Snapshot { - test_name: self.case.name.to_owned(), + test_name: self.name.to_owned(), pts, resolution: output_frame.resolution, data: new_snapshot, }) } } - -#[derive(Debug, Clone)] -pub struct TestInput { - pub name: String, - pub resolution: Resolution, - pub data: FrameData, -} - -impl TestInput { - const COLOR_VARIANTS: [RGBColor; 17] = [ - // RED, input_0 - RGBColor(255, 0, 0), - // GREEN, input_1 - RGBColor(0, 255, 0), - // YELLOW, input_2 - RGBColor(255, 255, 0), - // MAGENTA, input_3 - RGBColor(255, 0, 255), - // BLUE, input_4 - RGBColor(0, 0, 255), - // CYAN, input_5 - RGBColor(0, 255, 255), - // ORANGE, input_6 - RGBColor(255, 165, 0), - // WHITE, input_7 - RGBColor(255, 255, 255), - // GRAY, input_8 - RGBColor(128, 128, 128), - // LIGHT_RED, input_9 - RGBColor(255, 128, 128), - // LIGHT_BLUE, input_10 - RGBColor(128, 128, 255), - // LIGHT_GREEN, input_11 - RGBColor(128, 255, 128), - // PINK, input_12 - RGBColor(255, 192, 203), - // PURPLE, input_13 - RGBColor(128, 0, 128), - // BROWN, input_14 - RGBColor(165, 42, 42), - // YELLOW_GREEN, input_15 - RGBColor(154, 205, 50), - // LIGHT_YELLOW, input_16 - RGBColor(255, 255, 224), - ]; - - pub fn new(index: usize) -> Self { - Self::new_with_resolution( - index, - Resolution { - width: 640, - height: 360, - }, - ) - } - - pub fn new_with_resolution(index: usize, resolution: Resolution) -> Self { - let color = Self::COLOR_VARIANTS[index].to_yuv(); - let mut y_plane = vec![0; resolution.width * resolution.height]; - let mut u_plane = vec![0; (resolution.width * resolution.height) / 4]; - let mut v_plane = vec![0; (resolution.width * resolution.height) / 4]; - - let yuv_color = |x: usize, y: usize| { - const BORDER_SIZE: usize = 18; - const GRID_SIZE: usize = 72; - - let is_border_in_x = - x <= BORDER_SIZE || (x <= resolution.width && x >= resolution.width - BORDER_SIZE); - let is_border_in_y: bool = y <= BORDER_SIZE - || (y <= resolution.height && y >= resolution.height - BORDER_SIZE); - let is_on_grid = (x / GRID_SIZE + y / GRID_SIZE) % 2 == 0; - - let mut y = color.0; - if is_border_in_x || is_border_in_y || is_on_grid { - y -= 0.2; - } - - (y.clamp(0.0, 1.0), color.1, color.2) - }; - - for x_coord in 0..resolution.width { - for y_coord in 0..resolution.height { - let (y, u, v) = yuv_color(x_coord, y_coord); - if x_coord % 2 == 0 && y_coord % 2 == 0 { - let (_, u2, v2) = yuv_color(x_coord + 1, y_coord); - let (_, u3, v3) = yuv_color(x_coord, y_coord + 1); - let (_, u4, v4) = yuv_color(x_coord + 1, y_coord + 1); - - let coord = (y_coord / 2) * (resolution.width / 2) + (x_coord / 2); - u_plane[coord] = ((u + u2 + u3 + u4) * 64.0) as u8; - v_plane[coord] = ((v + v2 + v3 + v4) * 64.0) as u8; - } - - y_plane[y_coord * resolution.width + x_coord] = (y * 255.0) as u8; - } - } - - let data = FrameData::PlanarYuv420(YuvPlanes { - y_plane: y_plane.into(), - u_plane: u_plane.into(), - v_plane: v_plane.into(), - }); - - Self { - name: format!("input_{index}"), - resolution, - data, - } - } -} - -#[derive(Debug, Clone)] -pub struct Snapshot { - pub test_name: String, - pub pts: Duration, - pub resolution: Resolution, - pub data: Vec, -} - -impl Snapshot { - pub fn save_path(&self) -> PathBuf { - snaphot_save_path(&self.test_name, &self.pts) - } -} - -#[derive(Debug)] -pub enum TestCaseError { - SnapshotNotFound(Snapshot), - Mismatch { - #[allow(dead_code)] - snapshot_from_disk: Box, Vec>>, - produced_snapshot: Snapshot, - diff: f32, - }, -} - -impl std::error::Error for TestCaseError {} - -impl Display for TestCaseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let err_msg = match self { - TestCaseError::SnapshotNotFound(Snapshot { test_name, pts, .. }) => format!( - "FAILED: \"{}\", PTS({}). Snapshot file not found. Generate snapshots first", - test_name, - pts.as_secs() - ), - TestCaseError::Mismatch { - produced_snapshot: Snapshot { test_name, pts, .. }, - diff, - .. - } => { - format!( - "FAILED: \"{}\", PTS({}). Snapshots are different error={}", - test_name, - pts.as_secs_f32(), - diff, - ) - } - }; - - f.write_str(&err_msg) - } -} diff --git a/src/snapshot_tests/tests.rs b/src/snapshot_tests/tests.rs deleted file mode 100644 index e14460595..000000000 --- a/src/snapshot_tests/tests.rs +++ /dev/null @@ -1,1807 +0,0 @@ -use std::time::Duration; - -use compositor_render::{ - image::{ImageSource, ImageSpec, ImageType}, - shader::ShaderSpec, - RendererId, RendererSpec, Resolution, -}; -use serde_json::{json, Value}; - -use super::test_case::{TestCase, TestInput, Updates}; - -const DEFAULT_RESOLUTION: Resolution = Resolution { - width: 640, - height: 360, -}; - -pub fn snapshot_tests() -> Vec { - let mut tests = Vec::new(); - tests.append(&mut base_snapshot_tests()); - tests.append(&mut view_snapshot_tests()); - tests.append(&mut transition_snapshot_tests()); - tests.append(&mut image_snapshot_tests()); - tests.append(&mut text_snapshot_tests()); - tests.append(&mut tiles_snapshot_tests()); - tests.append(&mut rescaler_snapshot_tests()); - tests.append(&mut shader_snapshot_tests()); - tests -} - -fn shader_snapshot_tests() -> Vec { - let mut base_params_snapshot_tests = shader_base_params_snapshot_tests(); - let mut user_params_snapshot_tests = shader_user_params_snapshot_tests(); - - base_params_snapshot_tests.append(&mut user_params_snapshot_tests); - base_params_snapshot_tests -} - -fn shader_user_params_snapshot_tests() -> Vec { - struct CircleLayout { - pub left_px: u32, - pub top_px: u32, - pub width_px: u32, - pub height_px: u32, - /// RGBA 0.0 - 1.0 range - pub background_color: [f32; 4], - } - - impl CircleLayout { - pub fn shader_param(&self) -> Value { - let background_color_params: Vec = self - .background_color - .iter() - .map(|val| { - { - json!({ - "type": "f32", - "value": val - }) - } - }) - .collect(); - - json!({ - "type": "struct", - "value": [ - { - "field_name": "left_px", - "type": "u32", - "value": self.left_px - }, - { - "field_name": "top_px", - "type": "u32", - "value": self.top_px - }, - { - "field_name": "width_px", - "type": "u32", - "value": self.width_px - }, - { - "field_name": "height_px", - "type": "u32", - "value": self.height_px - }, - { - "field_name": "background_color", - "type": "list", - "value": background_color_params - }, - ] - }) - } - } - - let input1 = TestInput::new(1); - let input2 = TestInput::new(2); - let input3 = TestInput::new(3); - let input4 = TestInput::new(4); - - const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; - const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0]; - const BLUE: [f32; 4] = [0.0, 0.0, 1.0, 1.0]; - const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; - - let circle_layout_shader = ( - RendererId("user_params_circle_layout".into()), - RendererSpec::Shader(ShaderSpec { - source: include_str!("../../snapshot_tests/shader/circle_layout.wgsl").into(), - }), - ); - - let layout1 = CircleLayout { - left_px: 0, - top_px: 0, - width_px: (DEFAULT_RESOLUTION.width / 2) as u32, - height_px: (DEFAULT_RESOLUTION.height / 2) as u32, - background_color: RED, - }; - - let layout2 = CircleLayout { - left_px: (DEFAULT_RESOLUTION.width / 2) as u32, - top_px: 0, - width_px: (DEFAULT_RESOLUTION.width / 2) as u32, - height_px: (DEFAULT_RESOLUTION.height / 2) as u32, - background_color: GREEN, - }; - - let layout3 = CircleLayout { - left_px: 0, - top_px: (DEFAULT_RESOLUTION.height / 2) as u32, - width_px: (DEFAULT_RESOLUTION.width / 2) as u32, - height_px: (DEFAULT_RESOLUTION.height / 2) as u32, - background_color: BLUE, - }; - - let layout4 = CircleLayout { - left_px: (DEFAULT_RESOLUTION.width / 2) as u32, - top_px: (DEFAULT_RESOLUTION.height / 2) as u32, - width_px: (DEFAULT_RESOLUTION.width / 2) as u32, - height_px: (DEFAULT_RESOLUTION.height / 2) as u32, - background_color: WHITE, - }; - - let shader_param = json!({ - "type": "list", - "value": [ - layout1.shader_param(), - layout2.shader_param(), - layout3.shader_param(), - layout4.shader_param(), - ] - }); - - let inputs = Vec::from([input1, input2, input3, input4]); - - let children: Vec = inputs - .iter() - .map(|input| { - json!({ - "type": "input_stream", - "input_id": input.name - }) - }) - .collect(); - - let circle_layout_scene = Box::new( - json!({ - "video": { - "root": { - "type": "shader", - "shader_id": "user_params_circle_layout", - "resolution": { - "width": DEFAULT_RESOLUTION.width, - "height": DEFAULT_RESOLUTION.height - }, - "shader_param": shader_param, - "children": children, - } - } - }) - .to_string(), - ); - - Vec::from([TestCase { - name: "shader/user_params_circle_layout", - scene_updates: Updates::Scene(circle_layout_scene.leak(), DEFAULT_RESOLUTION), - renderers: vec![circle_layout_shader], - inputs, - ..Default::default() - }]) -} - -fn shader_base_params_snapshot_tests() -> Vec { - let input1 = TestInput::new(1); - let input2 = TestInput::new(2); - let input3 = TestInput::new(3); - let input4 = TestInput::new(4); - let input5 = TestInput::new(5); - - let plane_id_shader = ( - RendererId("base_params_plane_id".into()), - RendererSpec::Shader(ShaderSpec { - source: include_str!("../../snapshot_tests/shader/layout_planes.wgsl").into(), - }), - ); - - let time_shader = ( - RendererId("base_params_time".into()), - RendererSpec::Shader(ShaderSpec { - source: include_str!("../../snapshot_tests/shader/fade_to_ball.wgsl").into(), - }), - ); - - let texture_count_shader = ( - RendererId("base_params_texture_count".into()), - RendererSpec::Shader(ShaderSpec { - source: include_str!( - "../../snapshot_tests/shader/color_output_with_texture_count.wgsl" - ) - .into(), - }), - ); - - let output_resolution_shader = ( - RendererId("base_params_output_resolution".into()), - RendererSpec::Shader(ShaderSpec { - source: include_str!("../../snapshot_tests/shader/red_border.wgsl").into(), - }), - ); - - Vec::from([ - TestCase { - name: "shader/base_params_plane_id_no_inputs", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/shader/base_params_plane_id_no_inputs.scene.json" - ), - DEFAULT_RESOLUTION, - ), - renderers: vec![plane_id_shader.clone()], - ..Default::default() - }, - TestCase { - name: "shader/base_params_plane_id_5_inputs", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/shader/base_params_plane_id_5_inputs.scene.json" - ), - DEFAULT_RESOLUTION, - ), - renderers: vec![plane_id_shader.clone()], - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - input5.clone(), - ], - ..Default::default() - }, - TestCase { - name: "shader/base_params_time", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/shader/base_params_time.scene.json"), - DEFAULT_RESOLUTION, - ), - renderers: vec![time_shader.clone()], - inputs: vec![input1.clone()], - timestamps: vec![ - Duration::from_secs(0), - Duration::from_secs(1), - Duration::from_secs(2), - ], - ..Default::default() - }, - TestCase { - name: "shader/base_params_output_resolution", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/shader/base_params_output_resolution.scene.json" - ), - DEFAULT_RESOLUTION, - ), - renderers: vec![output_resolution_shader.clone()], - inputs: vec![input1.clone()], - ..Default::default() - }, - TestCase { - name: "shader/base_params_texture_count_no_inputs", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/shader/base_params_texture_count_no_inputs.scene.json" - ), - DEFAULT_RESOLUTION, - ), - renderers: vec![texture_count_shader.clone()], - ..Default::default() - }, - TestCase { - name: "shader/base_params_texture_count_1_input", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/shader/base_params_texture_count_1_input.scene.json" - ), - DEFAULT_RESOLUTION, - ), - renderers: vec![texture_count_shader.clone()], - inputs: vec![input1.clone()], - ..Default::default() - }, - TestCase { - name: "shader/base_params_texture_count_2_inputs", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/shader/base_params_texture_count_2_inputs.scene.json" - ), - DEFAULT_RESOLUTION, - ), - renderers: vec![texture_count_shader.clone()], - inputs: vec![input1.clone(), input2.clone()], - ..Default::default() - }, - ]) -} - -fn rescaler_snapshot_tests() -> Vec { - let higher_than_default = Resolution { - width: DEFAULT_RESOLUTION.width, - height: DEFAULT_RESOLUTION.height + 100, - }; - let lower_than_default = Resolution { - width: DEFAULT_RESOLUTION.width, - height: DEFAULT_RESOLUTION.height - 100, - }; - let portrait_resolution = Resolution { - width: 360, - height: 640, - }; - Vec::from([ - TestCase { - name: "rescaler/fit_view_with_known_height", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_view_with_known_height.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "rescaler/fit_view_with_known_width", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_view_with_known_width.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "rescaler/fit_view_with_unknown_width_and_height", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_view_with_unknown_width_and_height.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "rescaler/fill_input_stream_inverted_aspect_ratio_align_top_left", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fill_input_stream_align_top_left.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, portrait_resolution)], - ..Default::default() - }, - TestCase { - name: "rescaler/fill_input_stream_inverted_aspect_ratio_align_bottom_right", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fill_input_stream_align_bottom_right.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, portrait_resolution)], - ..Default::default() - }, - TestCase { - name: "rescaler/fill_input_stream_lower_aspect_ratio_align_bottom_right", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fill_input_stream_align_bottom_right.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, lower_than_default)], - ..Default::default() - }, - TestCase { - name: "rescaler/fill_input_stream_lower_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fill_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, lower_than_default)], - ..Default::default() - }, - TestCase { - name: "rescaler/fill_input_stream_higher_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fill_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, higher_than_default)], - ..Default::default() - }, - TestCase { - name: "rescaler/fill_input_stream_inverted_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fill_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, portrait_resolution)], - ..Default::default() - }, - TestCase { - name: "rescaler/fill_input_stream_matching_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fill_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_lower_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, lower_than_default)], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_higher_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, higher_than_default)], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_higher_aspect_ratio_small_resolution", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, Resolution { width: higher_than_default.width / 10, height: higher_than_default.height / 10 })], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_inverted_aspect_ratio_align_top_left", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream_align_top_left.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, portrait_resolution)], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_inverted_aspect_ratio_align_bottom_right", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream_align_bottom_right.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, portrait_resolution)], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_lower_aspect_ratio_align_bottom_right", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream_align_bottom_right.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, lower_than_default)], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_inverted_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, portrait_resolution)], - ..Default::default() - }, - TestCase { - name: "rescaler/fit_input_stream_matching_aspect_ratio", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/fit_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/border_radius", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/border_radius.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/border_width", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/border_width.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/box_shadow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/box_shadow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/border_radius_border_box_shadow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/border_radius_border_box_shadow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/border_radius_box_shadow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/border_radius_box_shadow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/border_radius_box_shadow_fit_input_stream", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/border_radius_box_shadow_fit_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/border_radius_box_shadow_fill_input_stream", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/border_radius_box_shadow_fill_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/nested_border_width_radius", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/nested_border_width_radius.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "rescaler/nested_border_width_radius_aligned", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/nested_border_width_radius_aligned.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - // it is supposed to be cut off because of the rescaler that wraps it - name: "rescaler/border_radius_border_box_shadow_rescaled", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/rescaler/border_radius_border_box_shadow_rescaled.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - } - ]) -} - -fn tiles_snapshot_tests() -> Vec { - let input1 = TestInput::new(1); - let input2 = TestInput::new(2); - let input3 = TestInput::new(3); - let input4 = TestInput::new(4); - let input5 = TestInput::new(5); - let input6 = TestInput::new(6); - let input7 = TestInput::new(7); - let input8 = TestInput::new(8); - let input9 = TestInput::new(9); - let input10 = TestInput::new(10); - let input11 = TestInput::new(11); - let input12 = TestInput::new(12); - let input13 = TestInput::new(13); - let input14 = TestInput::new(14); - let input15 = TestInput::new(15); - let portrait_resolution = Resolution { - width: 360, - height: 640, - }; - let portrait_input1 = TestInput::new_with_resolution(1, portrait_resolution); - let portrait_input2 = TestInput::new_with_resolution(2, portrait_resolution); - let portrait_input3 = TestInput::new_with_resolution(3, portrait_resolution); - let portrait_input4 = TestInput::new_with_resolution(4, portrait_resolution); - let portrait_input5 = TestInput::new_with_resolution(5, portrait_resolution); - let portrait_input6 = TestInput::new_with_resolution(6, portrait_resolution); - let portrait_input7 = TestInput::new_with_resolution(7, portrait_resolution); - let portrait_input8 = TestInput::new_with_resolution(8, portrait_resolution); - let portrait_input9 = TestInput::new_with_resolution(9, portrait_resolution); - let portrait_input10 = TestInput::new_with_resolution(10, portrait_resolution); - let portrait_input11 = TestInput::new_with_resolution(11, portrait_resolution); - let portrait_input12 = TestInput::new_with_resolution(12, portrait_resolution); - let portrait_input13 = TestInput::new_with_resolution(13, portrait_resolution); - let portrait_input14 = TestInput::new_with_resolution(14, portrait_resolution); - let portrait_input15 = TestInput::new_with_resolution(15, portrait_resolution); - Vec::from([ - TestCase { - name: "tiles_transitions/tile_resize_entire_component_with_parent_transition", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/tiles_transitions/start_tile_resize.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/end_tile_resize_with_view_transition.scene.json"), - DEFAULT_RESOLUTION, - ) - ]), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - ], - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(150), - Duration::from_millis(350), - // TODO: This transition does not look great, but it would require automatic - // transitions triggered by a size change (not scene update) - Duration::from_millis(450), - Duration::from_millis(500), - ], - ..Default::default() - }, - TestCase { - name: "tiles_transitions/tile_resize_entire_component_without_parent_transition", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/tiles_transitions/start_tile_resize.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/end_tile_resize.scene.json"), - DEFAULT_RESOLUTION, - ) - ]), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - ], - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(150), - Duration::from_millis(350), - Duration::from_millis(500), - ], - ..Default::default() - }, - TestCase { - name: "tiles_transitions/change_order_of_3_inputs_with_id", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/tiles_transitions/start_with_3_inputs_all_id.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/end_with_3_inputs_3_id_different_order.scene.json"), - DEFAULT_RESOLUTION, - ) - ]), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - ], - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(150), - Duration::from_millis(350), - Duration::from_millis(500), - ], - ..Default::default() - }, - TestCase { - name: "tiles_transitions/replace_component_by_adding_id", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/end_with_3_inputs_1_id.scene.json"), - DEFAULT_RESOLUTION, - ) - ]), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - ], - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(150), - Duration::from_millis(350), - Duration::from_millis(500), - ], - ..Default::default() - }, - TestCase { - name: "tiles_transitions/add_2_inputs_at_the_end_to_3_tiles_scene", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/end_with_5_inputs_no_id.scene.json"), - DEFAULT_RESOLUTION, - ) - ]), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - input5.clone(), - ], - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(150), - Duration::from_millis(350), - Duration::from_millis(500), - ], - ..Default::default() - }, - TestCase { - name: "tiles_transitions/add_input_on_2nd_pos_to_3_tiles_scene", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/end_with_4_inputs_1_id.scene.json"), - DEFAULT_RESOLUTION, - ) - ]), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - ], - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(150), - Duration::from_millis(350), - Duration::from_millis(500), - ], - ..Default::default() - }, - TestCase { - name: "tiles_transitions/add_input_at_the_end_to_3_tiles_scene", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/end_with_4_inputs_no_id.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/tiles_transitions/after_end_with_4_inputs_no_id.scene.json"), - DEFAULT_RESOLUTION, - ) - ]), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - ], - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(150), - Duration::from_millis(350), - Duration::from_millis(500), - ], - ..Default::default() - }, - TestCase { - name: "tiles/01_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/01_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/02_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/02_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/03_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/03_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/04_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/04_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/05_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/05_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - input5.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/15_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/15_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![ - input1.clone(), - input2.clone(), - input3.clone(), - input4.clone(), - input5.clone(), - input6.clone(), - input7.clone(), - input8.clone(), - input9.clone(), - input10.clone(), - input11.clone(), - input12.clone(), - input13.clone(), - input14.clone(), - input15.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/01_portrait_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/01_portrait_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![portrait_input1.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/02_portrait_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/02_portrait_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![portrait_input1.clone(), portrait_input2.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/03_portrait_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/03_portrait_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![ - portrait_input1.clone(), - portrait_input2.clone(), - portrait_input3.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/05_portrait_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/05_portrait_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![ - portrait_input1.clone(), - portrait_input2.clone(), - portrait_input3.clone(), - portrait_input4.clone(), - portrait_input5.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/15_portrait_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/15_portrait_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![ - portrait_input1.clone(), - portrait_input2.clone(), - portrait_input3.clone(), - portrait_input4.clone(), - portrait_input5.clone(), - portrait_input6.clone(), - portrait_input7.clone(), - portrait_input8.clone(), - portrait_input9.clone(), - portrait_input10.clone(), - portrait_input11.clone(), - portrait_input12.clone(), - portrait_input13.clone(), - portrait_input14.clone(), - portrait_input15.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/01_portrait_inputs_on_portrait_output", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/01_portrait_inputs.scene.json"), - portrait_resolution, - ), - inputs: vec![portrait_input1.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/03_portrait_inputs_on_portrait_output", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/03_portrait_inputs.scene.json"), - portrait_resolution, - ), - inputs: vec![ - portrait_input1.clone(), - portrait_input2.clone(), - portrait_input3.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/03_inputs_on_portrait_output", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/03_inputs.scene.json"), - portrait_resolution, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/05_portrait_inputs_on_portrait_output", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/05_portrait_inputs.scene.json"), - portrait_resolution, - ), - inputs: vec![ - portrait_input1.clone(), - portrait_input2.clone(), - portrait_input3.clone(), - portrait_input4.clone(), - portrait_input5.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/15_portrait_inputs_on_portrait_output", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/15_portrait_inputs.scene.json"), - portrait_resolution, - ), - inputs: vec![ - portrait_input1.clone(), - portrait_input2.clone(), - portrait_input3.clone(), - portrait_input4.clone(), - portrait_input5.clone(), - portrait_input6.clone(), - portrait_input7.clone(), - portrait_input8.clone(), - portrait_input9.clone(), - portrait_input10.clone(), - portrait_input11.clone(), - portrait_input12.clone(), - portrait_input13.clone(), - portrait_input14.clone(), - portrait_input15.clone(), - ], - ..Default::default() - }, - TestCase { - name: "tiles/align_center_with_03_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/align_center_with_03_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/align_top_left_with_03_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/align_top_left_with_03_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/align_with_margin_and_padding_with_03_inputs", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/tiles/align_with_margin_and_padding_with_03_inputs.scene.json" - ), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/margin_with_03_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/margin_with_03_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/margin_and_padding_with_03_inputs", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/tiles/margin_and_padding_with_03_inputs.scene.json" - ), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase { - name: "tiles/padding_with_03_inputs", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/tiles/padding_with_03_inputs.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![input1.clone(), input2.clone(), input3.clone()], - ..Default::default() - }, - TestCase{ - name: "tiles/video_call_with_labels", - scene_updates: Updates::Scene(include_str!("../../snapshot_tests/tiles/video_call_with_labels.scene.json"), DEFAULT_RESOLUTION), - inputs: vec![portrait_input1.clone(), portrait_input2.clone(), portrait_input3.clone()], - ..Default::default() - } - ]) -} - -fn text_snapshot_tests() -> Vec { - Vec::from([ - TestCase { - name: "text/align_center", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/align_center.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/align_right", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/align_right.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/bold_text", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/bold_text.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/dimensions_fitted_column_with_long_text", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/text/dimensions_fitted_column_with_long_text.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/dimensions_fitted_column_with_short_text", - scene_updates: Updates::Scene( - include_str!( - "../../snapshot_tests/text/dimensions_fitted_column_with_short_text.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/dimensions_fitted", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/dimensions_fitted.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/dimensions_fixed", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/dimensions_fixed.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/dimensions_fixed_with_overflow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/dimensions_fixed_with_overflow.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/red_text_on_blue_background", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/red_text_on_blue_background.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/wrap_glyph", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/wrap_glyph.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/wrap_none", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/wrap_none.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - name: "text/wrap_word", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/wrap_word.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - TestCase { - // Test if removing text from scene works - name: "text/remove_text_in_view", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/text/align_center.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/view/empty_view.scene.json"), - DEFAULT_RESOLUTION, - ), - ]), - ..Default::default() - }, - TestCase { - // Test if removing text from scene works - name: "text/remove_text_as_root", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/text/root_text.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/view/empty_view.scene.json"), - DEFAULT_RESOLUTION, - ), - ]), - ..Default::default() - }, - TestCase { - name: "text/text_as_root", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/text/root_text.scene.json"), - DEFAULT_RESOLUTION, - ), - ..Default::default() - }, - ]) -} - -fn image_snapshot_tests() -> Vec { - let image_renderer = ( - RendererId("image_jpeg".into()), - RendererSpec::Image(ImageSpec { - src: ImageSource::Url { - url: "https://www.rust-lang.org/static/images/rust-social.jpg".to_string(), - }, - image_type: ImageType::Jpeg, - }), - ); - - Vec::from([ - TestCase { - name: "image/jpeg_as_root", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/image/jpeg_as_root.scene.json"), - DEFAULT_RESOLUTION, - ), - renderers: vec![image_renderer.clone()], - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "image/jpeg_in_view", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/image/jpeg_in_view.scene.json"), - DEFAULT_RESOLUTION, - ), - renderers: vec![image_renderer.clone()], - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "image/jpeg_in_view_overflow_fit", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/image/jpeg_in_view_overflow_fit.scene.json"), - DEFAULT_RESOLUTION, - ), - renderers: vec![image_renderer.clone()], - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - // Test if removing image from scene works - name: "image/remove_jpeg_as_root", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/image/jpeg_as_root.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/view/empty_view.scene.json"), - DEFAULT_RESOLUTION, - ), - ]), - renderers: vec![image_renderer.clone()], - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - // Test if removing image from scene works - name: "image/remove_jpeg_in_view", - scene_updates: Updates::Scenes(vec![ - ( - include_str!("../../snapshot_tests/image/jpeg_in_view.scene.json"), - DEFAULT_RESOLUTION, - ), - ( - include_str!("../../snapshot_tests/view/empty_view.scene.json"), - DEFAULT_RESOLUTION, - ), - ]), - renderers: vec![image_renderer.clone()], - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - ]) -} - -fn transition_snapshot_tests() -> Vec { - Vec::from([ - TestCase { - name: "transition/change_rescaler_absolute_and_send_next_update", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_rescaler_absolute_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_rescaler_absolute_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_rescaler_absolute_after_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_secs(0), - Duration::from_secs(5), - Duration::from_secs(9), - Duration::from_secs(10), - ], - ..Default::default() - }, - TestCase { - name: "transition/change_view_width_and_send_abort_transition", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_after_end_without_id.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_secs(0), - Duration::from_secs(5), - Duration::from_secs(10), - ], - ..Default::default() - }, - TestCase { - name: "transition/change_view_width_and_send_next_update", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_after_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_secs(0), - Duration::from_secs(5), - Duration::from_secs(10), - ], - ..Default::default() - }, - TestCase { - name: "transition/change_view_width", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_width_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_secs(0), - Duration::from_secs(5), - Duration::from_secs(10), - Duration::from_secs(100), - ], - ..Default::default() - }, - TestCase { - name: "transition/change_view_height", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_view_height_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_height_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_secs(0), - Duration::from_secs(5), - Duration::from_secs(10), - ], - ..Default::default() - }, - TestCase { - name: "transition/change_view_absolute", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_view_absolute_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_absolute_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_secs(0), - Duration::from_secs(5), - Duration::from_secs(9), - Duration::from_secs(10), - ], - ..Default::default() - }, - TestCase { - name: "transition/change_view_absolute_cubic_bezier", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_view_absolute_cubic_bezier_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_absolute_cubic_bezier_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(2500), - Duration::from_secs(5000), - ], - ..Default::default() - }, - TestCase { - name: "transition/change_view_absolute_cubic_bezier_linear_like", - scene_updates: Updates::Scenes(vec![ - ( - include_str!( - "../../snapshot_tests/transition/change_view_absolute_cubic_bezier_linear_like_start.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ( - include_str!( - "../../snapshot_tests/transition/change_view_absolute_cubic_bezier_linear_like_end.scene.json" - ), - DEFAULT_RESOLUTION, - ), - ]), - timestamps: vec![ - Duration::from_millis(0), - Duration::from_millis(2500), - Duration::from_secs(5000), - ], - ..Default::default() - }, - ]) -} - -fn view_snapshot_tests() -> Vec { - Vec::from([ - TestCase { - name: "view/overflow_hidden_with_input_stream_children", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/overflow_hidden_with_input_stream_children.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new_with_resolution(1, Resolution { width: 180, height: 200 })], - ..Default::default() - }, - TestCase { - name: "view/overflow_hidden_with_view_children", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/overflow_hidden_with_view_children.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![], - ..Default::default() - }, - TestCase { - name: "view/constant_width_views_row", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/constant_width_views_row.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/constant_width_views_row_with_overflow_hidden", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/constant_width_views_row_with_overflow_hidden.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/constant_width_views_row_with_overflow_visible", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/constant_width_views_row_with_overflow_visible.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/constant_width_views_row_with_overflow_fit", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/constant_width_views_row_with_overflow_fit.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/dynamic_width_views_row", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/dynamic_width_views_row.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/dynamic_and_constant_width_views_row", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/dynamic_and_constant_width_views_row.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/dynamic_and_constant_width_views_row_with_overflow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/dynamic_and_constant_width_views_row_with_overflow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/constant_width_and_height_views_row", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/constant_width_and_height_views_row.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/view_with_absolute_positioning_partially_covered_by_sibling", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/view_with_absolute_positioning_partially_covered_by_sibling.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/view_with_absolute_positioning_render_over_siblings", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/view_with_absolute_positioning_render_over_siblings.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/root_view_with_background_color", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/root_view_with_background_color.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_radius", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_width", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_width.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/box_shadow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/box_shadow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/box_shadow_sibling", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/box_shadow_sibling.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_radius_border_box_shadow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius_border_box_shadow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_radius_box_shadow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius_box_shadow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_radius_box_shadow_overflow_hidden", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius_box_shadow_overflow_hidden.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_radius_box_shadow_overflow_fit", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius_box_shadow_overflow_fit.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_radius_box_shadow_rescaler_input_stream", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius_box_shadow_rescaler_input_stream.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/nested_border_width_radius", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/nested_border_width_radius.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/nested_border_width_radius_aligned", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/nested_border_width_radius_aligned.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/nested_border_width_radius_multi_child", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/nested_border_width_radius_multi_child.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - // it is supposed to be cut off because of the rescaler that wraps it - name: "view/border_radius_border_box_shadow_rescaled", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius_border_box_shadow_rescaled.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/root_border_radius_border_box_shadow", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/root_border_radius_border_box_shadow.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - TestCase { - name: "view/border_radius_border_box_shadow_rescaled_and_hidden_by_parent", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/view/border_radius_border_box_shadow_rescaled_and_hidden_by_parent.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }, - ]) -} - -fn base_snapshot_tests() -> Vec { - Vec::from([TestCase { - name: "simple_input_pass_through", - scene_updates: Updates::Scene( - include_str!("../../snapshot_tests/simple_input_pass_through.scene.json"), - DEFAULT_RESOLUTION, - ), - inputs: vec![TestInput::new(1)], - ..Default::default() - }]) -} diff --git a/src/snapshot_tests/text_tests.rs b/src/snapshot_tests/text_tests.rs new file mode 100644 index 000000000..da14b75d5 --- /dev/null +++ b/src/snapshot_tests/text_tests.rs @@ -0,0 +1,118 @@ +use super::{scene_from_json, scenes_from_json, snapshots_path, test_case::TestCase, TestRunner}; + +#[test] +fn text_tests() { + let mut runner = TestRunner::new(snapshots_path().join("text")); + + runner.add(TestCase { + name: "text/align_center", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/align_center.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/align_right", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/align_right.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/bold_text", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/bold_text.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/dimensions_fitted_column_with_long_text", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/dimensions_fitted_column_with_long_text.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/dimensions_fitted_column_with_short_text", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/dimensions_fitted_column_with_short_text.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/dimensions_fitted", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/dimensions_fitted.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/dimensions_fixed", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/dimensions_fixed.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/dimensions_fixed_with_overflow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/dimensions_fixed_with_overflow.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/red_text_on_blue_background", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/red_text_on_blue_background.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/wrap_glyph", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/wrap_glyph.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/wrap_none", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/wrap_none.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + name: "text/wrap_word", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/wrap_word.scene.json" + )), + ..Default::default() + }); + runner.add(TestCase { + // Test if removing text from scene works + name: "text/remove_text_in_view", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/text/align_center.scene.json"), + include_str!("../../snapshot_tests/view/empty_view.scene.json"), + ]), + ..Default::default() + }); + runner.add(TestCase { + // Test if removing text from scene works + name: "text/remove_text_as_root", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/text/root_text.scene.json"), + include_str!("../../snapshot_tests/view/empty_view.scene.json"), + ]), + ..Default::default() + }); + runner.add(TestCase { + name: "text/text_as_root", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/text/root_text.scene.json" + )), + ..Default::default() + }); + + runner.run() +} diff --git a/src/snapshot_tests/tiles_tests.rs b/src/snapshot_tests/tiles_tests.rs new file mode 100644 index 000000000..2e8f64315 --- /dev/null +++ b/src/snapshot_tests/tiles_tests.rs @@ -0,0 +1,318 @@ +use compositor_render::Resolution; + +use super::{input::TestInput, scene_from_json, snapshots_path, test_case::TestCase, TestRunner}; + +#[test] +fn tiles_tests() { + let mut runner = TestRunner::new(snapshots_path().join("tiles")); + + let input1 = TestInput::new(1); + let input2 = TestInput::new(2); + let input3 = TestInput::new(3); + let input4 = TestInput::new(4); + let input5 = TestInput::new(5); + let input6 = TestInput::new(6); + let input7 = TestInput::new(7); + let input8 = TestInput::new(8); + let input9 = TestInput::new(9); + let input10 = TestInput::new(10); + let input11 = TestInput::new(11); + let input12 = TestInput::new(12); + let input13 = TestInput::new(13); + let input14 = TestInput::new(14); + let input15 = TestInput::new(15); + let portrait_resolution = Resolution { + width: 360, + height: 640, + }; + let portrait_input1 = TestInput::new_with_resolution(1, portrait_resolution); + let portrait_input2 = TestInput::new_with_resolution(2, portrait_resolution); + let portrait_input3 = TestInput::new_with_resolution(3, portrait_resolution); + let portrait_input4 = TestInput::new_with_resolution(4, portrait_resolution); + let portrait_input5 = TestInput::new_with_resolution(5, portrait_resolution); + let portrait_input6 = TestInput::new_with_resolution(6, portrait_resolution); + let portrait_input7 = TestInput::new_with_resolution(7, portrait_resolution); + let portrait_input8 = TestInput::new_with_resolution(8, portrait_resolution); + let portrait_input9 = TestInput::new_with_resolution(9, portrait_resolution); + let portrait_input10 = TestInput::new_with_resolution(10, portrait_resolution); + let portrait_input11 = TestInput::new_with_resolution(11, portrait_resolution); + let portrait_input12 = TestInput::new_with_resolution(12, portrait_resolution); + let portrait_input13 = TestInput::new_with_resolution(13, portrait_resolution); + let portrait_input14 = TestInput::new_with_resolution(14, portrait_resolution); + let portrait_input15 = TestInput::new_with_resolution(15, portrait_resolution); + + runner.add(TestCase { + name: "tiles/01_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/01_inputs.scene.json" + )), + inputs: vec![input1.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/02_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/02_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/03_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/03_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/04_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/04_inputs.scene.json" + )), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/05_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/05_inputs.scene.json" + )), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + input5.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/15_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/15_inputs.scene.json" + )), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + input5.clone(), + input6.clone(), + input7.clone(), + input8.clone(), + input9.clone(), + input10.clone(), + input11.clone(), + input12.clone(), + input13.clone(), + input14.clone(), + input15.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/01_portrait_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/01_portrait_inputs.scene.json" + )), + inputs: vec![portrait_input1.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/02_portrait_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/02_portrait_inputs.scene.json" + )), + inputs: vec![portrait_input1.clone(), portrait_input2.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/03_portrait_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/03_portrait_inputs.scene.json" + )), + inputs: vec![ + portrait_input1.clone(), + portrait_input2.clone(), + portrait_input3.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/05_portrait_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/05_portrait_inputs.scene.json" + )), + inputs: vec![ + portrait_input1.clone(), + portrait_input2.clone(), + portrait_input3.clone(), + portrait_input4.clone(), + portrait_input5.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/15_portrait_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/15_portrait_inputs.scene.json" + )), + inputs: vec![ + portrait_input1.clone(), + portrait_input2.clone(), + portrait_input3.clone(), + portrait_input4.clone(), + portrait_input5.clone(), + portrait_input6.clone(), + portrait_input7.clone(), + portrait_input8.clone(), + portrait_input9.clone(), + portrait_input10.clone(), + portrait_input11.clone(), + portrait_input12.clone(), + portrait_input13.clone(), + portrait_input14.clone(), + portrait_input15.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/01_portrait_inputs_on_portrait_output", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/01_portrait_inputs.scene.json" + )), + resolution: portrait_resolution, + inputs: vec![portrait_input1.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/03_portrait_inputs_on_portrait_output", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/03_portrait_inputs.scene.json" + )), + resolution: portrait_resolution, + inputs: vec![ + portrait_input1.clone(), + portrait_input2.clone(), + portrait_input3.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/03_inputs_on_portrait_output", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/03_inputs.scene.json" + )), + resolution: portrait_resolution, + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/05_portrait_inputs_on_portrait_output", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/05_portrait_inputs.scene.json" + )), + resolution: portrait_resolution, + inputs: vec![ + portrait_input1.clone(), + portrait_input2.clone(), + portrait_input3.clone(), + portrait_input4.clone(), + portrait_input5.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/15_portrait_inputs_on_portrait_output", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/15_portrait_inputs.scene.json" + )), + resolution: portrait_resolution, + inputs: vec![ + portrait_input1.clone(), + portrait_input2.clone(), + portrait_input3.clone(), + portrait_input4.clone(), + portrait_input5.clone(), + portrait_input6.clone(), + portrait_input7.clone(), + portrait_input8.clone(), + portrait_input9.clone(), + portrait_input10.clone(), + portrait_input11.clone(), + portrait_input12.clone(), + portrait_input13.clone(), + portrait_input14.clone(), + portrait_input15.clone(), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/align_center_with_03_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/align_center_with_03_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/align_top_left_with_03_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/align_top_left_with_03_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/align_with_margin_and_padding_with_03_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/align_with_margin_and_padding_with_03_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/margin_with_03_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/margin_with_03_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/margin_and_padding_with_03_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/margin_and_padding_with_03_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/padding_with_03_inputs", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/padding_with_03_inputs.scene.json" + )), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles/video_call_with_labels", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/tiles/video_call_with_labels.scene.json" + )), + inputs: vec![ + portrait_input1.clone(), + portrait_input2.clone(), + portrait_input3.clone(), + ], + ..Default::default() + }); + + runner.run() +} diff --git a/src/snapshot_tests/tiles_transitions_tests.rs b/src/snapshot_tests/tiles_transitions_tests.rs new file mode 100644 index 000000000..1b71e75c6 --- /dev/null +++ b/src/snapshot_tests/tiles_transitions_tests.rs @@ -0,0 +1,174 @@ +use std::time::Duration; + +use super::{input::TestInput, scenes_from_json, snapshots_path, test_case::TestCase, TestRunner}; + +#[test] +fn tiles_transitions_tests() { + let mut runner = TestRunner::new(snapshots_path().join("tiles_transitions")); + + let input1 = TestInput::new(1); + let input2 = TestInput::new(2); + let input3 = TestInput::new(3); + let input4 = TestInput::new(4); + let input5 = TestInput::new(5); + + runner.add(TestCase { + name: "tiles_transitions/tile_resize_entire_component_with_parent_transition", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/tiles_transitions/start_tile_resize.scene.json"), + include_str!("../../snapshot_tests/tiles_transitions/end_tile_resize_with_view_transition.scene.json"), + ]), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + ], + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(150), + Duration::from_millis(350), + // TODO: This transition does not look great, but it would require automatic + // transitions triggered by a size change (not scene update) + Duration::from_millis(450), + Duration::from_millis(500), + ], + ..Default::default() + }); + + runner.add(TestCase { + name: "tiles_transitions/tile_resize_entire_component_without_parent_transition", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/tiles_transitions/start_tile_resize.scene.json"), + include_str!("../../snapshot_tests/tiles_transitions/end_tile_resize.scene.json"), + ]), + inputs: vec![input1.clone(), input2.clone(), input3.clone()], + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(150), + Duration::from_millis(350), + Duration::from_millis(500), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles_transitions/change_order_of_3_inputs_with_id", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/tiles_transitions/start_with_3_inputs_all_id.scene.json"), + include_str!("../../snapshot_tests/tiles_transitions/end_with_3_inputs_3_id_different_order.scene.json"), + ]), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + ], + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(150), + Duration::from_millis(350), + Duration::from_millis(500), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles_transitions/replace_component_by_adding_id", + scene_updates: scenes_from_json(&[ + include_str!( + "../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json" + ), + include_str!( + "../../snapshot_tests/tiles_transitions/end_with_3_inputs_1_id.scene.json" + ), + ]), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + ], + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(150), + Duration::from_millis(350), + Duration::from_millis(500), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles_transitions/add_2_inputs_at_the_end_to_3_tiles_scene", + scene_updates: scenes_from_json(&[ + include_str!( + "../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json" + ), + include_str!( + "../../snapshot_tests/tiles_transitions/end_with_5_inputs_no_id.scene.json" + ), + ]), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + input5.clone(), + ], + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(150), + Duration::from_millis(350), + Duration::from_millis(500), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles_transitions/add_input_on_2nd_pos_to_3_tiles_scene", + scene_updates: scenes_from_json(&[ + include_str!( + "../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json" + ), + include_str!( + "../../snapshot_tests/tiles_transitions/end_with_4_inputs_1_id.scene.json" + ), + ]), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + ], + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(150), + Duration::from_millis(350), + Duration::from_millis(500), + ], + ..Default::default() + }); + runner.add(TestCase { + name: "tiles_transitions/add_input_at_the_end_to_3_tiles_scene", + scene_updates: scenes_from_json(&[ + include_str!( + "../../snapshot_tests/tiles_transitions/start_with_3_inputs_no_id.scene.json" + ), + include_str!( + "../../snapshot_tests/tiles_transitions/end_with_4_inputs_no_id.scene.json" + ), + include_str!( + "../../snapshot_tests/tiles_transitions/after_end_with_4_inputs_no_id.scene.json" + ), + ]), + inputs: vec![ + input1.clone(), + input2.clone(), + input3.clone(), + input4.clone(), + ], + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(150), + Duration::from_millis(350), + Duration::from_millis(500), + ], + ..Default::default() + }); + + runner.run() +} diff --git a/src/snapshot_tests/transition_tests.rs b/src/snapshot_tests/transition_tests.rs new file mode 100644 index 000000000..0a813f698 --- /dev/null +++ b/src/snapshot_tests/transition_tests.rs @@ -0,0 +1,95 @@ +use std::time::Duration; + +use super::{scenes_from_json, snapshots_path, test_case::TestCase, TestRunner}; + +#[test] +fn transitions_tests() { + let mut runner = TestRunner::new(snapshots_path().join("transition")); + let default = TestCase { + timestamps: vec![ + Duration::from_millis(0), + Duration::from_millis(2500), + Duration::from_millis(5000), + Duration::from_millis(7500), + Duration::from_millis(9000), + Duration::from_millis(10000), + ], + ..Default::default() + }; + + runner.add(TestCase { + name: "transition/change_rescaler_absolute_and_send_next_update", + scene_updates: scenes_from_json(&[ + include_str!( + "../../snapshot_tests/transition/change_rescaler_absolute_start.scene.json" + ), + include_str!("../../snapshot_tests/transition/change_rescaler_absolute_end.scene.json"), + include_str!( + "../../snapshot_tests/transition/change_rescaler_absolute_after_end.scene.json" + ), + ]), + ..default.clone() + }); + runner.add(TestCase { + name: "transition/change_view_width_and_send_abort_transition", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/transition/change_view_width_start.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_width_end.scene.json"), + include_str!( + "../../snapshot_tests/transition/change_view_width_after_end_without_id.scene.json" + ), + ]), + ..default.clone() + }); + runner.add(TestCase { + name: "transition/change_view_width_and_send_next_update", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/transition/change_view_width_start.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_width_end.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_width_after_end.scene.json"), + ]), + ..default.clone() + }); + runner.add(TestCase { + name: "transition/change_view_width", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/transition/change_view_width_start.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_width_end.scene.json"), + ]), + ..default.clone() + }); + runner.add(TestCase { + name: "transition/change_view_height", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/transition/change_view_height_start.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_height_end.scene.json"), + ]), + ..default.clone() + }); + runner.add(TestCase { + name: "transition/change_view_absolute", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/transition/change_view_absolute_start.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_absolute_end.scene.json"), + ]), + ..default.clone() + }); + runner.add(TestCase { + name: "transition/change_view_absolute_cubic_bezier", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/transition/change_view_absolute_cubic_bezier_start.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_absolute_cubic_bezier_end.scene.json"), + ]), + ..default.clone() + }); + runner.add(TestCase { + name: "transition/change_view_absolute_cubic_bezier_linear_like", + scene_updates: scenes_from_json(&[ + include_str!("../../snapshot_tests/transition/change_view_absolute_cubic_bezier_linear_like_start.scene.json"), + include_str!("../../snapshot_tests/transition/change_view_absolute_cubic_bezier_linear_like_end.scene.json"), + ]), + ..default.clone() + }); + + runner.run() +} diff --git a/src/snapshot_tests/utils.rs b/src/snapshot_tests/utils.rs index 893133f82..5454d0adc 100644 --- a/src/snapshot_tests/utils.rs +++ b/src/snapshot_tests/utils.rs @@ -1,8 +1,5 @@ use core::panic; use std::{ - collections::HashSet, - fs, - path::PathBuf, sync::{Arc, OnceLock}, time::Duration, }; @@ -12,20 +9,8 @@ use compositor_render::{ WgpuFeatures, YuvPlanes, }; -use super::test_case::OUTPUT_ID; - pub const SNAPSHOTS_DIR_NAME: &str = "snapshot_tests/snapshots/render_snapshots"; -fn global_wgpu_ctx( - force_gpu: bool, - features: wgpu::Features, -) -> (Arc, Arc) { - static CTX: OnceLock<(Arc, Arc)> = OnceLock::new(); - - CTX.get_or_init(|| create_wgpu_ctx(force_gpu, features, Default::default()).unwrap()) - .clone() -} - pub(super) fn frame_to_rgba(frame: &Frame) -> Vec { let FrameData::PlanarYuv420(YuvPlanes { y_plane, @@ -61,20 +46,11 @@ pub(super) fn frame_to_rgba(frame: &Frame) -> Vec { rgba_data } -pub(super) fn snapshots_diff(old_snapshot: &[u8], new_snapshot: &[u8]) -> f32 { - if old_snapshot.len() != new_snapshot.len() { - return 10000.0; - } - let square_error: f32 = old_snapshot - .iter() - .zip(new_snapshot) - .map(|(a, b)| (*a as i32 - *b as i32).pow(2) as f32) - .sum(); - - square_error / old_snapshot.len() as f32 -} - pub(super) fn create_renderer() -> Renderer { + static CTX: OnceLock<(Arc, Arc)> = OnceLock::new(); + let wgpu_ctx = + CTX.get_or_init(|| create_wgpu_ctx(false, Default::default(), Default::default()).unwrap()); + let (renderer, _event_loop) = Renderer::new(RendererOptions { web_renderer: web_renderer::WebRendererInitOptions { enable: false, @@ -84,42 +60,9 @@ pub(super) fn create_renderer() -> Renderer { framerate: Framerate { num: 30, den: 1 }, stream_fallback_timeout: Duration::from_secs(3), wgpu_features: WgpuFeatures::default(), - wgpu_ctx: Some(global_wgpu_ctx(false, Default::default())), + wgpu_ctx: Some(wgpu_ctx.clone()), load_system_fonts: false, }) .unwrap(); renderer } - -pub fn snapshots_path() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SNAPSHOTS_DIR_NAME) -} - -pub fn find_unused_snapshots( - produced_snapshots: &HashSet, - snapshots_path: PathBuf, -) -> Vec { - let mut unused_snapshots = Vec::new(); - for entry in fs::read_dir(snapshots_path).unwrap() { - let entry = entry.unwrap(); - if entry.file_type().unwrap().is_dir() { - let mut snapshots = find_unused_snapshots(produced_snapshots, entry.path()); - unused_snapshots.append(&mut snapshots); - continue; - } - if !entry.file_name().to_string_lossy().ends_with(".png") { - continue; - } - - if !produced_snapshots.contains(&entry.path()) { - unused_snapshots.push(entry.path()) - } - } - - unused_snapshots -} - -pub(super) fn snaphot_save_path(test_name: &str, pts: &Duration) -> PathBuf { - let out_file_name = format!("{}_{}_{}.png", test_name, pts.as_millis(), OUTPUT_ID); - snapshots_path().join(out_file_name) -} diff --git a/src/snapshot_tests/view_tests.rs b/src/snapshot_tests/view_tests.rs new file mode 100644 index 000000000..225983484 --- /dev/null +++ b/src/snapshot_tests/view_tests.rs @@ -0,0 +1,219 @@ +use compositor_render::Resolution; + +use super::{input::TestInput, scene_from_json, snapshots_path, test_case::TestCase, TestRunner}; + +#[test] +fn view_tests() { + let mut runner = TestRunner::new(snapshots_path().join("view")); + let default = TestCase { + inputs: vec![TestInput::new(1)], + ..Default::default() + }; + + runner.add(TestCase { + name: "view/overflow_hidden_with_input_stream_children", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/overflow_hidden_with_input_stream_children.scene.json" + )), + inputs: vec![TestInput::new_with_resolution( + 1, + Resolution { + width: 180, + height: 200, + }, + )], + ..Default::default() + }); + runner.add(TestCase { + name: "view/overflow_hidden_with_view_children", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/overflow_hidden_with_view_children.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/constant_width_views_row", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/constant_width_views_row.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/constant_width_views_row_with_overflow_hidden", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/constant_width_views_row_with_overflow_hidden.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/constant_width_views_row_with_overflow_visible", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/constant_width_views_row_with_overflow_visible.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/constant_width_views_row_with_overflow_fit", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/constant_width_views_row_with_overflow_fit.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/dynamic_width_views_row", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/dynamic_width_views_row.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/dynamic_and_constant_width_views_row", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/dynamic_and_constant_width_views_row.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/dynamic_and_constant_width_views_row_with_overflow", + scene_updates: scene_from_json( + include_str!("../../snapshot_tests/view/dynamic_and_constant_width_views_row_with_overflow.scene.json"), + ), + ..default.clone() + }); + runner.add(TestCase { + name: "view/constant_width_and_height_views_row", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/constant_width_and_height_views_row.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/view_with_absolute_positioning_partially_covered_by_sibling", + scene_updates: scene_from_json( + include_str!("../../snapshot_tests/view/view_with_absolute_positioning_partially_covered_by_sibling.scene.json"), + ), + ..default.clone() + }); + runner.add(TestCase { + name: "view/view_with_absolute_positioning_render_over_siblings", + scene_updates: scene_from_json( + include_str!("../../snapshot_tests/view/view_with_absolute_positioning_render_over_siblings.scene.json"), + ), + ..default.clone() + }); + runner.add(TestCase { + name: "view/root_view_with_background_color", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/root_view_with_background_color.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_radius", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_width", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_width.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/box_shadow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/box_shadow.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/box_shadow_sibling", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/box_shadow_sibling.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_radius_border_box_shadow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius_border_box_shadow.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_radius_box_shadow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius_box_shadow.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_radius_box_shadow_overflow_hidden", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius_box_shadow_overflow_hidden.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_radius_box_shadow_overflow_fit", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius_box_shadow_overflow_fit.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_radius_box_shadow_rescaler_input_stream", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius_box_shadow_rescaler_input_stream.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/nested_border_width_radius", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/nested_border_width_radius.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/nested_border_width_radius_aligned", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/nested_border_width_radius_aligned.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/nested_border_width_radius_multi_child", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/nested_border_width_radius_multi_child.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + // it is supposed to be cut off because of the rescaler that wraps it + name: "view/border_radius_border_box_shadow_rescaled", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius_border_box_shadow_rescaled.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/root_border_radius_border_box_shadow", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/root_border_radius_border_box_shadow.scene.json" + )), + ..default.clone() + }); + runner.add(TestCase { + name: "view/border_radius_border_box_shadow_rescaled_and_hidden_by_parent", + scene_updates: scene_from_json(include_str!( + "../../snapshot_tests/view/border_radius_border_box_shadow_rescaled_and_hidden_by_parent.scene.json" + )), + ..default.clone() + }); + + runner.run() +}