Path: blob/main/crates/misc/component-async-tests/tests/scenario/round_trip.rs
3114 views
use super::util::{config, make_component};1use component_async_tests::Ctx;2use component_async_tests::util::yield_times;3use futures::{4FutureExt,5channel::oneshot,6stream::{FuturesUnordered, TryStreamExt},7};8use std::sync::Arc;9use std::sync::atomic::{AtomicU32, Ordering::Relaxed};10use wasmtime::component::{11Accessor, AccessorTask, HasData, HasSelf, Instance, Linker, ResourceTable, Val,12};13use wasmtime::{Engine, Result, Store, Trap, format_err};14use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};1516#[tokio::test]17pub async fn async_round_trip_stackful() -> Result<()> {18test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT).await19}2021#[tokio::test]22pub async fn async_round_trip_synchronous() -> Result<()> {23test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT)24.await25}2627#[tokio::test]28pub async fn async_round_trip_wait() -> Result<()> {29test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT).await30}3132#[tokio::test]33pub async fn async_round_trip_stackless_plus_stackless() -> Result<()> {34test_round_trip_composed(35test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,36test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,37)38.await39}4041#[tokio::test]42pub async fn async_round_trip_stackless_plus_stackless_plus_stackless() -> Result<()> {43test_round_trip_composed_more(44test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,45test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,46test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,47)48.await49}5051#[tokio::test]52async fn async_round_trip_synchronous_plus_stackless() -> Result<()> {53test_round_trip_composed(54test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,55test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,56)57.await58}5960#[tokio::test]61async fn async_round_trip_stackless_plus_synchronous() -> Result<()> {62test_round_trip_composed(63test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,64test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,65)66.await67}6869#[tokio::test]70async fn async_round_trip_synchronous_plus_synchronous() -> Result<()> {71test_round_trip_composed(72test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,73test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,74)75.await76}7778#[tokio::test]79async fn async_round_trip_wait_plus_wait() -> Result<()> {80test_round_trip_composed(81test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,82test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,83)84.await85}8687#[tokio::test]88async fn async_round_trip_synchronous_plus_wait() -> Result<()> {89test_round_trip_composed(90test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,91test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,92)93.await94}9596#[tokio::test]97async fn async_round_trip_wait_plus_synchronous() -> Result<()> {98test_round_trip_composed(99test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,100test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,101)102.await103}104105#[tokio::test]106async fn async_round_trip_stackless_plus_wait() -> Result<()> {107test_round_trip_composed(108test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,109test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,110)111.await112}113114#[tokio::test]115async fn async_round_trip_wait_plus_stackless() -> Result<()> {116test_round_trip_composed(117test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,118test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,119)120.await121}122123#[tokio::test]124async fn async_round_trip_stackful_plus_stackful() -> Result<()> {125test_round_trip_composed(126test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,127test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,128)129.await130}131132#[tokio::test]133async fn async_round_trip_stackful_plus_stackless() -> Result<()> {134test_round_trip_composed(135test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,136test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,137)138.await139}140141#[tokio::test]142async fn async_round_trip_stackless_plus_stackful() -> Result<()> {143test_round_trip_composed(144test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,145test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,146)147.await148}149150#[tokio::test]151async fn async_round_trip_synchronous_plus_stackful() -> Result<()> {152test_round_trip_composed(153test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,154test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,155)156.await157}158159#[tokio::test]160async fn async_round_trip_stackful_plus_synchronous() -> Result<()> {161test_round_trip_composed(162test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,163test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,164)165.await166}167168#[tokio::test]169pub async fn async_round_trip_stackless() -> Result<()> {170test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT).await171}172173#[tokio::test]174pub async fn async_round_trip_stackless_joined() -> Result<()> {175tokio::join!(176async {177test_round_trip_uncomposed(178test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,179)180.await181.unwrap()182},183async {184test_round_trip_uncomposed(185test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,186)187.await188.unwrap()189},190);191192Ok(())193}194195#[tokio::test]196pub async fn async_round_trip_stackless_sync_import() -> Result<()> {197test_round_trip_uncomposed(198test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_SYNC_IMPORT_COMPONENT,199)200.await201}202203#[tokio::test]204pub async fn async_round_trip_stackless_recurse() -> Result<()> {205test_round_trip_recurse(206test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,207false,208)209.await210}211212#[tokio::test]213pub async fn async_round_trip_stackless_recurse_trap() -> Result<()> {214let error = test_round_trip_recurse(215test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,216true,217)218.await219.unwrap_err();220221assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);222223Ok(())224}225226#[tokio::test]227pub async fn async_round_trip_synchronous_recurse() -> Result<()> {228test_round_trip_recurse(229test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,230false,231)232.await233}234235#[tokio::test]236pub async fn async_round_trip_synchronous_recurse_trap() -> Result<()> {237let error = test_round_trip_recurse(238test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,239true,240)241.await242.unwrap_err();243244assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);245246Ok(())247}248249async fn test_round_trip_recurse(component: &str, same_instance: bool) -> Result<()> {250pub struct MyCtx {251wasi: WasiCtx,252table: ResourceTable,253instance: Option<Arc<Instance>>,254}255256impl WasiView for MyCtx {257fn ctx(&mut self) -> WasiCtxView<'_> {258WasiCtxView {259ctx: &mut self.wasi,260table: &mut self.table,261}262}263}264265impl HasData for MyCtx {266type Data<'a> = &'a mut Self;267}268269impl component_async_tests::round_trip::bindings::local::local::baz::HostWithStore for MyCtx {270async fn foo<T: Send>(accessor: &Accessor<T, Self>, s: String) -> wasmtime::Result<String> {271if let Some(instance) = accessor.with(|mut access| access.get().instance.take()) {272run(accessor, &instance).await?;273accessor.with(|mut access| access.get().instance = Some(instance));274}275Ok(format!("{s} - entered host - exited host"))276}277}278279impl component_async_tests::round_trip::bindings::local::local::baz::Host for MyCtx {}280281async fn run<T: Send>(accessor: &Accessor<T, MyCtx>, instance: &Instance) -> Result<()> {282let round_trip = accessor.with(|mut access| {283component_async_tests::round_trip::bindings::RoundTrip::new(&mut access, &instance)284})?;285286let input = "hello, world!";287let expected = "hello, world! - entered guest - entered host - exited host - exited guest";288289let actual = round_trip290.local_local_baz()291.call_foo(accessor, input.into())292.await?;293294assert_eq!(expected, actual);295296Ok(())297}298299let engine = Engine::new(&config())?;300301let mut linker = Linker::new(&engine);302303wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;304component_async_tests::round_trip::bindings::local::local::baz::add_to_linker::<_, MyCtx>(305&mut linker,306|ctx| ctx,307)?;308309let component = make_component(&engine, &[component]).await?;310311let mut store = Store::new(312&engine,313MyCtx {314wasi: WasiCtxBuilder::new().inherit_stdio().build(),315table: ResourceTable::default(),316instance: None,317},318);319320let instance = Arc::new(linker.instantiate_async(&mut store, &component).await?);321store.data_mut().instance = Some(instance.clone());322323let instance = if same_instance {324instance325} else {326Arc::new(linker.instantiate_async(&mut store, &component).await?)327};328329store330.run_concurrent(async move |accessor| {331run(&accessor.with_getter(|ctx| ctx), &instance).await332})333.await??;334335store.assert_concurrent_state_empty();336337Ok(())338}339340pub async fn test_round_trip(341components: &[&str],342inputs_and_outputs: &[(&str, &str)],343) -> Result<()> {344let engine = Engine::new(&config())?;345346let make_store = || {347Store::new(348&engine,349Ctx {350wasi: WasiCtxBuilder::new().inherit_stdio().build(),351table: ResourceTable::default(),352continue_: false,353},354)355};356357let component = make_component(&engine, components).await?;358359// On miri, we only use one call style per test since they take so long to360// run. On non-miri, we use every call style for each test.361static CALL_STYLE_COUNTER: AtomicU32 = AtomicU32::new(0);362let call_style = CALL_STYLE_COUNTER.fetch_add(1, Relaxed) % 5;363364// First, test the `wasmtime-wit-bindgen` static API:365{366let mut linker = Linker::new(&engine);367368wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;369component_async_tests::round_trip::bindings::local::local::baz::add_to_linker::<_, Ctx>(370&mut linker,371|ctx| ctx,372)?;373374let mut store = make_store();375376let instance = linker.instantiate_async(&mut store, &component).await?;377let round_trip =378component_async_tests::round_trip::bindings::RoundTrip::new(&mut store, &instance)?;379380if call_style == 0 || !cfg!(miri) {381// Run the test using `StoreContextMut::run_concurrent`:382store383.run_concurrent({384let inputs_and_outputs = inputs_and_outputs385.iter()386.map(|(a, b)| (String::from(*a), String::from(*b)))387.collect::<Vec<_>>();388389async move |accessor| {390let mut futures = FuturesUnordered::new();391for (input, output) in &inputs_and_outputs {392let output = output.clone();393futures.push(394round_trip395.local_local_baz()396.call_foo(accessor, input.clone())397.map(move |v| v.map(move |v| (v, output)))398.boxed(),399);400}401402while let Some((actual, expected)) = futures.try_next().await? {403assert_eq!(expected, actual);404}405406Ok::<_, wasmtime::Error>(())407}408})409.await??;410411store.assert_concurrent_state_empty();412}413414if call_style == 1 || !cfg!(miri) {415// And again using `Instance::spawn`:416struct Task {417instance: Instance,418inputs_and_outputs: Vec<(String, String)>,419tx: oneshot::Sender<()>,420}421422impl AccessorTask<Ctx, HasSelf<Ctx>> for Task {423async fn run(self, accessor: &Accessor<Ctx>) -> Result<()> {424let round_trip = accessor.with(|mut store| {425component_async_tests::round_trip::bindings::RoundTrip::new(426&mut store,427&self.instance,428)429})?;430431let mut futures = FuturesUnordered::new();432for (input, output) in &self.inputs_and_outputs {433let output = output.clone();434futures.push(435round_trip436.local_local_baz()437.call_foo(accessor, input.clone())438.map(move |v| v.map(move |v| (v, output)))439.boxed(),440);441}442443while let Some((actual, expected)) = futures.try_next().await? {444assert_eq!(expected, actual);445}446447_ = self.tx.send(());448449Ok(())450}451}452453let (tx, rx) = oneshot::channel();454store.spawn(Task {455instance,456inputs_and_outputs: inputs_and_outputs457.iter()458.map(|(a, b)| (String::from(*a), String::from(*b)))459.collect::<Vec<_>>(),460tx,461});462463store.run_concurrent(async |_| rx.await).await??;464465store.assert_concurrent_state_empty();466}467468if call_style == 2 || !cfg!(miri) {469// And again using `TypedFunc::call_async`-based bindings:470let round_trip =471component_async_tests::round_trip::non_concurrent_export_bindings::RoundTrip::new(472&mut store, &instance,473)?;474475for (input, expected) in inputs_and_outputs {476assert_eq!(477*expected,478&round_trip479.local_local_baz()480.call_foo(&mut store, input)481.await?482);483}484485store.assert_concurrent_state_empty();486}487}488489// Now do it again using the dynamic API (except for WASI, where we stick with the static API):490{491let mut linker = Linker::new(&engine);492493wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;494linker495.root()496.instance("local:local/baz")?497.func_new_concurrent("foo", |_, _, params, results| {498Box::pin(async move {499yield_times(5).await;500let Some(Val::String(s)) = params.into_iter().next() else {501unreachable!()502};503results[0] = Val::String(format!("{s} - entered host - exited host"));504Ok(())505})506})?;507508let mut store = make_store();509510let instance = linker.instantiate_async(&mut store, &component).await?;511let baz_instance = instance512.get_export_index(&mut store, None, "local:local/baz")513.ok_or_else(|| format_err!("can't find `local:local/baz` in instance"))?;514let foo_function = instance515.get_export_index(&mut store, Some(&baz_instance), "foo")516.ok_or_else(|| format_err!("can't find `foo` in instance"))?;517let foo_function = instance518.get_func(&mut store, foo_function)519.ok_or_else(|| format_err!("can't find `foo` in instance"))?;520521if call_style == 3 || !cfg!(miri) {522store523.run_concurrent(async |store| {524// Start three concurrent calls and then join them all:525let mut futures = FuturesUnordered::new();526for (input, output) in inputs_and_outputs {527let output = (*output).to_owned();528futures.push(async move {529let mut results = vec![Val::Bool(false)];530foo_function531.call_concurrent(532store,533&[Val::String((*input).to_owned())],534&mut results,535)536.await?;537wasmtime::error::Ok((results, output))538});539}540541while let Some((actual, expected)) = futures.try_next().await? {542let Some(Val::String(actual)) = actual.into_iter().next() else {543unreachable!()544};545assert_eq!(expected, actual);546}547wasmtime::error::Ok(())548})549.await??;550551store.assert_concurrent_state_empty();552}553554if call_style == 4 || !cfg!(miri) {555// Now do it again using `Func::call_async`:556for (input, expected) in inputs_and_outputs {557let mut results = [Val::Bool(false)];558foo_function559.call_async(560&mut store,561&[Val::String((*input).to_owned())],562&mut results,563)564.await?;565let Val::String(actual) = &results[0] else {566unreachable!()567};568assert_eq!(*expected, actual);569}570571store.assert_concurrent_state_empty();572}573}574575Ok(())576}577578pub async fn test_round_trip_uncomposed(component: &str) -> Result<()> {579test_round_trip(580&[component],581&[582(583"hello, world!",584"hello, world! - entered guest - entered host - exited host - exited guest",585),586(587"¡hola, mundo!",588"¡hola, mundo! - entered guest - entered host - exited host - exited guest",589),590(591"hi y'all!",592"hi y'all! - entered guest - entered host - exited host - exited guest",593),594],595)596.await597}598599pub async fn test_round_trip_composed(a: &str, b: &str) -> Result<()> {600test_round_trip(601&[a, b],602&[603(604"hello, world!",605"hello, world! - entered guest - entered guest - entered host \606- exited host - exited guest - exited guest",607),608(609"¡hola, mundo!",610"¡hola, mundo! - entered guest - entered guest - entered host \611- exited host - exited guest - exited guest",612),613(614"hi y'all!",615"hi y'all! - entered guest - entered guest - entered host \616- exited host - exited guest - exited guest",617),618],619)620.await621}622623pub async fn test_round_trip_composed_more(a: &str, b: &str, c: &str) -> Result<()> {624test_round_trip(625&[a, b, c],626&[627(628"hello, world!",629"hello, world! - entered guest - entered guest - entered guest - entered host \630- exited host - exited guest - exited guest - exited guest",631),632(633"¡hola, mundo!",634"¡hola, mundo! - entered guest - entered guest - entered guest - entered host \635- exited host - exited guest - exited guest - exited guest",636),637(638"hi y'all!",639"hi y'all! - entered guest - entered guest - entered guest - entered host \640- exited host - exited guest - exited guest - exited guest",641),642],643)644.await645}646647648