Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 3b91e3b

Browse files
authored
Merge pull request #216 from bytecodealliance/dicej/miri-async-tests
make it easier to run `component-async-tests` with miri
2 parents 93576ab + a5b94f1 commit 3b91e3b

File tree

12 files changed

+410
-259
lines changed

12 files changed

+410
-259
lines changed

crates/misc/component-async-tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ wasm-compose = { workspace = true }
2727
wasmparser = { workspace = true }
2828
wasmtime = { workspace = true, features = [
2929
"default",
30+
"pulley",
3031
"cranelift",
3132
"component-model-async",
3233
] }

crates/misc/component-async-tests/src/util.rs

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
use std::sync::{Arc, Mutex, Once};
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};
26
use std::time::Duration;
37

4-
use anyhow::Result;
8+
use anyhow::{Result, anyhow, bail};
59
use futures::stream::{FuturesUnordered, TryStreamExt};
610
use tokio::fs;
11+
use tokio::sync::Mutex;
712
use wasm_compose::composer::ComponentComposer;
813
use wasmtime::component::{Component, Linker, ResourceTable};
914
use wasmtime::{Config, Engine, Store};
@@ -20,7 +25,14 @@ pub fn config() -> Config {
2025
init_logger();
2126

2227
let mut config = Config::new();
23-
config.cranelift_debug_verifier(true);
28+
if env::var_os("MIRI_TEST_CWASM_DIR").is_some() {
29+
config.target("pulley64").unwrap();
30+
config.memory_reservation(1 << 20);
31+
config.memory_guard_size(0);
32+
config.signals_based_traps(false);
33+
} else {
34+
config.cranelift_debug_verifier(true);
35+
}
2436
config.wasm_component_model(true);
2537
config.wasm_component_model_async(true);
2638
config.wasm_component_model_async_builtins(true);
@@ -34,7 +46,7 @@ pub fn config() -> Config {
3446
///
3547
/// a is the "root" component, and b is composed into it
3648
#[allow(unused)]
37-
pub async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
49+
async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
3850
let dir = tempfile::tempdir()?;
3951

4052
let a_file = dir.path().join("a.wasm");
@@ -54,18 +66,111 @@ pub async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
5466
.compose()
5567
}
5668

69+
pub async fn make_component(engine: &Engine, components: &[&str]) -> Result<Component> {
70+
fn cwasm_name(components: &[&str]) -> Result<String> {
71+
if components.is_empty() {
72+
Err(anyhow!("expected at least one path"))
73+
} else {
74+
let names = components
75+
.iter()
76+
.map(|&path| {
77+
let path = Path::new(path);
78+
if let Some(name) = path.file_name() {
79+
Ok(name)
80+
} else {
81+
Err(anyhow!(
82+
"expected path with at least two components; got: {}",
83+
path.display()
84+
))
85+
}
86+
})
87+
.collect::<Result<Vec<_>>>()?;
88+
89+
Ok(format!(
90+
"{}.cwasm",
91+
names
92+
.iter()
93+
.map(|name| { name.to_str().unwrap() })
94+
.collect::<Vec<_>>()
95+
.join("+")
96+
))
97+
}
98+
}
99+
100+
async fn compile(engine: &Engine, components: &[&str]) -> Result<Vec<u8>> {
101+
match components {
102+
[component] => engine.precompile_component(&fs::read(component).await?),
103+
[a, b] => engine
104+
.precompile_component(&compose(&fs::read(a).await?, &fs::read(b).await?).await?),
105+
_ => Err(anyhow!("expected one or two paths")),
106+
}
107+
}
108+
109+
async fn load(engine: &Engine, components: &[&str]) -> Result<Vec<u8>> {
110+
let cwasm_path = if let Some(cwasm_dir) = &env::var_os("MIRI_TEST_CWASM_DIR") {
111+
Some(Path::new(cwasm_dir).join(cwasm_name(components)?))
112+
} else {
113+
None
114+
};
115+
116+
if let Some(cwasm_path) = &cwasm_path {
117+
if let Ok(compiled) = fs::read(cwasm_path).await {
118+
return Ok(compiled);
119+
}
120+
}
121+
122+
if cfg!(miri) {
123+
bail!(
124+
"Running these tests with miri requires precompiled .cwasm files.\n\
125+
Please set the `MIRI_TEST_CWASM_DIR` environment variable to the\n\
126+
absolute path of a valid directory, then run the test(s)\n\
127+
_without_ miri, and finally run them again _with_ miri."
128+
)
129+
}
130+
131+
let compiled = compile(engine, components).await?;
132+
if let Some(cwasm_path) = &cwasm_path {
133+
fs::write(cwasm_path, &compiled).await?;
134+
}
135+
Ok(compiled)
136+
}
137+
138+
static CACHE: LazyLock<Mutex<HashMap<Vec<String>, Arc<Mutex<Option<Arc<Vec<u8>>>>>>>> =
139+
LazyLock::new(|| Mutex::new(HashMap::new()));
140+
141+
let compiled = {
142+
let entry = CACHE
143+
.lock()
144+
.await
145+
.entry(components.iter().map(|&s| s.to_owned()).collect())
146+
.or_insert_with(|| Arc::new(Mutex::new(None)))
147+
.clone();
148+
149+
let mut entry = entry.lock().await;
150+
if let Some(component) = entry.deref() {
151+
component.clone()
152+
} else {
153+
let component = Arc::new(load(engine, components).await?);
154+
*entry = Some(component.clone());
155+
component
156+
}
157+
};
158+
159+
Ok(unsafe { Component::deserialize(&engine, &*compiled)? })
160+
}
161+
57162
#[allow(unused)]
58-
pub async fn test_run(component: &[u8]) -> Result<()> {
59-
test_run_with_count(component, 3).await
163+
pub async fn test_run(components: &[&str]) -> Result<()> {
164+
test_run_with_count(components, 3).await
60165
}
61166

62-
pub async fn test_run_with_count(component: &[u8], count: usize) -> Result<()> {
167+
pub async fn test_run_with_count(components: &[&str], count: usize) -> Result<()> {
63168
let mut config = config();
64169
config.epoch_interruption(true);
65170

66171
let engine = Engine::new(&config)?;
67172

68-
let component = Component::new(&engine, component)?;
173+
let component = make_component(&engine, components).await?;
69174

70175
let mut linker = Linker::new(&engine);
71176

@@ -90,7 +195,7 @@ pub async fn test_run_with_count(component: &[u8], count: usize) -> Result<()> {
90195
wasi: WasiCtxBuilder::new().inherit_stdio().build(),
91196
table: ResourceTable::default(),
92197
continue_: false,
93-
wakers: Arc::new(Mutex::new(None)),
198+
wakers: Arc::new(std::sync::Mutex::new(None)),
94199
},
95200
);
96201
store.set_epoch_deadline(1);
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use anyhow::Result;
2-
use tokio::fs;
32

4-
use component_async_tests::util::{compose, test_run};
3+
use component_async_tests::util::test_run;
54

65
// No-op function; we only test this by composing it in `async_backpressure_caller`
76
#[allow(
@@ -12,7 +11,9 @@ pub fn async_backpressure_callee() {}
1211

1312
#[tokio::test]
1413
pub async fn async_backpressure_caller() -> Result<()> {
15-
let caller = &fs::read(test_programs_artifacts::ASYNC_BACKPRESSURE_CALLER_COMPONENT).await?;
16-
let callee = &fs::read(test_programs_artifacts::ASYNC_BACKPRESSURE_CALLEE_COMPONENT).await?;
17-
test_run(&compose(caller, callee).await?).await
14+
test_run(&[
15+
test_programs_artifacts::ASYNC_BACKPRESSURE_CALLER_COMPONENT,
16+
test_programs_artifacts::ASYNC_BACKPRESSURE_CALLEE_COMPONENT,
17+
])
18+
.await
1819
}

crates/misc/component-async-tests/tests/scenario/borrowing.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,73 @@ use std::time::Duration;
33

44
use anyhow::Result;
55
use futures::stream::{FuturesUnordered, TryStreamExt};
6-
use tokio::fs;
7-
use wasmtime::component::{Component, Linker, ResourceTable};
6+
use wasmtime::component::{Linker, ResourceTable};
87
use wasmtime::{Engine, Store};
98
use wasmtime_wasi::p2::WasiCtxBuilder;
109

11-
use component_async_tests::util::{compose, config};
10+
use component_async_tests::util::{config, make_component};
1211

1312
#[tokio::test]
1413
pub async fn async_borrowing_caller() -> Result<()> {
15-
let caller = &fs::read(test_programs_artifacts::ASYNC_BORROWING_CALLER_COMPONENT).await?;
16-
let callee = &fs::read(test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT).await?;
17-
test_run_bool(&compose(caller, callee).await?, false).await
14+
test_run_bool(
15+
&[
16+
test_programs_artifacts::ASYNC_BORROWING_CALLER_COMPONENT,
17+
test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT,
18+
],
19+
false,
20+
)
21+
.await
1822
}
1923

2024
#[tokio::test]
2125
async fn async_borrowing_caller_misbehave() -> Result<()> {
22-
let caller = &fs::read(test_programs_artifacts::ASYNC_BORROWING_CALLER_COMPONENT).await?;
23-
let callee = &fs::read(test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT).await?;
2426
let error = format!(
2527
"{:?}",
26-
test_run_bool(&compose(caller, callee).await?, true)
27-
.await
28-
.unwrap_err()
28+
test_run_bool(
29+
&[
30+
test_programs_artifacts::ASYNC_BORROWING_CALLER_COMPONENT,
31+
test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT
32+
],
33+
true
34+
)
35+
.await
36+
.unwrap_err()
2937
);
3038
assert!(error.contains("unknown handle index"), "{error}");
3139
Ok(())
3240
}
3341

3442
#[tokio::test]
3543
async fn async_borrowing_callee_misbehave() -> Result<()> {
36-
let callee = &fs::read(test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT).await?;
37-
let error = format!("{:?}", test_run_bool(callee, true).await.unwrap_err());
44+
let error = format!(
45+
"{:?}",
46+
test_run_bool(
47+
&[test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT],
48+
true
49+
)
50+
.await
51+
.unwrap_err()
52+
);
3853
assert!(error.contains("unknown handle index"), "{error}");
3954
Ok(())
4055
}
4156

4257
#[tokio::test]
4358
pub async fn async_borrowing_callee() -> Result<()> {
44-
let callee = &fs::read(test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT).await?;
45-
test_run_bool(callee, false).await
59+
test_run_bool(
60+
&[test_programs_artifacts::ASYNC_BORROWING_CALLEE_COMPONENT],
61+
false,
62+
)
63+
.await
4664
}
4765

48-
pub async fn test_run_bool(component: &[u8], v: bool) -> Result<()> {
66+
pub async fn test_run_bool(components: &[&str], v: bool) -> Result<()> {
4967
let mut config = config();
5068
config.epoch_interruption(true);
5169

5270
let engine = Engine::new(&config)?;
5371

54-
let component = Component::new(&engine, component)?;
72+
let component = make_component(&engine, components).await?;
5573

5674
let mut linker = Linker::new(&engine);
5775

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
use anyhow::Result;
2-
use tokio::fs;
32

4-
use component_async_tests::util::{compose, test_run};
3+
use component_async_tests::util::test_run;
54

65
#[tokio::test]
76
pub async fn async_error_context() -> Result<()> {
8-
test_run(&fs::read(test_programs_artifacts::ASYNC_ERROR_CONTEXT_COMPONENT).await?).await
7+
test_run(&[test_programs_artifacts::ASYNC_ERROR_CONTEXT_COMPONENT]).await
98
}
109

1110
#[tokio::test]
1211
pub async fn async_error_context_callee() -> Result<()> {
13-
test_run(&fs::read(test_programs_artifacts::ASYNC_ERROR_CONTEXT_COMPONENT).await?).await
12+
test_run(&[test_programs_artifacts::ASYNC_ERROR_CONTEXT_COMPONENT]).await
1413
}
1514

1615
#[tokio::test]
1716
pub async fn async_error_context_caller() -> Result<()> {
18-
let caller = &fs::read(test_programs_artifacts::ASYNC_ERROR_CONTEXT_CALLER_COMPONENT).await?;
19-
let callee = &fs::read(test_programs_artifacts::ASYNC_ERROR_CONTEXT_CALLEE_COMPONENT).await?;
20-
test_run(&compose(caller, callee).await?).await
21-
}
22-
23-
#[tokio::test]
24-
async fn async_error_context_roundtrip() -> Result<()> {
25-
let caller = &fs::read(test_programs_artifacts::ASYNC_ERROR_CONTEXT_CALLER_COMPONENT).await?;
26-
let callee = &fs::read(test_programs_artifacts::ASYNC_ERROR_CONTEXT_CALLEE_COMPONENT).await?;
27-
test_run(&compose(caller, callee).await?).await
17+
test_run(&[
18+
test_programs_artifacts::ASYNC_ERROR_CONTEXT_CALLER_COMPONENT,
19+
test_programs_artifacts::ASYNC_ERROR_CONTEXT_CALLEE_COMPONENT,
20+
])
21+
.await
2822
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use anyhow::Result;
2-
use tokio::fs;
32

4-
use component_async_tests::util::{compose, test_run};
3+
use component_async_tests::util::test_run;
54

65
// No-op function; we only test this by composing it in `async_post_return_caller`
76
#[allow(
@@ -12,7 +11,9 @@ pub fn async_post_return_callee() {}
1211

1312
#[tokio::test]
1413
pub async fn async_post_return_caller() -> Result<()> {
15-
let caller = &fs::read(test_programs_artifacts::ASYNC_POST_RETURN_CALLER_COMPONENT).await?;
16-
let callee = &fs::read(test_programs_artifacts::ASYNC_POST_RETURN_CALLEE_COMPONENT).await?;
17-
test_run(&compose(caller, callee).await?).await
14+
test_run(&[
15+
test_programs_artifacts::ASYNC_POST_RETURN_CALLER_COMPONENT,
16+
test_programs_artifacts::ASYNC_POST_RETURN_CALLEE_COMPONENT,
17+
])
18+
.await
1819
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use anyhow::Result;
2-
use tokio::fs;
32

43
use component_async_tests::util::test_run;
54

65
#[tokio::test]
76
pub async fn async_read_resource_stream() -> Result<()> {
8-
test_run(&fs::read(test_programs_artifacts::ASYNC_READ_RESOURCE_STREAM_COMPONENT).await?).await
7+
test_run(&[test_programs_artifacts::ASYNC_READ_RESOURCE_STREAM_COMPONENT]).await
98
}

0 commit comments

Comments
 (0)