mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 05:27:08 -06:00
M0.37: resolve relocated test binaries and expose GPU failures
This commit is contained in:
@@ -7,7 +7,7 @@ use std::process::{Command, Stdio};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
fn bin() -> Command {
|
||||
Command::new(env!("CARGO_BIN_EXE_effectcraft-cli"))
|
||||
Command::new(crate::binary())
|
||||
}
|
||||
|
||||
/// Run with `--json`; returns (exit code, parsed stdout).
|
||||
|
||||
@@ -2,3 +2,8 @@
|
||||
|
||||
mod cli;
|
||||
mod qa_template;
|
||||
|
||||
/// Nextest archives relocate binaries; plain Cargo keeps the compile-time path valid.
|
||||
fn binary() -> std::ffi::OsString {
|
||||
std::env::var_os("NEXTEST_BIN_EXE_effectcraft_cli").unwrap_or_else(|| env!("CARGO_BIN_EXE_effectcraft-cli").into())
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::process::Command;
|
||||
use serde_json::Value;
|
||||
|
||||
fn cli(dir: &PathBuf, args: &[&str]) -> (i32, Value) {
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_effectcraft-cli")).current_dir(dir).args(args).arg("--json").output().unwrap();
|
||||
let out = Command::new(crate::binary()).current_dir(dir).args(args).arg("--json").output().unwrap();
|
||||
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
let v = serde_json::from_str(stdout.trim()).unwrap_or_else(|e| panic!("{args:?}: {e}: {stdout} {}", String::from_utf8_lossy(&out.stderr)));
|
||||
(out.status.code().unwrap_or(-1), v)
|
||||
@@ -191,7 +191,7 @@ 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(env!("CARGO_BIN_EXE_effectcraft-cli")).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));
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ fn assert_template_output_preserves_inputs(dir: &PathBuf, out: &str) -> Value {
|
||||
let template_before = std::fs::read(dir.join("lt.ectemplate")).unwrap();
|
||||
let values_before = std::fs::read(dir.join("values.json")).unwrap();
|
||||
// Read the inputs before interpreting the subprocess status or error text.
|
||||
let result = Command::new(env!("CARGO_BIN_EXE_effectcraft-cli"))
|
||||
let result = Command::new(crate::binary())
|
||||
.current_dir(dir)
|
||||
.args(["render-frame", "--template", "lt.ectemplate", "--values", "values.json", "--time", "1", "--out", out, "--json"])
|
||||
.output()
|
||||
@@ -527,7 +527,7 @@ fn template_unknown_control_does_not_replace_extracted_media() {
|
||||
let invalid_values_before = std::fs::read(dir.join("bad-values.json")).unwrap();
|
||||
|
||||
// Capture every file before interpreting status/JSON, so a failed command cannot hide a write.
|
||||
let result = Command::new(env!("CARGO_BIN_EXE_effectcraft-cli"))
|
||||
let result = Command::new(crate::binary())
|
||||
.current_dir(&dir)
|
||||
.args(["render-frame", "--template", "lt.ectemplate", "--values", "bad-values.json", "--time", "1", "--out", "failed.png", "--json"])
|
||||
.output()
|
||||
@@ -623,11 +623,8 @@ fn template_missing_output_does_not_replace_extracted_media() {
|
||||
assert_ne!(sentinel_before, source_before);
|
||||
|
||||
// Observe writes before interpreting the actual CLI usage exit.
|
||||
let result = Command::new(env!("CARGO_BIN_EXE_effectcraft-cli"))
|
||||
.current_dir(&dir)
|
||||
.args(["render", "--template", "lt.ectemplate", "--values", "values.json", "--json"])
|
||||
.output()
|
||||
.unwrap();
|
||||
let result =
|
||||
Command::new(crate::binary()).current_dir(&dir).args(["render", "--template", "lt.ectemplate", "--values", "values.json", "--json"]).output().unwrap();
|
||||
let template_after = std::fs::read(dir.join("lt.ectemplate")).unwrap();
|
||||
let values_after = std::fs::read(dir.join("values.json")).unwrap();
|
||||
let source_after = std::fs::read(dir.join("source.png")).unwrap();
|
||||
|
||||
@@ -6,6 +6,11 @@ use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Nextest archives relocate binaries; plain Cargo keeps the compile-time path valid.
|
||||
fn binary() -> std::ffi::OsString {
|
||||
std::env::var_os("NEXTEST_BIN_EXE_effectcraft").unwrap_or_else(|| env!("CARGO_BIN_EXE_effectcraft").into())
|
||||
}
|
||||
|
||||
/// A settings folder of its own, so a launch that got past the arguments would show up as
|
||||
/// preferences (Recent Footage) written there instead of in the user's settings.
|
||||
fn config_dir(name: &str) -> PathBuf {
|
||||
@@ -19,7 +24,7 @@ fn config_dir(name: &str) -> PathBuf {
|
||||
/// regression fails instead of leaving a window open: the process is killed after 5 s.
|
||||
fn rejects(name: &str, args: &[&str], env: &[(&str, &str)], message: &str) {
|
||||
let config = config_dir(name);
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_effectcraft"));
|
||||
let mut cmd = Command::new(binary());
|
||||
cmd.args(args).env_remove("EFFECTCRAFT_CONTROL_PORT").env_remove("RUST_LOG").env("EFFECTCRAFT_CONFIG_DIR", &config);
|
||||
for (k, v) in env {
|
||||
cmd.env(k, v);
|
||||
@@ -67,7 +72,7 @@ fn a_bad_control_port_stops_the_launch() {
|
||||
|
||||
#[test]
|
||||
fn version_still_prints_with_a_bad_port_variable() {
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_effectcraft")).arg("--version").env("EFFECTCRAFT_CONTROL_PORT", "abc").output().unwrap();
|
||||
let out = Command::new(binary()).arg("--version").env("EFFECTCRAFT_CONTROL_PORT", "abc").output().unwrap();
|
||||
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
|
||||
assert_eq!(String::from_utf8(out.stdout).unwrap(), format!("effectcraft {}\n", env!("CARGO_PKG_VERSION")));
|
||||
}
|
||||
|
||||
+30
-1
@@ -52,6 +52,26 @@ pub(crate) fn gpu() -> Option<&'static Gpu> {
|
||||
static G: OnceLock<Result<Gpu, String>> = OnceLock::new();
|
||||
hold_gpu_lock();
|
||||
let g = G.get_or_init(|| {
|
||||
// Readback mapping/allocation errors become None without necessarily retiring the
|
||||
// context. Preserve their original messages alongside the failure's health snapshot.
|
||||
struct GpuTestLog;
|
||||
impl log::Log for GpuTestLog {
|
||||
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
|
||||
metadata.level() <= log::Level::Warn && metadata.target().starts_with("effectcraft_gpu")
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record<'_>) {
|
||||
if self.enabled(record.metadata()) {
|
||||
eprintln!("{} {}: {}", record.level(), record.target(), record.args());
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
static LOGGER: GpuTestLog = GpuTestLog;
|
||||
if log::set_logger(&LOGGER).is_ok() {
|
||||
log::set_max_level(log::LevelFilter::Warn);
|
||||
}
|
||||
let g = Gpu::try_headless();
|
||||
match &g {
|
||||
Ok(g) => eprintln!("effectcraft-gpu tests: adapter {}", g.ctx.name),
|
||||
@@ -481,7 +501,16 @@ pub(crate) fn effect_direct(id: &str, vals: &[(&str, Value)], adjustment: bool)
|
||||
let buf = effectcraft_effects::Buf { img, offset: [0.0, 0.0], scale };
|
||||
let ctx = || effectcraft_effects::EffectCtx { params: ¶ms, time: 0.25, layer_size: size, seed: 11, adjustment, env: Default::default() };
|
||||
let cpu = (spec.render)(&ctx(), buf.clone());
|
||||
let out = effectcraft_render::Accelerator::effects(g, &[effectcraft_render::FxStep { spec, ctx: ctx() }], &buf, None).expect("the GPU runs the effect");
|
||||
let started = std::time::Instant::now();
|
||||
let out = effectcraft_render::Accelerator::effects(g, &[effectcraft_render::FxStep { spec, ctx: ctx() }], &buf, None).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"the GPU runs {id} {vals:?} adj {adjustment} scale {scale}; elapsed={:?}, health={:?}, unavailable={:?}, transfers={:?}",
|
||||
started.elapsed(),
|
||||
g.ctx.check_health(),
|
||||
g.ctx.unavailable_kernels(),
|
||||
g.ctx.transfer_stats()
|
||||
)
|
||||
});
|
||||
if expect_empty {
|
||||
assert!(out.img.data.iter().all(|p| p[3] == 0.0), "{id} {vals:?} adj {adjustment} scale {scale}: Cut Away 100 produced non-zero alpha");
|
||||
}
|
||||
|
||||
@@ -237,8 +237,17 @@ fn median_levels_match_cpu_division() {
|
||||
// A monotone row's median is its centre, including repeated edges. Exercise both
|
||||
// the bisection and sliding-histogram kernels at every quantised level.
|
||||
let mut enc = crate::context::Enc::new(&g.ctx);
|
||||
let started = std::time::Instant::now();
|
||||
let median = crate::fx_noise::median_image(&mut enc, &input, radius, false);
|
||||
let actual = enc.download(&median).unwrap();
|
||||
let actual = enc.download(&median).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"median download radius {radius}; elapsed={:?}, health={:?}, unavailable={:?}, transfers={:?}",
|
||||
started.elapsed(),
|
||||
g.ctx.check_health(),
|
||||
g.ctx.unavailable_kernels(),
|
||||
g.ctx.transfer_stats()
|
||||
)
|
||||
});
|
||||
for (level, (want, got)) in image.data.iter().zip(&actual.data).enumerate() {
|
||||
assert_eq!(got, want, "radius {radius}, level {level}");
|
||||
}
|
||||
@@ -273,7 +282,16 @@ fn dust_strict_threshold_boundaries() {
|
||||
let buf = effectcraft_effects::Buf { img, offset: [0.0; 2], scale: 1.0 };
|
||||
let cpu = (spec.render)(&ctx(), buf.clone());
|
||||
assert_eq!(cpu.img.data[24] != original, replaced, "CPU radius {radius}, threshold {threshold}");
|
||||
let gpu = effectcraft_render::Accelerator::effects(g, &[effectcraft_render::FxStep { spec, ctx: ctx() }], &buf, None).unwrap();
|
||||
let started = std::time::Instant::now();
|
||||
let gpu = effectcraft_render::Accelerator::effects(g, &[effectcraft_render::FxStep { spec, ctx: ctx() }], &buf, None).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"dust radius {radius}, threshold {threshold}; elapsed={:?}, health={:?}, unavailable={:?}, transfers={:?}",
|
||||
started.elapsed(),
|
||||
g.ctx.check_health(),
|
||||
g.ctx.unavailable_kernels(),
|
||||
g.ctx.transfer_stats()
|
||||
)
|
||||
});
|
||||
assert_eq!(gpu.img.data[24], cpu.img.data[24], "radius {radius}, threshold {threshold}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
use serde_json::{Value, json};
|
||||
use std::process::{Command, Output};
|
||||
|
||||
/// Nextest archives relocate binaries; plain Cargo keeps the compile-time path valid.
|
||||
fn binary() -> std::ffi::OsString {
|
||||
std::env::var_os("NEXTEST_BIN_EXE_xtask").unwrap_or_else(|| env!("CARGO_BIN_EXE_xtask").into())
|
||||
}
|
||||
|
||||
struct ResultsDir(tempfile::TempDir);
|
||||
impl ResultsDir {
|
||||
fn new() -> Self {
|
||||
@@ -12,7 +17,7 @@ impl ResultsDir {
|
||||
let candidate = self.0.path().join("candidate.jsonl");
|
||||
std::fs::write(&baseline, old).unwrap();
|
||||
std::fs::write(&candidate, new).unwrap();
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_xtask"));
|
||||
let mut command = Command::new(binary());
|
||||
command.args(["bench", "--compare"]).arg(baseline).arg("--with").arg(candidate);
|
||||
if gate {
|
||||
command.args(["--fail-over", "5"]);
|
||||
|
||||
Reference in New Issue
Block a user