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 AppContext, Element, ElementBox, Entity, FontCache, Handle, RenderContext, Subscription,
23 TestAppContext, View,
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 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 panic::resume_unwind(error);
182 }
183 }
184 }
185 }
186}
187
188pub struct Observation<T> {
189 rx: channel::Receiver<T>,
190 _subscription: Subscription,
191}
192
193impl<T> futures::Stream for Observation<T> {
194 type Item = T;
195
196 fn poll_next(
197 mut self: std::pin::Pin<&mut Self>,
198 cx: &mut std::task::Context<'_>,
199 ) -> std::task::Poll<Option<Self::Item>> {
200 self.rx.poll_next_unpin(cx)
201 }
202}
203
204pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
205 let (tx, rx) = smol::channel::unbounded();
206 let _subscription = cx.update(|cx| {
207 cx.observe(entity, move |_, _| {
208 let _ = smol::block_on(tx.send(()));
209 })
210 });
211
212 Observation { rx, _subscription }
213}
214
215pub fn subscribe<T: Entity>(
216 entity: &impl Handle<T>,
217 cx: &mut TestAppContext,
218) -> Observation<T::Event>
219where
220 T::Event: Clone,
221{
222 let (tx, rx) = smol::channel::unbounded();
223 let _subscription = cx.update(|cx| {
224 cx.subscribe(entity, move |_, event, _| {
225 let _ = smol::block_on(tx.send(event.clone()));
226 })
227 });
228
229 Observation { rx, _subscription }
230}
231
232pub struct EmptyView;
233
234impl Entity for EmptyView {
235 type Event = ();
236}
237
238impl View for EmptyView {
239 fn ui_name() -> &'static str {
240 "empty view"
241 }
242
243 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
244 Element::boxed(Empty::new())
245 }
246}