Path: blob/main/crates/misc/component-async-tests/tests/scenario/util.rs
1693 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 anyhow::{Result, anyhow, bail};8use component_async_tests::{Ctx, sleep};9use futures::stream::{FuturesUnordered, TryStreamExt};10use tokio::fs;11use tokio::sync::Mutex;12use wasm_compose::composer::ComponentComposer;13use wasmtime::component::{Component, Linker, ResourceTable};14use wasmtime::{Config, Engine, Store};15use wasmtime_wasi::WasiCtxBuilder;1617pub fn init_logger() {18static ONCE: Once = Once::new();19ONCE.call_once(env_logger::init);20}2122pub fn config() -> Config {23init_logger();2425let mut config = Config::new();26if env::var_os("MIRI_TEST_CWASM_DIR").is_some() {27config.target("pulley64").unwrap();28config.memory_reservation(1 << 20);29config.memory_guard_size(0);30config.signals_based_traps(false);31} else {32config.cranelift_debug_verifier(true);33config.cranelift_wasmtime_debug_checks(true);34}35config.wasm_component_model(true);36config.wasm_component_model_async(true);37config.wasm_component_model_async_builtins(true);38config.wasm_component_model_async_stackful(true);39config.wasm_component_model_error_context(true);40config.async_support(true);41config42}4344/// Compose two components45///46/// a is the "root" component, and b is composed into it47async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {48let dir = tempfile::tempdir()?;4950let a_file = dir.path().join("a.wasm");51fs::write(&a_file, a).await?;5253let b_file = dir.path().join("b.wasm");54fs::write(&b_file, b).await?;5556ComponentComposer::new(57&a_file,58&wasm_compose::config::Config {59dir: dir.path().to_owned(),60definitions: vec![b_file.to_owned()],61..Default::default()62},63)64.compose()65}6667pub async fn make_component(engine: &Engine, components: &[&str]) -> Result<Component> {68fn cwasm_name(components: &[&str]) -> Result<String> {69if components.is_empty() {70Err(anyhow!("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(anyhow!(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>> {99match components {100[component] => engine.precompile_component(&fs::read(component).await?),101[a, b] => engine102.precompile_component(&compose(&fs::read(a).await?, &fs::read(b).await?).await?),103_ => Err(anyhow!("expected one or two paths")),104}105}106107async fn load(engine: &Engine, components: &[&str]) -> Result<Vec<u8>> {108let cwasm_path = if let Some(cwasm_dir) = &env::var_os("MIRI_TEST_CWASM_DIR") {109Some(Path::new(cwasm_dir).join(cwasm_name(components)?))110} else {111None112};113114if let Some(cwasm_path) = &cwasm_path {115if let Ok(compiled) = fs::read(cwasm_path).await {116return Ok(compiled);117}118}119120if cfg!(miri) {121bail!(122"Running these tests with miri requires precompiled .cwasm files.\n\123Please set the `MIRI_TEST_CWASM_DIR` environment variable to the\n\124absolute path of a valid directory, then run the test(s)\n\125_without_ miri, and finally run them again _with_ miri."126)127}128129let compiled = compile(engine, components).await?;130if let Some(cwasm_path) = &cwasm_path {131fs::write(cwasm_path, &compiled).await?;132}133Ok(compiled)134}135136static CACHE: LazyLock<Mutex<HashMap<Vec<String>, Arc<Mutex<Option<Arc<Vec<u8>>>>>>>> =137LazyLock::new(|| Mutex::new(HashMap::new()));138139let compiled = {140let entry = CACHE141.lock()142.await143.entry(components.iter().map(|&s| s.to_owned()).collect())144.or_insert_with(|| Arc::new(Mutex::new(None)))145.clone();146147let mut entry = entry.lock().await;148if let Some(component) = entry.deref() {149component.clone()150} else {151let component = Arc::new(load(engine, components).await?);152*entry = Some(component.clone());153component154}155};156157Ok(unsafe { Component::deserialize(&engine, &*compiled)? })158}159160pub async fn test_run(components: &[&str]) -> Result<()> {161test_run_with_count(components, 3).await162}163164pub async fn test_run_with_count(components: &[&str], count: usize) -> Result<()> {165let mut config = config();166// As of this writing, miri/pulley/epochs is a problematic combination, so167// we don't test it.168if env::var_os("MIRI_TEST_CWASM_DIR").is_none() {169config.epoch_interruption(true);170}171172let engine = Engine::new(&config)?;173174let component = make_component(&engine, components).await?;175176let mut linker = Linker::new(&engine);177178wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;179component_async_tests::yield_host::bindings::local::local::continue_::add_to_linker::<_, Ctx>(180&mut linker,181|ctx| ctx,182)?;183component_async_tests::yield_host::bindings::local::local::ready::add_to_linker::<_, Ctx>(184&mut linker,185|ctx| ctx,186)?;187component_async_tests::resource_stream::bindings::local::local::resource_stream::add_to_linker::<188_,189Ctx,190>(&mut linker, |ctx| ctx)?;191sleep::local::local::sleep::add_to_linker::<_, Ctx>(&mut linker, |ctx| ctx)?;192193let mut store = Store::new(194&engine,195Ctx {196wasi: WasiCtxBuilder::new().inherit_stdio().build(),197table: ResourceTable::default(),198continue_: false,199wakers: Arc::new(std::sync::Mutex::new(None)),200},201);202203if env::var_os("MIRI_TEST_CWASM_DIR").is_none() {204store.set_epoch_deadline(1);205206std::thread::spawn(move || {207std::thread::sleep(Duration::from_secs(10));208engine.increment_epoch();209});210}211212let instance = linker.instantiate_async(&mut store, &component).await?;213let yield_host =214component_async_tests::yield_host::bindings::YieldHost::new(&mut store, &instance)?;215216// Start `count` concurrent calls and then join them all:217instance218.run_concurrent(&mut store, async |store| {219let mut futures = FuturesUnordered::new();220for _ in 0..count {221futures.push(yield_host.local_local_run().call_run(store));222}223224while let Some(()) = futures.try_next().await? {225// continue226}227anyhow::Ok(())228})229.await??;230231Ok(())232}233234235