Skip to content

Commit 34dd941

Browse files
committed
Add simple WIT guest and roundtrip tests
Add tests that ensure that all value types can be round-trip'd through a WIT guest, as well as some basic tests on host resources. Together, these are a pretty decent test of the WIT functionality, especially the hyperlight-specific serialization, which is the bit most likely to cause problems. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
1 parent e23332d commit 34dd941

File tree

11 files changed

+1209
-4
lines changed

11 files changed

+1209
-4
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ exclude = [
2121
"src/tests/rust_guests/callbackguest",
2222
"src/tests/rust_guests/dummyguest",
2323
"src/tests/rust_guests/simpleguest",
24+
"src/tests/rust_guests/witguest",
2425
]
2526

2627
[workspace.package]

Justfile

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ default-target := "debug"
1212
simpleguest_source := "src/tests/rust_guests/simpleguest/target/x86_64-unknown-none"
1313
dummyguest_source := "src/tests/rust_guests/dummyguest/target/x86_64-unknown-none"
1414
callbackguest_source := "src/tests/rust_guests/callbackguest/target/x86_64-unknown-none"
15+
witguest_source := "src/tests/rust_guests/witguest/target/x86_64-unknown-none"
1516
rust_guests_bin_dir := "src/tests/rust_guests/bin"
1617

1718
################
@@ -28,15 +29,21 @@ build target=default-target:
2829
# build testing guest binaries
2930
guests: build-and-move-rust-guests build-and-move-c-guests
3031

31-
build-rust-guests target=default-target:
32+
witguest-wit:
33+
cargo install --locked wasm-tools
34+
cd src/tests/rust_guests/witguest && wasm-tools component wit guest.wit -w -o interface.wasm
35+
36+
build-rust-guests target=default-target: (witguest-wit)
3237
cd src/tests/rust_guests/callbackguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }}
3338
cd src/tests/rust_guests/simpleguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }}
3439
cd src/tests/rust_guests/dummyguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }}
40+
cd src/tests/rust_guests/witguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }}
3541

3642
@move-rust-guests target=default-target:
3743
cp {{ callbackguest_source }}/{{ target }}/callbackguest* {{ rust_guests_bin_dir }}/{{ target }}/
3844
cp {{ simpleguest_source }}/{{ target }}/simpleguest* {{ rust_guests_bin_dir }}/{{ target }}/
3945
cp {{ dummyguest_source }}/{{ target }}/dummyguest* {{ rust_guests_bin_dir }}/{{ target }}/
46+
cp {{ witguest_source }}/{{ target }}/witguest* {{ rust_guests_bin_dir }}/{{ target }}/
4047

4148
build-and-move-rust-guests: (build-rust-guests "debug") (move-rust-guests "debug") (build-rust-guests "release") (move-rust-guests "release")
4249
build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build-c-guests "release") (move-c-guests "release")
@@ -48,6 +55,8 @@ clean-rust:
4855
cd src/tests/rust_guests/simpleguest && cargo clean
4956
cd src/tests/rust_guests/dummyguest && cargo clean
5057
cd src/tests/rust_guests/callbackguest && cargo clean
58+
cd src/tests/rust_guests/witguest && cargo clean
59+
cd src/tests/rust_guests/witguest && rm -f interface.wasm
5160
git clean -fdx src/tests/c_guests/bin src/tests/rust_guests/bin
5261

5362
################
@@ -127,6 +136,7 @@ fmt-check:
127136
cargo +nightly fmt --manifest-path src/tests/rust_guests/callbackguest/Cargo.toml -- --check
128137
cargo +nightly fmt --manifest-path src/tests/rust_guests/simpleguest/Cargo.toml -- --check
129138
cargo +nightly fmt --manifest-path src/tests/rust_guests/dummyguest/Cargo.toml -- --check
139+
cargo +nightly fmt --manifest-path src/tests/rust_guests/witguest/Cargo.toml -- --check
130140
cargo +nightly fmt --manifest-path src/hyperlight_guest_capi/Cargo.toml -- --check
131141

132142
check-license-headers:
@@ -137,14 +147,16 @@ fmt-apply:
137147
cargo +nightly fmt --manifest-path src/tests/rust_guests/callbackguest/Cargo.toml
138148
cargo +nightly fmt --manifest-path src/tests/rust_guests/simpleguest/Cargo.toml
139149
cargo +nightly fmt --manifest-path src/tests/rust_guests/dummyguest/Cargo.toml
150+
cargo +nightly fmt --manifest-path src/tests/rust_guests/witguest/Cargo.toml
140151
cargo +nightly fmt --manifest-path src/hyperlight_guest_capi/Cargo.toml
141152

142-
clippy target=default-target:
153+
clippy target=default-target: (witguest-wit)
143154
cargo clippy --all-targets --all-features --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
144155

145-
clippy-guests target=default-target:
156+
clippy-guests target=default-target: (witguest-wit)
146157
cd src/tests/rust_guests/simpleguest && cargo clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
147158
cd src/tests/rust_guests/callbackguest && cargo clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
159+
cd src/tests/rust_guests/witguest && cargo clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
148160

149161
clippy-apply-fix-unix:
150162
cargo clippy --fix --all

src/hyperlight_common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ anyhow = { version = "1.0.98", default-features = false }
2020
log = "0.4.27"
2121
tracing = { version = "0.1.41", optional = true }
2222
arbitrary = {version = "1.4.1", optional = true, features = ["derive"]}
23-
spin = "0.9.8"
23+
spin = "0.10.0"
2424

2525
[features]
2626
default = ["tracing"]

src/hyperlight_host/tests/wit_test.rs

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
#![allow(clippy::disallowed_macros)]
17+
18+
use std::sync::{Arc, Mutex};
19+
20+
use hyperlight_common::resource::BorrowedResourceGuard;
21+
use hyperlight_host::{GuestBinary, MultiUseGuestCallContext, UninitializedSandbox};
22+
use hyperlight_testing::wit_guest_as_string;
23+
24+
extern crate alloc;
25+
mod bindings {
26+
hyperlight_component_macro::host_bindgen!("../tests/rust_guests/witguest/interface.wasm");
27+
}
28+
29+
use bindings::*;
30+
31+
struct Host {}
32+
33+
impl test::wit::Roundtrip for Host {
34+
fn roundtrip_bool(&mut self, x: bool) -> bool {
35+
x
36+
}
37+
fn roundtrip_s8(&mut self, x: i8) -> i8 {
38+
x
39+
}
40+
fn roundtrip_s16(&mut self, x: i16) -> i16 {
41+
x
42+
}
43+
fn roundtrip_s32(&mut self, x: i32) -> i32 {
44+
x
45+
}
46+
fn roundtrip_s64(&mut self, x: i64) -> i64 {
47+
x
48+
}
49+
fn roundtrip_u8(&mut self, x: u8) -> u8 {
50+
x
51+
}
52+
fn roundtrip_u16(&mut self, x: u16) -> u16 {
53+
x
54+
}
55+
fn roundtrip_u32(&mut self, x: u32) -> u32 {
56+
x
57+
}
58+
fn roundtrip_u64(&mut self, x: u64) -> u64 {
59+
x
60+
}
61+
fn roundtrip_f32(&mut self, x: f32) -> f32 {
62+
x
63+
}
64+
fn roundtrip_f64(&mut self, x: f64) -> f64 {
65+
x
66+
}
67+
fn roundtrip_char(&mut self, x: char) -> char {
68+
x
69+
}
70+
fn roundtrip_string(&mut self, x: alloc::string::String) -> alloc::string::String {
71+
x
72+
}
73+
fn roundtrip_list(&mut self, x: alloc::vec::Vec<u8>) -> alloc::vec::Vec<u8> {
74+
x
75+
}
76+
fn roundtrip_tuple(&mut self, x: (alloc::string::String, u8)) -> (alloc::string::String, u8) {
77+
x
78+
}
79+
fn roundtrip_option(
80+
&mut self,
81+
x: ::core::option::Option<alloc::string::String>,
82+
) -> ::core::option::Option<alloc::string::String> {
83+
x
84+
}
85+
fn roundtrip_result(
86+
&mut self,
87+
x: ::core::result::Result<char, alloc::string::String>,
88+
) -> ::core::result::Result<char, alloc::string::String> {
89+
x
90+
}
91+
fn roundtrip_record(
92+
&mut self,
93+
x: test::wit::roundtrip::Testrecord,
94+
) -> test::wit::roundtrip::Testrecord {
95+
x
96+
}
97+
fn roundtrip_flags_small(
98+
&mut self,
99+
x: test::wit::roundtrip::Smallflags,
100+
) -> test::wit::roundtrip::Smallflags {
101+
x
102+
}
103+
fn roundtrip_flags_large(
104+
&mut self,
105+
x: test::wit::roundtrip::Largeflags,
106+
) -> test::wit::roundtrip::Largeflags {
107+
x
108+
}
109+
fn roundtrip_variant(
110+
&mut self,
111+
x: test::wit::roundtrip::Testvariant,
112+
) -> test::wit::roundtrip::Testvariant {
113+
x
114+
}
115+
fn roundtrip_enum(
116+
&mut self,
117+
x: test::wit::roundtrip::Testenum,
118+
) -> test::wit::roundtrip::Testenum {
119+
x
120+
}
121+
}
122+
123+
struct TestResource {
124+
n_calls: u32,
125+
x: String,
126+
last: char,
127+
}
128+
129+
use std::sync::atomic::AtomicBool;
130+
use std::sync::atomic::Ordering::Relaxed;
131+
// We only have 1 test that uses this, and it isn't a proptest or
132+
// anything, so it should only run once. If multiple tests using this
133+
// could run in parallel, there would be problems.
134+
static HAS_BEEN_DROPPED: AtomicBool = AtomicBool::new(false);
135+
136+
impl Drop for TestResource {
137+
fn drop(&mut self) {
138+
assert_eq!(self.x, "strabc");
139+
assert_eq!(self.last, 'c');
140+
assert!(!HAS_BEEN_DROPPED.swap(true, Relaxed));
141+
}
142+
}
143+
144+
impl test::wit::host_resource::Testresource for Host {
145+
type T = Arc<Mutex<TestResource>>;
146+
fn new(&mut self, x: String, last: char) -> Self::T {
147+
Arc::new(Mutex::new(TestResource {
148+
n_calls: 0,
149+
x,
150+
last,
151+
}))
152+
}
153+
fn append_char(&mut self, self_: BorrowedResourceGuard<'_, Self::T>, c: char) {
154+
let mut self_ = self_.lock().unwrap();
155+
match self_.n_calls {
156+
// These line up to the initial values and calls made by
157+
// witguest.rs. Mostly, this just checks that (even after
158+
// round-tripping an owned reference through the host), we
159+
// do always seem to get the correct structure.
160+
0 => {
161+
assert_eq!(self_.x, "str");
162+
assert_eq!(self_.last, 'z');
163+
}
164+
1 => {
165+
assert_eq!(self_.x, "stra");
166+
assert_eq!(self_.last, 'a');
167+
}
168+
2 => {
169+
assert_eq!(self_.x, "strab");
170+
assert_eq!(self_.last, 'b');
171+
}
172+
_ => panic!(),
173+
};
174+
self_.n_calls += 1;
175+
self_.x.push(c);
176+
self_.last = c;
177+
}
178+
}
179+
180+
impl test::wit::HostResource for Host {
181+
fn roundtrip_own(&mut self, owned: Arc<Mutex<TestResource>>) -> Arc<Mutex<TestResource>> {
182+
owned
183+
}
184+
185+
fn return_own(&mut self, _: Arc<Mutex<TestResource>>) {
186+
// Not much to do here other than let it be dropped
187+
}
188+
}
189+
190+
#[allow(refining_impl_trait)]
191+
impl test::wit::TestImports for Host {
192+
type Roundtrip = Self;
193+
fn roundtrip(&mut self) -> &mut Self {
194+
self
195+
}
196+
type HostResource = Self;
197+
fn host_resource(&mut self) -> &mut Self {
198+
self
199+
}
200+
}
201+
202+
fn sb() -> TestSandbox<Host, MultiUseGuestCallContext> {
203+
let path = wit_guest_as_string().unwrap();
204+
let guest_path = GuestBinary::FilePath(path);
205+
let uninit = UninitializedSandbox::new(guest_path, None).unwrap();
206+
test::wit::Test::instantiate(uninit, Host {})
207+
}
208+
209+
mod wit_test {
210+
211+
use proptest::prelude::*;
212+
213+
use crate::bindings::test::wit::{Roundtrip, TestExports, TestHostResource, roundtrip};
214+
use crate::sb;
215+
216+
prop_compose! {
217+
fn arb_testrecord()(contents in ".*", length in any::<u64>()) -> roundtrip::Testrecord {
218+
roundtrip::Testrecord { contents, length }
219+
}
220+
}
221+
222+
prop_compose! {
223+
fn arb_smallflags()(flag_a: bool, flag_b: bool, flag_c: bool) -> roundtrip::Smallflags {
224+
roundtrip::Smallflags { flag_a, flag_b, flag_c }
225+
}
226+
}
227+
228+
prop_compose! {
229+
fn arb_largeflags()(
230+
flag00: bool, flag01: bool, flag02: bool, flag03: bool, flag04: bool, flag05: bool, flag06: bool, flag07: bool,
231+
flag08: bool, flag09: bool, flag0a: bool, flag0b: bool, flag0c: bool, flag0d: bool, flag0e: bool, flag0f: bool,
232+
233+
flag10: bool, flag11: bool, flag12: bool, flag13: bool, flag14: bool, flag15: bool, flag16: bool, flag17: bool,
234+
flag18: bool, flag19: bool, flag1a: bool, flag1b: bool, flag1c: bool, flag1d: bool, flag1e: bool, flag1f: bool,
235+
) -> roundtrip::Largeflags {
236+
roundtrip::Largeflags {
237+
flag00, flag01, flag02, flag03, flag04, flag05, flag06, flag07,
238+
flag08, flag09, flag0a, flag0b, flag0c, flag0d, flag0e, flag0f,
239+
240+
flag10, flag11, flag12, flag13, flag14, flag15, flag16, flag17,
241+
flag18, flag19, flag1a, flag1b, flag1c, flag1d, flag1e, flag1f,
242+
}
243+
}
244+
}
245+
246+
fn arb_testvariant() -> impl Strategy<Value = roundtrip::Testvariant> {
247+
use roundtrip::Testvariant::*;
248+
prop_oneof![
249+
Just(VariantA),
250+
any::<String>().prop_map(VariantB),
251+
any::<char>().prop_map(VariantC),
252+
]
253+
}
254+
255+
fn arb_testenum() -> impl Strategy<Value = roundtrip::Testenum> {
256+
use roundtrip::Testenum::*;
257+
prop_oneof![Just(EnumA), Just(EnumB), Just(EnumC),]
258+
}
259+
260+
macro_rules! make_test {
261+
($fn:ident, $($ty:tt)*) => {
262+
proptest! {
263+
#[test]
264+
fn $fn(x $($ty)*) {
265+
assert_eq!(x, sb().roundtrip().$fn(x.clone()))
266+
}
267+
}
268+
}
269+
}
270+
271+
make_test! { roundtrip_bool, : bool }
272+
make_test! { roundtrip_u8, : u8 }
273+
make_test! { roundtrip_u16, : u16 }
274+
make_test! { roundtrip_u32, : u32 }
275+
make_test! { roundtrip_u64, : u64 }
276+
make_test! { roundtrip_s8, : i8 }
277+
make_test! { roundtrip_s16, : i16 }
278+
make_test! { roundtrip_s32, : i32 }
279+
make_test! { roundtrip_s64, : i64 }
280+
make_test! { roundtrip_f32, : f32 }
281+
make_test! { roundtrip_f64, : f64 }
282+
make_test! { roundtrip_char, : char }
283+
make_test! { roundtrip_string, : String }
284+
285+
make_test! { roundtrip_list, : Vec<u8> }
286+
make_test! { roundtrip_tuple, : (String, u8) }
287+
make_test! { roundtrip_option, : Option<String> }
288+
make_test! { roundtrip_result, : Result<char, String> }
289+
290+
make_test! { roundtrip_record, in arb_testrecord() }
291+
make_test! { roundtrip_flags_small, in arb_smallflags() }
292+
make_test! { roundtrip_flags_large, in arb_largeflags() }
293+
make_test! { roundtrip_variant, in arb_testvariant() }
294+
make_test! { roundtrip_enum, in arb_testenum() }
295+
296+
#[test]
297+
fn test_host_resource() {
298+
{
299+
sb().test_host_resource().test();
300+
}
301+
use std::sync::atomic::Ordering::Relaxed;
302+
assert!(crate::HAS_BEEN_DROPPED.load(Relaxed));
303+
}
304+
}

0 commit comments

Comments
 (0)