Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/misc/component-async-tests/tests/scenario/util.rs
1693 views
1
use std::collections::HashMap;
2
use std::env;
3
use std::ops::Deref;
4
use std::path::Path;
5
use std::sync::{Arc, LazyLock, Once};
6
use std::time::Duration;
7
8
use anyhow::{Result, anyhow, bail};
9
use component_async_tests::{Ctx, sleep};
10
use futures::stream::{FuturesUnordered, TryStreamExt};
11
use tokio::fs;
12
use tokio::sync::Mutex;
13
use wasm_compose::composer::ComponentComposer;
14
use wasmtime::component::{Component, Linker, ResourceTable};
15
use wasmtime::{Config, Engine, Store};
16
use wasmtime_wasi::WasiCtxBuilder;
17
18
pub fn init_logger() {
19
static ONCE: Once = Once::new();
20
ONCE.call_once(env_logger::init);
21
}
22
23
pub fn config() -> Config {
24
init_logger();
25
26
let mut config = Config::new();
27
if env::var_os("MIRI_TEST_CWASM_DIR").is_some() {
28
config.target("pulley64").unwrap();
29
config.memory_reservation(1 << 20);
30
config.memory_guard_size(0);
31
config.signals_based_traps(false);
32
} else {
33
config.cranelift_debug_verifier(true);
34
config.cranelift_wasmtime_debug_checks(true);
35
}
36
config.wasm_component_model(true);
37
config.wasm_component_model_async(true);
38
config.wasm_component_model_async_builtins(true);
39
config.wasm_component_model_async_stackful(true);
40
config.wasm_component_model_error_context(true);
41
config.async_support(true);
42
config
43
}
44
45
/// Compose two components
46
///
47
/// a is the "root" component, and b is composed into it
48
async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
49
let dir = tempfile::tempdir()?;
50
51
let a_file = dir.path().join("a.wasm");
52
fs::write(&a_file, a).await?;
53
54
let b_file = dir.path().join("b.wasm");
55
fs::write(&b_file, b).await?;
56
57
ComponentComposer::new(
58
&a_file,
59
&wasm_compose::config::Config {
60
dir: dir.path().to_owned(),
61
definitions: vec![b_file.to_owned()],
62
..Default::default()
63
},
64
)
65
.compose()
66
}
67
68
pub async fn make_component(engine: &Engine, components: &[&str]) -> Result<Component> {
69
fn cwasm_name(components: &[&str]) -> Result<String> {
70
if components.is_empty() {
71
Err(anyhow!("expected at least one path"))
72
} else {
73
let names = components
74
.iter()
75
.map(|&path| {
76
let path = Path::new(path);
77
if let Some(name) = path.file_name() {
78
Ok(name)
79
} else {
80
Err(anyhow!(
81
"expected path with at least two components; got: {}",
82
path.display()
83
))
84
}
85
})
86
.collect::<Result<Vec<_>>>()?;
87
88
Ok(format!(
89
"{}.cwasm",
90
names
91
.iter()
92
.map(|name| { name.to_str().unwrap() })
93
.collect::<Vec<_>>()
94
.join("+")
95
))
96
}
97
}
98
99
async fn compile(engine: &Engine, components: &[&str]) -> Result<Vec<u8>> {
100
match components {
101
[component] => engine.precompile_component(&fs::read(component).await?),
102
[a, b] => engine
103
.precompile_component(&compose(&fs::read(a).await?, &fs::read(b).await?).await?),
104
_ => Err(anyhow!("expected one or two paths")),
105
}
106
}
107
108
async fn load(engine: &Engine, components: &[&str]) -> Result<Vec<u8>> {
109
let cwasm_path = if let Some(cwasm_dir) = &env::var_os("MIRI_TEST_CWASM_DIR") {
110
Some(Path::new(cwasm_dir).join(cwasm_name(components)?))
111
} else {
112
None
113
};
114
115
if let Some(cwasm_path) = &cwasm_path {
116
if let Ok(compiled) = fs::read(cwasm_path).await {
117
return Ok(compiled);
118
}
119
}
120
121
if cfg!(miri) {
122
bail!(
123
"Running these tests with miri requires precompiled .cwasm files.\n\
124
Please set the `MIRI_TEST_CWASM_DIR` environment variable to the\n\
125
absolute path of a valid directory, then run the test(s)\n\
126
_without_ miri, and finally run them again _with_ miri."
127
)
128
}
129
130
let compiled = compile(engine, components).await?;
131
if let Some(cwasm_path) = &cwasm_path {
132
fs::write(cwasm_path, &compiled).await?;
133
}
134
Ok(compiled)
135
}
136
137
static CACHE: LazyLock<Mutex<HashMap<Vec<String>, Arc<Mutex<Option<Arc<Vec<u8>>>>>>>> =
138
LazyLock::new(|| Mutex::new(HashMap::new()));
139
140
let compiled = {
141
let entry = CACHE
142
.lock()
143
.await
144
.entry(components.iter().map(|&s| s.to_owned()).collect())
145
.or_insert_with(|| Arc::new(Mutex::new(None)))
146
.clone();
147
148
let mut entry = entry.lock().await;
149
if let Some(component) = entry.deref() {
150
component.clone()
151
} else {
152
let component = Arc::new(load(engine, components).await?);
153
*entry = Some(component.clone());
154
component
155
}
156
};
157
158
Ok(unsafe { Component::deserialize(&engine, &*compiled)? })
159
}
160
161
pub async fn test_run(components: &[&str]) -> Result<()> {
162
test_run_with_count(components, 3).await
163
}
164
165
pub async fn test_run_with_count(components: &[&str], count: usize) -> Result<()> {
166
let mut config = config();
167
// As of this writing, miri/pulley/epochs is a problematic combination, so
168
// we don't test it.
169
if env::var_os("MIRI_TEST_CWASM_DIR").is_none() {
170
config.epoch_interruption(true);
171
}
172
173
let engine = Engine::new(&config)?;
174
175
let component = make_component(&engine, components).await?;
176
177
let mut linker = Linker::new(&engine);
178
179
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
180
component_async_tests::yield_host::bindings::local::local::continue_::add_to_linker::<_, Ctx>(
181
&mut linker,
182
|ctx| ctx,
183
)?;
184
component_async_tests::yield_host::bindings::local::local::ready::add_to_linker::<_, Ctx>(
185
&mut linker,
186
|ctx| ctx,
187
)?;
188
component_async_tests::resource_stream::bindings::local::local::resource_stream::add_to_linker::<
189
_,
190
Ctx,
191
>(&mut linker, |ctx| ctx)?;
192
sleep::local::local::sleep::add_to_linker::<_, Ctx>(&mut linker, |ctx| ctx)?;
193
194
let mut store = Store::new(
195
&engine,
196
Ctx {
197
wasi: WasiCtxBuilder::new().inherit_stdio().build(),
198
table: ResourceTable::default(),
199
continue_: false,
200
wakers: Arc::new(std::sync::Mutex::new(None)),
201
},
202
);
203
204
if env::var_os("MIRI_TEST_CWASM_DIR").is_none() {
205
store.set_epoch_deadline(1);
206
207
std::thread::spawn(move || {
208
std::thread::sleep(Duration::from_secs(10));
209
engine.increment_epoch();
210
});
211
}
212
213
let instance = linker.instantiate_async(&mut store, &component).await?;
214
let yield_host =
215
component_async_tests::yield_host::bindings::YieldHost::new(&mut store, &instance)?;
216
217
// Start `count` concurrent calls and then join them all:
218
instance
219
.run_concurrent(&mut store, async |store| {
220
let mut futures = FuturesUnordered::new();
221
for _ in 0..count {
222
futures.push(yield_host.local_local_run().call_run(store));
223
}
224
225
while let Some(()) = futures.try_next().await? {
226
// continue
227
}
228
anyhow::Ok(())
229
})
230
.await??;
231
232
Ok(())
233
}
234
235