Path: blob/main/crates/misc/component-async-tests/tests/scenario/round_trip.rs
1693 views
use std::sync::{1Arc, Mutex,2atomic::{AtomicU32, Ordering::Relaxed},3};4use std::time::Duration;56use super::util::{config, make_component};7use anyhow::{Result, anyhow};8use component_async_tests::Ctx;9use component_async_tests::util::sleep;10use futures::{11FutureExt,12channel::oneshot,13stream::{FuturesUnordered, TryStreamExt},14};15use wasmtime::component::{Accessor, AccessorTask, HasSelf, Instance, Linker, ResourceTable, Val};16use wasmtime::{Engine, Store};17use wasmtime_wasi::WasiCtxBuilder;1819#[tokio::test]20pub async fn async_round_trip_stackful() -> Result<()> {21test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT).await22}2324#[tokio::test]25pub async fn async_round_trip_synchronous() -> Result<()> {26test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT)27.await28}2930#[tokio::test]31pub async fn async_round_trip_wait() -> Result<()> {32test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT).await33}3435#[tokio::test]36pub async fn async_round_trip_stackless_plus_stackless() -> Result<()> {37test_round_trip_composed(38test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,39test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,40)41.await42}4344#[tokio::test]45async fn async_round_trip_synchronous_plus_stackless() -> Result<()> {46test_round_trip_composed(47test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,48test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,49)50.await51}5253#[tokio::test]54async fn async_round_trip_stackless_plus_synchronous() -> Result<()> {55test_round_trip_composed(56test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,57test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,58)59.await60}6162#[tokio::test]63async fn async_round_trip_synchronous_plus_synchronous() -> Result<()> {64test_round_trip_composed(65test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,66test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,67)68.await69}7071#[tokio::test]72async fn async_round_trip_wait_plus_wait() -> Result<()> {73test_round_trip_composed(74test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,75test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,76)77.await78}7980#[tokio::test]81async fn async_round_trip_synchronous_plus_wait() -> Result<()> {82test_round_trip_composed(83test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,84test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,85)86.await87}8889#[tokio::test]90async fn async_round_trip_wait_plus_synchronous() -> Result<()> {91test_round_trip_composed(92test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,93test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,94)95.await96}9798#[tokio::test]99async fn async_round_trip_stackless_plus_wait() -> Result<()> {100test_round_trip_composed(101test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,102test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,103)104.await105}106107#[tokio::test]108async fn async_round_trip_wait_plus_stackless() -> Result<()> {109test_round_trip_composed(110test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,111test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,112)113.await114}115116#[tokio::test]117async fn async_round_trip_stackful_plus_stackful() -> Result<()> {118test_round_trip_composed(119test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,120test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,121)122.await123}124125#[tokio::test]126async fn async_round_trip_stackful_plus_stackless() -> Result<()> {127test_round_trip_composed(128test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,129test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,130)131.await132}133134#[tokio::test]135async fn async_round_trip_stackless_plus_stackful() -> Result<()> {136test_round_trip_composed(137test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,138test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,139)140.await141}142143#[tokio::test]144async fn async_round_trip_synchronous_plus_stackful() -> Result<()> {145test_round_trip_composed(146test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,147test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,148)149.await150}151152#[tokio::test]153async fn async_round_trip_stackful_plus_synchronous() -> Result<()> {154test_round_trip_composed(155test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,156test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,157)158.await159}160161#[tokio::test]162pub async fn async_round_trip_stackless() -> Result<()> {163test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT).await164}165166#[tokio::test]167pub async fn async_round_trip_stackless_joined() -> Result<()> {168tokio::join!(169async {170test_round_trip_uncomposed(171test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,172)173.await174.unwrap()175},176async {177test_round_trip_uncomposed(178test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,179)180.await181.unwrap()182},183);184185Ok(())186}187188#[tokio::test]189pub async fn async_round_trip_stackless_sync_import() -> Result<()> {190test_round_trip_uncomposed(191test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_SYNC_IMPORT_COMPONENT,192)193.await194}195196pub async fn test_round_trip(197components: &[&str],198inputs_and_outputs: &[(&str, &str)],199) -> Result<()> {200let engine = Engine::new(&config())?;201202let make_store = || {203Store::new(204&engine,205Ctx {206wasi: WasiCtxBuilder::new().inherit_stdio().build(),207table: ResourceTable::default(),208continue_: false,209wakers: Arc::new(Mutex::new(None)),210},211)212};213214let component = make_component(&engine, components).await?;215216// On miri, we only use one call style per test since they take so long to217// run. On non-miri, we use every call style for each test.218static CALL_STYLE_COUNTER: AtomicU32 = AtomicU32::new(0);219let call_style = CALL_STYLE_COUNTER.fetch_add(1, Relaxed) % 5;220221// First, test the `wasmtime-wit-bindgen` static API:222{223let mut linker = Linker::new(&engine);224225wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;226component_async_tests::round_trip::bindings::local::local::baz::add_to_linker::<_, Ctx>(227&mut linker,228|ctx| ctx,229)?;230231let mut store = make_store();232233let instance = linker.instantiate_async(&mut store, &component).await?;234let round_trip =235component_async_tests::round_trip::bindings::RoundTrip::new(&mut store, &instance)?;236237if call_style == 0 || !cfg!(miri) {238// Run the test using `Instance::run_concurrent`:239instance240.run_concurrent(&mut store, {241let inputs_and_outputs = inputs_and_outputs242.iter()243.map(|(a, b)| (String::from(*a), String::from(*b)))244.collect::<Vec<_>>();245246async move |accessor| {247let mut futures = FuturesUnordered::new();248for (input, output) in &inputs_and_outputs {249let output = output.clone();250futures.push(251round_trip252.local_local_baz()253.call_foo(accessor, input.clone())254.map(move |v| v.map(move |v| (v, output)))255.boxed(),256);257}258259while let Some((actual, expected)) = futures.try_next().await? {260assert_eq!(expected, actual);261}262263Ok::<_, wasmtime::Error>(())264}265})266.await??;267268instance.assert_concurrent_state_empty(&mut store);269}270271if call_style == 1 || !cfg!(miri) {272// And again using `Instance::spawn`:273struct Task {274instance: Instance,275inputs_and_outputs: Vec<(String, String)>,276tx: oneshot::Sender<()>,277}278279impl AccessorTask<Ctx, HasSelf<Ctx>, Result<()>> for Task {280async fn run(self, accessor: &Accessor<Ctx>) -> Result<()> {281let round_trip = accessor.with(|mut store| {282component_async_tests::round_trip::bindings::RoundTrip::new(283&mut store,284&self.instance,285)286})?;287288let mut futures = FuturesUnordered::new();289for (input, output) in &self.inputs_and_outputs {290let output = output.clone();291futures.push(292round_trip293.local_local_baz()294.call_foo(accessor, input.clone())295.map(move |v| v.map(move |v| (v, output)))296.boxed(),297);298}299300while let Some((actual, expected)) = futures.try_next().await? {301assert_eq!(expected, actual);302}303304_ = self.tx.send(());305306Ok(())307}308}309310let (tx, rx) = oneshot::channel();311instance.spawn(312&mut store,313Task {314instance,315inputs_and_outputs: inputs_and_outputs316.iter()317.map(|(a, b)| (String::from(*a), String::from(*b)))318.collect::<Vec<_>>(),319tx,320},321);322323instance324.run_concurrent(&mut store, async |_| rx.await)325.await??;326327instance.assert_concurrent_state_empty(&mut store);328}329330if call_style == 2 || !cfg!(miri) {331// And again using `TypedFunc::call_async`-based bindings:332let round_trip =333component_async_tests::round_trip::non_concurrent_export_bindings::RoundTrip::new(334&mut store, &instance,335)?;336337for (input, expected) in inputs_and_outputs {338assert_eq!(339*expected,340&round_trip341.local_local_baz()342.call_foo(&mut store, input)343.await?344);345}346347instance.assert_concurrent_state_empty(&mut store);348}349}350351// Now do it again using the dynamic API (except for WASI, where we stick with the static API):352{353let mut linker = Linker::new(&engine);354355wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;356linker357.root()358.instance("local:local/baz")?359.func_new_concurrent("[async]foo", |_, params, results| {360Box::pin(async move {361sleep(Duration::from_millis(10)).await;362let Some(Val::String(s)) = params.into_iter().next() else {363unreachable!()364};365results[0] = Val::String(format!("{s} - entered host - exited host"));366Ok(())367})368})?;369370let mut store = make_store();371372let instance = linker.instantiate_async(&mut store, &component).await?;373let baz_instance = instance374.get_export_index(&mut store, None, "local:local/baz")375.ok_or_else(|| anyhow!("can't find `local:local/baz` in instance"))?;376let foo_function = instance377.get_export_index(&mut store, Some(&baz_instance), "[async]foo")378.ok_or_else(|| anyhow!("can't find `foo` in instance"))?;379let foo_function = instance380.get_func(&mut store, foo_function)381.ok_or_else(|| anyhow!("can't find `foo` in instance"))?;382383if call_style == 3 || !cfg!(miri) {384instance385.run_concurrent(&mut store, async |store| {386// Start three concurrent calls and then join them all:387let mut futures = FuturesUnordered::new();388for (input, output) in inputs_and_outputs {389let output = (*output).to_owned();390futures.push(async move {391let mut results = vec![Val::Bool(false)];392foo_function393.call_concurrent(394store,395&[Val::String((*input).to_owned())],396&mut results,397)398.await?;399anyhow::Ok((results, output))400});401}402403while let Some((actual, expected)) = futures.try_next().await? {404let Some(Val::String(actual)) = actual.into_iter().next() else {405unreachable!()406};407assert_eq!(expected, actual);408}409anyhow::Ok(())410})411.await??;412413instance.assert_concurrent_state_empty(&mut store);414}415416if call_style == 4 || !cfg!(miri) {417// Now do it again using `Func::call_async`:418for (input, expected) in inputs_and_outputs {419let mut results = [Val::Bool(false)];420foo_function421.call_async(422&mut store,423&[Val::String((*input).to_owned())],424&mut results,425)426.await?;427let Val::String(actual) = &results[0] else {428unreachable!()429};430assert_eq!(*expected, actual);431foo_function.post_return_async(&mut store).await?;432}433434instance.assert_concurrent_state_empty(&mut store);435}436}437438Ok(())439}440441pub async fn test_round_trip_uncomposed(component: &str) -> Result<()> {442test_round_trip(443&[component],444&[445(446"hello, world!",447"hello, world! - entered guest - entered host - exited host - exited guest",448),449(450"¡hola, mundo!",451"¡hola, mundo! - entered guest - entered host - exited host - exited guest",452),453(454"hi y'all!",455"hi y'all! - entered guest - entered host - exited host - exited guest",456),457],458)459.await460}461462pub async fn test_round_trip_composed(a: &str, b: &str) -> Result<()> {463test_round_trip(464&[a, b],465&[466(467"hello, world!",468"hello, world! - entered guest - entered guest - entered host \469- exited host - exited guest - exited guest",470),471(472"¡hola, mundo!",473"¡hola, mundo! - entered guest - entered guest - entered host \474- exited host - exited guest - exited guest",475),476(477"hi y'all!",478"hi y'all! - entered guest - entered guest - entered host \479- exited host - exited guest - exited guest",480),481],482)483.await484}485486487