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