Merge pull request #1802

This commit is contained in:
Brandon Thomas
2026-10-18 11:12:27 -04:00
2 changed files with 96 additions and 6 deletions
+44
View File
@@ -623,3 +623,47 @@ fn gpu_adapter_name_empty_is_unset() {
let all = format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
assert!(!all.contains("WGPU_ADAPTER_NAME"), "{all}");
}
/// Renders two seconds of the demo at quarter resolution (long enough for several progress
/// updates) and returns stderr, which `output()` reads through a pipe: not a terminal.
fn render_demo_stderr(name: &str, extra: &[&str]) -> String {
let out = tmp(name);
let mut args = vec!["render", "--demo", "--format", "gif", "--start", "0", "--end", "2", "--resolution", "quarter", "--out", out.to_str().unwrap()];
args.extend_from_slice(extra);
let o = bin().args(&args).output().unwrap();
let stderr = String::from_utf8_lossy(&o.stderr).to_string();
assert!(o.status.success(), "{args:?}: {stderr}");
let _ = std::fs::remove_file(out);
stderr
}
/// `render --progress` with a piped stderr reports the render's frames as
/// `progress <frames done> <frames total> <seconds elapsed> <seconds left|->` lines, one per
/// change (#1778).
#[test]
fn render_progress_reports_frames_on_a_piped_stderr() {
let stderr = render_demo_stderr("progress.gif", &["--progress"]);
let mut last_done = None;
let mut totals = vec![];
for line in stderr.lines().filter(|l| l.starts_with("progress")) {
let fields: Vec<&str> = line.split(' ').collect();
assert_eq!(fields.len(), 5, "{line}");
let done: u64 = fields[1].parse().unwrap_or_else(|e| panic!("{line}: frames done: {e}"));
let total: u64 = fields[2].parse().unwrap_or_else(|e| panic!("{line}: frames total: {e}"));
let elapsed: f64 = fields[3].parse().unwrap_or_else(|e| panic!("{line}: seconds elapsed: {e}"));
assert!(fields[4] == "-" || fields[4].parse::<f64>().is_ok_and(|left| left >= 0.0), "{line}: seconds left");
assert!(done <= total && total > 0 && elapsed >= 0.0, "{line}");
assert!(last_done.is_none_or(|last| done > last), "one line per change, and frames only go up: {stderr}");
last_done = Some(done);
totals.push(total);
}
assert!(!totals.is_empty(), "no progress line on a piped stderr: {stderr}");
assert!(totals.iter().all(|&t| t == totals[0]), "one render, one total: {stderr}");
}
/// Without `--progress`, the same render writes no progress lines to a piped stderr (#1778).
#[test]
fn render_without_progress_writes_no_progress_lines() {
let stderr = render_demo_stderr("no-progress.gif", &[]);
assert!(!stderr.lines().any(|l| l.starts_with("progress")), "{stderr}");
}
+52 -6
View File
@@ -690,10 +690,11 @@ fn template_frame_output_cannot_alias_its_values_through_a_hard_link() {
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn template_values_fill_a_media_replacement_control_with_a_file_path() {
/// A template whose `Clip` control is a Media Replacement showing `source.png` (blue), with
/// `other.png` (red) beside it to replace it with, exported as `lt.ectemplate` in a fresh `dir`.
fn media_template(case: &str) -> PathBuf {
let temp_root = std::env::temp_dir().canonicalize().unwrap();
let dir = temp_root.join(format!("ec-qa-template-media-control-{}", std::process::id()));
let dir = temp_root.join(format!("ec-qa-template-{case}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("build.jsx"), BUILD).unwrap();
@@ -708,11 +709,24 @@ fn template_values_fill_a_media_replacement_control_with_a_file_path() {
let params = serde_json::json!({"comp": "Lower Third", "layer": layer, "name": "Clip"}).to_string();
ok(&dir, &["exec", "essential.addMedia", &params, "t.ecproj", "--save"]);
ok(&dir, &["exec", "essential.exportTemplate", r#"{"comp":"Lower Third","path":"lt.ectemplate"}"#, "t.ecproj"]);
dir
}
std::fs::write(dir.join("values.json"), r#"{"Clip":"other.png"}"#).unwrap();
ok(&dir, &["render-frame", "--template", "lt.ectemplate", "--values", "values.json", "--time", "1", "--out", "out.png"]);
/// Renders the template's frame with `values` and checks that the control shows `other.png`.
fn assert_renders_the_replacement(dir: &PathBuf, values: &str) {
std::fs::write(dir.join("values.json"), values).unwrap();
ok(dir, &["render-frame", "--template", "lt.ectemplate", "--values", "values.json", "--time", "1", "--out", "out.png"]);
let px = image::open(dir.join("out.png")).unwrap().to_rgba8().get_pixel(8, 8).0;
assert!((px[0] as i32 - 200).abs() <= 2 && (px[1] as i32 - 20).abs() <= 2 && (px[2] as i32 - 40).abs() <= 2, "replacement media rendered: {px:?}");
assert!(
(px[0] as i32 - 200).abs() <= 2 && (px[1] as i32 - 20).abs() <= 2 && (px[2] as i32 - 40).abs() <= 2,
"{values}: replacement media rendered: {px:?}"
);
}
#[test]
fn template_values_fill_a_media_replacement_control_with_a_file_path() {
let dir = media_template("media-control");
assert_renders_the_replacement(&dir, r#"{"Clip":"other.png"}"#);
// A control that is not given a path is rejected with a clear message.
std::fs::write(dir.join("bad.json"), r#"{"Clip":5}"#).unwrap();
@@ -721,3 +735,35 @@ fn template_values_fill_a_media_replacement_control_with_a_file_path() {
assert!(reply["error"].as_str().unwrap().contains("media control"), "{reply}");
let _ = std::fs::remove_dir_all(&dir);
}
/// The object forms `{"path"|"file"|"media": …}` take a file path too, and the control is found
/// by name the way `essential.set` finds it: exactly, then ignoring case (#1787).
#[test]
fn template_media_control_takes_a_path_object_and_a_name_in_any_case() {
let dir = media_template("media-forms");
for values in [
r#"{"Clip":{"path":"other.png"}}"#,
r#"{"Clip":{"file":"other.png"}}"#,
r#"{"Clip":{"media":"other.png"}}"#,
r#"{"clip":"other.png"}"#,
r#"{"CLIP":{"path":"other.png"}}"#,
] {
let _ = std::fs::remove_file(dir.join("out.png"));
assert_renders_the_replacement(&dir, values);
}
let _ = std::fs::remove_dir_all(&dir);
}
/// A media path that cannot be imported fails the render with an error naming the control and
/// the path, and writes no frame (#1787).
#[test]
fn template_media_control_reports_a_path_that_cannot_be_imported() {
let dir = media_template("media-missing");
std::fs::write(dir.join("missing.json"), r#"{"Clip":"missing.png"}"#).unwrap();
let (code, reply) = cli(&dir, &["render-frame", "--template", "lt.ectemplate", "--values", "missing.json", "--time", "1", "--out", "missing-out.png"]);
assert_eq!(code, 1, "{reply}");
let error = reply["error"].as_str().unwrap();
assert!(error.contains("control `Clip`: cannot import missing.png"), "{reply}");
assert!(!dir.join("missing-out.png").exists(), "{reply}");
let _ = std::fs::remove_dir_all(&dir);
}