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}