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