test.rs

  1use std::{
  2    fmt::Write,
  3    panic::{self, RefUnwindSafe},
  4    rc::Rc,
  5    sync::{
  6        atomic::{AtomicU64, Ordering::SeqCst},
  7        Arc,
  8    },
  9};
 10
 11use futures::StreamExt;
 12use parking_lot::Mutex;
 13use smol::channel;
 14
 15use crate::{
 16    app::ref_counts::LeakDetector,
 17    elements::Empty,
 18    executor::{self, ExecutorEvent},
 19    platform,
 20    util::CwdBacktrace,
 21    Element, ElementBox, Entity, FontCache, Handle, MutableAppContext, Platform, RenderContext,
 22    Subscription, TestAppContext, View,
 23};
 24
 25#[cfg(test)]
 26#[ctor::ctor]
 27fn init_logger() {
 28    if std::env::var("RUST_LOG").is_ok() {
 29        env_logger::init();
 30    }
 31}
 32
 33// #[global_allocator]
 34// static ALLOC: dhat::Alloc = dhat::Alloc;
 35
 36pub fn run_test(
 37    mut num_iterations: u64,
 38    mut starting_seed: u64,
 39    max_retries: usize,
 40    detect_nondeterminism: bool,
 41    test_fn: &mut (dyn RefUnwindSafe
 42              + Fn(
 43        &mut MutableAppContext,
 44        Rc<platform::test::ForegroundPlatform>,
 45        Arc<executor::Deterministic>,
 46        u64,
 47    )),
 48    fn_name: String,
 49) {
 50    // let _profiler = dhat::Profiler::new_heap();
 51
 52    let is_randomized = num_iterations > 1;
 53    if is_randomized {
 54        if let Ok(value) = std::env::var("SEED") {
 55            starting_seed = value.parse().expect("invalid SEED variable");
 56        }
 57        if let Ok(value) = std::env::var("ITERATIONS") {
 58            num_iterations = value.parse().expect("invalid ITERATIONS variable");
 59        }
 60    }
 61
 62    let atomic_seed = AtomicU64::new(starting_seed as u64);
 63    let mut retries = 0;
 64
 65    loop {
 66        let result = panic::catch_unwind(|| {
 67            let foreground_platform = Rc::new(platform::test::foreground_platform());
 68            let platform = Arc::new(platform::test::platform());
 69            let font_system = platform.fonts();
 70            let font_cache = Arc::new(FontCache::new(font_system));
 71            let mut prev_runnable_history: Option<Vec<ExecutorEvent>> = None;
 72
 73            for _ in 0..num_iterations {
 74                let seed = atomic_seed.load(SeqCst);
 75
 76                if is_randomized {
 77                    dbg!(seed);
 78                }
 79
 80                let deterministic = executor::Deterministic::new(seed);
 81                if detect_nondeterminism {
 82                    deterministic.set_previous_execution_history(prev_runnable_history.clone());
 83                    deterministic.enable_runnable_backtrace();
 84                }
 85
 86                let leak_detector = Arc::new(Mutex::new(LeakDetector::default()));
 87                let mut cx = TestAppContext::new(
 88                    foreground_platform.clone(),
 89                    platform.clone(),
 90                    deterministic.build_foreground(usize::MAX),
 91                    deterministic.build_background(),
 92                    font_cache.clone(),
 93                    leak_detector.clone(),
 94                    0,
 95                    fn_name.clone(),
 96                );
 97                cx.update(|cx| {
 98                    test_fn(cx, foreground_platform.clone(), deterministic.clone(), seed);
 99                });
100
101                cx.update(|cx| cx.remove_all_windows());
102                deterministic.run_until_parked();
103                cx.update(|cx| cx.clear_globals());
104
105                leak_detector.lock().detect();
106
107                if detect_nondeterminism {
108                    let curr_runnable_history = deterministic.execution_history();
109                    if let Some(prev_runnable_history) = prev_runnable_history {
110                        let mut prev_entries = prev_runnable_history.iter().fuse();
111                        let mut curr_entries = curr_runnable_history.iter().fuse();
112
113                        let mut nondeterministic = false;
114                        let mut common_history_prefix = Vec::new();
115                        let mut prev_history_suffix = Vec::new();
116                        let mut curr_history_suffix = Vec::new();
117                        loop {
118                            match (prev_entries.next(), curr_entries.next()) {
119                                (None, None) => break,
120                                (None, Some(curr_id)) => curr_history_suffix.push(*curr_id),
121                                (Some(prev_id), None) => prev_history_suffix.push(*prev_id),
122                                (Some(prev_id), Some(curr_id)) => {
123                                    if nondeterministic {
124                                        prev_history_suffix.push(*prev_id);
125                                        curr_history_suffix.push(*curr_id);
126                                    } else if prev_id == curr_id {
127                                        common_history_prefix.push(*curr_id);
128                                    } else {
129                                        nondeterministic = true;
130                                        prev_history_suffix.push(*prev_id);
131                                        curr_history_suffix.push(*curr_id);
132                                    }
133                                }
134                            }
135                        }
136
137                        if nondeterministic {
138                            let mut error = String::new();
139                            writeln!(&mut error, "Common prefix: {:?}", common_history_prefix)
140                                .unwrap();
141                            writeln!(&mut error, "Previous suffix: {:?}", prev_history_suffix)
142                                .unwrap();
143                            writeln!(&mut error, "Current suffix: {:?}", curr_history_suffix)
144                                .unwrap();
145
146                            let last_common_backtrace = common_history_prefix
147                                .last()
148                                .map(|event| deterministic.runnable_backtrace(event.id()));
149
150                            writeln!(
151                                &mut error,
152                                "Last future that ran on both executions: {:?}",
153                                last_common_backtrace.as_ref().map(CwdBacktrace)
154                            )
155                            .unwrap();
156                            panic!("Detected non-determinism.\n{}", error);
157                        }
158                    }
159                    prev_runnable_history = Some(curr_runnable_history);
160                }
161
162                if !detect_nondeterminism {
163                    atomic_seed.fetch_add(1, SeqCst);
164                }
165            }
166        });
167
168        match result {
169            Ok(_) => {
170                break;
171            }
172            Err(error) => {
173                if retries < max_retries {
174                    retries += 1;
175                    println!("retrying: attempt {}", retries);
176                } else {
177                    if is_randomized {
178                        eprintln!("failing seed: {}", atomic_seed.load(SeqCst));
179                    }
180                    panic::resume_unwind(error);
181                }
182            }
183        }
184    }
185}
186
187pub struct Observation<T> {
188    rx: channel::Receiver<T>,
189    _subscription: Subscription,
190}
191
192impl<T> futures::Stream for Observation<T> {
193    type Item = T;
194
195    fn poll_next(
196        mut self: std::pin::Pin<&mut Self>,
197        cx: &mut std::task::Context<'_>,
198    ) -> std::task::Poll<Option<Self::Item>> {
199        self.rx.poll_next_unpin(cx)
200    }
201}
202
203pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
204    let (tx, rx) = smol::channel::unbounded();
205    let _subscription = cx.update(|cx| {
206        cx.observe(entity, move |_, _| {
207            let _ = smol::block_on(tx.send(()));
208        })
209    });
210
211    Observation { rx, _subscription }
212}
213
214pub fn subscribe<T: Entity>(
215    entity: &impl Handle<T>,
216    cx: &mut TestAppContext,
217) -> Observation<T::Event>
218where
219    T::Event: Clone,
220{
221    let (tx, rx) = smol::channel::unbounded();
222    let _subscription = cx.update(|cx| {
223        cx.subscribe(entity, move |_, event, _| {
224            let _ = smol::block_on(tx.send(event.clone()));
225        })
226    });
227
228    Observation { rx, _subscription }
229}
230
231pub struct EmptyView;
232
233impl Entity for EmptyView {
234    type Event = ();
235}
236
237impl View for EmptyView {
238    fn ui_name() -> &'static str {
239        "empty view"
240    }
241
242    fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
243        Element::boxed(Empty::new())
244    }
245}