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::{BackingStorage, 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<B>>>` into a
38/// [`TestInstance`]. Called from the `register!` macro — the `.trs` closure
39/// returns a concrete backend-parameterized `TurboTasks`, and this function
40/// erases the type while retaining eviction access via a capturing closure.
41pub fn test_instance<B>(tt: Arc<TurboTasks<TurboTasksBackend<B>>>) -> TestInstance
42where
43    B: BackingStorage + 'static,
44{
45    let tt_for_evict = tt.clone();
46    let snapshot_and_evict = Box::new(move || {
47        let _ = tt_for_evict
48            .backend()
49            .snapshot_and_evict_for_testing(&*tt_for_evict);
50    });
51    TestInstance {
52        tt: tt as Arc<dyn TurboTasksApi>,
53        snapshot_and_evict,
54    }
55}
56
57#[macro_export]
58macro_rules! register {
59    () => {{
60        fn create_turbo_tasks(name: &str, initial: bool) -> turbo_tasks_testing::TestInstance {
61            let inner = include!(concat!(
62                env!("CARGO_MANIFEST_DIR"),
63                "/tests/test_config.trs"
64            ));
65            turbo_tasks_testing::test_instance((inner)(name, initial))
66        }
67        turbo_tasks_testing::Registration::new(create_turbo_tasks)
68    }};
69}
70
71pub async fn run_once_without_cache_check<T>(
72    registration: &Registration,
73    fut: impl Future<Output = T> + Send + 'static,
74) -> T
75where
76    T: TraceRawVcs + Send + 'static,
77{
78    let name = closure_to_name(&fut);
79    let instance = registration.create_turbo_tasks(&name, true);
80    turbo_tasks::run_once(instance.tt, async move { Ok(fut.await) })
81        .await
82        .unwrap()
83}
84
85pub async fn run_without_cache_check<T>(
86    registration: &Registration,
87    fut: impl Future<Output = T> + Send + 'static,
88) -> T
89where
90    T: TraceRawVcs + Send + 'static,
91{
92    let name = closure_to_name(&fut);
93    let instance = registration.create_turbo_tasks(&name, true);
94    turbo_tasks::run(instance.tt, async move { Ok(fut.await) })
95        .await
96        .unwrap()
97}
98
99fn closure_to_name<T>(value: &T) -> String {
100    let name = std::any::type_name_of_val(value);
101    name.replace("::{{closure}}", "").replace("::", "_")
102}
103
104pub async fn run_once<T, F>(
105    registration: &Registration,
106    mut fut: impl FnMut() -> F + Send + 'static,
107) -> Result<()>
108where
109    F: Future<Output = Result<T>> + Send + 'static,
110    T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static,
111{
112    run_with_tt(registration, move |tt| turbo_tasks::run_once(tt, fut())).await
113}
114
115pub async fn run<T, F>(
116    registration: &Registration,
117    mut fut: impl FnMut() -> F + Send + 'static,
118) -> Result<()>
119where
120    F: Future<Output = Result<T>> + Send + 'static,
121    T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static,
122{
123    run_with_tt(registration, move |tt| turbo_tasks::run(tt, fut())).await
124}
125
126pub async fn run_with_tt<T, F>(
127    registration: &Registration,
128    mut fut: impl FnMut(Arc<dyn TurboTasksApi>) -> F + Send + 'static,
129) -> Result<()>
130where
131    F: Future<Output = Result<T>> + Send + 'static,
132    T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static,
133{
134    let infinite_initial_runs = env::var("INFINITE_INITIAL_RUNS").is_ok();
135    let infinite_memory_runs = !infinite_initial_runs && env::var("INFINITE_MEMORY_RUNS").is_ok();
136    let single_run = infinite_initial_runs || env::var("SINGLE_RUN").is_ok();
137    let name = closure_to_name(&fut);
138    let mut i = 1;
139    loop {
140        let instance = registration.create_turbo_tasks(&name, true);
141        println!("Run #{i} (without cache)");
142        let start = std::time::Instant::now();
143        let first = fut(instance.tt.clone()).await?;
144        println!("Run #{i} took {:?}", start.elapsed());
145        i += 1;
146        if !single_run {
147            let max_run = if infinite_memory_runs { usize::MAX } else { 10 };
148            for _ in 0..max_run {
149                // Snapshot + evict between runs. Forces every subsequent read to
150                // go through the restore path instead of the warm in-memory cache,
151                // so tests exercise persistence on every iteration — not just the
152                // initial cold run and the post-`stop_and_wait` fs-cache runs.
153                (instance.snapshot_and_evict)();
154                println!("Run #{i} (with memory cache, same TurboTasks instance, post-evict)");
155                let start = std::time::Instant::now();
156                let second = fut(instance.tt.clone()).await?;
157                println!("Run #{i} took {:?}", start.elapsed());
158                i += 1;
159                assert_eq!(first, second);
160            }
161        }
162        let start = std::time::Instant::now();
163        instance.tt.stop_and_wait().await;
164        println!("Stopping TurboTasks took {:?}", start.elapsed());
165        if !single_run {
166            for _ in 10..20 {
167                let instance = registration.create_turbo_tasks(&name, false);
168                println!("Run #{i} (with filesystem cache if available, new TurboTasks instance)");
169                let start = std::time::Instant::now();
170                let third = fut(instance.tt.clone()).await?;
171                println!("Run #{i} took {:?}", start.elapsed());
172                i += 1;
173                let start = std::time::Instant::now();
174                instance.tt.stop_and_wait().await;
175                println!("Stopping TurboTasks took {:?}", start.elapsed());
176                assert_eq!(first, third);
177            }
178        }
179        if !infinite_initial_runs {
180            break;
181        }
182    }
183    Ok(())
184}