1 use super::util::{config, make_component};
2 use component_async_tests::Ctx;
3 use component_async_tests::util::yield_times;
4 use futures::{
5     FutureExt,
6     channel::oneshot,
7     stream::{FuturesUnordered, TryStreamExt},
8 };
9 use std::sync::Arc;
10 use std::sync::atomic::{AtomicU32, Ordering::Relaxed};
11 use wasmtime::component::{
12     Accessor, AccessorTask, HasData, HasSelf, Instance, Linker, ResourceTable, Val,
13 };
14 use wasmtime::{Engine, Result, Store, Trap, format_err};
15 use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
16 
17 #[tokio::test]
async_round_trip_stackful() -> Result<()>18 pub async fn async_round_trip_stackful() -> Result<()> {
19     test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT).await
20 }
21 
22 #[tokio::test]
async_round_trip_synchronous() -> Result<()>23 pub async fn async_round_trip_synchronous() -> Result<()> {
24     test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT)
25         .await
26 }
27 
28 #[tokio::test]
async_round_trip_wait() -> Result<()>29 pub async fn async_round_trip_wait() -> Result<()> {
30     test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT).await
31 }
32 
33 #[tokio::test]
async_round_trip_stackless_plus_stackless() -> Result<()>34 pub async fn async_round_trip_stackless_plus_stackless() -> Result<()> {
35     test_round_trip_composed(
36         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
37         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
38     )
39     .await
40 }
41 
42 #[tokio::test]
async_round_trip_stackless_plus_stackless_plus_stackless() -> Result<()>43 pub async fn async_round_trip_stackless_plus_stackless_plus_stackless() -> Result<()> {
44     test_round_trip_composed_more(
45         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
46         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
47         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
48     )
49     .await
50 }
51 
52 #[tokio::test]
async_round_trip_synchronous_plus_stackless() -> Result<()>53 async fn async_round_trip_synchronous_plus_stackless() -> Result<()> {
54     test_round_trip_composed(
55         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
56         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
57     )
58     .await
59 }
60 
61 #[tokio::test]
async_round_trip_stackless_plus_synchronous() -> Result<()>62 async fn async_round_trip_stackless_plus_synchronous() -> Result<()> {
63     test_round_trip_composed(
64         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
65         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
66     )
67     .await
68 }
69 
70 #[tokio::test]
async_round_trip_synchronous_plus_synchronous() -> Result<()>71 async fn async_round_trip_synchronous_plus_synchronous() -> Result<()> {
72     test_round_trip_composed(
73         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
74         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
75     )
76     .await
77 }
78 
79 #[tokio::test]
async_round_trip_wait_plus_wait() -> Result<()>80 async fn async_round_trip_wait_plus_wait() -> Result<()> {
81     test_round_trip_composed(
82         test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
83         test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
84     )
85     .await
86 }
87 
88 #[tokio::test]
async_round_trip_synchronous_plus_wait() -> Result<()>89 async fn async_round_trip_synchronous_plus_wait() -> Result<()> {
90     test_round_trip_composed(
91         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
92         test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
93     )
94     .await
95 }
96 
97 #[tokio::test]
async_round_trip_wait_plus_synchronous() -> Result<()>98 async fn async_round_trip_wait_plus_synchronous() -> Result<()> {
99     test_round_trip_composed(
100         test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
101         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
102     )
103     .await
104 }
105 
106 #[tokio::test]
async_round_trip_stackless_plus_wait() -> Result<()>107 async fn async_round_trip_stackless_plus_wait() -> Result<()> {
108     test_round_trip_composed(
109         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
110         test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
111     )
112     .await
113 }
114 
115 #[tokio::test]
async_round_trip_wait_plus_stackless() -> Result<()>116 async fn async_round_trip_wait_plus_stackless() -> Result<()> {
117     test_round_trip_composed(
118         test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
119         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
120     )
121     .await
122 }
123 
124 #[tokio::test]
async_round_trip_stackful_plus_stackful() -> Result<()>125 async fn async_round_trip_stackful_plus_stackful() -> Result<()> {
126     test_round_trip_composed(
127         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
128         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
129     )
130     .await
131 }
132 
133 #[tokio::test]
async_round_trip_stackful_plus_stackless() -> Result<()>134 async fn async_round_trip_stackful_plus_stackless() -> Result<()> {
135     test_round_trip_composed(
136         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
137         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
138     )
139     .await
140 }
141 
142 #[tokio::test]
async_round_trip_stackless_plus_stackful() -> Result<()>143 async fn async_round_trip_stackless_plus_stackful() -> Result<()> {
144     test_round_trip_composed(
145         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
146         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
147     )
148     .await
149 }
150 
151 #[tokio::test]
async_round_trip_synchronous_plus_stackful() -> Result<()>152 async fn async_round_trip_synchronous_plus_stackful() -> Result<()> {
153     test_round_trip_composed(
154         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
155         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
156     )
157     .await
158 }
159 
160 #[tokio::test]
async_round_trip_stackful_plus_synchronous() -> Result<()>161 async fn async_round_trip_stackful_plus_synchronous() -> Result<()> {
162     test_round_trip_composed(
163         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
164         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
165     )
166     .await
167 }
168 
169 #[tokio::test]
async_round_trip_stackless() -> Result<()>170 pub async fn async_round_trip_stackless() -> Result<()> {
171     test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT).await
172 }
173 
174 #[tokio::test]
async_round_trip_stackless_joined() -> Result<()>175 pub async fn async_round_trip_stackless_joined() -> Result<()> {
176     tokio::join!(
177         async {
178             test_round_trip_uncomposed(
179                 test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
180             )
181             .await
182             .unwrap()
183         },
184         async {
185             test_round_trip_uncomposed(
186                 test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
187             )
188             .await
189             .unwrap()
190         },
191     );
192 
193     Ok(())
194 }
195 
196 #[tokio::test]
async_round_trip_stackless_sync_import() -> Result<()>197 pub async fn async_round_trip_stackless_sync_import() -> Result<()> {
198     test_round_trip_uncomposed(
199         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_SYNC_IMPORT_COMPONENT,
200     )
201     .await
202 }
203 
204 #[tokio::test]
async_round_trip_stackless_recurse() -> Result<()>205 pub async fn async_round_trip_stackless_recurse() -> Result<()> {
206     test_round_trip_recurse(
207         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
208         false,
209     )
210     .await
211 }
212 
213 #[tokio::test]
async_round_trip_stackless_recurse_trap() -> Result<()>214 pub async fn async_round_trip_stackless_recurse_trap() -> Result<()> {
215     let error = test_round_trip_recurse(
216         test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
217         true,
218     )
219     .await
220     .unwrap_err();
221 
222     assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);
223 
224     Ok(())
225 }
226 
227 #[tokio::test]
async_round_trip_synchronous_recurse() -> Result<()>228 pub async fn async_round_trip_synchronous_recurse() -> Result<()> {
229     test_round_trip_recurse(
230         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
231         false,
232     )
233     .await
234 }
235 
236 #[tokio::test]
async_round_trip_synchronous_recurse_trap() -> Result<()>237 pub async fn async_round_trip_synchronous_recurse_trap() -> Result<()> {
238     let error = test_round_trip_recurse(
239         test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
240         true,
241     )
242     .await
243     .unwrap_err();
244 
245     assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);
246 
247     Ok(())
248 }
249 
test_round_trip_recurse(component: &str, same_instance: bool) -> Result<()>250 async fn test_round_trip_recurse(component: &str, same_instance: bool) -> Result<()> {
251     pub struct MyCtx {
252         wasi: WasiCtx,
253         table: ResourceTable,
254         instance: Option<Arc<Instance>>,
255     }
256 
257     impl WasiView for MyCtx {
258         fn ctx(&mut self) -> WasiCtxView<'_> {
259             WasiCtxView {
260                 ctx: &mut self.wasi,
261                 table: &mut self.table,
262             }
263         }
264     }
265 
266     impl HasData for MyCtx {
267         type Data<'a> = &'a mut Self;
268     }
269 
270     impl component_async_tests::round_trip::bindings::local::local::baz::HostWithStore for MyCtx {
271         async fn foo<T: Send>(accessor: &Accessor<T, Self>, s: String) -> wasmtime::Result<String> {
272             if let Some(instance) = accessor.with(|mut access| access.get().instance.take()) {
273                 run(accessor, &instance).await?;
274                 accessor.with(|mut access| access.get().instance = Some(instance));
275             }
276             Ok(format!("{s} - entered host - exited host"))
277         }
278     }
279 
280     impl component_async_tests::round_trip::bindings::local::local::baz::Host for MyCtx {}
281 
282     async fn run<T: Send>(accessor: &Accessor<T, MyCtx>, instance: &Instance) -> Result<()> {
283         let round_trip = accessor.with(|mut access| {
284             component_async_tests::round_trip::bindings::RoundTrip::new(&mut access, &instance)
285         })?;
286 
287         let input = "hello, world!";
288         let expected = "hello, world! - entered guest - entered host - exited host - exited guest";
289 
290         let actual = round_trip
291             .local_local_baz()
292             .call_foo(accessor, input.into())
293             .await?;
294 
295         assert_eq!(expected, actual);
296 
297         Ok(())
298     }
299 
300     let engine = Engine::new(&config())?;
301 
302     let mut linker = Linker::new(&engine);
303 
304     wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
305     component_async_tests::round_trip::bindings::local::local::baz::add_to_linker::<_, MyCtx>(
306         &mut linker,
307         |ctx| ctx,
308     )?;
309 
310     let component = make_component(&engine, &[component]).await?;
311 
312     let mut store = Store::new(
313         &engine,
314         MyCtx {
315             wasi: WasiCtxBuilder::new().inherit_stdio().build(),
316             table: ResourceTable::default(),
317             instance: None,
318         },
319     );
320 
321     let instance = Arc::new(linker.instantiate_async(&mut store, &component).await?);
322     store.data_mut().instance = Some(instance.clone());
323 
324     let instance = if same_instance {
325         instance
326     } else {
327         Arc::new(linker.instantiate_async(&mut store, &component).await?)
328     };
329 
330     store
331         .run_concurrent(async move |accessor| {
332             run(&accessor.with_getter(|ctx| ctx), &instance).await
333         })
334         .await??;
335 
336     store.assert_concurrent_state_empty();
337 
338     Ok(())
339 }
340 
test_round_trip( components: &[&str], inputs_and_outputs: &[(&str, &str)], ) -> Result<()>341 pub async fn test_round_trip(
342     components: &[&str],
343     inputs_and_outputs: &[(&str, &str)],
344 ) -> Result<()> {
345     let engine = Engine::new(&config())?;
346 
347     let make_store = || {
348         Store::new(
349             &engine,
350             Ctx {
351                 wasi: WasiCtxBuilder::new().inherit_stdio().build(),
352                 table: ResourceTable::default(),
353                 continue_: false,
354             },
355         )
356     };
357 
358     let component = make_component(&engine, components).await?;
359 
360     // On miri, we only use one call style per test since they take so long to
361     // run.  On non-miri, we use every call style for each test.
362     static CALL_STYLE_COUNTER: AtomicU32 = AtomicU32::new(0);
363     let call_style = CALL_STYLE_COUNTER.fetch_add(1, Relaxed) % 5;
364 
365     // First, test the `wasmtime-wit-bindgen` static API:
366     {
367         let mut linker = Linker::new(&engine);
368 
369         wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
370         component_async_tests::round_trip::bindings::local::local::baz::add_to_linker::<_, Ctx>(
371             &mut linker,
372             |ctx| ctx,
373         )?;
374 
375         let mut store = make_store();
376 
377         let instance = linker.instantiate_async(&mut store, &component).await?;
378         let round_trip =
379             component_async_tests::round_trip::bindings::RoundTrip::new(&mut store, &instance)?;
380 
381         if call_style == 0 || !cfg!(miri) {
382             // Run the test using `StoreContextMut::run_concurrent`:
383             store
384                 .run_concurrent({
385                     let inputs_and_outputs = inputs_and_outputs
386                         .iter()
387                         .map(|(a, b)| (String::from(*a), String::from(*b)))
388                         .collect::<Vec<_>>();
389 
390                     async move |accessor| {
391                         let mut futures = FuturesUnordered::new();
392                         for (input, output) in &inputs_and_outputs {
393                             let output = output.clone();
394                             futures.push(
395                                 round_trip
396                                     .local_local_baz()
397                                     .call_foo(accessor, input.clone())
398                                     .map(move |v| v.map(move |v| (v, output)))
399                                     .boxed(),
400                             );
401                         }
402 
403                         while let Some((actual, expected)) = futures.try_next().await? {
404                             assert_eq!(expected, actual);
405                         }
406 
407                         Ok::<_, wasmtime::Error>(())
408                     }
409                 })
410                 .await??;
411 
412             store.assert_concurrent_state_empty();
413         }
414 
415         if call_style == 1 || !cfg!(miri) {
416             // And again using `Instance::spawn`:
417             struct Task {
418                 instance: Instance,
419                 inputs_and_outputs: Vec<(String, String)>,
420                 tx: oneshot::Sender<()>,
421             }
422 
423             impl AccessorTask<Ctx, HasSelf<Ctx>> for Task {
424                 async fn run(self, accessor: &Accessor<Ctx>) -> Result<()> {
425                     let round_trip = accessor.with(|mut store| {
426                         component_async_tests::round_trip::bindings::RoundTrip::new(
427                             &mut store,
428                             &self.instance,
429                         )
430                     })?;
431 
432                     let mut futures = FuturesUnordered::new();
433                     for (input, output) in &self.inputs_and_outputs {
434                         let output = output.clone();
435                         futures.push(
436                             round_trip
437                                 .local_local_baz()
438                                 .call_foo(accessor, input.clone())
439                                 .map(move |v| v.map(move |v| (v, output)))
440                                 .boxed(),
441                         );
442                     }
443 
444                     while let Some((actual, expected)) = futures.try_next().await? {
445                         assert_eq!(expected, actual);
446                     }
447 
448                     _ = self.tx.send(());
449 
450                     Ok(())
451                 }
452             }
453 
454             let (tx, rx) = oneshot::channel();
455             store.spawn(Task {
456                 instance,
457                 inputs_and_outputs: inputs_and_outputs
458                     .iter()
459                     .map(|(a, b)| (String::from(*a), String::from(*b)))
460                     .collect::<Vec<_>>(),
461                 tx,
462             });
463 
464             store.run_concurrent(async |_| rx.await).await??;
465 
466             store.assert_concurrent_state_empty();
467         }
468 
469         if call_style == 2 || !cfg!(miri) {
470             // And again using `TypedFunc::call_async`-based bindings:
471             let round_trip =
472                 component_async_tests::round_trip::non_concurrent_export_bindings::RoundTrip::new(
473                     &mut store, &instance,
474                 )?;
475 
476             for (input, expected) in inputs_and_outputs {
477                 assert_eq!(
478                     *expected,
479                     &round_trip
480                         .local_local_baz()
481                         .call_foo(&mut store, input)
482                         .await?
483                 );
484             }
485 
486             store.assert_concurrent_state_empty();
487         }
488     }
489 
490     // Now do it again using the dynamic API (except for WASI, where we stick with the static API):
491     {
492         let mut linker = Linker::new(&engine);
493 
494         wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
495         linker
496             .root()
497             .instance("local:local/baz")?
498             .func_new_concurrent("foo", |_, _, params, results| {
499                 Box::pin(async move {
500                     yield_times(5).await;
501                     let Some(Val::String(s)) = params.into_iter().next() else {
502                         unreachable!()
503                     };
504                     results[0] = Val::String(format!("{s} - entered host - exited host"));
505                     Ok(())
506                 })
507             })?;
508 
509         let mut store = make_store();
510 
511         let instance = linker.instantiate_async(&mut store, &component).await?;
512         let baz_instance = instance
513             .get_export_index(&mut store, None, "local:local/baz")
514             .ok_or_else(|| format_err!("can't find `local:local/baz` in instance"))?;
515         let foo_function = instance
516             .get_export_index(&mut store, Some(&baz_instance), "foo")
517             .ok_or_else(|| format_err!("can't find `foo` in instance"))?;
518         let foo_function = instance
519             .get_func(&mut store, foo_function)
520             .ok_or_else(|| format_err!("can't find `foo` in instance"))?;
521 
522         if call_style == 3 || !cfg!(miri) {
523             store
524                 .run_concurrent(async |store| {
525                     // Start three concurrent calls and then join them all:
526                     let mut futures = FuturesUnordered::new();
527                     for (input, output) in inputs_and_outputs {
528                         let output = (*output).to_owned();
529                         futures.push(async move {
530                             let mut results = vec![Val::Bool(false)];
531                             foo_function
532                                 .call_concurrent(
533                                     store,
534                                     &[Val::String((*input).to_owned())],
535                                     &mut results,
536                                 )
537                                 .await?;
538                             wasmtime::error::Ok((results, output))
539                         });
540                     }
541 
542                     while let Some((actual, expected)) = futures.try_next().await? {
543                         let Some(Val::String(actual)) = actual.into_iter().next() else {
544                             unreachable!()
545                         };
546                         assert_eq!(expected, actual);
547                     }
548                     wasmtime::error::Ok(())
549                 })
550                 .await??;
551 
552             store.assert_concurrent_state_empty();
553         }
554 
555         if call_style == 4 || !cfg!(miri) {
556             // Now do it again using `Func::call_async`:
557             for (input, expected) in inputs_and_outputs {
558                 let mut results = [Val::Bool(false)];
559                 foo_function
560                     .call_async(
561                         &mut store,
562                         &[Val::String((*input).to_owned())],
563                         &mut results,
564                     )
565                     .await?;
566                 let Val::String(actual) = &results[0] else {
567                     unreachable!()
568                 };
569                 assert_eq!(*expected, actual);
570             }
571 
572             store.assert_concurrent_state_empty();
573         }
574     }
575 
576     Ok(())
577 }
578 
test_round_trip_uncomposed(component: &str) -> Result<()>579 pub async fn test_round_trip_uncomposed(component: &str) -> Result<()> {
580     test_round_trip(
581         &[component],
582         &[
583             (
584                 "hello, world!",
585                 "hello, world! - entered guest - entered host - exited host - exited guest",
586             ),
587             (
588                 "¡hola, mundo!",
589                 "¡hola, mundo! - entered guest - entered host - exited host - exited guest",
590             ),
591             (
592                 "hi y'all!",
593                 "hi y'all! - entered guest - entered host - exited host - exited guest",
594             ),
595         ],
596     )
597     .await
598 }
599 
test_round_trip_composed(a: &str, b: &str) -> Result<()>600 pub async fn test_round_trip_composed(a: &str, b: &str) -> Result<()> {
601     test_round_trip(
602         &[a, b],
603         &[
604             (
605                 "hello, world!",
606                 "hello, world! - entered guest - entered guest - entered host \
607                  - exited host - exited guest - exited guest",
608             ),
609             (
610                 "¡hola, mundo!",
611                 "¡hola, mundo! - entered guest - entered guest - entered host \
612                  - exited host - exited guest - exited guest",
613             ),
614             (
615                 "hi y'all!",
616                 "hi y'all! - entered guest - entered guest - entered host \
617                  - exited host - exited guest - exited guest",
618             ),
619         ],
620     )
621     .await
622 }
623 
test_round_trip_composed_more(a: &str, b: &str, c: &str) -> Result<()>624 pub async fn test_round_trip_composed_more(a: &str, b: &str, c: &str) -> Result<()> {
625     test_round_trip(
626         &[a, b, c],
627         &[
628             (
629                 "hello, world!",
630                 "hello, world! - entered guest - entered guest - entered guest - entered host \
631                  - exited host - exited guest - exited guest - exited guest",
632             ),
633             (
634                 "¡hola, mundo!",
635                 "¡hola, mundo! - entered guest - entered guest - entered guest - entered host \
636                  - exited host - exited guest - exited guest - exited guest",
637             ),
638             (
639                 "hi y'all!",
640                 "hi y'all! - entered guest - entered guest - entered guest - entered host \
641                  - exited host - exited guest - exited guest - exited guest",
642             ),
643         ],
644     )
645     .await
646 }
647