Skip to content

Commit f1b57f6

Browse files
authored
feat(runner): add async-runner for coverage testing (#47)
2 parents 8ca6964 + b204233 commit f1b57f6

File tree

5 files changed

+183
-4
lines changed

5 files changed

+183
-4
lines changed

devenv.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@
5252
scripts."wasm-trampoline-coverage" = {
5353
description = "Run wasm-trampoline-coverage";
5454
exec = ''
55-
tests/runner/build.sh
55+
tests/runner/build.sh >/dev/null
5656
cargo llvm-cov clean --workspace
5757
cargo llvm-cov test --workspace --no-report --release
5858
cargo llvm-cov run --bin runner -p runner --release --no-report
59+
cargo llvm-cov run --bin async-runner -p runner --release --no-report
5960
cargo llvm-cov report --release --cobertura --output-path coverage.cobertura.xml
6061
cargo llvm-cov report --release --lcov --output-path coverage.lcov
6162
'';

tests/runner/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ semver.workspace = true
99
tokio = { version = "1.0", features = ["full"] }
1010
wasmtime = { workspace = true, features = ["component-model", "async"] }
1111
wasm-trampoline = { path = "../.." }
12+
13+
[[bin]]
14+
name = "async-runner"
15+
16+
[[bin]]
17+
name = "runner"

tests/runner/build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ for x in kvstore logger application; do
88
target/wasm32-unknown-unknown/release/$x.wasm > target/wasm32-unknown-unknown/release/$x.component.wasm
99
done
1010

11-
cargo run -p runner --release
11+
cargo run -p runner --bin runner --release
12+
cargo run -p runner --bin async-runner --release

tests/runner/src/main.rs renamed to tests/runner/src/bin/async-runner.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ mod runner {
6868
}
6969

7070
// TODO(bill): directory from command line
71-
const WASM_DIR: &str = "target/wasm32-unknown-unknown/release/";
71+
const WASM_DIR: &str = "wasm32-unknown-unknown/release/";
7272

7373
// TODO(bill): packages from command line
7474
async fn add_package(
@@ -78,7 +78,10 @@ mod runner {
7878
version: Version,
7979
) -> Result<wasm_trampoline::PackageId, wasm_trampoline::AddPackageError> {
8080
eprintln!("Loading {path} component...");
81-
let wasm_dir = Path::new(WASM_DIR);
81+
let wasm_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
82+
.join("../..")
83+
.join("target")
84+
.join(WASM_DIR);
8285
let wasm_file = format!("{path}.component.wasm").to_string();
8386
let pkg_bytes = fs::read(wasm_dir.join(&wasm_file))
8487
.await

tests/runner/src/bin/runner.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#[cfg(target_family = "wasm")]
2+
fn main() {
3+
// This is a no-op for the wasm target, as the main function is not used.
4+
eprintln!("This is a WebAssembly target, no main function to run.");
5+
}
6+
7+
#[cfg(not(target_family = "wasm"))]
8+
#[tokio::main]
9+
async fn main() -> anyhow::Result<()> {
10+
runner::main().await
11+
}
12+
13+
#[cfg(not(target_family = "wasm"))]
14+
mod runner {
15+
use anyhow::Error;
16+
use semver::Version;
17+
use std::path::Path;
18+
19+
use std::sync::Arc;
20+
use tokio::fs;
21+
use wasm_trampoline::{CompositionGraph, GuestCall, GuestResult, Trampoline};
22+
use wasmtime::{Config, Engine, Store, component::Linker};
23+
24+
wasmtime::component::bindgen!({
25+
path: "../wasm/application/wit",
26+
async: false,
27+
});
28+
29+
// Define our store data type
30+
#[derive(Debug)]
31+
struct AppData {
32+
stack_depth: usize,
33+
}
34+
35+
// Simple async trampoline that just passes calls through
36+
struct PassthroughTrampoline {}
37+
impl Trampoline<AppData, ()> for PassthroughTrampoline {
38+
fn bounce<'c>(
39+
&self,
40+
mut call: GuestCall<'c, AppData, ()>,
41+
) -> Result<GuestResult<'c, AppData, ()>, Error> {
42+
eprintln!(
43+
"[{}] Bounced call '{}#{}'",
44+
call.store().data().stack_depth,
45+
call.interface(),
46+
call.method(),
47+
);
48+
49+
call.store_mut().data_mut().stack_depth += 1;
50+
51+
let mut result = call.call()?;
52+
53+
result.store_mut().data_mut().stack_depth -= 1;
54+
55+
eprintln!(
56+
"[{}] Bounced return '{}#{}'",
57+
result.store().data().stack_depth,
58+
result.interface(),
59+
result.method(),
60+
);
61+
62+
Ok(result)
63+
}
64+
}
65+
66+
// TODO(bill): directory from command line
67+
const WASM_DIR: &str = "wasm32-unknown-unknown/release/";
68+
69+
// TODO(bill): packages from command line
70+
async fn add_package(
71+
graph: &mut CompositionGraph<AppData>,
72+
path: &str,
73+
name: &str,
74+
version: Version,
75+
) -> Result<wasm_trampoline::PackageId, wasm_trampoline::AddPackageError> {
76+
eprintln!("Loading {path} component...");
77+
let wasm_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
78+
.join("../..")
79+
.join("target")
80+
.join(WASM_DIR);
81+
let wasm_file = format!("{path}.component.wasm").to_string();
82+
let pkg_bytes = fs::read(wasm_dir.join(&wasm_file))
83+
.await
84+
.unwrap_or_else(|_| {
85+
panic!(
86+
"Failed to read {}/{wasm_file} Make sure it's been compiled",
87+
wasm_dir.display()
88+
)
89+
});
90+
91+
let trampoline: Arc<dyn Trampoline<AppData, ()>> = Arc::new(PassthroughTrampoline {});
92+
let pkg = wasm_trampoline::PackageTrampoline::with_default_context(trampoline, ());
93+
94+
let ret = graph.add_package(name.to_string(), version, pkg_bytes, pkg);
95+
eprintln!("{name} component loaded successfully.");
96+
ret
97+
}
98+
99+
pub async fn main() -> anyhow::Result<()> {
100+
let verbose = false; // TODO(bill): command line option
101+
// Configure the WebAssembly engine
102+
let mut config = Config::new();
103+
config.wasm_component_model(true);
104+
config.async_support(false);
105+
106+
let engine = Engine::new(&config)?;
107+
let mut linker = Linker::new(&engine);
108+
let mut store = Store::new(&engine, AppData { stack_depth: 0 });
109+
110+
// Add global functions to the linker.
111+
linker.root().func_wrap(
112+
"println",
113+
|_store: wasmtime::StoreContextMut<'_, AppData>, args: (String,)| {
114+
let (message,) = args;
115+
eprintln!("{}", message);
116+
Ok(())
117+
},
118+
)?;
119+
120+
// Create our composition graph
121+
let mut graph = CompositionGraph::<AppData>::new();
122+
123+
// Load the logger component
124+
add_package(&mut graph, "logger", "test:logging", Version::new(1, 1, 1)).await?;
125+
add_package(&mut graph, "logger", "test:logging", Version::new(1, 1, 1))
126+
.await
127+
.expect_err("Duplicate logger component should not be allowed");
128+
129+
// Load the KV store component
130+
let _kvstore_id =
131+
add_package(&mut graph, "kvstore", "test:kvstore", Version::new(2, 1, 6)).await?;
132+
133+
// Load the application component
134+
let app_id = add_package(
135+
&mut graph,
136+
"application",
137+
"test:application",
138+
Version::new(0, 4, 0),
139+
)
140+
.await?;
141+
142+
// Instantiate the components
143+
eprintln!("Instantiating components...");
144+
if verbose {
145+
eprintln!("graph: {graph:#?}");
146+
}
147+
148+
let instance = graph.instantiate(app_id, &mut linker, &mut store, &engine)?;
149+
150+
eprintln!("Components instantiated successfully.");
151+
152+
let application = Application::new(&mut store, &instance)?;
153+
154+
application
155+
.test_application_greeter()
156+
.call_set_name(&mut store, "Dave")?;
157+
158+
let hello = application
159+
.test_application_greeter()
160+
.call_hello(&mut store)?;
161+
162+
println!("Greeter Output: {:?}", &hello);
163+
assert_eq!(hello, "Hello Dave!");
164+
165+
println!("Test completed successfully!");
166+
Ok(())
167+
}
168+
}

0 commit comments

Comments
 (0)