mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
- render --progress with a piped stderr writes well-formed
`progress <done> <total> <elapsed> <left|->` lines, one per change, and
the same render without the flag writes none.
- A template's Media Replacement control takes {"path"|"file"|"media": …},
is found by name ignoring case, and a path that cannot be imported fails
with an error naming the control and the path.
This commit is contained in:
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -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", ¶ms, "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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user