1use crate::{
2 elements::Empty, executor, platform, Element, ElementBox, Entity, FontCache, Handle,
3 LeakDetector, MutableAppContext, Platform, RenderContext, Subscription, TestAppContext, View,
4};
5use futures::StreamExt;
6use parking_lot::Mutex;
7use smol::channel;
8use std::{
9 panic::{self, RefUnwindSafe},
10 rc::Rc,
11 sync::{
12 atomic::{AtomicU64, Ordering::SeqCst},
13 Arc,
14 },
15};
16
17#[cfg(test)]
18#[ctor::ctor]
19fn init_logger() {
20 if std::env::var("RUST_LOG").is_ok() {
21 env_logger::init();
22 }
23}
24
25// #[global_allocator]
26// static ALLOC: dhat::Alloc = dhat::Alloc;
27
28pub fn run_test(
29 mut num_iterations: u64,
30 mut starting_seed: u64,
31 max_retries: usize,
32 test_fn: &mut (dyn RefUnwindSafe
33 + Fn(
34 &mut MutableAppContext,
35 Rc<platform::test::ForegroundPlatform>,
36 Arc<executor::Deterministic>,
37 u64,
38 bool,
39 )),
40 fn_name: String,
41) {
42 // let _profiler = dhat::Profiler::new_heap();
43
44 let is_randomized = num_iterations > 1;
45 if is_randomized {
46 if let Ok(value) = std::env::var("SEED") {
47 starting_seed = value.parse().expect("invalid SEED variable");
48 }
49 if let Ok(value) = std::env::var("ITERATIONS") {
50 num_iterations = value.parse().expect("invalid ITERATIONS variable");
51 }
52 }
53
54 let atomic_seed = AtomicU64::new(starting_seed as u64);
55 let mut retries = 0;
56
57 loop {
58 let result = panic::catch_unwind(|| {
59 let foreground_platform = Rc::new(platform::test::foreground_platform());
60 let platform = Arc::new(platform::test::platform());
61 let font_system = platform.fonts();
62 let font_cache = Arc::new(FontCache::new(font_system));
63
64 loop {
65 let seed = atomic_seed.fetch_add(1, SeqCst);
66 let is_last_iteration = seed + 1 >= starting_seed + num_iterations;
67
68 if is_randomized {
69 dbg!(seed);
70 }
71
72 let deterministic = executor::Deterministic::new(seed);
73 let leak_detector = Arc::new(Mutex::new(LeakDetector::default()));
74 let mut cx = TestAppContext::new(
75 foreground_platform.clone(),
76 platform.clone(),
77 deterministic.build_foreground(usize::MAX),
78 deterministic.build_background(),
79 font_cache.clone(),
80 leak_detector.clone(),
81 0,
82 fn_name.clone(),
83 );
84 cx.update(|cx| {
85 test_fn(
86 cx,
87 foreground_platform.clone(),
88 deterministic.clone(),
89 seed,
90 is_last_iteration,
91 );
92 });
93
94 cx.update(|cx| cx.remove_all_windows());
95 deterministic.run_until_parked();
96 cx.update(|cx| cx.clear_globals());
97
98 leak_detector.lock().detect();
99 if is_last_iteration {
100 break;
101 }
102 }
103 });
104
105 match result {
106 Ok(_) => {
107 break;
108 }
109 Err(error) => {
110 if retries < max_retries {
111 retries += 1;
112 println!("retrying: attempt {}", retries);
113 } else {
114 if is_randomized {
115 eprintln!("failing seed: {}", atomic_seed.load(SeqCst) - 1);
116 }
117 panic::resume_unwind(error);
118 }
119 }
120 }
121 }
122}
123
124pub struct Observation<T> {
125 rx: channel::Receiver<T>,
126 _subscription: Subscription,
127}
128
129impl<T> futures::Stream for Observation<T> {
130 type Item = T;
131
132 fn poll_next(
133 mut self: std::pin::Pin<&mut Self>,
134 cx: &mut std::task::Context<'_>,
135 ) -> std::task::Poll<Option<Self::Item>> {
136 self.rx.poll_next_unpin(cx)
137 }
138}
139
140pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
141 let (tx, rx) = smol::channel::unbounded();
142 let _subscription = cx.update(|cx| {
143 cx.observe(entity, move |_, _| {
144 let _ = smol::block_on(tx.send(()));
145 })
146 });
147
148 Observation { rx, _subscription }
149}
150
151pub fn subscribe<T: Entity>(
152 entity: &impl Handle<T>,
153 cx: &mut TestAppContext,
154) -> Observation<T::Event>
155where
156 T::Event: Clone,
157{
158 let (tx, rx) = smol::channel::unbounded();
159 let _subscription = cx.update(|cx| {
160 cx.subscribe(entity, move |_, event, _| {
161 let _ = smol::block_on(tx.send(event.clone()));
162 })
163 });
164
165 Observation { rx, _subscription }
166}
167
168pub struct EmptyView;
169
170impl Entity for EmptyView {
171 type Event = ();
172}
173
174impl View for EmptyView {
175 fn ui_name() -> &'static str {
176 "empty view"
177 }
178
179 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
180 Element::boxed(Empty::new())
181 }
182}