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");