1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
/// Execute the wiggle guest conversion code to exercise it
mod convert_just_errno {
use anyhow::Result;
use wiggle::GuestMemory;
use wiggle_test::{impl_errno, HostMemory, WasiCtx};
/// The `errors` argument to the wiggle gives us a hook to map a rich error
/// type like this one (typical of wiggle use cases in wasi-common and beyond)
/// down to the flat error enums that witx can specify.
#[derive(Debug, thiserror::Error)]
pub enum RichError {
#[error("Invalid argument: {0}")]
InvalidArg(String),
#[error("Won't cross picket line: {0}")]
PicketLine(String),
}
// Define an errno with variants corresponding to RichError. Use it in a
// trivial function.
wiggle::from_witx!({
witx_literal: "
(typename $errno (enum (@witx tag u8) $ok $invalid_arg $picket_line))
(module $one_error_conversion
(@interface func (export \"foo\")
(param $strike u32)
(result $err (expected (error $errno)))))
",
errors: { errno => trappable ErrnoT },
});
impl_errno!(types::Errno);
impl From<RichError> for types::ErrnoT {
fn from(rich: RichError) -> types::ErrnoT {
match rich {
RichError::InvalidArg(s) => {
types::ErrnoT::from(types::Errno::InvalidArg).context(s)
}
RichError::PicketLine(s) => {
types::ErrnoT::from(types::Errno::PicketLine).context(s)
}
}
}
}
impl<'a> one_error_conversion::OneErrorConversion for WasiCtx<'a> {
fn foo(&mut self, _memory: &mut GuestMemory<'_>, strike: u32) -> Result<(), types::ErrnoT> {
// We use the argument to this function to exercise all of the
// possible error cases we could hit here
match strike {
0 => Ok(()),
1 => Err(RichError::PicketLine(format!("I'm not a scab")))?,
_ => Err(RichError::InvalidArg(format!("out-of-bounds: {strike}")))?,
}
}
}
#[test]
fn one_error_conversion_test() {
let mut ctx = WasiCtx::new();
let mut host_memory = HostMemory::new();
let mut memory = host_memory.guest_memory();
// Exercise each of the branches in `foo`.
// Start with the success case:
let r0 = one_error_conversion::foo(&mut ctx, &mut memory, 0).unwrap();
assert_eq!(
r0,
types::Errno::Ok as i32,
"Expected return value for strike=0"
);
assert!(ctx.log.borrow().is_empty(), "No error log for strike=0");
// First error case:
let r1 = one_error_conversion::foo(&mut ctx, &mut memory, 1).unwrap();
assert_eq!(
r1,
types::Errno::PicketLine as i32,
"Expected return value for strike=1"
);
// Second error case:
let r2 = one_error_conversion::foo(&mut ctx, &mut memory, 2).unwrap();
assert_eq!(
r2,
types::Errno::InvalidArg as i32,
"Expected return value for strike=2"
);
}
}
/// Type-check the wiggle guest conversion code against a more complex case where
/// we use two distinct error types.
mod convert_multiple_error_types {
pub use super::convert_just_errno::RichError;
use anyhow::Result;
use wiggle::GuestMemory;
use wiggle_test::{impl_errno, WasiCtx};
/// Test that we can map multiple types of errors.
#[derive(Debug, thiserror::Error)]
#[allow(dead_code)]
pub enum AnotherRichError {
#[error("I've had this many cups of coffee and can't even think straight: {0}")]
TooMuchCoffee(usize),
}
// Just like the prior test, except that we have a second errno type. This should mean there
// are two functions in UserErrorConversion.
// Additionally, test that the function "baz" marked noreturn always returns a wasmtime::Trap.
wiggle::from_witx!({
witx_literal: "
(typename $errno (enum (@witx tag u8) $ok $invalid_arg $picket_line))
(typename $errno2 (enum (@witx tag u8) $ok $too_much_coffee))
(module $two_error_conversions
(@interface func (export \"foo\")
(param $strike u32)
(result $err (expected (error $errno))))
(@interface func (export \"bar\")
(param $drink u32)
(result $err (expected (error $errno2))))
(@interface func (export \"baz\")
(param $drink u32)
(@witx noreturn)))
",
errors: { errno => RichError, errno2 => AnotherRichError },
});
impl_errno!(types::Errno);
impl_errno!(types::Errno2);
// The UserErrorConversion trait will also have two methods for this test. They correspond to
// each member of the `errors` mapping.
// Bodies elided.
impl<'a> types::UserErrorConversion for WasiCtx<'a> {
fn errno_from_rich_error(&mut self, _e: RichError) -> Result<types::Errno> {
unimplemented!()
}
fn errno2_from_another_rich_error(
&mut self,
_e: AnotherRichError,
) -> Result<types::Errno2> {
unimplemented!()
}
}
// And here's the witx module trait impl, bodies elided
impl<'a> two_error_conversions::TwoErrorConversions for WasiCtx<'a> {
fn foo(&mut self, _: &mut GuestMemory<'_>, _: u32) -> Result<(), RichError> {
unimplemented!()
}
fn bar(&mut self, _: &mut GuestMemory<'_>, _: u32) -> Result<(), AnotherRichError> {
unimplemented!()
}
fn baz(&mut self, _: &mut GuestMemory<'_>, _: u32) -> anyhow::Error {
unimplemented!()
}
}
}
|