test.rs

  1use crate::{
  2    executor, platform, Entity, FontCache, Handle, LeakDetector, MutableAppContext, Platform,
  3    Subscription, TestAppContext,
  4};
  5use futures::StreamExt;
  6use parking_lot::Mutex;
  7use smol::channel;
  8use std::{
  9    panic::{self, RefUnwindSafe},
 10    rc::Rc,
 11    sync::{
 12        atomic::{AtomicU64, Ordering::SeqCst},
 13        Arc,
 14    },
 15};
 16
 17#[cfg(test)]
 18#[ctor::ctor]
 19fn init_logger() {
 20    if std::env::var("RUST_LOG").is_ok() {
 21        env_logger::init();
 22    }
 23}
 24
 25pub fn run_test(
 26    mut num_iterations: u64,
 27    mut starting_seed: u64,
 28    max_retries: usize,
 29    test_fn: &mut (dyn RefUnwindSafe
 30              + Fn(
 31        &mut MutableAppContext,
 32        Rc<platform::test::ForegroundPlatform>,
 33        Arc<executor::Deterministic>,
 34        u64,
 35        bool,
 36    )),
 37) {
 38    let is_randomized = num_iterations > 1;
 39    if is_randomized {
 40        if let Ok(value) = std::env::var("SEED") {
 41            starting_seed = value.parse().expect("invalid SEED variable");
 42        }
 43        if let Ok(value) = std::env::var("ITERATIONS") {
 44            num_iterations = value.parse().expect("invalid ITERATIONS variable");
 45        }
 46    }
 47
 48    let atomic_seed = AtomicU64::new(starting_seed as u64);
 49    let mut retries = 0;
 50
 51    loop {
 52        let result = panic::catch_unwind(|| {
 53            let foreground_platform = Rc::new(platform::test::foreground_platform());
 54            let platform = Arc::new(platform::test::platform());
 55            let font_system = platform.fonts();
 56            let font_cache = Arc::new(FontCache::new(font_system));
 57
 58            loop {
 59                let seed = atomic_seed.fetch_add(1, SeqCst);
 60                let is_last_iteration = seed + 1 >= starting_seed + num_iterations;
 61
 62                if is_randomized {
 63                    dbg!(seed);
 64                }
 65
 66                let deterministic = executor::Deterministic::new(seed);
 67                let leak_detector = Arc::new(Mutex::new(LeakDetector::default()));
 68                let mut cx = TestAppContext::new(
 69                    foreground_platform.clone(),
 70                    platform.clone(),
 71                    deterministic.build_foreground(usize::MAX),
 72                    deterministic.build_background(),
 73                    font_cache.clone(),
 74                    leak_detector.clone(),
 75                    0,
 76                );
 77                cx.update(|cx| {
 78                    test_fn(
 79                        cx,
 80                        foreground_platform.clone(),
 81                        deterministic.clone(),
 82                        seed,
 83                        is_last_iteration,
 84                    )
 85                });
 86
 87                deterministic.run_until_parked();
 88                leak_detector.lock().detect();
 89                if is_last_iteration {
 90                    break;
 91                }
 92            }
 93        });
 94
 95        match result {
 96            Ok(_) => {
 97                break;
 98            }
 99            Err(error) => {
100                if retries < max_retries {
101                    retries += 1;
102                    println!("retrying: attempt {}", retries);
103                } else {
104                    if is_randomized {
105                        eprintln!("failing seed: {}", atomic_seed.load(SeqCst) - 1);
106                    }
107                    panic::resume_unwind(error);
108                }
109            }
110        }
111    }
112}
113
114pub struct Observation<T> {
115    rx: channel::Receiver<T>,
116    _subscription: Subscription,
117}
118
119impl<T> futures::Stream for Observation<T> {
120    type Item = T;
121
122    fn poll_next(
123        mut self: std::pin::Pin<&mut Self>,
124        cx: &mut std::task::Context<'_>,
125    ) -> std::task::Poll<Option<Self::Item>> {
126        self.rx.poll_next_unpin(cx)
127    }
128}
129
130pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
131    let (tx, rx) = smol::channel::unbounded();
132    let _subscription = cx.update(|cx| {
133        cx.observe(entity, move |_, _| {
134            let _ = smol::block_on(tx.send(()));
135        })
136    });
137
138    Observation { rx, _subscription }
139}
140
141pub fn subscribe<T: Entity>(
142    entity: &impl Handle<T>,
143    cx: &mut TestAppContext,
144) -> Observation<T::Event>
145where
146    T::Event: Clone,
147{
148    let (tx, rx) = smol::channel::unbounded();
149    let _subscription = cx.update(|cx| {
150        cx.subscribe(entity, move |_, event, _| {
151            let _ = smol::block_on(tx.send(event.clone()));
152        })
153    });
154
155    Observation { rx, _subscription }
156}