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        bool,
 37    )),
 38) {
 39    let is_randomized = num_iterations > 1;
 40    if is_randomized {
 41        if let Ok(value) = std::env::var("SEED") {
 42            starting_seed = value.parse().expect("invalid SEED variable");
 43        }
 44        if let Ok(value) = std::env::var("ITERATIONS") {
 45            num_iterations = value.parse().expect("invalid ITERATIONS variable");
 46        }
 47    }
 48
 49    let atomic_seed = AtomicU64::new(starting_seed as u64);
 50    let mut retries = 0;
 51
 52    loop {
 53        let result = panic::catch_unwind(|| {
 54            let foreground_platform = Rc::new(platform::test::foreground_platform());
 55            let platform = Arc::new(platform::test::platform());
 56            let font_system = platform.fonts();
 57            let font_cache = Arc::new(FontCache::new(font_system));
 58
 59            loop {
 60                let seed = atomic_seed.fetch_add(1, SeqCst);
 61                let is_last_iteration = seed + 1 >= starting_seed + num_iterations;
 62
 63                if is_randomized {
 64                    dbg!(seed);
 65                }
 66
 67                let deterministic = executor::Deterministic::new(seed);
 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                    0,
 75                );
 76                cx.update(|cx| {
 77                    test_fn(
 78                        cx,
 79                        foreground_platform.clone(),
 80                        deterministic,
 81                        seed,
 82                        is_last_iteration,
 83                    )
 84                });
 85
 86                if is_last_iteration {
 87                    break;
 88                }
 89            }
 90        });
 91
 92        match result {
 93            Ok(_) => {
 94                break;
 95            }
 96            Err(error) => {
 97                if retries < max_retries {
 98                    retries += 1;
 99                    println!("retrying: attempt {}", retries);
100                } else {
101                    if is_randomized {
102                        eprintln!("failing seed: {}", atomic_seed.load(SeqCst) - 1);
103                    }
104                    panic::resume_unwind(error);
105                }
106            }
107        }
108    }
109}
110
111pub struct Observation<T> {
112    rx: channel::Receiver<T>,
113    _subscription: Subscription,
114}
115
116impl<T> futures::Stream for Observation<T> {
117    type Item = T;
118
119    fn poll_next(
120        mut self: std::pin::Pin<&mut Self>,
121        cx: &mut std::task::Context<'_>,
122    ) -> std::task::Poll<Option<Self::Item>> {
123        self.rx.poll_next_unpin(cx)
124    }
125}
126
127pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
128    let (tx, rx) = smol::channel::unbounded();
129    let _subscription = cx.update(|cx| {
130        cx.observe(entity, move |_, _| {
131            let _ = smol::block_on(tx.send(()));
132        })
133    });
134
135    Observation { rx, _subscription }
136}
137
138pub fn subscribe<T: Entity>(
139    entity: &impl Handle<T>,
140    cx: &mut TestAppContext,
141) -> Observation<T::Event>
142where
143    T::Event: Clone,
144{
145    let (tx, rx) = smol::channel::unbounded();
146    let _subscription = cx.update(|cx| {
147        cx.subscribe(entity, move |_, event, _| {
148            let _ = smol::block_on(tx.send(event.clone()));
149        })
150    });
151
152    Observation { rx, _subscription }
153}