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