1use std::{
2 panic::{self, RefUnwindSafe},
3 rc::Rc,
4 sync::{
5 atomic::{AtomicU64, Ordering::SeqCst},
6 Arc,
7 },
8};
9
10use futures::StreamExt;
11use smol::channel;
12
13use crate::{
14 executor, platform, Entity, FontCache, Handle, MutableAppContext, Platform, Subscription,
15 TestAppContext,
16};
17
18#[cfg(test)]
19#[ctor::ctor]
20fn init_logger() {
21 env_logger::builder()
22 .filter_level(log::LevelFilter::Info)
23 .init();
24}
25
26pub fn run_test(
27 mut num_iterations: u64,
28 mut starting_seed: u64,
29 max_retries: usize,
30 test_fn: &mut (dyn RefUnwindSafe
31 + Fn(&mut MutableAppContext, Rc<platform::test::ForegroundPlatform>, u64)),
32) {
33 let is_randomized = num_iterations > 1;
34 if is_randomized {
35 if let Ok(value) = std::env::var("SEED") {
36 starting_seed = value.parse().expect("invalid SEED variable");
37 }
38 if let Ok(value) = std::env::var("ITERATIONS") {
39 num_iterations = value.parse().expect("invalid ITERATIONS variable");
40 }
41 }
42
43 let atomic_seed = AtomicU64::new(starting_seed as u64);
44 let mut retries = 0;
45
46 loop {
47 let result = panic::catch_unwind(|| {
48 let foreground_platform = Rc::new(platform::test::foreground_platform());
49 let platform = Arc::new(platform::test::platform());
50 let font_system = platform.fonts();
51 let font_cache = Arc::new(FontCache::new(font_system));
52
53 loop {
54 let seed = atomic_seed.load(SeqCst);
55 if seed >= starting_seed + num_iterations {
56 break;
57 }
58
59 if is_randomized {
60 dbg!(seed);
61 }
62
63 let (foreground, background) = executor::deterministic(seed);
64 let mut cx = TestAppContext::new(
65 foreground_platform.clone(),
66 platform.clone(),
67 foreground.clone(),
68 background.clone(),
69 font_cache.clone(),
70 0,
71 );
72 cx.update(|cx| test_fn(cx, foreground_platform.clone(), seed));
73
74 atomic_seed.fetch_add(1, SeqCst);
75 }
76 });
77
78 match result {
79 Ok(_) => {
80 break;
81 }
82 Err(error) => {
83 if retries < max_retries {
84 retries += 1;
85 println!("retrying: attempt {}", retries);
86 } else {
87 if is_randomized {
88 eprintln!("failing seed: {}", atomic_seed.load(SeqCst));
89 }
90 panic::resume_unwind(error);
91 }
92 }
93 }
94 }
95}
96
97pub struct Observation<T> {
98 rx: channel::Receiver<T>,
99 _subscription: Subscription,
100}
101
102impl<T> futures::Stream for Observation<T> {
103 type Item = T;
104
105 fn poll_next(
106 mut self: std::pin::Pin<&mut Self>,
107 cx: &mut std::task::Context<'_>,
108 ) -> std::task::Poll<Option<Self::Item>> {
109 self.rx.poll_next_unpin(cx)
110 }
111}
112
113pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
114 let (tx, rx) = smol::channel::unbounded();
115 let _subscription = cx.update(|cx| {
116 cx.observe(entity, move |_, _| {
117 let _ = smol::block_on(tx.send(()));
118 })
119 });
120
121 Observation { rx, _subscription }
122}
123
124pub fn subscribe<T: Entity>(
125 entity: &impl Handle<T>,
126 cx: &mut TestAppContext,
127) -> Observation<T::Event>
128where
129 T::Event: Clone,
130{
131 let (tx, rx) = smol::channel::unbounded();
132 let _subscription = cx.update(|cx| {
133 cx.subscribe(entity, move |_, event, _| {
134 let _ = smol::block_on(tx.send(event.clone()));
135 })
136 });
137
138 Observation { rx, _subscription }
139}