Path: blob/main/crates/misc/component-async-tests/tests/scenario/util.rs
3084 views
use std::collections::HashMap;1use std::env;2use std::ops::Deref;3use std::path::Path;4use std::sync::{Arc, LazyLock, Once};5use std::time::Duration;67use component_async_tests::{Ctx, sleep};8use futures::stream::{FuturesUnordered, TryStreamExt};9use tokio::fs;10use tokio::sync::Mutex;11use wasm_compose::composer::ComponentComposer;12use wasmtime::component::{Component, Linker, ResourceTable};13use wasmtime::{Config, Engine, Result, Store, ToWasmtimeResult as _, bail, format_err};14use wasmtime_wasi::WasiCtxBuilder;1516pub fn init_logger() {17static ONCE: Once = Once::new();18ONCE.call_once(env_logger::init);19}2021pub fn config() -> Config {22init_logger();2324let mut config = Config::new();25if env::var_os("MIRI_TEST_CWASM_DIR").is_some() {26config.target("pulley64").unwrap();27config.memory_reservation(1 << 20);28config.memory_guard_size(0);29config.signals_based_traps(false);30} else {31config.cranelift_debug_verifier(true);32config.cranelift_wasmtime_debug_checks(true);33}34config.wasm_component_model(true);35config.wasm_component_model_async(true);36config.wasm_component_model_async_builtins(true);37config.wasm_component_model_async_stackful(true);38config.wasm_component_model_threading(true);39config.wasm_component_model_error_context(true);40config41}4243/// Compose two components44///45/// a is the "root" component, and b is composed into it46async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {47let dir = tempfile::tempdir()?;4849let a_file = dir.path().join("a.wasm");50fs::write(&a_file, a).await?;5152let b_file = dir.path().join("b.wasm");53fs::write(&b_file, b).await?;5455ComponentComposer::new(56&a_file,57&wasm_compose::config::Config {58dir: dir.path().to_owned(),59definitions: vec![b_file.to_owned()],60..Default::default()61},62)63.compose()64.to_wasmtime_result()65}6667pub async fn make_component(engine: &Engine, components: &[&str]) -> Result<Component> {68fn cwasm_name(components: &[&str]) -> Result<String> {69if components.is_empty() {70Err(format_err!("expected at least one path"))71} else {72let names = components73.iter()74.map(|&path| {75let path = Path::new(path);76if let Some(name) = path.file_name() {77Ok(name)78} else {79Err(format_err!(80"expected path with at least two components; got: {}",81path.display()82))83}84})85.collect::<Result<Vec<_>>>()?;8687Ok(format!(88"{}.cwasm",89names90.iter()91.map(|name| { name.to_str().unwrap() })92.collect::<Vec<_>>()93.join("+")94))95}96}9798async fn compile(engine: &Engine, components: &[&str]) -> Result<Vec<u8>> {99let mut composed = None::<Vec<u8>>;100for component in components {101let component = fs::read(component).await?;102if let Some(other) = composed.take() {103composed = Some(compose(&other, &component).await?);104} else {105composed = Some(component);106}107}108engine.precompile_component(109&composed.ok_or_else(|| format_err!("expected at least one component"))?,110)111}112113async fn load(engine: &Engine, components: &[&str]) -> Result<Vec<u8>> {114let cwasm_path = if let Some(cwasm_dir) = &env::var_os("MIRI_TEST_CWASM_DIR") {115Some(Path::new(cwasm_dir).join(cwasm_name(components)?))116} else {117None118};119120if let Some(cwasm_path) = &cwasm_path {121if let Ok(compiled) = fs::read(cwasm_path).await {122return Ok(compiled);123}124}125126if cfg!(miri) {127bail!(128"Running these tests with miri requires precompiled .cwasm files.\n\129Please set the `MIRI_TEST_CWASM_DIR` environment variable to the\n\130absolute path of a valid directory, then run the test(s)\n\131_without_ miri, and finally run them again _with_ miri."132)133}134135let compiled = compile(engine, components).await?;136if let Some(cwasm_path) = &cwasm_path {137fs::write(cwasm_path, &compiled).await?;138}139Ok(compiled)140}141142static CACHE: LazyLock<Mutex<HashMap<Vec<String>, Arc<Mutex<Option<Arc<Vec<u8>>>>>>>> =143LazyLock::new(|| Mutex::new(HashMap::new()));144145let compiled = {146let entry = CACHE147.lock()148.await149.entry(components.iter().map(|&s| s.to_owned()).collect())150.or_insert_with(|| Arc::new(Mutex::new(None)))151.clone();152153let mut entry = entry.lock().await;154if let Some(component) = entry.deref() {155component.clone()156} else {157let component = Arc::new(load(engine, components).await?);158*entry = Some(component.clone());159component160}161};162163Ok(unsafe { Component::deserialize(&engine, &*compiled)? })164}165166pub async fn test_run(components: &[&str]) -> Result<()> {167test_run_with_count(components, 3).await168}169170pub async fn test_run_with_count(components: &[&str], count: usize) -> Result<()> {171let mut config = config();172// As of this writing, miri/pulley/epochs is a problematic combination, so173// we don't test it.174if env::var_os("MIRI_TEST_CWASM_DIR").is_none() {175config.epoch_interruption(true);176}177178let engine = Engine::new(&config)?;179180let component = make_component(&engine, components).await?;181182let mut linker = Linker::new(&engine);183184wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;185component_async_tests::yield_host::bindings::local::local::continue_::add_to_linker::<_, Ctx>(186&mut linker,187|ctx| ctx,188)?;189component_async_tests::yield_host::bindings::local::local::ready::add_to_linker::<_, Ctx>(190&mut linker,191|ctx| ctx,192)?;193component_async_tests::resource_stream::bindings::local::local::resource_stream::add_to_linker::<194_,195Ctx,196>(&mut linker, |ctx| ctx)?;197sleep::local::local::sleep::add_to_linker::<_, Ctx>(&mut linker, |ctx| ctx)?;198199let mut store = Store::new(200&engine,201Ctx {202wasi: WasiCtxBuilder::new().inherit_stdio().build(),203table: ResourceTable::default(),204continue_: false,205},206);207208if env::var_os("MIRI_TEST_CWASM_DIR").is_none() {209store.set_epoch_deadline(1);210211std::thread::spawn(move || {212std::thread::sleep(Duration::from_secs(10));213engine.increment_epoch();214});215}216217let yield_host = component_async_tests::yield_host::bindings::YieldHost::instantiate_async(218&mut store, &component, &linker,219)220.await?;221222// Start `count` concurrent calls and then join them all:223store224.run_concurrent(async |store| {225let mut futures = FuturesUnordered::new();226for _ in 0..count {227futures.push(yield_host.local_local_run().call_run(store));228}229230while let Some(()) = futures.try_next().await? {231// continue232}233wasmtime::error::Ok(())234})235.await??;236237Ok(())238}239240241