use super::*; use solidcraft_geom::{Aabb3, Mesh}; fn request() -> ImageRequest { ImageRequest { camera: Default::default(), width: 16, height: 16, background: [40, 80, 120], transparent_background: false } } fn empty() -> ProductScene { ProductScene { meshes: Vec::new(), bounds: Aabb3::EMPTY } } fn id(owner: u64, serial: u64) -> JobId { JobId { session: owner, serial, generation: serial } } fn drain(host: &CpuHost) { for _ in 0..10_000 { if !host.pump(4096).unwrap() { return; } } panic!("small cooperative jobs did not drain"); } #[test] fn queue_is_bounded_and_cancelled_entries_release_their_reservations() { let host = CpuHost::cooperative(); let a = host.submit(id(10, 1), 100, empty(), request()).unwrap(); let b = host.submit(id(20, 1), 200, empty(), request()).unwrap(); let before = host.admitted_bytes(); assert_eq!(host.submit(id(30, 1), 300, empty(), request()).err().unwrap().code, ErrorCode::QueueFull); assert_eq!(host.admitted_bytes(), before); assert_eq!(a.cancel().unwrap().progress.phase, Phase::Cancelled); let c = host.submit(id(30, 1), 300, empty(), request()).unwrap(); drain(&host); assert_eq!(a.status().unwrap().progress.phase, Phase::Cancelled); for (handle, owner, generation) in [(&b, 20, 200), (&c, 30, 300)] { let frame = handle.frame().unwrap().unwrap(); assert_eq!(frame.info.job, id(owner, 1)); assert_eq!(frame.info.scene_generation, generation); assert_eq!(frame.info.executor.as_str(), "cpu-product"); assert_eq!(image::load_from_memory(frame.png()).unwrap().width(), 16); } assert!(host.admitted_bytes() > 0 && host.admitted_bytes() < before); let frame = b.frame().unwrap().unwrap(); drop(b); drop(c); assert!(host.admitted_bytes() > 0, "an externally retained frame retains its lease"); drop(frame); assert_eq!(host.admitted_bytes(), 0); } #[test] fn cancellation_while_encoding_cannot_publish_a_late_frame() { let host = CpuHost::cooperative(); let job = host.submit(id(1, 1), 8, empty(), request()).unwrap(); for _ in 0..10_000 { host.pump(64).unwrap(); let status = job.status().unwrap(); if status.progress.phase == Phase::Encoding && status.progress.done > 0 { break; } } assert_eq!(job.status().unwrap().progress.phase, Phase::Encoding); assert_eq!(job.cancel().unwrap().progress.phase, Phase::Cancelling); drain(&host); assert_eq!(job.status().unwrap().progress.phase, Phase::Cancelled); assert!(job.frame().unwrap().is_none()); assert_eq!(host.admitted_bytes(), 0); } #[test] fn malformed_preparation_fails_the_job_without_stopping_the_host() { let host = CpuHost::cooperative(); let scene = ProductScene { bounds: Aabb3::EMPTY, meshes: vec![crate::ProductMesh { name: "Broken".into(), mesh: Arc::new(Mesh { triangles: vec![[99, 99, 99]], ..Default::default() }), color: [0; 3], opacity: 1.0, surface: None, faces: Vec::new(), }], }; let broken = host.submit(id(1, 1), 1, scene, request()).unwrap(); drain(&host); let status = broken.status().unwrap(); assert_eq!(status.progress.phase, Phase::Failed); assert_eq!(status.error.unwrap().code, ErrorCode::InvalidScene); assert!(broken.frame().unwrap().is_none()); assert!(host.is_available()); let healthy = host.submit(id(1, 2), 1, empty(), request()).unwrap(); drain(&host); assert_eq!(healthy.status().unwrap().progress.phase, Phase::Completed); } #[test] fn closing_the_host_cancels_active_and_queued_work() { let host = CpuHost::cooperative(); let budget = host.0.shared.budget.clone(); let active = host.submit(id(1, 1), 1, empty(), request()).unwrap(); let queued = host.submit(id(2, 1), 1, empty(), request()).unwrap(); host.pump(1).unwrap(); drop(host); assert_eq!(active.status().unwrap().progress.phase, Phase::Cancelled); assert_eq!(queued.status().unwrap().progress.phase, Phase::Cancelled); assert_eq!(budget.used.load(Ordering::Acquire), 0); } #[test] fn a_foreign_frame_is_rejected_even_if_a_broken_host_puts_it_in_the_result_slot() { let host = CpuHost::cooperative(); let job = host.submit(id(1, 1), 1, empty(), request()).unwrap(); drain(&host); { let mut data = job.0.data.lock().unwrap(); let frame = Arc::get_mut(data.frame.as_mut().unwrap()).unwrap(); frame.info.job.session = 2; } assert_eq!(job.frame().err().unwrap().code, ErrorCode::WrongOwner); } #[cfg(not(target_arch = "wasm32"))] #[test] fn native_job_finishes_without_any_poll_or_ui_pump() { let host = CpuHost::for_platform().unwrap(); assert_eq!(host.kind(), HostKind::NativeWorker); let job = host.submit(id(1, 1), 12, empty(), request()).unwrap(); // A generous deadlock watchdog, not a performance assertion. let status = job.wait_timeout(std::time::Duration::from_secs(60)).unwrap(); assert_eq!(status.progress.phase, Phase::Completed); assert_eq!(job.frame().unwrap().unwrap().info.scene_generation, 12); }