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

Commit c794509

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 + cb6c977 commit c794509

12 files changed

Lines changed: 412 additions & 259 deletions

File tree

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: 116 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,16 @@ 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+
config.debug_info(false);
34+
} else {
35+
config.cranelift_debug_verifier(true);
36+
config.debug_info(true);
37+
}
2438
config.wasm_component_model(true);
2539
config.wasm_component_model_async(true);
2640
config.wasm_component_model_async_builtins(true);
@@ -34,7 +48,7 @@ pub fn config() -> Config {
3448
///
3549
/// a is the "root" component, and b is composed into it
3650
#[allow(unused)]
37-
pub async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
51+
async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
3852
let dir = tempfile::tempdir()?;
3953

4054
let a_file = dir.path().join("a.wasm");
@@ -54,18 +68,111 @@ pub async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
5468
.compose()
5569
}
5670

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

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

66173
let engine = Engine::new(&config)?;
67174

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

70177
let mut linker = Linker::new(&engine);
71178

@@ -90,7 +197,7 @@ pub async fn test_run_with_count(component: &[u8], count: usize) -> Result<()> {
90197
wasi: WasiCtxBuilder::new().inherit_stdio().build(),
91198
table: ResourceTable::default(),
92199
continue_: false,
93-
wakers: Arc::new(Mutex::new(None)),
200+
wakers: Arc::new(std::sync::Mutex::new(None)),
94201
},
95202
);
96203
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)