test.rs

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