diff --git a/apps/effectcraft-cli/tests/it/qa_template.rs b/apps/effectcraft-cli/tests/it/qa_template.rs index 8cb533fda..ffbdb94e9 100644 --- a/apps/effectcraft-cli/tests/it/qa_template.rs +++ b/apps/effectcraft-cli/tests/it/qa_template.rs @@ -152,6 +152,13 @@ fn essential_graphics_template_via_script_and_commands() { assert_eq!(image::open(&frames[0]).unwrap().to_rgba8(), edit); assert_eq!(std::fs::read(dir.join("lt.ectemplate")).unwrap(), original_template); + invalid_template_values_fail_cleanly(&dir); + template_usage_errors(&dir); + rational_template_keeps_exact_rate(&dir, &original_template); + let _ = std::fs::remove_dir_all(&dir); +} + +fn invalid_template_values_fail_cleanly(dir: &PathBuf) { // Invalid values fail before the queue creates an output directory, or frame rendering // overwrites an existing output. A JSON object and existing control names are required. for (name, values, expected) in [ @@ -164,25 +171,27 @@ fn essential_graphics_template_via_script_and_commands() { std::fs::write(dir.join(&values_file), values).unwrap(); let output_dir = dir.join(format!("failed-{name}")); let out = output_dir.join("frame_[#####].png"); - let (code, error) = cli(&dir, &["render", "--template", "lt.ectemplate", "--values", &values_file, "--out", out.to_str().unwrap()]); + let (code, error) = cli(dir, &["render", "--template", "lt.ectemplate", "--values", &values_file, "--out", out.to_str().unwrap()]); assert_eq!(code, 1, "{name}: {error}"); assert!(error["error"].as_str().unwrap().contains(expected), "{name}: {error}"); assert!(!output_dir.exists(), "{name}: invalid input created render output"); std::fs::write(dir.join("untouched.png"), b"existing output").unwrap(); - let (code, error) = cli(&dir, &["render-frame", "--template", "lt.ectemplate", "--values", &values_file, "--out", "untouched.png"]); + let (code, error) = cli(dir, &["render-frame", "--template", "lt.ectemplate", "--values", &values_file, "--out", "untouched.png"]); assert_eq!(code, 1, "{name}: {error}"); assert_eq!(std::fs::read(dir.join("untouched.png")).unwrap(), b"existing output"); } - let (code, error) = cli(&dir, &["render-frame", "--template", "lt.ectemplate", "--values", "missing-values.json", "--out", "missing.png"]); + let (code, error) = cli(dir, &["render-frame", "--template", "lt.ectemplate", "--values", "missing-values.json", "--out", "missing.png"]); assert_eq!(code, 1, "{error}"); assert!(error["error"].as_str().unwrap().contains("cannot read values"), "{error}"); assert!(!dir.join("missing.png").exists()); std::fs::write(dir.join("large-values.json"), vec![b' '; 1024 * 1024 + 1]).unwrap(); - let (code, error) = cli(&dir, &["render-frame", "--template", "lt.ectemplate", "--values", "large-values.json", "--out", "large.png"]); + let (code, error) = cli(dir, &["render-frame", "--template", "lt.ectemplate", "--values", "large-values.json", "--out", "large.png"]); assert_eq!(code, 1, "{error}"); assert!(error["error"].as_str().unwrap().contains("exceeds 1 MiB"), "{error}"); assert!(!dir.join("large.png").exists()); +} +fn template_usage_errors(dir: &PathBuf) { for args in [ vec!["render-frame", "--values", "values.json"], vec!["info", "--template", "lt.ectemplate"], @@ -191,10 +200,12 @@ fn essential_graphics_template_via_script_and_commands() { vec!["render", "--template", "lt.ectemplate", "--queue"], vec!["render-frame", "--template", "lt.ectemplate", "--bridge", "1"], ] { - let output = Command::new(crate::binary()).current_dir(&dir).args(&args).output().unwrap(); + let output = Command::new(crate::binary()).current_dir(dir).args(&args).output().unwrap(); assert_eq!(output.status.code(), Some(2), "{args:?}: {}", String::from_utf8_lossy(&output.stderr)); } +} +fn rational_template_keeps_exact_rate(dir: &PathBuf, original_template: &[u8]) { // File-loaded templates can use exact rational rates that comp.new's float parser rounds. // Preserve that rate, Tick duration, work area, background and locked source layers. let mut project = effectcraft_engine::project::Project::from_json(&std::fs::read_to_string(dir.join("t.ecproj")).unwrap()).unwrap(); @@ -207,13 +218,13 @@ fn essential_graphics_template_via_script_and_commands() { comp.background = [0.08, 0.16, 0.24]; comp.layers.first_mut().unwrap().switches.locked = true; std::fs::write(dir.join("rational.ecproj"), project.to_json()).unwrap(); - ok(&dir, &["exec", "essential.exportTemplate", r#"{"comp":"Lower Third","path":"rational.ectemplate"}"#, "rational.ecproj"]); + ok(dir, &["exec", "essential.exportTemplate", r#"{"comp":"Lower Third","path":"rational.ectemplate"}"#, "rational.ecproj"]); let rational_template = std::fs::read(dir.join("rational.ectemplate")).unwrap(); - let rational = ok(&dir, &["render-frame", "--template", "rational.ectemplate", "--frame", "5", "--out", "rational.png"]); + let rational = ok(dir, &["render-frame", "--template", "rational.ectemplate", "--frame", "5", "--out", "rational.png"]); assert!((rational["time"].as_f64().unwrap() - 5.0 / rate.as_f64()).abs() < 1e-9, "{rational}"); assert_eq!((rational["width"].as_u64().unwrap(), rational["height"].as_u64().unwrap()), (320, 90)); let output = dir.join("rational-work-area/frame_[#####].png"); - ok(&dir, &["render", "--template", "rational.ectemplate", "--work-area", "--out", output.to_str().unwrap(), "--format", "png", "--resolution", "0.05"]); + ok(dir, &["render", "--template", "rational.ectemplate", "--work-area", "--out", output.to_str().unwrap(), "--format", "png", "--resolution", "0.05"]); let count = std::fs::read_dir(dir.join("rational-work-area")) .unwrap() .filter_map(Result::ok) @@ -222,8 +233,8 @@ fn essential_graphics_template_via_script_and_commands() { assert_eq!(count, 4, "the template's exact work area contains frames 3 through 6"); assert_eq!(std::fs::read(dir.join("rational.ectemplate")).unwrap(), rational_template); assert_eq!(std::fs::read(dir.join("lt.ectemplate")).unwrap(), original_template); - let _ = std::fs::remove_dir_all(&dir); } + fn template_output_alias_fixture(case: &str) -> PathBuf { let dir = std::env::temp_dir().join(format!("ec-qa-template-output-{case}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); diff --git a/apps/effectcraft-web/src/frames.rs b/apps/effectcraft-web/src/frames.rs index c7f350526..69089e308 100644 --- a/apps/effectcraft-web/src/frames.rs +++ b/apps/effectcraft-web/src/frames.rs @@ -311,10 +311,11 @@ pub fn stats() -> Value { }) } +type PendingFrameCheck = (js_sys::Function, js_sys::Function, (u32, u32, Vec), f64); + thread_local! { /// `workerFrameCheck` promises waiting for their frame: id → (resolve, reject, local frame). - #[allow(clippy::type_complexity)] - static CHECKS: RefCell), f64)>> = RefCell::new(Default::default()); + static CHECKS: RefCell> = RefCell::new(Default::default()); } /// `effectcraft.workerFrameCheck({time?, scale?, backend?})`: render the active comp's frame in diff --git a/apps/effectcraft/src/main.rs b/apps/effectcraft/src/main.rs index 6df015290..17bbb7e1b 100644 --- a/apps/effectcraft/src/main.rs +++ b/apps/effectcraft/src/main.rs @@ -9,8 +9,8 @@ //! //! `--control ` (or `EFFECTCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server; //! see `effectcraft_ui_egui::control` for the methods. -#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")] // Built everywhere so its tests run on every platform; only Linux AppImages use it. mod appearance; diff --git a/apps/effectcraft/tests/args.rs b/apps/effectcraft/tests/args.rs index 41281eb23..62c591b45 100644 --- a/apps/effectcraft/tests/args.rs +++ b/apps/effectcraft/tests/args.rs @@ -1,6 +1,4 @@ //! The desktop binary rejects a bad command line before it starts anything (#997). -// Integration tests: unwrapping and panicking on failure is fine here. -#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] use std::path::PathBuf; use std::process::{Command, Stdio};