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