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