diff --git a/Cargo.toml b/Cargo.toml index 1d65b8610284..e834e6634999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,13 @@ wiggle-generate = { path = "crates/generate" } wiggle-runtime = { path = "crates/runtime" } [dev-dependencies] +wiggle-test = { path = "crates/test" } proptest = "0.9" [workspace] members = [ "crates/generate", - "crates/runtime" + "crates/runtime", + "crates/test", ] exclude = ["crates/WASI"] diff --git a/crates/test/.gitignore b/crates/test/.gitignore new file mode 100644 index 000000000000..a9d37c560c6a --- /dev/null +++ b/crates/test/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml new file mode 100644 index 000000000000..88c599e46c92 --- /dev/null +++ b/crates/test/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "wiggle-test" +version = "0.1.0" +authors = ["Pat Hickey ", "Jakub Konka "] +edition = "2018" + +[dependencies] +wiggle-runtime = { path = "../runtime" } +proptest = "0.9" diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs new file mode 100644 index 000000000000..2ff31211c109 --- /dev/null +++ b/crates/test/src/lib.rs @@ -0,0 +1,127 @@ +use proptest::prelude::*; +use wiggle_runtime::GuestMemory; + +#[repr(align(4096))] +pub struct HostMemory { + buffer: [u8; 4096], +} +impl HostMemory { + pub fn new() -> Self { + HostMemory { buffer: [0; 4096] } + } + + pub fn guest_memory<'a>(&'a mut self) -> GuestMemory<'a> { + GuestMemory::new(self.buffer.as_mut_ptr(), self.buffer.len() as u32) + } + + pub fn mem_area_strat(align: u32) -> BoxedStrategy { + prop::num::u32::ANY + .prop_filter_map("needs to fit in memory", move |p| { + let p_aligned = p - (p % align); // Align according to argument + let ptr = p_aligned % 4096; // Put inside memory + if ptr + align < 4096 { + Some(MemArea { ptr, len: align }) + } else { + None + } + }) + .boxed() + } +} + +#[derive(Debug)] +pub struct MemArea { + pub ptr: u32, + pub len: u32, +} + +impl MemArea { + // This code is a whole lot like the Region::overlaps func thats at the core of the code under + // test. + // So, I implemented this one with std::ops::Range so it is less likely I wrote the same bug in two + // places. + pub fn overlapping(&self, b: &Self) -> bool { + // a_range is all elems in A + let a_range = std::ops::Range { + start: self.ptr, + end: self.ptr + self.len, // std::ops::Range is open from the right + }; + // b_range is all elems in B + let b_range = std::ops::Range { + start: b.ptr, + end: b.ptr + b.len, + }; + // No element in B is contained in A: + for b_elem in b_range.clone() { + if a_range.contains(&b_elem) { + return true; + } + } + // No element in A is contained in B: + for a_elem in a_range { + if b_range.contains(&a_elem) { + return true; + } + } + return false; + } + pub fn non_overlapping_set(areas: &[&Self]) -> bool { + // A is all areas + for (i, a) in areas.iter().enumerate() { + // (A, B) is every pair of areas + for b in areas[i + 1..].iter() { + if a.overlapping(b) { + return false; + } + } + } + return true; + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn hostmemory_is_aligned() { + let mut h = HostMemory::new(); + assert_eq!(h.buffer.as_mut_ptr() as usize % 4096, 0); + let mut h = Box::new(HostMemory::new()); + assert_eq!(h.buffer.as_mut_ptr() as usize % 4096, 0); + } +} + +use wiggle_runtime::GuestError; + +pub struct WasiCtx { + pub guest_errors: Vec, +} + +impl WasiCtx { + pub fn new() -> Self { + Self { + guest_errors: vec![], + } + } +} + +// Errno is used as a first return value in the functions above, therefore +// it must implement GuestErrorType with type Context = WasiCtx. +// The context type should let you do logging or debugging or whatever you need +// with these errors. We just push them to vecs. +#[macro_export] +macro_rules! impl_errno { + ( $errno:ty ) => { + impl wiggle_runtime::GuestErrorType for $errno { + type Context = WasiCtx; + fn success() -> $errno { + <$errno>::Ok + } + fn from_error(e: GuestError, ctx: &mut WasiCtx) -> $errno { + eprintln!("GUEST ERROR: {:?}", e); + ctx.guest_errors.push(e); + types::Errno::InvalidArg + } + } + }; +} diff --git a/tests/errno.witx b/tests/errno.witx new file mode 100644 index 000000000000..5197c2c2249a --- /dev/null +++ b/tests/errno.witx @@ -0,0 +1,8 @@ +(typename $errno + (enum u32 + $ok + $invalid_arg + $dont_want_to + $physically_unable + $picket_line)) + diff --git a/tests/main.rs b/tests/main.rs index 422173b7af0b..43254739fb12 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,33 +1,18 @@ use proptest::prelude::*; use std::convert::TryFrom; use wiggle_runtime::{ - GuestArray, GuestError, GuestErrorType, GuestMemory, GuestPtr, GuestPtrMut, GuestRef, - GuestRefMut, GuestString, + GuestArray, GuestError, GuestPtr, GuestPtrMut, GuestRef, GuestRefMut, GuestString, }; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle_generate::from_witx!({ witx: ["tests/test.witx"], ctx: WasiCtx, }); -pub struct WasiCtx { - guest_errors: Vec, -} - -impl WasiCtx { - pub fn new() -> Self { - Self { - guest_errors: vec![], - } - } -} +impl_errno!(types::Errno); impl foo::Foo for WasiCtx { - fn bar(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> { - println!("BAR: {} {}", an_int, an_float); - Ok(()) - } - fn baz( &mut self, input1: types::Excuse, @@ -153,108 +138,6 @@ impl foo::Foo for WasiCtx { Ok(res) } } -// Errno is used as a first return value in the functions above, therefore -// it must implement GuestErrorType with type Context = WasiCtx. -// The context type should let you do logging or debugging or whatever you need -// with these errors. We just push them to vecs. -impl GuestErrorType for types::Errno { - type Context = WasiCtx; - fn success() -> types::Errno { - types::Errno::Ok - } - fn from_error(e: GuestError, ctx: &mut WasiCtx) -> types::Errno { - eprintln!("GUEST ERROR: {:?}", e); - ctx.guest_errors.push(e); - types::Errno::InvalidArg - } -} - -#[repr(align(4096))] -struct HostMemory { - buffer: [u8; 4096], -} -impl HostMemory { - pub fn new() -> Self { - HostMemory { buffer: [0; 4096] } - } - pub fn as_mut_ptr(&mut self) -> *mut u8 { - self.buffer.as_mut_ptr() - } - pub fn len(&self) -> usize { - self.buffer.len() - } - pub fn mem_area_strat(align: u32) -> BoxedStrategy { - prop::num::u32::ANY - .prop_filter_map("needs to fit in memory", move |p| { - let p_aligned = p - (p % align); // Align according to argument - let ptr = p_aligned % 4096; // Put inside memory - if ptr + align < 4096 { - Some(MemArea { ptr, len: align }) - } else { - None - } - }) - .boxed() - } -} - -#[derive(Debug)] -struct MemArea { - ptr: u32, - len: u32, -} - -// This code is a whole lot like the Region::overlaps func thats at the core of the code under -// test. -// So, I implemented this one with std::ops::Range so it is less likely I wrote the same bug in two -// places. -fn overlapping(a: &MemArea, b: &MemArea) -> bool { - // a_range is all elems in A - let a_range = std::ops::Range { - start: a.ptr, - end: a.ptr + a.len, // std::ops::Range is open from the right - }; - // b_range is all elems in B - let b_range = std::ops::Range { - start: b.ptr, - end: b.ptr + b.len, - }; - // No element in B is contained in A: - for b_elem in b_range.clone() { - if a_range.contains(&b_elem) { - return true; - } - } - // No element in A is contained in B: - for a_elem in a_range { - if b_range.contains(&a_elem) { - return true; - } - } - return false; -} - -fn non_overlapping_set(areas: &[&MemArea]) -> bool { - // A is all areas - for (i, a) in areas.iter().enumerate() { - // (A, B) is every pair of areas - for b in areas[i + 1..].iter() { - if overlapping(a, b) { - return false; - } - } - } - return true; -} - -#[test] -fn hostmemory_is_aligned() { - let mut h = HostMemory::new(); - assert_eq!(h.as_mut_ptr() as usize % 4096, 0); - let mut h = Box::new(HostMemory::new()); - assert_eq!(h.as_mut_ptr() as usize % 4096, 0); -} - #[derive(Debug)] struct BatExercise { pub input: u32, @@ -265,7 +148,7 @@ impl BatExercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); let bat_err = foo::bat( &mut ctx, @@ -352,7 +235,7 @@ impl BazExercise { }, ) .prop_filter("non-overlapping pointers", |e| { - non_overlapping_set(&[ + MemArea::non_overlapping_set(&[ &e.input2_loc, &e.input3_loc, &e.input4_loc, @@ -364,7 +247,7 @@ impl BazExercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); *guest_memory .ptr_mut(self.input2_loc.ptr) @@ -454,7 +337,7 @@ impl SumOfPairExercise { return_loc, }) .prop_filter("non-overlapping pointers", |e| { - non_overlapping_set(&[&e.input_loc, &e.return_loc]) + MemArea::non_overlapping_set(&[&e.input_loc, &e.return_loc]) }) .boxed() } @@ -462,7 +345,7 @@ impl SumOfPairExercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); *guest_memory .ptr_mut(self.input_loc.ptr) @@ -542,7 +425,7 @@ impl SumPairPtrsExercise { }, ) .prop_filter("non-overlapping pointers", |e| { - non_overlapping_set(&[ + MemArea::non_overlapping_set(&[ &e.input_first_loc, &e.input_second_loc, &e.input_struct_loc, @@ -554,7 +437,7 @@ impl SumPairPtrsExercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); *guest_memory .ptr_mut(self.input_first_loc.ptr) @@ -643,7 +526,7 @@ impl ReduceExcusesExcercise { .prop_filter("non-overlapping pointers", |e| { let mut all = vec![&e.array_ptr_loc, &e.array_len_loc, &e.return_ptr_loc]; all.extend(e.excuse_ptr_locs.iter()); - non_overlapping_set(&all) + MemArea::non_overlapping_set(&all) }) .boxed() } @@ -651,7 +534,7 @@ impl ReduceExcusesExcercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); // Populate memory with pointers to generated Excuse values for (&excuse, ptr) in self.excuse_values.iter().zip(self.excuse_ptr_locs.iter()) { @@ -739,7 +622,7 @@ impl PopulateExcusesExcercise { .prop_filter("non-overlapping pointers", |e| { let mut all = vec![&e.array_ptr_loc, &e.array_len_loc]; all.extend(e.elements.iter()); - non_overlapping_set(&all) + MemArea::non_overlapping_set(&all) }) .boxed() } @@ -747,7 +630,7 @@ impl PopulateExcusesExcercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); // Populate array length info *guest_memory @@ -838,7 +721,7 @@ impl ConfigureCarExercise { }, ) .prop_filter("non-overlapping ptrs", |e| { - non_overlapping_set(&[&e.other_config_by_ptr, &e.return_ptr_loc]) + MemArea::non_overlapping_set(&[&e.other_config_by_ptr, &e.return_ptr_loc]) }) .boxed() } @@ -846,7 +729,7 @@ impl ConfigureCarExercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); // Populate input ptr *guest_memory @@ -916,7 +799,11 @@ impl HelloStringExercise { }, ) .prop_filter("non-overlapping pointers", |e| { - non_overlapping_set(&[&e.string_ptr_loc, &e.string_len_loc, &e.return_ptr_loc]) + MemArea::non_overlapping_set(&[ + &e.string_ptr_loc, + &e.string_len_loc, + &e.return_ptr_loc, + ]) }) .boxed() } @@ -924,7 +811,7 @@ impl HelloStringExercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); // Populate string length *guest_memory @@ -993,7 +880,7 @@ impl CookieCutterExercise { pub fn test(&self) { let mut ctx = WasiCtx::new(); let mut host_memory = HostMemory::new(); - let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32); + let mut guest_memory = host_memory.guest_memory(); let res = foo::cookie_cutter( &mut ctx, diff --git a/tests/test.witx b/tests/test.witx index 4a0930a616fc..78f69a3276e7 100644 --- a/tests/test.witx +++ b/tests/test.witx @@ -1,10 +1,4 @@ -(typename $errno - (enum u32 - $ok - $invalid_arg - $dont_want_to - $physically_unable - $picket_line)) +(use "errno.witx") (typename $excuse (enum u8 @@ -44,10 +38,6 @@ (typename $excuse_array (array (@witx pointer $excuse))) (module $foo - (@interface func (export "bar") - (param $an_int u32) - (param $an_float f32) - (result $error $errno)) (@interface func (export "baz") (param $an_excuse $excuse) (param $an_excuse_by_reference (@witx pointer $excuse)) diff --git a/tests/trivial.rs b/tests/trivial.rs new file mode 100644 index 000000000000..de97dd8a05b7 --- /dev/null +++ b/tests/trivial.rs @@ -0,0 +1,55 @@ +use proptest::prelude::*; +use wiggle_runtime::GuestError; +use wiggle_test::{impl_errno, HostMemory, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/trivial.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl trivial::Trivial for WasiCtx { + fn int_float_args(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> { + println!("INT FLOAT ARGS: {} {}", an_int, an_float); + Ok(()) + } +} + +// There's nothing meaningful to test here - this just demonstrates the test machinery + +#[derive(Debug)] +struct IntFloatExercise { + pub an_int: u32, + pub an_float: f32, +} + +impl IntFloatExercise { + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + let e = trivial::int_float_args( + &mut ctx, + &mut guest_memory, + self.an_int as i32, + self.an_float, + ); + + assert_eq!(e, types::Errno::Ok.into(), "int_float_args error"); + } + + pub fn strat() -> BoxedStrategy { + (prop::num::u32::ANY, prop::num::f32::ANY) + .prop_map(|(an_int, an_float)| IntFloatExercise { an_int, an_float }) + .boxed() + } +} + +proptest! { + #[test] + fn int_float_exercise(e in IntFloatExercise::strat()) { + e.test() + } +} diff --git a/tests/trivial.witx b/tests/trivial.witx new file mode 100644 index 000000000000..3fbbf13e5c51 --- /dev/null +++ b/tests/trivial.witx @@ -0,0 +1,8 @@ +(use "errno.witx") + +(module $trivial + (@interface func (export "int_float_args") + (param $an_int u32) + (param $an_float f32) + (result $error $errno)) +)