Skip to main content

turbo_tasks_testing/
run.rs

1use std::{env, fmt::Debug, future::Future, sync::Arc};
2
3use anyhow::Result;
4use turbo_tasks::{TurboTasks, TurboTasksApi, trace::TraceRawVcs};
5use turbo_tasks_backend::TurboTasksBackend;
6
7/// A freshly created test instance: the `TurboTasks` handle (type-erased to
8/// `Arc<dyn TurboTasksApi>`) and a closure that, when called, takes a
9/// snapshot and evicts all evictable tasks on that instance.
10///
11/// The eviction closure captures the concrete backend type internally so
12/// harness code holding an erased `TurboTasksApi` can still reach the
13/// `snapshot_and_evict` API.
14pub struct TestInstance {
15    pub tt: Arc<dyn TurboTasksApi>,
16    pub snapshot_and_evict: Box<dyn Fn() + Send + Sync>,
17}
18
19/// Type-erased factory returned by the `register!` macro. Stays non-generic so
20/// call sites can write `static REGISTRATION: Registration = register!();`
21/// without naming the backing storage type.
22pub struct Registration {
23    create_turbo_tasks: fn(&str, bool) -> TestInstance,
24}
25
26impl Registration {
27    #[doc(hidden)]
28    pub const fn new(create_turbo_tasks: fn(&str, bool) -> TestInstance) -> Self {
29        Registration { create_turbo_tasks }
30    }
31
32    pub fn create_turbo_tasks(&self, name: &str, initial: bool) -> TestInstance {
33        (self.create_turbo_tasks)(name, initial)
34    }
35}
36
37/// Wrap a concrete `Arc<TurboTasks<TurboTasksBackend>>` into a
38/// [`TestInstance`]. Called from the `register!` macro — the `.trs` closure
39/// returns a concrete `TurboTasks`, and this function erases the type while
40/// retaining eviction access via a capturing closure.
41pub fn test_instance(tt: Arc<TurboTasks<TurboTasksBackend>>) -> TestInstance {
42    let tt_for_evict = tt.clone();
43    let snapshot_and_evict = Box::new(move || {
44        let _ = tt_for_evict
45            .backend()
46            .snapshot_and_evict_for_testing(&tt_for_evict);
47    });
48    TestInstance {
49        tt: tt as Arc<dyn TurboTasksApi>,
50        snapshot_and_evict,
51    }
52}
53
54#[macro_export]
55macro_rules! register {
56    () => {{
57        fn create_turbo_tasks(name: &str, initial: bool) -> turbo_tasks_testing::TestInstance {
58            let inner = include!(concat!(
59                env!("CARGO_MANIFEST_DIR"),
60                "/tests/test_config.trs"
61            ));
62            turbo_tasks_testing::test_instance((inner)(name, initial))
63        }
64        turbo_tasks_testing::Registration::new(create_turbo_tasks)
65    }};
66}
67
68pub async fn run_once_without_cache_check<T>(
69    registration: &Registration,
70    fut: impl Future<Output = T> + Send + 'static,
71) -> T
72where
73    T: TraceRawVcs + Send + 'static,
74{
75    let name = closure_to_name(&fut);
76    let instance = registration.create_turbo_tasks(&name, true);
77    turbo_tasks::run_once(instance.tt, async move { Ok(fut.await) })
78        .await
79        .unwrap()
80}
81
82pub async fn run_without_cache_check<T>(
83    registration: &Registration,
84    fut: impl Future<Output = T> + Send + 'static,
85) -> T
86where
87    T: TraceRawVcs + Send + 'static,
88{
89    let name = closure_to_name(&fut);
90    let instance = registration.create_turbo_tasks(&name, true);
91    turbo_tasks::run(instance.tt, async move { Ok(fut.await) })
92        .await
93        .unwrap()
94}
95
96fn closure_to_name<T>(value: &T) -> String {
97    let name = std::any::type_name_of_val(value);
98    name.replace("::{{closure}}", "").replace("::", "_")
99}
100
101pub async fn run_once<T, F>(
102    registration: &Registration,
103    mut fut: impl FnMut() -> F + Send + 'static,
104) -> Result<()>
105where
106    F: Future<Output = Result<T>> + Send + 'static,
107    T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static,
108{
109    run_with_tt(registration, move |tt| turbo_tasks::run_once(tt, fut())).await
110}
111
112pub async fn run<T, F>(
113    registration: &Registration,
114    mut fut: impl FnMut() -> F + Send + 'static,
115) -> Result<()>
116where
117    F: Future<Output = Result<T>> + Send + 'static,
118    T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static,
119{
120    run_with_tt(registration, move |tt| turbo_tasks::run(tt, fut())).await
121}
122
123pub async fn run_with_tt<T, F>(
124    registration: &Registration,
125    mut fut: impl FnMut(Arc<dyn TurboTasksApi>) -> F + Send + 'static,
126) -> Result<()>
127where
128    F: Future<Output = Result<T>> + Send + 'static,
129    T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static,
130{
131    let infinite_initial_runs = env::var("INFINITE_INITIAL_RUNS").is_ok();
132    let infinite_memory_runs = !infinite_initial_runs && env::var("INFINITE_MEMORY_RUNS").is_ok();
133    let single_run = infinite_initial_runs || env::var("SINGLE_RUN").is_ok();
134    let name = closure_to_name(&fut);
135    let mut i = 1;
136    loop {
137        let instance = registration.create_turbo_tasks(&name, true);
138        println!("Run #{i} (without cache)");
139        let start = std::time::Instant::now();
140        let first = fut(instance.tt.clone()).await?;
141        println!("Run #{i} took {:?}", start.elapsed());
142        i += 1;
143        if !single_run {
144            let max_run = if infinite_memory_runs { usize::MAX } else { 10 };
145            for _ in 0..max_run {
146                // Snapshot + evict between runs. Forces every subsequent read to
147                // go through the restore path instead of the warm in-memory cache,
148                // so tests exercise persistence on every iteration — not just the
149                // initial cold run and the post-`stop_and_wait` fs-cache runs.
150                (instance.snapshot_and_evict)();
151                println!("Run #{i} (with memory cache, same TurboTasks instance, post-evict)");
152                let start = std::time::Instant::now();
153                let second = fut(instance.tt.clone()).await?;
154                println!("Run #{i} took {:?}", start.elapsed());
155                i += 1;
156                assert_eq!(first, second);
157            }
158        }
159        let start = std::time::Instant::now();
160        instance.tt.stop_and_wait().await;
161        println!("Stopping TurboTasks took {:?}", start.elapsed());
162        if !single_run {
163            for _ in 10..20 {
164                let instance = registration.create_turbo_tasks(&name, false);
165                println!("Run #{i} (with filesystem cache if available, new TurboTasks instance)");
166                let start = std::time::Instant::now();
167                let third = fut(instance.tt.clone()).await?;
168                println!("Run #{i} took {:?}", start.elapsed());
169                i += 1;
170                let start = std::time::Instant::now();
171                instance.tt.stop_and_wait().await;
172                println!("Stopping TurboTasks took {:?}", start.elapsed());
173                assert_eq!(first, third);
174            }
175        }
176        if !infinite_initial_runs {
177            break;
178        }
179    }
180    Ok(())
181}