diff --git a/tests/arrays.rs b/tests/arrays.rs new file mode 100644 index 0000000000..467dd3cc27 --- /dev/null +++ b/tests/arrays.rs @@ -0,0 +1,247 @@ +use proptest::prelude::*; +use wiggle_runtime::{GuestArray, GuestError, GuestPtr, GuestPtrMut}; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/arrays.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl arrays::Arrays for WasiCtx { + fn reduce_excuses( + &mut self, + excuses: &types::ConstExcuseArray, + ) -> Result { + let last = wiggle_runtime::GuestTypeClone::read_from_guest( + &excuses + .iter() + .last() + .expect("input array is non-empty") + .expect("valid ptr to ptr"), + ) + .expect("valid ptr to some Excuse value"); + Ok(*last.as_ref().expect("dereferencing ptr should succeed")) + } + + fn populate_excuses(&mut self, excuses: &types::ExcuseArray) -> Result<(), types::Errno> { + for excuse in excuses.iter() { + let ptr_to_ptr = + wiggle_runtime::GuestTypeClone::read_from_guest(&excuse.expect("valid ptr to ptr")) + .expect("valid ptr to some Excuse value"); + let mut ptr = ptr_to_ptr + .as_ref_mut() + .expect("dereferencing mut ptr should succeed"); + *ptr = types::Excuse::Sleeping; + } + Ok(()) + } +} + +#[derive(Debug)] +struct ReduceExcusesExcercise { + excuse_values: Vec, + excuse_ptr_locs: Vec, + array_ptr_loc: MemArea, + array_len_loc: MemArea, + return_ptr_loc: MemArea, +} + +impl ReduceExcusesExcercise { + pub fn strat() -> BoxedStrategy { + (1..256u32) + .prop_flat_map(|len| { + let len_usize = len as usize; + ( + proptest::collection::vec(excuse_strat(), len_usize..=len_usize), + proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize), + HostMemory::mem_area_strat(4 * len), + HostMemory::mem_area_strat(4), + HostMemory::mem_area_strat(4), + ) + }) + .prop_map( + |(excuse_values, excuse_ptr_locs, array_ptr_loc, array_len_loc, return_ptr_loc)| { + Self { + excuse_values, + excuse_ptr_locs, + array_ptr_loc, + array_len_loc, + return_ptr_loc, + } + }, + ) + .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()); + MemArea::non_overlapping_set(&all) + }) + .boxed() + } + + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + 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()) { + *guest_memory + .ptr_mut(ptr.ptr) + .expect("ptr mut to Excuse value") + .as_ref_mut() + .expect("deref ptr mut to Excuse value") = excuse; + } + + // Populate array length info + *guest_memory + .ptr_mut(self.array_len_loc.ptr) + .expect("ptr to array len") + .as_ref_mut() + .expect("deref ptr mut to array len") = self.excuse_ptr_locs.len() as u32; + + // Populate the array with pointers to generated Excuse values + { + let mut next: GuestPtrMut<'_, GuestPtr> = guest_memory + .ptr_mut(self.array_ptr_loc.ptr) + .expect("ptr to array mut"); + for ptr in &self.excuse_ptr_locs { + next.write_ptr_to_guest( + &guest_memory + .ptr::(ptr.ptr) + .expect("ptr to Excuse value"), + ); + next = next.elem(1).expect("increment ptr by 1"); + } + } + + let res = arrays::reduce_excuses( + &mut ctx, + &mut guest_memory, + self.array_ptr_loc.ptr as i32, + self.array_len_loc.ptr as i32, + self.return_ptr_loc.ptr as i32, + ); + + assert_eq!(res, types::Errno::Ok.into(), "reduce excuses errno"); + + let expected = *self + .excuse_values + .last() + .expect("generated vec of excuses should be non-empty"); + let given: types::Excuse = *guest_memory + .ptr(self.return_ptr_loc.ptr) + .expect("ptr to returned value") + .as_ref() + .expect("deref ptr to returned value"); + assert_eq!(expected, given, "reduce excuses return val"); + } +} +proptest! { + #[test] + fn reduce_excuses(e in ReduceExcusesExcercise::strat()) { + e.test() + } +} + +fn excuse_strat() -> impl Strategy { + prop_oneof![ + Just(types::Excuse::DogAte), + Just(types::Excuse::Traffic), + Just(types::Excuse::Sleeping), + ] + .boxed() +} + +#[derive(Debug)] +struct PopulateExcusesExcercise { + array_ptr_loc: MemArea, + array_len_loc: MemArea, + elements: Vec, +} + +impl PopulateExcusesExcercise { + pub fn strat() -> BoxedStrategy { + (1..256u32) + .prop_flat_map(|len| { + let len_usize = len as usize; + ( + HostMemory::mem_area_strat(4 * len), + HostMemory::mem_area_strat(4), + proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize), + ) + }) + .prop_map(|(array_ptr_loc, array_len_loc, elements)| Self { + array_ptr_loc, + array_len_loc, + elements, + }) + .prop_filter("non-overlapping pointers", |e| { + let mut all = vec![&e.array_ptr_loc, &e.array_len_loc]; + all.extend(e.elements.iter()); + MemArea::non_overlapping_set(&all) + }) + .boxed() + } + + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + // Populate array length info + *guest_memory + .ptr_mut(self.array_len_loc.ptr) + .expect("ptr mut to array len") + .as_ref_mut() + .expect("deref ptr mut to array len") = self.elements.len() as u32; + + // Populate array with valid pointers to Excuse type in memory + { + let mut next: GuestPtrMut<'_, GuestPtrMut> = guest_memory + .ptr_mut(self.array_ptr_loc.ptr) + .expect("ptr mut to the first element of array"); + for ptr in &self.elements { + next.write_ptr_to_guest( + &guest_memory + .ptr_mut::(ptr.ptr) + .expect("ptr mut to Excuse value"), + ); + next = next.elem(1).expect("increment ptr by 1"); + } + } + + let res = arrays::populate_excuses( + &mut ctx, + &mut guest_memory, + self.array_ptr_loc.ptr as i32, + self.array_len_loc.ptr as i32, + ); + assert_eq!(res, types::Errno::Ok.into(), "populate excuses errno"); + + let arr: GuestArray<'_, GuestPtr<'_, types::Excuse>> = guest_memory + .ptr(self.array_ptr_loc.ptr) + .expect("ptr to the first element of array") + .array(self.elements.len() as u32) + .expect("as array"); + for el in arr.iter() { + let ptr_to_ptr = + wiggle_runtime::GuestTypeClone::read_from_guest(&el.expect("valid ptr to ptr")) + .expect("valid ptr to some Excuse value"); + assert_eq!( + *ptr_to_ptr + .as_ref() + .expect("dereferencing ptr to some Excuse value"), + types::Excuse::Sleeping, + "element should equal Excuse::Sleeping" + ); + } + } +} +proptest! { + #[test] + fn populate_excuses(e in PopulateExcusesExcercise::strat()) { + e.test() + } +} diff --git a/tests/arrays.witx b/tests/arrays.witx new file mode 100644 index 0000000000..e8a81cee95 --- /dev/null +++ b/tests/arrays.witx @@ -0,0 +1,17 @@ +(use "errno.witx") +(use "excuse.witx") + +(typename $const_excuse_array (array (@witx const_pointer $excuse))) +(typename $excuse_array (array (@witx pointer $excuse))) + +(module $arrays + (@interface func (export "reduce_excuses") + (param $excuses $const_excuse_array) + (result $error $errno) + (result $reduced $excuse) + ) + (@interface func (export "populate_excuses") + (param $excuses $excuse_array) + (result $error $errno) + ) +) diff --git a/tests/atoms.rs b/tests/atoms.rs new file mode 100644 index 0000000000..216613766f --- /dev/null +++ b/tests/atoms.rs @@ -0,0 +1,99 @@ +use proptest::prelude::*; +use wiggle_runtime::{GuestError, GuestRef}; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/atoms.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl atoms::Atoms 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(()) + } + fn double_int_return_float(&mut self, an_int: u32) -> Result { + Ok((an_int as f32) * 2.0) + } +} + +// 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 = atoms::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() + } +} +#[derive(Debug)] +struct DoubleIntExercise { + pub input: u32, + pub return_loc: MemArea, +} + +impl DoubleIntExercise { + 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 = atoms::double_int_return_float( + &mut ctx, + &mut guest_memory, + self.input as i32, + self.return_loc.ptr as i32, + ); + + let return_val: GuestRef = guest_memory + .ptr(self.return_loc.ptr) + .expect("return loc ptr") + .as_ref() + .expect("return val ref"); + assert_eq!(e, types::Errno::Ok.into(), "errno"); + assert_eq!(*return_val, (self.input as f32) * 2.0, "return val"); + } + + pub fn strat() -> BoxedStrategy { + (prop::num::u32::ANY, HostMemory::mem_area_strat(4)) + .prop_map(|(input, return_loc)| DoubleIntExercise { input, return_loc }) + .boxed() + } +} + +proptest! { + #[test] + fn double_int_return_float(e in DoubleIntExercise::strat()) { + e.test() + } +} diff --git a/tests/atoms.witx b/tests/atoms.witx new file mode 100644 index 0000000000..ef496d7600 --- /dev/null +++ b/tests/atoms.witx @@ -0,0 +1,12 @@ +(use "errno.witx") + +(module $atoms + (@interface func (export "int_float_args") + (param $an_int u32) + (param $an_float f32) + (result $error $errno)) + (@interface func (export "double_int_return_float") + (param $an_int u32) + (result $error $errno) + (result $doubled_it f32)) +) diff --git a/tests/excuse.witx b/tests/excuse.witx new file mode 100644 index 0000000000..14a927164e --- /dev/null +++ b/tests/excuse.witx @@ -0,0 +1,6 @@ +(typename $excuse + (enum u8 + $dog_ate + $traffic + $sleeping)) + diff --git a/tests/flags.rs b/tests/flags.rs new file mode 100644 index 0000000000..0dcc16d3b4 --- /dev/null +++ b/tests/flags.rs @@ -0,0 +1,104 @@ +use proptest::prelude::*; +use std::convert::TryFrom; +use wiggle_runtime::{GuestError, GuestPtr}; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/flags.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl flags::Flags for WasiCtx { + fn configure_car( + &mut self, + old_config: types::CarConfig, + other_config_ptr: GuestPtr, + ) -> Result { + let other_config = *other_config_ptr.as_ref().map_err(|e| { + eprintln!("old_config_ptr error: {}", e); + types::Errno::InvalidArg + })?; + Ok(old_config ^ other_config) + } +} + +fn car_config_strat() -> impl Strategy { + (1u8..=types::CarConfig::ALL_FLAGS.into()) + .prop_map(|v| { + types::CarConfig::try_from(v).expect("invalid value for types::CarConfig flag") + }) + .boxed() +} + +#[derive(Debug)] +struct ConfigureCarExercise { + old_config: types::CarConfig, + other_config: types::CarConfig, + other_config_by_ptr: MemArea, + return_ptr_loc: MemArea, +} + +impl ConfigureCarExercise { + pub fn strat() -> BoxedStrategy { + ( + car_config_strat(), + car_config_strat(), + HostMemory::mem_area_strat(4), + HostMemory::mem_area_strat(4), + ) + .prop_map( + |(old_config, other_config, other_config_by_ptr, return_ptr_loc)| Self { + old_config, + other_config, + other_config_by_ptr, + return_ptr_loc, + }, + ) + .prop_filter("non-overlapping ptrs", |e| { + MemArea::non_overlapping_set(&[&e.other_config_by_ptr, &e.return_ptr_loc]) + }) + .boxed() + } + + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + // Populate input ptr + *guest_memory + .ptr_mut(self.other_config_by_ptr.ptr) + .expect("ptr mut to CarConfig") + .as_ref_mut() + .expect("deref ptr mut to CarConfig") = self.other_config; + + let res = flags::configure_car( + &mut ctx, + &mut guest_memory, + self.old_config.into(), + self.other_config_by_ptr.ptr as i32, + self.return_ptr_loc.ptr as i32, + ); + assert_eq!(res, types::Errno::Ok.into(), "configure car errno"); + + let res_config = *guest_memory + .ptr::(self.return_ptr_loc.ptr) + .expect("ptr to returned CarConfig") + .as_ref() + .expect("deref to CarConfig value"); + + assert_eq!( + self.old_config ^ self.other_config, + res_config, + "returned CarConfig should be an XOR of inputs" + ); + } +} +proptest! { + #[test] + fn configure_car(e in ConfigureCarExercise::strat()) { + e.test() + } +} diff --git a/tests/flags.witx b/tests/flags.witx new file mode 100644 index 0000000000..b46f73d5b5 --- /dev/null +++ b/tests/flags.witx @@ -0,0 +1,16 @@ +(use "errno.witx") + +(typename $car_config + (flags u8 + $automatic + $awd + $suv)) + +(module $flags + (@interface func (export "configure_car") + (param $old_config $car_config) + (param $old_config_by_ptr (@witx const_pointer $car_config)) + (result $error $errno) + (result $new_config $car_config) + ) +) diff --git a/tests/ints.rs b/tests/ints.rs new file mode 100644 index 0000000000..f6a00ccc4a --- /dev/null +++ b/tests/ints.rs @@ -0,0 +1,81 @@ +use proptest::prelude::*; +use std::convert::TryFrom; +use wiggle_runtime::GuestError; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/ints.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl ints::Ints for WasiCtx { + fn cookie_cutter(&mut self, init_cookie: types::Cookie) -> Result { + let res = if init_cookie == types::Cookie::START { + types::Bool::True + } else { + types::Bool::False + }; + Ok(res) + } +} + +fn cookie_strat() -> impl Strategy { + (0..std::u64::MAX) + .prop_map(|x| types::Cookie::try_from(x).expect("within range of cookie")) + .boxed() +} + +#[derive(Debug)] +struct CookieCutterExercise { + cookie: types::Cookie, + return_ptr_loc: MemArea, +} + +impl CookieCutterExercise { + pub fn strat() -> BoxedStrategy { + (cookie_strat(), HostMemory::mem_area_strat(4)) + .prop_map(|(cookie, return_ptr_loc)| Self { + cookie, + return_ptr_loc, + }) + .boxed() + } + + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + let res = ints::cookie_cutter( + &mut ctx, + &mut guest_memory, + self.cookie.into(), + self.return_ptr_loc.ptr as i32, + ); + assert_eq!(res, types::Errno::Ok.into(), "cookie cutter errno"); + + let is_cookie_start = *guest_memory + .ptr::(self.return_ptr_loc.ptr) + .expect("ptr to returned Bool") + .as_ref() + .expect("deref to Bool value"); + + assert_eq!( + if is_cookie_start == types::Bool::True { + true + } else { + false + }, + self.cookie == types::Cookie::START, + "returned Bool should test if input was Cookie::START", + ); + } +} +proptest! { + #[test] + fn cookie_cutter(e in CookieCutterExercise::strat()) { + e.test() + } +} diff --git a/tests/ints.witx b/tests/ints.witx new file mode 100644 index 0000000000..09dc62f5ec --- /dev/null +++ b/tests/ints.witx @@ -0,0 +1,18 @@ +(use "errno.witx") + +(typename $cookie + (int u64 + (const $start 0))) + +(typename $bool + (enum u8 + $false + $true)) + +(module $ints + (@interface func (export "cookie_cutter") + (param $init_cookie $cookie) + (result $error $errno) + (result $is_start $bool) + ) +) diff --git a/tests/main.rs b/tests/main.rs deleted file mode 100644 index 43254739fb..0000000000 --- a/tests/main.rs +++ /dev/null @@ -1,915 +0,0 @@ -use proptest::prelude::*; -use std::convert::TryFrom; -use wiggle_runtime::{ - 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, -}); - -impl_errno!(types::Errno); - -impl foo::Foo for WasiCtx { - fn baz( - &mut self, - input1: types::Excuse, - input2_ptr: GuestPtrMut, - input3_ptr: GuestPtr, - input4_ptr_ptr: GuestPtrMut>, - ) -> Result<(), types::Errno> { - println!("BAZ input1 {:?}", input1); - // Read enum value from mutable: - let mut input2_ref: GuestRefMut = input2_ptr.as_ref_mut().map_err(|e| { - eprintln!("input2_ptr error: {}", e); - types::Errno::InvalidArg - })?; - let input2: types::Excuse = *input2_ref; - println!("input2 {:?}", input2); - - // Read enum value from immutable ptr: - let input3 = *input3_ptr.as_ref().map_err(|e| { - eprintln!("input3_ptr error: {}", e); - types::Errno::InvalidArg - })?; - println!("input3 {:?}", input3); - - // Write enum to mutable ptr: - *input2_ref = input3; - println!("wrote to input2_ref {:?}", input3); - - // Read ptr value from mutable ptr: - let input4_ptr: GuestPtr = wiggle_runtime::GuestTypeClone::read_from_guest( - &input4_ptr_ptr.as_immut(), - ) - .map_err(|e| { - eprintln!("input4_ptr_ptr error: {}", e); - types::Errno::InvalidArg - })?; - - // Read enum value from that ptr: - let input4: types::Excuse = *input4_ptr.as_ref().map_err(|e| { - eprintln!("input4_ptr error: {}", e); - types::Errno::InvalidArg - })?; - println!("input4 {:?}", input4); - - // Write ptr value to mutable ptr: - input4_ptr_ptr.write_ptr_to_guest(&input2_ptr.as_immut()); - - Ok(()) - } - - fn bat(&mut self, an_int: u32) -> Result { - Ok((an_int as f32) * 2.0) - } - - fn sum_of_pair(&mut self, an_pair: &types::PairInts) -> Result { - Ok(an_pair.first as i64 + an_pair.second as i64) - } - - fn sum_of_pair_of_ptrs(&mut self, an_pair: &types::PairIntPtrs) -> Result { - let first = *an_pair - .first - .as_ref() - .expect("dereferencing GuestPtr should succeed"); - let second = *an_pair - .second - .as_ref() - .expect("dereferncing GuestPtr should succeed"); - Ok(first as i64 + second as i64) - } - - fn reduce_excuses( - &mut self, - excuses: &types::ConstExcuseArray, - ) -> Result { - let last = wiggle_runtime::GuestTypeClone::read_from_guest( - &excuses - .iter() - .last() - .expect("input array is non-empty") - .expect("valid ptr to ptr"), - ) - .expect("valid ptr to some Excuse value"); - Ok(*last.as_ref().expect("dereferencing ptr should succeed")) - } - - fn populate_excuses(&mut self, excuses: &types::ExcuseArray) -> Result<(), types::Errno> { - for excuse in excuses.iter() { - let ptr_to_ptr = - wiggle_runtime::GuestTypeClone::read_from_guest(&excuse.expect("valid ptr to ptr")) - .expect("valid ptr to some Excuse value"); - let mut ptr = ptr_to_ptr - .as_ref_mut() - .expect("dereferencing mut ptr should succeed"); - *ptr = types::Excuse::Sleeping; - } - Ok(()) - } - - fn configure_car( - &mut self, - old_config: types::CarConfig, - other_config_ptr: GuestPtr, - ) -> Result { - let other_config = *other_config_ptr.as_ref().map_err(|e| { - eprintln!("old_config_ptr error: {}", e); - types::Errno::InvalidArg - })?; - Ok(old_config ^ other_config) - } - - fn hello_string(&mut self, a_string: &GuestString<'_>) -> Result { - let as_ref = a_string.as_ref().expect("deref ptr should succeed"); - let as_str = as_ref.as_str().expect("valid UTF-8 string"); - println!("a_string='{}'", as_str); - Ok(as_str.len() as u32) - } - - fn cookie_cutter(&mut self, init_cookie: types::Cookie) -> Result { - let res = if init_cookie == types::Cookie::START { - types::Bool::True - } else { - types::Bool::False - }; - Ok(res) - } -} -#[derive(Debug)] -struct BatExercise { - pub input: u32, - pub return_loc: MemArea, -} - -impl BatExercise { - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - let bat_err = foo::bat( - &mut ctx, - &mut guest_memory, - self.input as i32, - self.return_loc.ptr as i32, - ); - - let return_val: GuestRef = guest_memory - .ptr(self.return_loc.ptr) - .expect("return loc ptr") - .as_ref() - .expect("return val ref"); - assert_eq!(bat_err, types::Errno::Ok.into(), "bat errno"); - assert_eq!(*return_val, (self.input as f32) * 2.0, "bat return val"); - } - - pub fn strat() -> BoxedStrategy { - (prop::num::u32::ANY, HostMemory::mem_area_strat(4)) - .prop_map(|(input, return_loc)| BatExercise { input, return_loc }) - .boxed() - } -} - -proptest! { - #[test] - fn bat(e in BatExercise::strat()) { - e.test() - } -} - -fn excuse_strat() -> impl Strategy { - prop_oneof![ - Just(types::Excuse::DogAte), - Just(types::Excuse::Traffic), - Just(types::Excuse::Sleeping), - ] - .boxed() -} - -#[derive(Debug)] -struct BazExercise { - pub input1: types::Excuse, - pub input2: types::Excuse, - pub input2_loc: MemArea, - pub input3: types::Excuse, - pub input3_loc: MemArea, - pub input4: types::Excuse, - pub input4_loc: MemArea, - pub input4_ptr_loc: MemArea, -} - -impl BazExercise { - pub fn strat() -> BoxedStrategy { - ( - excuse_strat(), - excuse_strat(), - HostMemory::mem_area_strat(4), - excuse_strat(), - HostMemory::mem_area_strat(4), - excuse_strat(), - HostMemory::mem_area_strat(4), - HostMemory::mem_area_strat(4), - ) - .prop_map( - |( - input1, - input2, - input2_loc, - input3, - input3_loc, - input4, - input4_loc, - input4_ptr_loc, - )| BazExercise { - input1, - input2, - input2_loc, - input3, - input3_loc, - input4, - input4_loc, - input4_ptr_loc, - }, - ) - .prop_filter("non-overlapping pointers", |e| { - MemArea::non_overlapping_set(&[ - &e.input2_loc, - &e.input3_loc, - &e.input4_loc, - &e.input4_ptr_loc, - ]) - }) - .boxed() - } - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - *guest_memory - .ptr_mut(self.input2_loc.ptr) - .expect("input2 ptr") - .as_ref_mut() - .expect("input2 ref_mut") = self.input2; - - *guest_memory - .ptr_mut(self.input3_loc.ptr) - .expect("input3 ptr") - .as_ref_mut() - .expect("input3 ref_mut") = self.input3; - - *guest_memory - .ptr_mut(self.input4_loc.ptr) - .expect("input4 ptr") - .as_ref_mut() - .expect("input4 ref_mut") = self.input4; - - *guest_memory - .ptr_mut(self.input4_ptr_loc.ptr) - .expect("input4 ptr ptr") - .as_ref_mut() - .expect("input4 ptr ref_mut") = self.input4_loc.ptr; - - let baz_err = foo::baz( - &mut ctx, - &mut guest_memory, - self.input1.into(), - self.input2_loc.ptr as i32, - self.input3_loc.ptr as i32, - self.input4_ptr_loc.ptr as i32, - ); - assert_eq!(baz_err, types::Errno::Ok.into(), "baz errno"); - - // Implementation of baz writes input3 to the input2_loc: - let written_to_input2_loc: i32 = *guest_memory - .ptr(self.input2_loc.ptr) - .expect("input2 ptr") - .as_ref() - .expect("input2 ref"); - - assert_eq!( - written_to_input2_loc, - self.input3.into(), - "baz written to input2" - ); - - // Implementation of baz writes input2_loc to input4_ptr_loc: - let written_to_input4_ptr: u32 = *guest_memory - .ptr(self.input4_ptr_loc.ptr) - .expect("input4_ptr_loc ptr") - .as_ref() - .expect("input4_ptr_loc ref"); - - assert_eq!( - written_to_input4_ptr, self.input2_loc.ptr, - "baz written to input4_ptr" - ); - } -} -proptest! { - #[test] - fn baz(e in BazExercise::strat()) { - e.test(); - } -} - -#[derive(Debug)] -struct SumOfPairExercise { - pub input: types::PairInts, - pub input_loc: MemArea, - pub return_loc: MemArea, -} - -impl SumOfPairExercise { - pub fn strat() -> BoxedStrategy { - ( - prop::num::i32::ANY, - prop::num::i32::ANY, - HostMemory::mem_area_strat(8), - HostMemory::mem_area_strat(8), - ) - .prop_map(|(first, second, input_loc, return_loc)| SumOfPairExercise { - input: types::PairInts { first, second }, - input_loc, - return_loc, - }) - .prop_filter("non-overlapping pointers", |e| { - MemArea::non_overlapping_set(&[&e.input_loc, &e.return_loc]) - }) - .boxed() - } - - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - *guest_memory - .ptr_mut(self.input_loc.ptr) - .expect("input ptr") - .as_ref_mut() - .expect("input ref_mut") = self.input.first; - *guest_memory - .ptr_mut(self.input_loc.ptr + 4) - .expect("input ptr") - .as_ref_mut() - .expect("input ref_mut") = self.input.second; - let sum_err = foo::sum_of_pair( - &mut ctx, - &mut guest_memory, - self.input_loc.ptr as i32, - self.return_loc.ptr as i32, - ); - - assert_eq!(sum_err, types::Errno::Ok.into(), "sum errno"); - - let return_val: i64 = *guest_memory - .ptr(self.return_loc.ptr) - .expect("return ptr") - .as_ref() - .expect("return ref"); - - assert_eq!( - return_val, - self.input.first as i64 + self.input.second as i64, - "sum return value" - ); - } -} - -proptest! { - #[test] - fn sum_of_pair(e in SumOfPairExercise::strat()) { - e.test(); - } -} - -#[derive(Debug)] -struct SumPairPtrsExercise { - input_first: i32, - input_second: i32, - input_first_loc: MemArea, - input_second_loc: MemArea, - input_struct_loc: MemArea, - return_loc: MemArea, -} - -impl SumPairPtrsExercise { - pub fn strat() -> BoxedStrategy { - ( - prop::num::i32::ANY, - prop::num::i32::ANY, - HostMemory::mem_area_strat(4), - HostMemory::mem_area_strat(4), - HostMemory::mem_area_strat(8), - HostMemory::mem_area_strat(8), - ) - .prop_map( - |( - input_first, - input_second, - input_first_loc, - input_second_loc, - input_struct_loc, - return_loc, - )| SumPairPtrsExercise { - input_first, - input_second, - input_first_loc, - input_second_loc, - input_struct_loc, - return_loc, - }, - ) - .prop_filter("non-overlapping pointers", |e| { - MemArea::non_overlapping_set(&[ - &e.input_first_loc, - &e.input_second_loc, - &e.input_struct_loc, - &e.return_loc, - ]) - }) - .boxed() - } - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - *guest_memory - .ptr_mut(self.input_first_loc.ptr) - .expect("input_first ptr") - .as_ref_mut() - .expect("input_first ref") = self.input_first; - *guest_memory - .ptr_mut(self.input_second_loc.ptr) - .expect("input_second ptr") - .as_ref_mut() - .expect("input_second ref") = self.input_second; - - *guest_memory - .ptr_mut(self.input_struct_loc.ptr) - .expect("input_struct ptr") - .as_ref_mut() - .expect("input_struct ref") = self.input_first_loc.ptr; - *guest_memory - .ptr_mut(self.input_struct_loc.ptr + 4) - .expect("input_struct ptr") - .as_ref_mut() - .expect("input_struct ref") = self.input_second_loc.ptr; - - let res = foo::sum_of_pair_of_ptrs( - &mut ctx, - &mut guest_memory, - self.input_struct_loc.ptr as i32, - self.return_loc.ptr as i32, - ); - - assert_eq!(res, types::Errno::Ok.into(), "sum of pair of ptrs errno"); - - let doubled: i64 = *guest_memory - .ptr(self.return_loc.ptr) - .expect("return ptr") - .as_ref() - .expect("return ref"); - - assert_eq!( - doubled, - (self.input_first as i64) + (self.input_second as i64), - "sum of pair of ptrs return val" - ); - } -} -proptest! { - #[test] - fn sum_of_pair_of_ptrs(e in SumPairPtrsExercise::strat()) { - e.test() - } -} - -#[derive(Debug)] -struct ReduceExcusesExcercise { - excuse_values: Vec, - excuse_ptr_locs: Vec, - array_ptr_loc: MemArea, - array_len_loc: MemArea, - return_ptr_loc: MemArea, -} - -impl ReduceExcusesExcercise { - pub fn strat() -> BoxedStrategy { - (1..256u32) - .prop_flat_map(|len| { - let len_usize = len as usize; - ( - proptest::collection::vec(excuse_strat(), len_usize..=len_usize), - proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize), - HostMemory::mem_area_strat(4 * len), - HostMemory::mem_area_strat(4), - HostMemory::mem_area_strat(4), - ) - }) - .prop_map( - |(excuse_values, excuse_ptr_locs, array_ptr_loc, array_len_loc, return_ptr_loc)| { - Self { - excuse_values, - excuse_ptr_locs, - array_ptr_loc, - array_len_loc, - return_ptr_loc, - } - }, - ) - .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()); - MemArea::non_overlapping_set(&all) - }) - .boxed() - } - - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - 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()) { - *guest_memory - .ptr_mut(ptr.ptr) - .expect("ptr mut to Excuse value") - .as_ref_mut() - .expect("deref ptr mut to Excuse value") = excuse; - } - - // Populate array length info - *guest_memory - .ptr_mut(self.array_len_loc.ptr) - .expect("ptr to array len") - .as_ref_mut() - .expect("deref ptr mut to array len") = self.excuse_ptr_locs.len() as u32; - - // Populate the array with pointers to generated Excuse values - { - let mut next: GuestPtrMut<'_, GuestPtr> = guest_memory - .ptr_mut(self.array_ptr_loc.ptr) - .expect("ptr to array mut"); - for ptr in &self.excuse_ptr_locs { - next.write_ptr_to_guest( - &guest_memory - .ptr::(ptr.ptr) - .expect("ptr to Excuse value"), - ); - next = next.elem(1).expect("increment ptr by 1"); - } - } - - let res = foo::reduce_excuses( - &mut ctx, - &mut guest_memory, - self.array_ptr_loc.ptr as i32, - self.array_len_loc.ptr as i32, - self.return_ptr_loc.ptr as i32, - ); - - assert_eq!(res, types::Errno::Ok.into(), "reduce excuses errno"); - - let expected = *self - .excuse_values - .last() - .expect("generated vec of excuses should be non-empty"); - let given: types::Excuse = *guest_memory - .ptr(self.return_ptr_loc.ptr) - .expect("ptr to returned value") - .as_ref() - .expect("deref ptr to returned value"); - assert_eq!(expected, given, "reduce excuses return val"); - } -} -proptest! { - #[test] - fn reduce_excuses(e in ReduceExcusesExcercise::strat()) { - e.test() - } -} - -#[derive(Debug)] -struct PopulateExcusesExcercise { - array_ptr_loc: MemArea, - array_len_loc: MemArea, - elements: Vec, -} - -impl PopulateExcusesExcercise { - pub fn strat() -> BoxedStrategy { - (1..256u32) - .prop_flat_map(|len| { - let len_usize = len as usize; - ( - HostMemory::mem_area_strat(4 * len), - HostMemory::mem_area_strat(4), - proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize), - ) - }) - .prop_map(|(array_ptr_loc, array_len_loc, elements)| Self { - array_ptr_loc, - array_len_loc, - elements, - }) - .prop_filter("non-overlapping pointers", |e| { - let mut all = vec![&e.array_ptr_loc, &e.array_len_loc]; - all.extend(e.elements.iter()); - MemArea::non_overlapping_set(&all) - }) - .boxed() - } - - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - // Populate array length info - *guest_memory - .ptr_mut(self.array_len_loc.ptr) - .expect("ptr mut to array len") - .as_ref_mut() - .expect("deref ptr mut to array len") = self.elements.len() as u32; - - // Populate array with valid pointers to Excuse type in memory - { - let mut next: GuestPtrMut<'_, GuestPtrMut> = guest_memory - .ptr_mut(self.array_ptr_loc.ptr) - .expect("ptr mut to the first element of array"); - for ptr in &self.elements { - next.write_ptr_to_guest( - &guest_memory - .ptr_mut::(ptr.ptr) - .expect("ptr mut to Excuse value"), - ); - next = next.elem(1).expect("increment ptr by 1"); - } - } - - let res = foo::populate_excuses( - &mut ctx, - &mut guest_memory, - self.array_ptr_loc.ptr as i32, - self.array_len_loc.ptr as i32, - ); - assert_eq!(res, types::Errno::Ok.into(), "populate excuses errno"); - - let arr: GuestArray<'_, GuestPtr<'_, types::Excuse>> = guest_memory - .ptr(self.array_ptr_loc.ptr) - .expect("ptr to the first element of array") - .array(self.elements.len() as u32) - .expect("as array"); - for el in arr.iter() { - let ptr_to_ptr = - wiggle_runtime::GuestTypeClone::read_from_guest(&el.expect("valid ptr to ptr")) - .expect("valid ptr to some Excuse value"); - assert_eq!( - *ptr_to_ptr - .as_ref() - .expect("dereferencing ptr to some Excuse value"), - types::Excuse::Sleeping, - "element should equal Excuse::Sleeping" - ); - } - } -} -proptest! { - #[test] - fn populate_excuses(e in PopulateExcusesExcercise::strat()) { - e.test() - } -} - -fn car_config_strat() -> impl Strategy { - (1u8..=types::CarConfig::ALL_FLAGS.into()) - .prop_map(|v| { - types::CarConfig::try_from(v).expect("invalid value for types::CarConfig flag") - }) - .boxed() -} - -#[derive(Debug)] -struct ConfigureCarExercise { - old_config: types::CarConfig, - other_config: types::CarConfig, - other_config_by_ptr: MemArea, - return_ptr_loc: MemArea, -} - -impl ConfigureCarExercise { - pub fn strat() -> BoxedStrategy { - ( - car_config_strat(), - car_config_strat(), - HostMemory::mem_area_strat(4), - HostMemory::mem_area_strat(4), - ) - .prop_map( - |(old_config, other_config, other_config_by_ptr, return_ptr_loc)| Self { - old_config, - other_config, - other_config_by_ptr, - return_ptr_loc, - }, - ) - .prop_filter("non-overlapping ptrs", |e| { - MemArea::non_overlapping_set(&[&e.other_config_by_ptr, &e.return_ptr_loc]) - }) - .boxed() - } - - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - // Populate input ptr - *guest_memory - .ptr_mut(self.other_config_by_ptr.ptr) - .expect("ptr mut to CarConfig") - .as_ref_mut() - .expect("deref ptr mut to CarConfig") = self.other_config; - - let res = foo::configure_car( - &mut ctx, - &mut guest_memory, - self.old_config.into(), - self.other_config_by_ptr.ptr as i32, - self.return_ptr_loc.ptr as i32, - ); - assert_eq!(res, types::Errno::Ok.into(), "configure car errno"); - - let res_config = *guest_memory - .ptr::(self.return_ptr_loc.ptr) - .expect("ptr to returned CarConfig") - .as_ref() - .expect("deref to CarConfig value"); - - assert_eq!( - self.old_config ^ self.other_config, - res_config, - "returned CarConfig should be an XOR of inputs" - ); - } -} -proptest! { - #[test] - fn configure_car(e in ConfigureCarExercise::strat()) { - e.test() - } -} - -fn test_string_strategy() -> impl Strategy { - "\\p{Greek}{1,256}" -} - -#[derive(Debug)] -struct HelloStringExercise { - test_word: String, - string_ptr_loc: MemArea, - string_len_loc: MemArea, - return_ptr_loc: MemArea, -} - -impl HelloStringExercise { - pub fn strat() -> BoxedStrategy { - (test_string_strategy(),) - .prop_flat_map(|(test_word,)| { - ( - Just(test_word.clone()), - HostMemory::mem_area_strat(test_word.len() as u32), - HostMemory::mem_area_strat(4), - HostMemory::mem_area_strat(4), - ) - }) - .prop_map( - |(test_word, string_ptr_loc, string_len_loc, return_ptr_loc)| Self { - test_word, - string_ptr_loc, - string_len_loc, - return_ptr_loc, - }, - ) - .prop_filter("non-overlapping pointers", |e| { - MemArea::non_overlapping_set(&[ - &e.string_ptr_loc, - &e.string_len_loc, - &e.return_ptr_loc, - ]) - }) - .boxed() - } - - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - // Populate string length - *guest_memory - .ptr_mut(self.string_len_loc.ptr) - .expect("ptr mut to string len") - .as_ref_mut() - .expect("deref ptr mut to string len") = self.test_word.len() as u32; - - // Populate string in guest's memory - { - let mut next: GuestPtrMut<'_, u8> = guest_memory - .ptr_mut(self.string_ptr_loc.ptr) - .expect("ptr mut to the first byte of string"); - for byte in self.test_word.as_bytes() { - *next.as_ref_mut().expect("deref mut") = *byte; - next = next.elem(1).expect("increment ptr by 1"); - } - } - - let res = foo::hello_string( - &mut ctx, - &mut guest_memory, - self.string_ptr_loc.ptr as i32, - self.string_len_loc.ptr as i32, - self.return_ptr_loc.ptr as i32, - ); - assert_eq!(res, types::Errno::Ok.into(), "hello string errno"); - - let given = *guest_memory - .ptr::(self.return_ptr_loc.ptr) - .expect("ptr to return value") - .as_ref() - .expect("deref ptr to return value"); - assert_eq!(self.test_word.len() as u32, given); - } -} -proptest! { - #[test] - fn hello_string(e in HelloStringExercise::strat()) { - e.test() - } -} - -fn cookie_strat() -> impl Strategy { - (0..std::u64::MAX) - .prop_map(|x| types::Cookie::try_from(x).expect("within range of cookie")) - .boxed() -} - -#[derive(Debug)] -struct CookieCutterExercise { - cookie: types::Cookie, - return_ptr_loc: MemArea, -} - -impl CookieCutterExercise { - pub fn strat() -> BoxedStrategy { - (cookie_strat(), HostMemory::mem_area_strat(4)) - .prop_map(|(cookie, return_ptr_loc)| Self { - cookie, - return_ptr_loc, - }) - .boxed() - } - - pub fn test(&self) { - let mut ctx = WasiCtx::new(); - let mut host_memory = HostMemory::new(); - let mut guest_memory = host_memory.guest_memory(); - - let res = foo::cookie_cutter( - &mut ctx, - &mut guest_memory, - self.cookie.into(), - self.return_ptr_loc.ptr as i32, - ); - assert_eq!(res, types::Errno::Ok.into(), "cookie cutter errno"); - - let is_cookie_start = *guest_memory - .ptr::(self.return_ptr_loc.ptr) - .expect("ptr to returned Bool") - .as_ref() - .expect("deref to Bool value"); - - assert_eq!( - if is_cookie_start == types::Bool::True { - true - } else { - false - }, - self.cookie == types::Cookie::START, - "returned Bool should test if input was Cookie::START", - ); - } -} -proptest! { - #[test] - fn cookie_cutter(e in CookieCutterExercise::strat()) { - e.test() - } -} diff --git a/tests/pointers.rs b/tests/pointers.rs new file mode 100644 index 0000000000..a85e11ec9f --- /dev/null +++ b/tests/pointers.rs @@ -0,0 +1,197 @@ +use proptest::prelude::*; +use wiggle_runtime::{GuestError, GuestPtr, GuestPtrMut, GuestRefMut}; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/pointers.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl pointers::Pointers for WasiCtx { + fn pointers_and_enums( + &mut self, + input1: types::Excuse, + input2_ptr: GuestPtrMut, + input3_ptr: GuestPtr, + input4_ptr_ptr: GuestPtrMut>, + ) -> Result<(), types::Errno> { + println!("BAZ input1 {:?}", input1); + // Read enum value from mutable: + let mut input2_ref: GuestRefMut = input2_ptr.as_ref_mut().map_err(|e| { + eprintln!("input2_ptr error: {}", e); + types::Errno::InvalidArg + })?; + let input2: types::Excuse = *input2_ref; + println!("input2 {:?}", input2); + + // Read enum value from immutable ptr: + let input3 = *input3_ptr.as_ref().map_err(|e| { + eprintln!("input3_ptr error: {}", e); + types::Errno::InvalidArg + })?; + println!("input3 {:?}", input3); + + // Write enum to mutable ptr: + *input2_ref = input3; + println!("wrote to input2_ref {:?}", input3); + + // Read ptr value from mutable ptr: + let input4_ptr: GuestPtr = wiggle_runtime::GuestTypeClone::read_from_guest( + &input4_ptr_ptr.as_immut(), + ) + .map_err(|e| { + eprintln!("input4_ptr_ptr error: {}", e); + types::Errno::InvalidArg + })?; + + // Read enum value from that ptr: + let input4: types::Excuse = *input4_ptr.as_ref().map_err(|e| { + eprintln!("input4_ptr error: {}", e); + types::Errno::InvalidArg + })?; + println!("input4 {:?}", input4); + + // Write ptr value to mutable ptr: + input4_ptr_ptr.write_ptr_to_guest(&input2_ptr.as_immut()); + + Ok(()) + } +} + +fn excuse_strat() -> impl Strategy { + prop_oneof![ + Just(types::Excuse::DogAte), + Just(types::Excuse::Traffic), + Just(types::Excuse::Sleeping), + ] + .boxed() +} + +#[derive(Debug)] +struct PointersAndEnumsExercise { + pub input1: types::Excuse, + pub input2: types::Excuse, + pub input2_loc: MemArea, + pub input3: types::Excuse, + pub input3_loc: MemArea, + pub input4: types::Excuse, + pub input4_loc: MemArea, + pub input4_ptr_loc: MemArea, +} + +impl PointersAndEnumsExercise { + pub fn strat() -> BoxedStrategy { + ( + excuse_strat(), + excuse_strat(), + HostMemory::mem_area_strat(4), + excuse_strat(), + HostMemory::mem_area_strat(4), + excuse_strat(), + HostMemory::mem_area_strat(4), + HostMemory::mem_area_strat(4), + ) + .prop_map( + |( + input1, + input2, + input2_loc, + input3, + input3_loc, + input4, + input4_loc, + input4_ptr_loc, + )| PointersAndEnumsExercise { + input1, + input2, + input2_loc, + input3, + input3_loc, + input4, + input4_loc, + input4_ptr_loc, + }, + ) + .prop_filter("non-overlapping pointers", |e| { + MemArea::non_overlapping_set(&[ + &e.input2_loc, + &e.input3_loc, + &e.input4_loc, + &e.input4_ptr_loc, + ]) + }) + .boxed() + } + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + *guest_memory + .ptr_mut(self.input2_loc.ptr) + .expect("input2 ptr") + .as_ref_mut() + .expect("input2 ref_mut") = self.input2; + + *guest_memory + .ptr_mut(self.input3_loc.ptr) + .expect("input3 ptr") + .as_ref_mut() + .expect("input3 ref_mut") = self.input3; + + *guest_memory + .ptr_mut(self.input4_loc.ptr) + .expect("input4 ptr") + .as_ref_mut() + .expect("input4 ref_mut") = self.input4; + + *guest_memory + .ptr_mut(self.input4_ptr_loc.ptr) + .expect("input4 ptr ptr") + .as_ref_mut() + .expect("input4 ptr ref_mut") = self.input4_loc.ptr; + + let e = pointers::pointers_and_enums( + &mut ctx, + &mut guest_memory, + self.input1.into(), + self.input2_loc.ptr as i32, + self.input3_loc.ptr as i32, + self.input4_ptr_loc.ptr as i32, + ); + assert_eq!(e, types::Errno::Ok.into(), "errno"); + + // Implementation of pointers_and_enums writes input3 to the input2_loc: + let written_to_input2_loc: i32 = *guest_memory + .ptr(self.input2_loc.ptr) + .expect("input2 ptr") + .as_ref() + .expect("input2 ref"); + + assert_eq!( + written_to_input2_loc, + self.input3.into(), + "pointers_and_enums written to input2" + ); + + // Implementation of pointers_and_enums writes input2_loc to input4_ptr_loc: + let written_to_input4_ptr: u32 = *guest_memory + .ptr(self.input4_ptr_loc.ptr) + .expect("input4_ptr_loc ptr") + .as_ref() + .expect("input4_ptr_loc ref"); + + assert_eq!( + written_to_input4_ptr, self.input2_loc.ptr, + "pointers_and_enums written to input4_ptr" + ); + } +} +proptest! { + #[test] + fn pointers_and_enums(e in PointersAndEnumsExercise::strat()) { + e.test(); + } +} diff --git a/tests/pointers.witx b/tests/pointers.witx new file mode 100644 index 0000000000..9a73a37520 --- /dev/null +++ b/tests/pointers.witx @@ -0,0 +1,11 @@ +(use "errno.witx") +(use "excuse.witx") + +(module $pointers + (@interface func (export "pointers_and_enums") + (param $an_excuse $excuse) + (param $an_excuse_by_reference (@witx pointer $excuse)) + (param $a_lamer_excuse (@witx const_pointer $excuse)) + (param $two_layers_of_excuses (@witx pointer (@witx const_pointer $excuse))) + (result $error $errno)) +) diff --git a/tests/strings.rs b/tests/strings.rs new file mode 100644 index 0000000000..f25c8dc9e6 --- /dev/null +++ b/tests/strings.rs @@ -0,0 +1,107 @@ +use proptest::prelude::*; +use wiggle_runtime::{GuestError, GuestPtrMut, GuestString}; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/strings.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl strings::Strings for WasiCtx { + fn hello_string(&mut self, a_string: &GuestString<'_>) -> Result { + let as_ref = a_string.as_ref().expect("deref ptr should succeed"); + let as_str = as_ref.as_str().expect("valid UTF-8 string"); + println!("a_string='{}'", as_str); + Ok(as_str.len() as u32) + } +} + +fn test_string_strategy() -> impl Strategy { + "\\p{Greek}{1,256}" +} + +#[derive(Debug)] +struct HelloStringExercise { + test_word: String, + string_ptr_loc: MemArea, + string_len_loc: MemArea, + return_ptr_loc: MemArea, +} + +impl HelloStringExercise { + pub fn strat() -> BoxedStrategy { + (test_string_strategy(),) + .prop_flat_map(|(test_word,)| { + ( + Just(test_word.clone()), + HostMemory::mem_area_strat(test_word.len() as u32), + HostMemory::mem_area_strat(4), + HostMemory::mem_area_strat(4), + ) + }) + .prop_map( + |(test_word, string_ptr_loc, string_len_loc, return_ptr_loc)| Self { + test_word, + string_ptr_loc, + string_len_loc, + return_ptr_loc, + }, + ) + .prop_filter("non-overlapping pointers", |e| { + MemArea::non_overlapping_set(&[ + &e.string_ptr_loc, + &e.string_len_loc, + &e.return_ptr_loc, + ]) + }) + .boxed() + } + + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + // Populate string length + *guest_memory + .ptr_mut(self.string_len_loc.ptr) + .expect("ptr mut to string len") + .as_ref_mut() + .expect("deref ptr mut to string len") = self.test_word.len() as u32; + + // Populate string in guest's memory + { + let mut next: GuestPtrMut<'_, u8> = guest_memory + .ptr_mut(self.string_ptr_loc.ptr) + .expect("ptr mut to the first byte of string"); + for byte in self.test_word.as_bytes() { + *next.as_ref_mut().expect("deref mut") = *byte; + next = next.elem(1).expect("increment ptr by 1"); + } + } + + let res = strings::hello_string( + &mut ctx, + &mut guest_memory, + self.string_ptr_loc.ptr as i32, + self.string_len_loc.ptr as i32, + self.return_ptr_loc.ptr as i32, + ); + assert_eq!(res, types::Errno::Ok.into(), "hello string errno"); + + let given = *guest_memory + .ptr::(self.return_ptr_loc.ptr) + .expect("ptr to return value") + .as_ref() + .expect("deref ptr to return value"); + assert_eq!(self.test_word.len() as u32, given); + } +} +proptest! { + #[test] + fn hello_string(e in HelloStringExercise::strat()) { + e.test() + } +} diff --git a/tests/strings.witx b/tests/strings.witx new file mode 100644 index 0000000000..ebc0f8bf05 --- /dev/null +++ b/tests/strings.witx @@ -0,0 +1,8 @@ +(use "errno.witx") +(module $strings + (@interface func (export "hello_string") + (param $a_string string) + (result $error $errno) + (result $total_bytes u32) + ) +) diff --git a/tests/structs.rs b/tests/structs.rs new file mode 100644 index 0000000000..c2e4263dad --- /dev/null +++ b/tests/structs.rs @@ -0,0 +1,202 @@ +use proptest::prelude::*; +use wiggle_runtime::GuestError; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle_generate::from_witx!({ + witx: ["tests/structs.witx"], + ctx: WasiCtx, +}); + +impl_errno!(types::Errno); + +impl structs::Structs for WasiCtx { + fn sum_of_pair(&mut self, an_pair: &types::PairInts) -> Result { + Ok(an_pair.first as i64 + an_pair.second as i64) + } + + fn sum_of_pair_of_ptrs(&mut self, an_pair: &types::PairIntPtrs) -> Result { + let first = *an_pair + .first + .as_ref() + .expect("dereferencing GuestPtr should succeed"); + let second = *an_pair + .second + .as_ref() + .expect("dereferncing GuestPtr should succeed"); + Ok(first as i64 + second as i64) + } +} + +#[derive(Debug)] +struct SumOfPairExercise { + pub input: types::PairInts, + pub input_loc: MemArea, + pub return_loc: MemArea, +} + +impl SumOfPairExercise { + pub fn strat() -> BoxedStrategy { + ( + prop::num::i32::ANY, + prop::num::i32::ANY, + HostMemory::mem_area_strat(8), + HostMemory::mem_area_strat(8), + ) + .prop_map(|(first, second, input_loc, return_loc)| SumOfPairExercise { + input: types::PairInts { first, second }, + input_loc, + return_loc, + }) + .prop_filter("non-overlapping pointers", |e| { + MemArea::non_overlapping_set(&[&e.input_loc, &e.return_loc]) + }) + .boxed() + } + + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + *guest_memory + .ptr_mut(self.input_loc.ptr) + .expect("input ptr") + .as_ref_mut() + .expect("input ref_mut") = self.input.first; + *guest_memory + .ptr_mut(self.input_loc.ptr + 4) + .expect("input ptr") + .as_ref_mut() + .expect("input ref_mut") = self.input.second; + let sum_err = structs::sum_of_pair( + &mut ctx, + &mut guest_memory, + self.input_loc.ptr as i32, + self.return_loc.ptr as i32, + ); + + assert_eq!(sum_err, types::Errno::Ok.into(), "sum errno"); + + let return_val: i64 = *guest_memory + .ptr(self.return_loc.ptr) + .expect("return ptr") + .as_ref() + .expect("return ref"); + + assert_eq!( + return_val, + self.input.first as i64 + self.input.second as i64, + "sum return value" + ); + } +} + +proptest! { + #[test] + fn sum_of_pair(e in SumOfPairExercise::strat()) { + e.test(); + } +} + +#[derive(Debug)] +struct SumPairPtrsExercise { + input_first: i32, + input_second: i32, + input_first_loc: MemArea, + input_second_loc: MemArea, + input_struct_loc: MemArea, + return_loc: MemArea, +} + +impl SumPairPtrsExercise { + pub fn strat() -> BoxedStrategy { + ( + prop::num::i32::ANY, + prop::num::i32::ANY, + HostMemory::mem_area_strat(4), + HostMemory::mem_area_strat(4), + HostMemory::mem_area_strat(8), + HostMemory::mem_area_strat(8), + ) + .prop_map( + |( + input_first, + input_second, + input_first_loc, + input_second_loc, + input_struct_loc, + return_loc, + )| SumPairPtrsExercise { + input_first, + input_second, + input_first_loc, + input_second_loc, + input_struct_loc, + return_loc, + }, + ) + .prop_filter("non-overlapping pointers", |e| { + MemArea::non_overlapping_set(&[ + &e.input_first_loc, + &e.input_second_loc, + &e.input_struct_loc, + &e.return_loc, + ]) + }) + .boxed() + } + pub fn test(&self) { + let mut ctx = WasiCtx::new(); + let mut host_memory = HostMemory::new(); + let mut guest_memory = host_memory.guest_memory(); + + *guest_memory + .ptr_mut(self.input_first_loc.ptr) + .expect("input_first ptr") + .as_ref_mut() + .expect("input_first ref") = self.input_first; + *guest_memory + .ptr_mut(self.input_second_loc.ptr) + .expect("input_second ptr") + .as_ref_mut() + .expect("input_second ref") = self.input_second; + + *guest_memory + .ptr_mut(self.input_struct_loc.ptr) + .expect("input_struct ptr") + .as_ref_mut() + .expect("input_struct ref") = self.input_first_loc.ptr; + *guest_memory + .ptr_mut(self.input_struct_loc.ptr + 4) + .expect("input_struct ptr") + .as_ref_mut() + .expect("input_struct ref") = self.input_second_loc.ptr; + + let res = structs::sum_of_pair_of_ptrs( + &mut ctx, + &mut guest_memory, + self.input_struct_loc.ptr as i32, + self.return_loc.ptr as i32, + ); + + assert_eq!(res, types::Errno::Ok.into(), "sum of pair of ptrs errno"); + + let doubled: i64 = *guest_memory + .ptr(self.return_loc.ptr) + .expect("return ptr") + .as_ref() + .expect("return ref"); + + assert_eq!( + doubled, + (self.input_first as i64) + (self.input_second as i64), + "sum of pair of ptrs return val" + ); + } +} +proptest! { + #[test] + fn sum_of_pair_of_ptrs(e in SumPairPtrsExercise::strat()) { + e.test() + } +} diff --git a/tests/structs.witx b/tests/structs.witx new file mode 100644 index 0000000000..e9b4140c1e --- /dev/null +++ b/tests/structs.witx @@ -0,0 +1,23 @@ + +(use "errno.witx") + +(typename $pair_ints + (struct + (field $first s32) + (field $second s32))) + +(typename $pair_int_ptrs + (struct + (field $first (@witx const_pointer s32)) + (field $second (@witx const_pointer s32)))) + +(module $structs + (@interface func (export "sum_of_pair") + (param $an_pair $pair_ints) + (result $error $errno) + (result $doubled s64)) + (@interface func (export "sum_of_pair_of_ptrs") + (param $an_pair $pair_int_ptrs) + (result $error $errno) + (result $doubled s64)) +) diff --git a/tests/test.witx b/tests/test.witx deleted file mode 100644 index 78f69a3276..0000000000 --- a/tests/test.witx +++ /dev/null @@ -1,84 +0,0 @@ -(use "errno.witx") - -(typename $excuse - (enum u8 - $dog_ate - $traffic - $sleeping)) - -(typename $car_config - (flags u8 - $automatic - $awd - $suv)) - -(typename $cookie - (int u64 - (const $start 0))) - -(typename $bool - (enum u8 - $false - $true)) - -(typename $pair_ints - (struct - (field $first s32) - (field $second s32))) - -(typename $pair_int_ptrs - (struct - (field $first (@witx const_pointer s32)) - (field $second (@witx const_pointer s32)))) - -(typename $named_ptr (@witx pointer f32)) -(typename $named_ptr_to_ptr (@witx pointer (@witx pointer f64))) - -(typename $const_excuse_array (array (@witx const_pointer $excuse))) -(typename $excuse_array (array (@witx pointer $excuse))) - -(module $foo - (@interface func (export "baz") - (param $an_excuse $excuse) - (param $an_excuse_by_reference (@witx pointer $excuse)) - (param $a_lamer_excuse (@witx const_pointer $excuse)) - (param $two_layers_of_excuses (@witx pointer (@witx const_pointer $excuse))) - (result $error $errno)) - (@interface func (export "bat") - (param $an_int u32) - (result $error $errno) - (result $doubled_it f32)) - (@interface func (export "sum_of_pair") - (param $an_pair $pair_ints) - (result $error $errno) - (result $doubled s64)) - (@interface func (export "sum_of_pair_of_ptrs") - (param $an_pair $pair_int_ptrs) - (result $error $errno) - (result $doubled s64)) - (@interface func (export "reduce_excuses") - (param $excuses $const_excuse_array) - (result $error $errno) - (result $reduced $excuse) - ) - (@interface func (export "populate_excuses") - (param $excuses $excuse_array) - (result $error $errno) - ) - (@interface func (export "configure_car") - (param $old_config $car_config) - (param $old_config_by_ptr (@witx const_pointer $car_config)) - (result $error $errno) - (result $new_config $car_config) - ) - (@interface func (export "hello_string") - (param $a_string string) - (result $error $errno) - (result $total_bytes u32) - ) - (@interface func (export "cookie_cutter") - (param $init_cookie $cookie) - (result $error $errno) - (result $is_start $bool) - ) -) diff --git a/tests/trivial.rs b/tests/trivial.rs deleted file mode 100644 index de97dd8a05..0000000000 --- a/tests/trivial.rs +++ /dev/null @@ -1,55 +0,0 @@ -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 deleted file mode 100644 index 3fbbf13e5c..0000000000 --- a/tests/trivial.witx +++ /dev/null @@ -1,8 +0,0 @@ -(use "errno.witx") - -(module $trivial - (@interface func (export "int_float_args") - (param $an_int u32) - (param $an_float f32) - (result $error $errno)) -)