| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | use std::io::{BufWriter, Write}; |
| 5 | use std::path::Path; |
| 6 | |
| 7 | /// Returns a list of all the `.slint` files in the `tests/cases` subfolders. |
| 8 | pub fn collect_test_cases() -> std::io::Result<Vec<test_driver_lib::TestCase>> { |
| 9 | let mut results = vec![]; |
| 10 | |
| 11 | let case_root_dir: std::path::PathBuf = [env!("CARGO_MANIFEST_DIR" ), "cases" ].iter().collect(); |
| 12 | |
| 13 | println!("cargo:rerun-if-env-changed=SLINT_TEST_FILTER" ); |
| 14 | let filter = std::env::var("SLINT_TEST_FILTER" ).ok(); |
| 15 | |
| 16 | for entry in walkdir::WalkDir::new(case_root_dir.clone()).follow_links(true) { |
| 17 | let entry = entry?; |
| 18 | let absolute_path = entry.into_path(); |
| 19 | if absolute_path.is_dir() { |
| 20 | println!("cargo:rerun-if-changed= {}" , absolute_path.display()); |
| 21 | continue; |
| 22 | } |
| 23 | let relative_path = |
| 24 | std::path::PathBuf::from(absolute_path.strip_prefix(&case_root_dir).unwrap()); |
| 25 | if let Some(filter) = &filter { |
| 26 | if !relative_path.to_str().unwrap().contains(filter) { |
| 27 | continue; |
| 28 | } |
| 29 | } |
| 30 | if let Some(ext) = absolute_path.extension() { |
| 31 | if ext == "60" || ext == "slint" { |
| 32 | results.push(test_driver_lib::TestCase { |
| 33 | absolute_path, |
| 34 | relative_path, |
| 35 | requested_style: None, |
| 36 | }); |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | Ok(results) |
| 41 | } |
| 42 | |
| 43 | fn main() -> std::io::Result<()> { |
| 44 | let default_font_path: std::path::PathBuf = |
| 45 | [env!("CARGO_MANIFEST_DIR" ), ".." , ".." , "demos" , "printerdemo" , "ui" , "fonts" ] |
| 46 | .iter() |
| 47 | .collect(); |
| 48 | |
| 49 | std::env::set_var("SLINT_DEFAULT_FONT" , default_font_path.clone()); |
| 50 | println!("cargo:rustc-env=SLINT_DEFAULT_FONT= {}" , default_font_path.display()); |
| 51 | |
| 52 | let mut generated_file = BufWriter::new(std::fs::File::create( |
| 53 | Path::new(&std::env::var_os("OUT_DIR" ).unwrap()).join("generated.rs" ), |
| 54 | )?); |
| 55 | |
| 56 | let references_root_dir: std::path::PathBuf = |
| 57 | [env!("CARGO_MANIFEST_DIR" ), "references" ].iter().collect(); |
| 58 | |
| 59 | let font_cache = i_slint_compiler::FontCache::default(); |
| 60 | |
| 61 | for (i, testcase) in |
| 62 | test_driver_lib::collect_test_cases("screenshots/cases" )?.into_iter().enumerate() |
| 63 | { |
| 64 | let mut reference_path = references_root_dir |
| 65 | .join(testcase.relative_path.clone()) |
| 66 | .with_extension("png" ) |
| 67 | .to_str() |
| 68 | .unwrap() |
| 69 | .escape_default() |
| 70 | .to_string(); |
| 71 | |
| 72 | reference_path = format!(" \"{reference_path}\"" ); |
| 73 | |
| 74 | println!("cargo:rerun-if-changed= {}" , testcase.absolute_path.display()); |
| 75 | let mut module_name = testcase.identifier(); |
| 76 | if module_name.starts_with(|c: char| !c.is_ascii_alphabetic()) { |
| 77 | module_name.insert(0, '_' ); |
| 78 | } |
| 79 | writeln!(generated_file, "#[path= \"{module_name}.rs \"] mod r# {module_name};" )?; |
| 80 | let source = std::fs::read_to_string(&testcase.absolute_path)?; |
| 81 | |
| 82 | let needle = "SLINT_SCALE_FACTOR=" ; |
| 83 | let scale_factor = source.find(needle).map(|p| { |
| 84 | let source = &source[p + needle.len()..]; |
| 85 | let scale_factor: f32 = source |
| 86 | .find(char::is_whitespace) |
| 87 | .and_then(|end| source[..end].parse().ok()) |
| 88 | .unwrap_or_else(|| { |
| 89 | panic!("Cannot parse {needle} for {}" , testcase.relative_path.display()) |
| 90 | }); |
| 91 | scale_factor |
| 92 | }); |
| 93 | |
| 94 | let needle = "ROTATION_THRESHOLD=" ; |
| 95 | let rotation_threshold = source.find(needle).map_or(0., |p| { |
| 96 | source[p + needle.len()..] |
| 97 | .find(char::is_whitespace) |
| 98 | .and_then(|end| source[p + needle.len()..][..end].parse().ok()) |
| 99 | .unwrap_or_else(|| { |
| 100 | panic!("Cannot parse {needle} for {}" , testcase.relative_path.display()) |
| 101 | }) |
| 102 | }); |
| 103 | let skip_clipping = source.contains("SKIP_CLIPPING" ); |
| 104 | |
| 105 | let needle = "SIZE=" ; |
| 106 | let (size_w, size_h) = source.find(needle).map_or((64, 64), |p| { |
| 107 | source[p + needle.len()..] |
| 108 | .find(char::is_whitespace) |
| 109 | .and_then(|end| source[p + needle.len()..][..end].split_once('x' )) |
| 110 | .and_then(|(w, h)| Some((w.parse().ok()?, h.parse().ok()?))) |
| 111 | .unwrap_or_else(|| { |
| 112 | panic!("Cannot parse {needle} for {}" , testcase.relative_path.display()) |
| 113 | }) |
| 114 | }); |
| 115 | |
| 116 | let mut output = BufWriter::new(std::fs::File::create( |
| 117 | Path::new(&std::env::var_os("OUT_DIR" ).unwrap()).join(format!(" {module_name}.rs" )), |
| 118 | )?); |
| 119 | |
| 120 | generate_source( |
| 121 | source.as_str(), |
| 122 | &mut output, |
| 123 | testcase, |
| 124 | scale_factor.unwrap_or(1.), |
| 125 | &font_cache, |
| 126 | ) |
| 127 | .unwrap(); |
| 128 | |
| 129 | write!( |
| 130 | output, |
| 131 | r" |
| 132 | #[test] fn t_ {i}() -> Result<(), Box<dyn std::error::Error>> {{ |
| 133 | use crate::testing; |
| 134 | |
| 135 | let window = testing::init_swr(); |
| 136 | window.set_size(slint::PhysicalSize::new( {size_w}, {size_h})); |
| 137 | let screenshot = {reference_path}; |
| 138 | let options = testing::TestCaseOptions {{ rotation_threshold: {rotation_threshold}f32, skip_clipping: {skip_clipping} }}; |
| 139 | |
| 140 | let instance = TestCase::new().unwrap(); |
| 141 | instance.show().unwrap(); |
| 142 | |
| 143 | testing::assert_with_render(screenshot, window.clone(), &options); |
| 144 | |
| 145 | testing::assert_with_render_by_line(screenshot, window.clone(), &options); |
| 146 | |
| 147 | Ok(()) |
| 148 | }}" , |
| 149 | )?; |
| 150 | } |
| 151 | |
| 152 | //Make sure to use a consistent style |
| 153 | println!("cargo:rustc-env=SLINT_STYLE=fluent" ); |
| 154 | println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1" ); |
| 155 | |
| 156 | Ok(()) |
| 157 | } |
| 158 | |
| 159 | fn generate_source( |
| 160 | source: &str, |
| 161 | output: &mut impl Write, |
| 162 | testcase: test_driver_lib::TestCase, |
| 163 | scale_factor: f32, |
| 164 | font_cache: &i_slint_compiler::FontCache, |
| 165 | ) -> Result<(), std::io::Error> { |
| 166 | use i_slint_compiler::{diagnostics::BuildDiagnostics, *}; |
| 167 | |
| 168 | let include_paths = test_driver_lib::extract_include_paths(source) |
| 169 | .map(std::path::PathBuf::from) |
| 170 | .collect::<Vec<_>>(); |
| 171 | |
| 172 | let mut diag = BuildDiagnostics::default(); |
| 173 | let syntax_node = parser::parse(source.to_owned(), Some(&testcase.absolute_path), &mut diag); |
| 174 | let mut compiler_config = CompilerConfiguration::new(generator::OutputFormat::Rust); |
| 175 | compiler_config.include_paths = include_paths; |
| 176 | compiler_config.embed_resources = EmbedResourcesKind::EmbedTextures; |
| 177 | compiler_config.enable_experimental = true; |
| 178 | compiler_config.style = Some("fluent" .to_string()); |
| 179 | compiler_config.const_scale_factor = scale_factor.into(); |
| 180 | compiler_config.font_cache = font_cache.clone(); |
| 181 | let (root_component, diag, loader) = |
| 182 | spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config)); |
| 183 | |
| 184 | if diag.has_errors() { |
| 185 | diag.print_warnings_and_exit_on_error(); |
| 186 | return Err(std::io::Error::new( |
| 187 | std::io::ErrorKind::Other, |
| 188 | format!("build error in {:?}" , testcase.absolute_path), |
| 189 | )); |
| 190 | } else { |
| 191 | diag.print(); |
| 192 | } |
| 193 | |
| 194 | generator::generate( |
| 195 | generator::OutputFormat::Rust, |
| 196 | output, |
| 197 | &root_component, |
| 198 | &loader.compiler_config, |
| 199 | )?; |
| 200 | Ok(()) |
| 201 | } |
| 202 | |