Merge pull request #16 from kubkon/pch/factor_tests

Start factoring tests
This commit is contained in:
Pat Hickey
2020-02-21 15:04:50 -08:00
committed by GitHub
9 changed files with 236 additions and 148 deletions

View File

@@ -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"]

2
crates/test/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target
Cargo.lock

9
crates/test/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "wiggle-test"
version = "0.1.0"
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>"]
edition = "2018"
[dependencies]
wiggle-runtime = { path = "../runtime" }
proptest = "0.9"

127
crates/test/src/lib.rs Normal file
View File

@@ -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<MemArea> {
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<GuestError>,
}
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
}
}
};
}

8
tests/errno.witx Normal file
View File

@@ -0,0 +1,8 @@
(typename $errno
(enum u32
$ok
$invalid_arg
$dont_want_to
$physically_unable
$picket_line))

View File

@@ -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<GuestError>,
}
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<MemArea> {
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,

View File

@@ -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))

55
tests/trivial.rs Normal file
View File

@@ -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<Self> {
(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()
}
}

8
tests/trivial.witx Normal file
View File

@@ -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))
)