From a650e6c21d354bf2421e025330303d3445265f15 Mon Sep 17 00:00:00 2001 From: Katt <238968166+KattKnight@users.noreply.github.com> Date: Fri, 9 Oct 2026 22:08:57 +0530 Subject: [PATCH] Tests: four failures on Windows (autosave paths, GPU viewer adapter, audio preview race) - automation / cli autosave restart tests: the MCP instructions embed the auto-save state as JSON, so a Windows path's backslashes are escaped there; look for the JSON form of the path. - ui_gpu: egui_kittest prefers a CPU adapter, which on Windows is WARP on DirectX 12, where FXC fails to compile the compositor's `pointwise` kernel ("compilation aborted unexpectedly"), so the viewer stayed on the CPU and the test failed before comparing anything. Build the harness on the adapter the headless compositor picked (falling back to egui_kittest's choice). - ui_audio_scrub (#103 test): with a fixed 2 s comp, a machine with many render threads cached the whole preview before the first step, so the sound rightly started at once and no silent frame was counted (3 of 24 runs failed with 8 copies in parallel). Size the comp to four times the frames playback requests per step, so caching takes at least four steps (0 of 24). Co-Authored-By: Claude Opus 5.5 --- apps/effectcraft-cli/tests/cli.rs | 4 +++- crates/automation/src/tests.rs | 4 +++- crates/ui-egui/tests/ui_audio_scrub.rs | 8 ++++++- crates/ui-egui/tests/ui_gpu.rs | 31 +++++++++++++++++++++++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/apps/effectcraft-cli/tests/cli.rs b/apps/effectcraft-cli/tests/cli.rs index 723016a8..dd867790 100644 --- a/apps/effectcraft-cli/tests/cli.rs +++ b/apps/effectcraft-cli/tests/cli.rs @@ -373,7 +373,9 @@ fn mcp_autosave_survives_kill_and_restart_without_touching_desktop_settings() { assert_eq!(manifest["ended"], false); let mut restarted = McpChild::start(&config, true); let init = restarted.rpc("initialize", json!({})); - assert!(init["instructions"].as_str().unwrap().contains(path)); + // The instructions carry the auto-save state as JSON, where a Windows path's backslashes + // are escaped. + assert!(init["instructions"].as_str().unwrap().contains(&Value::from(path).to_string())); assert_eq!(init["_meta"]["effectcraftAutoSave"]["previousSessions"][0]["autosave"], path); restarted.tool("open_project", json!({"path":path})); let comp = restarted.tool("get_comp", json!({"comp":"Survives restart"})); diff --git a/crates/automation/src/tests.rs b/crates/automation/src/tests.rs index 44a61cc1..e4b4f19e 100644 --- a/crates/automation/src/tests.rs +++ b/crates/automation/src/tests.rs @@ -269,7 +269,9 @@ fn autosave_is_durable_before_reply_and_queries_do_not_rotate() { drop(s); // Abrupt process loss has no EOF cleanup; the checkpoint already exists. let mut restarted = server().with_autosave(&dir).unwrap(); let init = rpc(&mut restarted, 1, "initialize", json!({})); - assert!(init["instructions"].as_str().unwrap().contains(path)); + // The instructions carry the auto-save state as JSON, where a Windows path's backslashes + // are escaped. + assert!(init["instructions"].as_str().unwrap().contains(&Value::from(path).to_string())); assert_eq!(init["_meta"]["effectcraftAutoSave"]["previousSessions"][0]["autosave"], path); call_json(&mut restarted, "open_project", json!({"path": path})); assert_eq!(call_json(&mut restarted, "get_project", json!({}))["items"][0]["name"], "Durable"); diff --git a/crates/ui-egui/tests/ui_audio_scrub.rs b/crates/ui-egui/tests/ui_audio_scrub.rs index 01843de7..02fefb14 100644 --- a/crates/ui-egui/tests/ui_audio_scrub.rs +++ b/crates/ui-egui/tests/ui_audio_scrub.rs @@ -119,7 +119,13 @@ fn cmd_dragging_the_current_time_scrubs_and_an_idle_scrub_closes() { #[test] fn preview_with_audio_shows_every_frame_and_sounds_once_cached() { let fake = Fake::default(); - let (app, cid) = setup(true, &fake); + let (mut app, cid) = setup(true, &fake); + // Playback requests at most twice as many frames ahead per step as there are render threads. + // Four times that many frames take at least four steps to cache, so frames are shown silently + // first however fast the renderer is (with a fixed 2 s comp, a machine with many cores cached + // all of it before the first step and the sound started at once). + let ahead = (app.frames.parallelism() * 2).max(4); + app.session.execute("comp.settings", json!({"comp": cid.0, "duration": (4 * ahead) as f64 / 30.0})).unwrap(); // A little under one frame of input time per step. let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_step_dt(1.0 / 31.0).build_eframe(|_| app); h.run_steps(2); diff --git a/crates/ui-egui/tests/ui_gpu.rs b/crates/ui-egui/tests/ui_gpu.rs index 4aca21a5..92d18a44 100644 --- a/crates/ui-egui/tests/ui_gpu.rs +++ b/crates/ui-egui/tests/ui_gpu.rs @@ -19,20 +19,45 @@ fn settle(h: &mut Harness<'_, EffectcraftApp>) { } } +/// egui_kittest's wgpu setup on the adapter the headless compositor picked (`name (Backend)`), +/// like the desktop app's window. Left alone, egui_kittest prefers a CPU adapter, which on +/// Windows is WARP on DirectX 12, where FXC fails to compile the compositor's kernels: the +/// viewer would stay on the CPU and the comparison would never run. +fn same_adapter(name: String) -> eframe::egui_wgpu::WgpuSetup { + let mut setup = egui_kittest::wgpu::default_wgpu_setup(); + if let eframe::egui_wgpu::WgpuSetup::CreateNew(c) = &mut setup { + let fallback = c.native_adapter_selector.take(); + c.native_adapter_selector = Some(std::sync::Arc::new(move |adapters, surface| { + let named = |a: &&eframe::wgpu::Adapter| format!("{} ({:?})", a.get_info().name, a.get_info().backend) == name; + match (adapters.iter().find(named), &fallback) { + (Some(a), _) => Ok(a.clone()), + (None, Some(f)) => f(adapters, surface), + (None, None) => adapters.first().cloned().ok_or_else(|| "No adapter found".to_owned()), + } + })); + } + setup +} + #[test] fn viewer_draws_gpu_frames_that_match_the_cpu() { let Some(probe) = effectcraft_gpu::Gpu::headless() else { eprintln!("no GPU adapter: skipping"); return; }; - eprintln!("headless compositor adapter: {}", effectcraft_engine::render::Accelerator::name(&probe)); + let adapter = effectcraft_engine::render::Accelerator::name(&probe); + eprintln!("headless compositor adapter: {adapter}"); // egui-wgpu creates its GL device with WebGL2's limits, which the compositor declines. - let gl = effectcraft_engine::render::Accelerator::name(&probe).ends_with("(Gl)"); + let gl = adapter.ends_with("(Gl)"); drop(probe); let mut s = Session::default(); s.execute("file.openDemoProject", json!({})).unwrap(); s.execute("time.set", json!({"time": 2.0})).unwrap(); - let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_pixels_per_point(1.0).wgpu().build_eframe(|_| EffectcraftApp::new(s)); + let mut h = Harness::builder() + .with_size(egui::vec2(1400.0, 900.0)) + .with_pixels_per_point(1.0) + .wgpu_setup(same_adapter(adapter)) + .build_eframe(|_| EffectcraftApp::new(s)); settle(&mut h); if gl && h.state().gpu_adapter().is_none() { eprintln!("egui-wgpu's GL device has WebGL2 limits: the viewer stays on the CPU, skipping");