test.rs

  1use crate::{
  2    elements::Empty, executor, platform, Element, ElementBox, Entity, FontCache, Handle,
  3    LeakDetector, MutableAppContext, Platform, RenderContext, Subscription, TestAppContext, View,
  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
 25// #[global_allocator]
 26// static ALLOC: dhat::Alloc = dhat::Alloc;
 27
 28pub fn run_test(
 29    mut num_iterations: u64,
 30    mut starting_seed: u64,
 31    max_retries: usize,
 32    test_fn: &mut (dyn RefUnwindSafe
 33              + Fn(
 34        &mut MutableAppContext,
 35        Rc<platform::test::ForegroundPlatform>,
 36        Arc<executor::Deterministic>,
 37        u64,
 38        bool,
 39    )),
 40    fn_name: String,
 41) {
 42    // let _profiler = dhat::Profiler::new_heap();
 43
 44    let is_randomized = num_iterations > 1;
 45    if is_randomized {
 46        if let Ok(value) = std::env::var("SEED") {
 47            starting_seed = value.parse().expect("invalid SEED variable");
 48        }
 49        if let Ok(value) = std::env::var("ITERATIONS") {
 50            num_iterations = value.parse().expect("invalid ITERATIONS variable");
 51        }
 52    }
 53
 54    let atomic_seed = AtomicU64::new(starting_seed as u64);
 55    let mut retries = 0;
 56
 57    loop {
 58        let result = panic::catch_unwind(|| {
 59            let foreground_platform = Rc::new(platform::test::foreground_platform());
 60            let platform = Arc::new(platform::test::platform());
 61            let font_system = platform.fonts();
 62            let font_cache = Arc::new(FontCache::new(font_system));
 63
 64            loop {
 65                let seed = atomic_seed.fetch_add(1, SeqCst);
 66                let is_last_iteration = seed + 1 >= starting_seed + num_iterations;
 67
 68                if is_randomized {
 69                    dbg!(seed);
 70                }
 71
 72                let deterministic = executor::Deterministic::new(seed);
 73                let leak_detector = Arc::new(Mutex::new(LeakDetector::default()));
 74                let mut cx = TestAppContext::new(
 75                    foreground_platform.clone(),
 76                    platform.clone(),
 77                    deterministic.build_foreground(usize::MAX),
 78                    deterministic.build_background(),
 79                    font_cache.clone(),
 80                    leak_detector.clone(),
 81                    0,
 82                    fn_name.clone(),
 83                );
 84                cx.update(|cx| {
 85                    test_fn(
 86                        cx,
 87                        foreground_platform.clone(),
 88                        deterministic.clone(),
 89                        seed,
 90                        is_last_iteration,
 91                    );
 92                });
 93
 94                cx.update(|cx| cx.remove_all_windows());
 95                deterministic.run_until_parked();
 96                cx.update(|cx| cx.clear_globals());
 97
 98                leak_detector.lock().detect();
 99                if is_last_iteration {
100                    break;
101                }
102            }
103        });
104
105        match result {
106            Ok(_) => {
107                break;
108            }
109            Err(error) => {
110                if retries < max_retries {
111                    retries += 1;
112                    println!("retrying: attempt {}", retries);
113                } else {
114                    if is_randomized {
115                        eprintln!("failing seed: {}", atomic_seed.load(SeqCst) - 1);
116                    }
117                    panic::resume_unwind(error);
118                }
119            }
120        }
121    }
122}
123
124pub struct Observation<T> {
125    rx: channel::Receiver<T>,
126    _subscription: Subscription,
127}
128
129impl<T> futures::Stream for Observation<T> {
130    type Item = T;
131
132    fn poll_next(
133        mut self: std::pin::Pin<&mut Self>,
134        cx: &mut std::task::Context<'_>,
135    ) -> std::task::Poll<Option<Self::Item>> {
136        self.rx.poll_next_unpin(cx)
137    }
138}
139
140pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
141    let (tx, rx) = smol::channel::unbounded();
142    let _subscription = cx.update(|cx| {
143        cx.observe(entity, move |_, _| {
144            let _ = smol::block_on(tx.send(()));
145        })
146    });
147
148    Observation { rx, _subscription }
149}
150
151pub fn subscribe<T: Entity>(
152    entity: &impl Handle<T>,
153    cx: &mut TestAppContext,
154) -> Observation<T::Event>
155where
156    T::Event: Clone,
157{
158    let (tx, rx) = smol::channel::unbounded();
159    let _subscription = cx.update(|cx| {
160        cx.subscribe(entity, move |_, event, _| {
161            let _ = smol::block_on(tx.send(event.clone()));
162        })
163    });
164
165    Observation { rx, _subscription }
166}
167
168pub struct EmptyView;
169
170impl Entity for EmptyView {
171    type Event = ();
172}
173
174impl View for EmptyView {
175    fn ui_name() -> &'static str {
176        "empty view"
177    }
178
179    fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
180        Element::boxed(Empty::new())
181    }
182}