use std::{ panic::{self, RefUnwindSafe}, rc::Rc, sync::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, }, }; use futures::StreamExt; use smol::channel; use crate::{ executor, platform, Entity, FontCache, Handle, MutableAppContext, Platform, Subscription, TestAppContext, }; #[cfg(test)] #[ctor::ctor] fn init_logger() { env_logger::builder() .filter_level(log::LevelFilter::Info) .init(); } pub fn run_test( mut num_iterations: u64, mut starting_seed: u64, max_retries: usize, test_fn: &mut (dyn RefUnwindSafe + Fn(&mut MutableAppContext, Rc, u64)), ) { let is_randomized = num_iterations > 1; if is_randomized { if let Ok(value) = std::env::var("SEED") { starting_seed = value.parse().expect("invalid SEED variable"); } if let Ok(value) = std::env::var("ITERATIONS") { num_iterations = value.parse().expect("invalid ITERATIONS variable"); } } let atomic_seed = AtomicU64::new(starting_seed as u64); let mut retries = 0; loop { let result = panic::catch_unwind(|| { let foreground_platform = Rc::new(platform::test::foreground_platform()); let platform = Arc::new(platform::test::platform()); let font_system = platform.fonts(); let font_cache = Arc::new(FontCache::new(font_system)); loop { let seed = atomic_seed.load(SeqCst); if seed >= starting_seed + num_iterations { break; } if is_randomized { dbg!(seed); } let (foreground, background) = executor::deterministic(seed); let mut cx = TestAppContext::new( foreground_platform.clone(), platform.clone(), foreground.clone(), background.clone(), font_cache.clone(), 0, ); cx.update(|cx| test_fn(cx, foreground_platform.clone(), seed)); atomic_seed.fetch_add(1, SeqCst); } }); match result { Ok(_) => { break; } Err(error) => { if retries < max_retries { retries += 1; println!("retrying: attempt {}", retries); } else { if is_randomized { eprintln!("failing seed: {}", atomic_seed.load(SeqCst)); } panic::resume_unwind(error); } } } } } pub struct Observation { rx: channel::Receiver, _subscription: Subscription, } impl futures::Stream for Observation { type Item = T; fn poll_next( mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { self.rx.poll_next_unpin(cx) } } pub fn observe(entity: &impl Handle, cx: &mut TestAppContext) -> Observation<()> { let (tx, rx) = smol::channel::unbounded(); let _subscription = cx.update(|cx| { cx.observe(entity, move |_, _| { let _ = smol::block_on(tx.send(())); }) }); Observation { rx, _subscription } } pub fn subscribe( entity: &impl Handle, cx: &mut TestAppContext, ) -> Observation where T::Event: Clone, { let (tx, rx) = smol::channel::unbounded(); let _subscription = cx.update(|cx| { cx.subscribe(entity, move |_, event, _| { let _ = smol::block_on(tx.send(event.clone())); }) }); Observation { rx, _subscription } }