test.rs

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