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