extern crate alloc; #[cfg(feature = "std")] extern crate std; use alloc::{ format, string::{String, ToString}, sync::Arc, }; use core::{ fmt, sync::atomic::{AtomicU32, Ordering::SeqCst}, }; #[cfg(feature = "std")] use std::backtrace::BacktraceStatus; use wasmtime_internal_core::error::{ Context, Error, OutOfMemory, Result, bail, ensure, format_err, }; #[derive(Debug)] struct TestError(u32); impl fmt::Display for TestError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } impl core::error::Error for TestError {} #[derive(Debug)] struct CountDrops(Arc); impl CountDrops { fn new(drops: &Arc) -> Self { CountDrops(drops.clone()) } } impl Drop for CountDrops { fn drop(&mut self) { self.0.fetch_add(1, SeqCst); } } impl fmt::Display for CountDrops { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } impl core::error::Error for CountDrops {} #[derive(Debug)] struct ChainError { message: String, source: Option>, } impl fmt::Display for ChainError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.message) } } impl core::error::Error for ChainError { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { let source = self.source.as_ref()?; Some(&**source) } } impl ChainError { fn new( message: impl Into, source: Option>, ) -> Self { let message = message.into(); Self { message, source } } } #[test] fn new() { let mut error = Error::new(TestError(42)); assert!(error.is::()); assert_eq!(error.downcast_ref::().unwrap().0, 42); error.downcast_mut::().unwrap().0 += 1; assert_eq!(error.downcast_ref::().unwrap().0, 43); } #[test] fn new_drops() { let drops = Arc::new(AtomicU32::new(0)); let error = Error::new(CountDrops::new(&drops)); assert_eq!(drops.load(SeqCst), 0); drop(error); assert_eq!(drops.load(SeqCst), 1); } #[test] fn from_error_with_large_align() { // The `{ConcreteError,DynError}::error` fields are not at the same // offset when the concrete error's type requires greater-than-pointer // alignment. Exercise our various conversions and accesses to make sure // that we do the right thing in this case (that is morally cast `*mut // DynError` to `*mut ConcreteError` rather than casting `*mut // TypeErasedError` to `*mut E`). #[derive(Debug)] #[repr(align(16))] struct LargeAlign { value: u128, } impl fmt::Display for LargeAlign { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } impl core::error::Error for LargeAlign {} let value = 0x1234_6578_1234_5678_1234_6578_1234_5678; let error = Error::from(LargeAlign { value }); assert!(error.is::()); assert_eq!(error.downcast_ref::().unwrap().value, value); } #[test] fn msg() { let error = Error::msg("uh oh!"); assert!(error.is::<&str>()); let msg = error.to_string(); assert_eq!(msg, "uh oh!"); } #[test] fn msg_drops() { let drops = Arc::new(AtomicU32::new(0)); let error = Error::msg(CountDrops::new(&drops)); assert_eq!(drops.load(SeqCst), 0); drop(error); assert_eq!(drops.load(SeqCst), 1); } #[test] fn from_error() { let error = Error::from(TestError(42)); assert!(error.is::()); assert_eq!(error.downcast_ref::().unwrap().0, 42); } #[test] fn into_boxed_dyn_error() { let error = ChainError::new("ouch", None); let error = ChainError::new("whoops", Some(Box::new(error))); let error = Error::new(error).context("yikes"); let error = error.into_boxed_dyn_error(); assert_eq!(error.to_string(), "yikes"); let error = error.source().unwrap(); assert_eq!(error.to_string(), "whoops"); let error = error.source().unwrap(); assert_eq!(error.to_string(), "ouch"); assert!(error.source().is_none()); } #[test] fn is() { // `is` is true for `Error::msg(T)` let e = Error::msg("str"); assert!(e.is::<&str>()); assert!(!e.is::()); assert!(!e.is::()); // `is` is true for `T` in the context chain. let e = e.context(TestError(42)); assert!(e.is::<&str>()); assert!(e.is::()); assert!(!e.is::()); // `is` is still true when there are multiple `T`s and `U`s in the // chain. let e = e.context(TestError(36)).context("another str"); assert!(e.is::<&str>()); assert!(e.is::()); assert!(!e.is::()); // `is` is true for `Error::from(T)`. let e = Error::from(TestError(36)); assert!(e.is::()); assert!(!e.is::<&str>()); assert!(!e.is::()); // `is` is true for `Error::from(OutOfMemory)`. let e = Error::from(OutOfMemory::new(5)); assert!(e.is::()); assert!(!e.is::()); assert!(!e.is::<&str>()); } #[test] fn is_for_root_cause_with_initial_error_source() { let error = std::fmt::Error; let error = ChainError::new("whoops", Some(Box::new(error))); let error = Error::new(error); assert!(error.root_cause().is::()); } #[test] #[cfg(feature = "backtrace")] fn backtrace() { // Backtrace on OOM. let e = Error::from(OutOfMemory::new(5)); assert_eq!(e.backtrace().status(), BacktraceStatus::Disabled); let backtraces_enabled = match std::env::var("RUST_LIB_BACKTRACE").or_else(|_| std::env::var("RUST_BACKTRACE")) { Err(_) => false, Ok(s) if s == "0" => false, Ok(_) => true, }; let assert_backtrace = |e: Error| { if backtraces_enabled { assert!(matches!( e.backtrace().status(), BacktraceStatus::Unsupported | BacktraceStatus::Captured )); } else { assert_eq!(e.backtrace().status(), BacktraceStatus::Disabled); } }; // Backtrace on `Error::msg`. assert_backtrace(Error::msg("whoops")); // Backtrace on `Error::new`. assert_backtrace(Error::new(TestError(42))); // Backtrace on context chain. assert_backtrace(Error::new(TestError(42)).context("yikes")); } #[test] fn format_err_macro_string_literal() { let error = format_err!("literal"); assert_eq!(error.to_string(), "literal"); } #[test] fn format_err_macro_format_implicit_args() { let x = 42; let y = 36; let error = format_err!("implicit args {x} {y}"); assert_eq!(error.to_string(), "implicit args 42 36"); } #[test] fn format_err_macro_format_explicit_args() { let a = 84; let b = 72; let error = format_err!("explicit args {x} {y}", x = a / 2, y = b / 2); assert_eq!(error.to_string(), "explicit args 42 36"); } #[test] fn format_err_macro_core_error() { let error = TestError(42); let error = format_err!(error); assert!(error.is::()); assert_eq!(error.to_string(), "TestError(42)"); } #[test] fn format_err_macro_core_error_chain() { let error = ChainError::new("ouch", None); let error = ChainError::new("yikes", Some(Box::new(error))); let error = ChainError::new("whoops", Some(Box::new(error))); let error = format_err!(error); let mut chain = error.chain(); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "whoops"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "yikes"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "ouch"); assert!(chain.next().is_none()); } #[test] fn format_err_macro_msg() { let error = 42; let error = format_err!(error); assert_eq!(error.to_string(), "42"); } #[test] #[cfg(feature = "anyhow")] fn format_err_is_anyhow() { let error: anyhow::Error = anyhow::anyhow!("oof"); let error: Error = format_err!(error); assert!(error.is::()); assert_eq!(error.to_string(), "oof"); } #[test] fn bail_macro() { fn bail_string_literal() -> Result<()> { bail!("whoops") } assert_eq!(bail_string_literal().unwrap_err().to_string(), "whoops"); fn bail_format_implicit(x: u32) -> Result<()> { bail!("yikes {x}") } assert_eq!( bail_format_implicit(42).unwrap_err().to_string(), "yikes 42" ); fn bail_format_explicit(y: u32) -> Result<()> { bail!("ouch {}", y + 1) } assert_eq!(bail_format_explicit(35).unwrap_err().to_string(), "ouch 36"); fn bail_core_error() -> Result<()> { bail!(TestError(13)) } assert_eq!(bail_core_error().unwrap_err().to_string(), "TestError(13)"); fn bail_display() -> Result<()> { let x = 1337; bail!(x) } assert_eq!(bail_display().unwrap_err().to_string(), "1337"); } #[test] fn ensure_macro() { fn ensure_string_literal(c: bool) -> Result<()> { ensure!(c, "whoops"); Ok(()) } assert!(ensure_string_literal(true).is_ok()); assert_eq!( ensure_string_literal(false).unwrap_err().to_string(), "whoops" ); fn ensure_format_implicit(c: bool, x: u32) -> Result<()> { ensure!(c, "yikes {x}"); Ok(()) } assert!(ensure_format_implicit(true, 42).is_ok()); assert_eq!( ensure_format_implicit(false, 42).unwrap_err().to_string(), "yikes 42" ); fn ensure_format_explicit(c: bool, y: u32) -> Result<()> { ensure!(c, "ouch {}", y + 1); Ok(()) } assert!(ensure_format_explicit(true, 35).is_ok()); assert_eq!( ensure_format_explicit(false, 35).unwrap_err().to_string(), "ouch 36" ); fn ensure_core_error(c: bool) -> Result<()> { ensure!(c, TestError(13)); Ok(()) } assert!(ensure_core_error(true).is_ok()); assert_eq!( ensure_core_error(false).unwrap_err().to_string(), "TestError(13)" ); fn ensure_display(c: bool) -> Result<()> { let x = 1337; ensure!(c, x); Ok(()) } assert!(ensure_display(true).is_ok()); assert_eq!(ensure_display(false).unwrap_err().to_string(), "1337"); fn ensure_bool_ref(c: &bool) -> Result<()> { ensure!(c, "whoops"); Ok(()) } assert!(ensure_bool_ref(&true).is_ok()); assert_eq!(ensure_bool_ref(&false).unwrap_err().to_string(), "whoops"); fn ensure_no_message(a: u32) -> Result<()> { ensure!(a == 42); Ok(()) } assert!(ensure_no_message(42).is_ok()); assert_eq!( ensure_no_message(0).unwrap_err().to_string(), "Condition failed: `a == 42`" ); } #[test] fn downcast() { // Error::msg(T) let error = Error::msg("uh oh"); let error = error.downcast::().unwrap_err(); let error = error.downcast::().unwrap_err(); assert_eq!(error.downcast::<&str>().unwrap(), "uh oh"); // Error::new() let error = Error::new(TestError(42)); let error = error.downcast::<&str>().unwrap_err(); let error = error.downcast::().unwrap_err(); assert_eq!(error.downcast::().unwrap().0, 42); // Error::from(oom) let error = Error::from(OutOfMemory::new(5)); let error = error.downcast::<&str>().unwrap_err(); let error = error.downcast::().unwrap_err(); assert!(error.downcast::().is_ok()); // First in context chain. let error = Error::new(TestError(42)) .context("yikes") .context(OutOfMemory::new(5)); let error = error.downcast::().unwrap_err(); assert!(error.downcast::().is_ok()); // Middle in context chain. let error = Error::new(TestError(42)) .context("yikes") .context(OutOfMemory::new(5)); let error = error.downcast::().unwrap_err(); assert_eq!(error.downcast::<&str>().unwrap(), "yikes"); // Last in context chain. let error = Error::new(TestError(42)) .context("yikes") .context(OutOfMemory::new(5)); let error = error.downcast::().unwrap_err(); assert_eq!(error.downcast::().unwrap().0, 42); // Multiple `T`s in the context chain gives the first one. let error = Error::new(TestError(42)).context(TestError(36)); assert_eq!(error.downcast::().unwrap().0, 36); } #[test] fn downcast_drops_everything() { // Error::new let drops = Arc::new(AtomicU32::new(0)); let error = Error::new(CountDrops::new(&drops)) .context(CountDrops::new(&drops)) .context(CountDrops::new(&drops)); assert_eq!(drops.load(SeqCst), 0); let c = error.downcast::().unwrap(); assert_eq!(drops.load(SeqCst), 2); drop(c); assert_eq!(drops.load(SeqCst), 3); // Error::msg let drops = Arc::new(AtomicU32::new(0)); let error = Error::msg(CountDrops(drops.clone())) .context(CountDrops(drops.clone())) .context(CountDrops(drops.clone())); assert_eq!(drops.load(SeqCst), 0); let c = error.downcast::().unwrap(); assert_eq!(drops.load(SeqCst), 2); drop(c); assert_eq!(drops.load(SeqCst), 3); } #[test] fn downcast_ref() { // `Error::msg(T)` let e = Error::msg("str"); assert_eq!(e.downcast_ref::<&str>().copied().unwrap(), "str"); assert!(e.downcast_ref::().is_none()); assert!(e.downcast_ref::().is_none()); // Context chain. let e = e.context(TestError(42)); assert_eq!(e.downcast_ref::<&str>().copied().unwrap(), "str"); assert_eq!(e.downcast_ref::().unwrap().0, 42); assert!(e.downcast_ref::().is_none()); // Multiple `T`s in the context chain gives you the first one. let e = e.context("another str"); assert_eq!(e.downcast_ref::<&str>().copied().unwrap(), "another str"); assert_eq!(e.downcast_ref::().unwrap().0, 42); assert!(e.downcast_ref::().is_none()); // `Error::from(T)` let e = Error::from(TestError(36)); assert_eq!(e.downcast_ref::().unwrap().0, 36); assert!(e.downcast_ref::<&str>().is_none()); assert!(e.downcast_ref::().is_none()); // `Error::from(OutOfMemory)` let e = Error::from(OutOfMemory::new(5)); assert!(e.downcast_ref::().is_some()); assert!(e.downcast_ref::().is_none()); assert!(e.downcast_ref::<&str>().is_none()); } #[test] fn downcast_mut() { // `Error::msg(T)` let mut e = Error::msg("str"); assert!(e.downcast_mut::().is_none()); assert!(e.downcast_mut::().is_none()); *e.downcast_mut::<&str>().unwrap() = "whoops"; assert_eq!(*e.downcast_ref::<&str>().unwrap(), "whoops"); // Context chain. let mut e = e.context(TestError(42)); assert!(e.downcast_mut::().is_none()); *e.downcast_mut::<&str>().unwrap() = "uh oh"; assert_eq!(*e.downcast_ref::<&str>().unwrap(), "uh oh"); e.downcast_mut::().unwrap().0 += 1; assert_eq!(e.downcast_ref::().unwrap().0, 43); // Multiple `T`s in the context chain gives you the first one. let mut e = e.context("another str"); *e.downcast_mut::<&str>().unwrap() = "yikes"; assert_eq!(*e.downcast_ref::<&str>().unwrap(), "yikes"); assert_eq!(format!("{e:#}"), "yikes: TestError(43): uh oh"); // `Error::from(T)` let mut e = Error::from(TestError(36)); assert!(e.downcast_mut::<&str>().is_none()); assert!(e.downcast_mut::().is_none()); e.downcast_mut::().unwrap().0 += 1; assert_eq!(e.downcast_ref::().unwrap().0, 37); // `Error::from(OutOfMemory)` let mut e = Error::from(OutOfMemory::new(5)); assert!(e.downcast_mut::().is_some()); assert!(e.downcast_mut::().is_none()); assert!(e.downcast_mut::<&str>().is_none()); } #[test] fn context_on_oom() { let error = Error::new(OutOfMemory::new(5)); let error = error.context("yikes"); assert!(error.is::()); assert!( !error.is::<&str>(), "shouldn't attempt to box up more context when we've already exhausted memory" ); } #[test] fn context_on_ok_result() { let result: Result = Ok(42); let result = result.context("uh oh").context(TestError(1337)); assert_eq!(result.unwrap(), 42); } #[test] fn context_on_err_result() { let result: Result = Err(Error::new(TestError(42))).context("uh oh"); let error = result.unwrap_err(); assert!(error.is::()); assert_eq!(error.downcast_ref::().unwrap().0, 42); assert!(error.is::<&str>()); assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); } #[test] fn context_on_some_option() { let option = Some(42); let result = option.context("uh oh").context(TestError(1337)); assert_eq!(result.unwrap(), 42); } #[test] fn context_on_none_option() { let option: Option = None; let result = option.context(TestError(42)).context("uh oh"); let error = result.unwrap_err(); assert!(error.is::()); assert_eq!(error.downcast_ref::().unwrap().0, 42); assert!(error.is::<&str>()); assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); } #[test] fn with_context_on_ok_result() { let result: Result = Ok(42); let result = result .with_context(|| "uh oh") .with_context(|| TestError(36)); assert_eq!(result.unwrap(), 42); } #[test] fn with_context_on_err_result() { let result: Result = Err(Error::new(TestError(36))); let result = result.with_context(|| "uh oh"); let error = result.unwrap_err(); assert!(error.is::()); assert!(error.is::<&str>()); assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); } #[test] fn with_context_on_some_option() { let option = Some(36); let result = option .with_context(|| "uh oh") .with_context(|| TestError(42)); assert_eq!(result.unwrap(), 36); } #[test] fn with_context_on_none_option() { let option: Option = None; let result = option .with_context(|| "uh oh") .with_context(|| TestError(42)); let error = result.unwrap_err(); assert!(error.is::<&str>()); assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); assert!(error.is::()); assert_eq!(error.downcast_ref::().unwrap().0, 42); } #[test] fn fmt_debug() { let error = Error::msg("whoops").context("uh oh").context("yikes"); let actual = format!("{error:?}"); let expected = "\ yikes Caused by: 0: uh oh 1: whoops "; #[cfg(feature = "backtrace")] { assert!(actual.starts_with(expected)); if let BacktraceStatus::Captured = error.backtrace().status() { assert!(actual.contains("Stack backtrace:")); } } #[cfg(not(feature = "backtrace"))] { assert_eq!(actual, expected); } } #[test] fn fmt_debug_with_single_cause() { let error = Error::msg("whoops").context("uh oh"); let actual = format!("{error:?}"); // NB: the causes are only numbered when there are multiple of them. let expected = "\ uh oh Caused by: whoops "; #[cfg(feature = "backtrace")] { assert!(actual.starts_with(expected)); if let BacktraceStatus::Captured = error.backtrace().status() { assert!(actual.contains("Stack backtrace:")); } } #[cfg(not(feature = "backtrace"))] { assert_eq!(actual, expected); } } #[test] fn fmt_debug_alternate() { let error = ChainError::new("root cause", None); let error = ChainError::new("whoops", Some(Box::new(error))); let error = Error::new(error) .context(TestError(42)) .context("yikes") .context("ouch"); let actual = format!("{error:#?}"); let actual = actual.trim(); println!("actual `{{:#?}}` output:\n{actual}"); let expected = r#" Error { inner: DynError { error: ouch, source: Error { inner: DynError { error: yikes, source: Error { inner: DynError { error: TestError( 42, ), source: Error { inner: DynError { error: ChainError { message: "whoops", source: Some( ChainError { message: "root cause", source: None, }, ), }, }, }, }, }, }, }, }, } "# .trim(); println!("expected `{{:#?}}` output:\n{expected}"); assert_eq!(actual, expected); } #[test] fn fmt_debug_alternate_with_oom() { let error = Error::new(OutOfMemory::new(5)); let actual = format!("{error:#?}"); let actual = actual.trim(); println!("actual `{{:#?}}` output:\n{actual}"); let expected = r#" Error { inner: Oom( OutOfMemory { requested_allocation_size: 5, }, ), } "# .trim(); println!("expected `{{:#?}}` output:\n{expected}"); assert_eq!(actual, expected); } #[test] fn fmt_display() { let error = Error::msg("whoops").context("uh oh").context("yikes"); assert_eq!(format!("{error}"), "yikes"); } #[test] fn fmt_display_alternate() { let error = Error::msg("ouch") .context("whoops") .context("uh oh") .context("yikes"); assert_eq!(format!("{error:#}"), "yikes: uh oh: whoops: ouch"); } #[test] fn chain() { let error = Error::msg("failure") .context("uh oh") .context(TestError(42)); let mut chain = error.chain(); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "TestError(42)"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "uh oh"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "failure"); assert!(chain.next().is_none()); for _ in 0..100 { assert!(chain.next().is_none(), "`Chain` is a fused iterator"); } } #[test] fn chain_on_error_with_source() { let error = ChainError::new("yikes", None); let error = ChainError::new("whoops", Some(Box::new(error))); let error = ChainError::new("uh oh", Some(Box::new(error))); let error = Error::new(error).context("ouch").context("oof"); let mut chain = error.chain(); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "oof"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "ouch"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "uh oh"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "whoops"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "yikes"); assert!(chain.next().is_none()); } #[test] fn root_cause() { let error = ChainError::new("yikes", None); let error = ChainError::new("whoops", Some(Box::new(error))); let error = ChainError::new("uh oh", Some(Box::new(error))); let error = Error::new(error).context("ouch").context("oof"); let root = error.root_cause(); assert_eq!(root.to_string(), "yikes"); assert!(root.source().is_none()); } #[test] fn chain_with_leaf_sources() { #[derive(Debug)] struct ErrorWithSource(String, Box); impl fmt::Display for ErrorWithSource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl core::error::Error for ErrorWithSource { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { Some(&*self.1) } } let error = Error::new(ErrorWithSource("leaf".to_string(), Box::new(TestError(42)))) .context("oof") .context("wow"); let mut chain = error.chain(); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "wow"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "oof"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "leaf"); let e = chain.next().unwrap(); assert_eq!(e.to_string(), "TestError(42)"); assert!(chain.next().is_none()); } #[test] fn oom_requested_allocation_size() { // Check that a bunch of interesting allocation sizes roundtrip. for shift in 0..30 { let bytes = (1 << shift) - 1; let oom = OutOfMemory::new(bytes); assert_eq!(oom.requested_allocation_size(), bytes); let bytes = 1 << shift; let oom = OutOfMemory::new(bytes); assert_eq!(oom.requested_allocation_size(), bytes); let bytes = (1 << shift) + 1; let oom = OutOfMemory::new(bytes); assert_eq!(oom.requested_allocation_size(), bytes); } // We will round down if the allocation size is too large, but make no // specific guarantees about that behavior. let oom = OutOfMemory::new(usize::MAX); assert!(oom.requested_allocation_size() <= usize::try_from(isize::MAX).unwrap()); } #[test] #[cfg(feature = "anyhow")] fn anyhow_preserves_downcast() -> Result<()> { { let e: Error = TestError(1).into(); assert!(e.downcast_ref::().is_some()); let e: anyhow::Error = e.into(); assert!(e.downcast_ref::().is_some()); } { let e = Error::from(TestError(1)).context("hi"); assert!(e.downcast_ref::().is_some()); assert!(e.downcast_ref::<&str>().is_some()); let e: anyhow::Error = e.into(); assert!(e.downcast_ref::().is_some()); assert!(e.downcast_ref::<&str>().is_some()); } Ok(()) } #[test] #[cfg(feature = "anyhow")] fn anyhow_source_downcast() -> Result<()> { let e: anyhow::Error = TestError(1).into(); assert!(e.downcast_ref::().is_some()); let e: Error = Error::from_anyhow(e); assert!(e.downcast_ref::().is_some()); let e: anyhow::Error = e.into(); assert!(e.downcast_ref::().is_some()); Ok(()) } #[cfg(feature = "anyhow")] fn assert_chain(actual: impl IntoIterator, expected: impl IntoIterator) where T: PartialEq, T: fmt::Debug, U: fmt::Debug, { let mut actual = actual.into_iter(); let mut expected = expected.into_iter(); loop { match (actual.next(), expected.next()) { (None, None) => return, (None, Some(x)) => { panic!("expected {x:?} next in chain, but actual chain is exhausted") } (Some(x), None) => panic!("expected chain to be exhausted, but found {x:?}"), (Some(x), Some(y)) => assert_eq!(x, y), } } } #[test] #[cfg(feature = "anyhow")] fn anyhow_wasmtime_anyhow_sandwich_chain() -> Result<()> { let e: anyhow::Error = TestError(1).into(); let e = e.context(TestError(2)); assert_chain( e.chain().map(|e| e.to_string()), ["TestError(2)", "TestError(1)"], ); let e: Error = Error::from_anyhow(e); let e = e.context(TestError(3)); assert_chain( e.chain().map(|e| e.to_string()), ["TestError(3)", "TestError(2)", "TestError(1)"], ); let e: anyhow::Error = e.into(); let e = e.context(TestError(4)); assert_chain( e.chain().map(|e| e.to_string()), [ "TestError(4)", "TestError(3)", "TestError(2)", "TestError(1)", ], ); Ok(()) } #[test] #[cfg(feature = "anyhow")] fn wasmtime_anyhow_wasmtime_sandwich_chain() -> Result<()> { let e: Error = TestError(1).into(); let e = e.context(TestError(2)); assert_chain( e.chain().map(|e| e.to_string()), ["TestError(2)", "TestError(1)"], ); let e: anyhow::Error = e.into(); let e = e.context(TestError(3)); assert_chain( e.chain().map(|e| e.to_string()), ["TestError(3)", "TestError(2)", "TestError(1)"], ); let e: Error = Error::from_anyhow(e); let e = e.context(TestError(4)); assert_chain( e.chain().map(|e| e.to_string()), [ "TestError(4)", "TestError(3)", "TestError(2)", "TestError(1)", ], ); Ok(()) }