diff --git a/Cargo.toml b/Cargo.toml index 1d65b86102..e834e66349 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 0000000000..a9d37c560c --- /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 0000000000..88c599e46c --- /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 0000000000..8b036111a3 --- /dev/null +++ b/crates/test/src/lib.rs @@ -0,0 +1,92 @@ +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); + } +} diff --git a/tests/main.rs b/tests/main.rs index 422173b7af..9b09d9bf69 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,9 +1,10 @@ use proptest::prelude::*; use std::convert::TryFrom; use wiggle_runtime::{ - GuestArray, GuestError, GuestErrorType, GuestMemory, GuestPtr, GuestPtrMut, GuestRef, - GuestRefMut, GuestString, + GuestArray, GuestError, GuestErrorType, GuestPtr, GuestPtrMut, GuestRef, GuestRefMut, + GuestString, }; +use wiggle_test::{HostMemory, MemArea}; wiggle_generate::from_witx!({ witx: ["tests/test.witx"], @@ -169,92 +170,6 @@ impl GuestErrorType for types::Errno { } } -#[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 +180,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 +267,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 +279,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 +369,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 +377,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 +457,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 +469,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 +558,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 +566,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 +654,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 +662,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 +753,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 +761,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