test.rs

 1use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
 2use futures::StreamExt as _;
 3use rand::prelude::*;
 4use smol::channel;
 5use std::{
 6    env,
 7    panic::{self, RefUnwindSafe},
 8};
 9
10pub fn run_test(
11    mut num_iterations: u64,
12    max_retries: usize,
13    test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)),
14    on_fail_fn: Option<fn()>,
15    _fn_name: String, // todo!("re-enable fn_name")
16) {
17    let starting_seed = env::var("SEED")
18        .map(|seed| seed.parse().expect("invalid SEED variable"))
19        .unwrap_or(0);
20    let is_randomized = num_iterations > 1;
21    if let Ok(iterations) = env::var("ITERATIONS") {
22        num_iterations = iterations.parse().expect("invalid ITERATIONS variable");
23    }
24
25    for seed in starting_seed..starting_seed + num_iterations {
26        let mut retry = 0;
27        loop {
28            if is_randomized {
29                eprintln!("seed = {seed}");
30            }
31            let result = panic::catch_unwind(|| {
32                let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed));
33                test_fn(dispatcher, seed);
34            });
35
36            match result {
37                Ok(_) => break,
38                Err(error) => {
39                    if retry < max_retries {
40                        println!("retrying: attempt {}", retry);
41                        retry += 1;
42                    } else {
43                        if is_randomized {
44                            eprintln!("failing seed: {}", seed);
45                        }
46                        on_fail_fn.map(|f| f());
47                        panic::resume_unwind(error);
48                    }
49                }
50            }
51        }
52    }
53}
54
55pub struct Observation<T> {
56    rx: channel::Receiver<T>,
57    _subscription: Subscription,
58}
59
60impl<T: 'static> futures::Stream for Observation<T> {
61    type Item = T;
62
63    fn poll_next(
64        mut self: std::pin::Pin<&mut Self>,
65        cx: &mut std::task::Context<'_>,
66    ) -> std::task::Poll<Option<Self::Item>> {
67        self.rx.poll_next_unpin(cx)
68    }
69}
70
71pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
72    let (tx, rx) = smol::channel::unbounded();
73    let _subscription = cx.update(|cx| {
74        cx.observe(entity, move |_, _| {
75            let _ = smol::block_on(tx.send(()));
76        })
77    });
78
79    Observation { rx, _subscription }
80}