1use crate::{
2 executor, platform, Entity, FontCache, Handle, LeakDetector, MutableAppContext, Platform,
3 Subscription, TestAppContext,
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) {
41 // let _profiler = dhat::Profiler::new_heap();
42
43 let is_randomized = num_iterations > 1;
44 if is_randomized {
45 if let Ok(value) = std::env::var("SEED") {
46 starting_seed = value.parse().expect("invalid SEED variable");
47 }
48 if let Ok(value) = std::env::var("ITERATIONS") {
49 num_iterations = value.parse().expect("invalid ITERATIONS variable");
50 }
51 }
52
53 let atomic_seed = AtomicU64::new(starting_seed as u64);
54 let mut retries = 0;
55
56 loop {
57 let result = panic::catch_unwind(|| {
58 let foreground_platform = Rc::new(platform::test::foreground_platform());
59 let platform = Arc::new(platform::test::platform());
60 let font_system = platform.fonts();
61 let font_cache = Arc::new(FontCache::new(font_system));
62
63 loop {
64 let seed = atomic_seed.fetch_add(1, SeqCst);
65 let is_last_iteration = seed + 1 >= starting_seed + num_iterations;
66
67 if is_randomized {
68 dbg!(seed);
69 }
70
71 let deterministic = executor::Deterministic::new(seed);
72 let leak_detector = Arc::new(Mutex::new(LeakDetector::default()));
73 let mut cx = TestAppContext::new(
74 foreground_platform.clone(),
75 platform.clone(),
76 deterministic.build_foreground(usize::MAX),
77 deterministic.build_background(),
78 font_cache.clone(),
79 leak_detector.clone(),
80 0,
81 );
82 cx.update(|cx| {
83 test_fn(
84 cx,
85 foreground_platform.clone(),
86 deterministic.clone(),
87 seed,
88 is_last_iteration,
89 )
90 });
91
92 deterministic.run_until_parked();
93 leak_detector.lock().detect();
94 if is_last_iteration {
95 break;
96 }
97 }
98 });
99
100 match result {
101 Ok(_) => {
102 break;
103 }
104 Err(error) => {
105 if retries < max_retries {
106 retries += 1;
107 println!("retrying: attempt {}", retries);
108 } else {
109 if is_randomized {
110 eprintln!("failing seed: {}", atomic_seed.load(SeqCst) - 1);
111 }
112 panic::resume_unwind(error);
113 }
114 }
115 }
116 }
117}
118
119pub struct Observation<T> {
120 rx: channel::Receiver<T>,
121 _subscription: Subscription,
122}
123
124impl<T> futures::Stream for Observation<T> {
125 type Item = T;
126
127 fn poll_next(
128 mut self: std::pin::Pin<&mut Self>,
129 cx: &mut std::task::Context<'_>,
130 ) -> std::task::Poll<Option<Self::Item>> {
131 self.rx.poll_next_unpin(cx)
132 }
133}
134
135pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
136 let (tx, rx) = smol::channel::unbounded();
137 let _subscription = cx.update(|cx| {
138 cx.observe(entity, move |_, _| {
139 let _ = smol::block_on(tx.send(()));
140 })
141 });
142
143 Observation { rx, _subscription }
144}
145
146pub fn subscribe<T: Entity>(
147 entity: &impl Handle<T>,
148 cx: &mut TestAppContext,
149) -> Observation<T::Event>
150where
151 T::Event: Clone,
152{
153 let (tx, rx) = smol::channel::unbounded();
154 let _subscription = cx.update(|cx| {
155 cx.subscribe(entity, move |_, event, _| {
156 let _ = smol::block_on(tx.send(event.clone()));
157 })
158 });
159
160 Observation { rx, _subscription }
161}