diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7dff904..21025a8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,26 +13,31 @@ on: types: - created - jobs: - coverage: + test: + name: coverage runs-on: ubuntu-latest - env: - CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v1 - uses: dtolnay/rust-toolchain@master with: target: x86_64-unknown-linux-gnu toolchain: nightly components: llvm-tools-preview + - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - name: Generate code coverage - run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json + run: > + curl -LsSf 'https://github.com/taiki-e/cargo-llvm-cov/releases/download/v0.5.23/cargo-llvm-cov-x86_64-unknown-linux-musl.tar.gz' + | tar xzf - + && mv cargo-llvm-cov $HOME/.cargo/bin + + - name: Run cargo-llvm-cov + run: cargo llvm-cov --doctests --all --all-features --lcov --output-path lcov.info + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - files: codecov.json - fail_ci_if_error: true + directory: ./ + fail_ci_if_error: false + files: ./lcov.info + verbose: true diff --git a/src/lib.rs b/src/lib.rs index 623523d..372cef2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -338,8 +338,11 @@ impl ErrorDown for Error { #[inline] fn downcast_chain_ref(&self) -> Option<&Error> { if self.is_chain::() { - // Use transmute when we've verified the types match - unsafe { Some(std::mem::transmute::<&Error, &Error>(self)) } + #[allow(clippy::cast_ptr_alignment)] + unsafe { + #[allow(trivial_casts)] + Some(*(self as *const dyn StdError as *const &Error)) + } } else { None } @@ -348,8 +351,11 @@ impl ErrorDown for Error { #[inline] fn downcast_chain_mut(&mut self) -> Option<&mut Error> { if self.is_chain::() { - // Use transmute when we've verified the types match - unsafe { Some(std::mem::transmute::<&mut Error, &mut Error>(self)) } + #[allow(clippy::cast_ptr_alignment)] + unsafe { + #[allow(trivial_casts)] + Some(&mut *(self as *mut dyn StdError as *mut &mut Error)) + } } else { None } @@ -357,8 +363,11 @@ impl ErrorDown for Error { #[inline] fn downcast_inner_ref(&self) -> Option<&T> { if self.is_chain::() { - // Use transmute when we've verified the types match - unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) } + #[allow(clippy::cast_ptr_alignment)] + unsafe { + #[allow(trivial_casts)] + Some(&(*(self as *const dyn StdError as *const &Error)).kind) + } } else { None } @@ -367,8 +376,11 @@ impl ErrorDown for Error { #[inline] fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is_chain::() { - // Use transmute when we've verified the types match - unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) } + #[allow(clippy::cast_ptr_alignment)] + unsafe { + #[allow(trivial_casts)] + Some(&mut (*(self as *mut dyn StdError as *mut &mut Error)).kind) + } } else { None } @@ -595,7 +607,6 @@ macro_rules! str_context { #[derive(Clone)] pub struct $e(pub String); impl $e { - #[allow(dead_code)] pub fn new>(s: S) -> Self { $e(s.into()) } @@ -677,12 +688,6 @@ macro_rules! str_context { /// do_some_io(filename).map_context(|e| ErrorKind::from(e))?; /// Ok(()) /// } -/// -/// # fn main() { -/// # if let Err(e) = func1() { -/// # eprintln!("Error:\n{:?}", e); -/// # } -/// # } /// ``` #[macro_export] macro_rules! err_kind { @@ -735,291 +740,3 @@ macro_rules! err_kind { } }; } - -#[cfg(test)] -mod tests { - use super::Context as _; - use super::*; - use std::io; - - #[test] - fn test_error_chain_with_multiple_causes() { - // Create a chain of errors - let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); - - str_context!(Level3Error); - str_context!(Level2Error); - str_context!(Level1Error); - - let err = Result::<(), _>::Err(io_error.into()) - .context(Level3Error("level 3".into())) - .context(Level2Error("level 2".into())) - .context(Level1Error("level 1".into())) - .unwrap_err(); - - // Test the error chain - assert!(err.is_chain::()); - assert!(err.find_chain_cause::().is_some()); - assert!(err.find_chain_cause::().is_some()); - assert!(err.find_chain_cause::().is_some()); - } - - #[test] - fn test_error_root_cause() { - let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); - - str_context!(WrapperError); - let err = Result::<(), _>::Err(io_error.into()) - .context(WrapperError("wrapper".into())) - .unwrap_err(); - - let root = err.root_cause().unwrap(); - assert!(root.is_chain::()); - } - - #[test] - fn test_error_display_and_debug() { - str_context!(CustomError); - let err = Error::new( - CustomError("test error".into()), - None, - Some("src/lib.rs:100".into()), - ); - - // Test Display formatting - assert_eq!(format!("{}", err), "test error"); - - // Test alternate Display formatting - assert_eq!(format!("{:#}", err), "test error"); - - // Test Debug formatting - let debug_output = format!("{:?}", err); - assert!(debug_output.contains("test error")); - assert!(debug_output.contains("src/lib.rs:100")); - } - - #[test] - fn test_error_annotation() { - let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); - let err = Result::<(), _>::Err(io_error.into()) - .annotate() - .unwrap_err(); - - assert!(err.source().is_some()); - err.source() - .unwrap() - .downcast_inner_ref::() - .unwrap(); - } - - #[test] - fn test_map_context() { - let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); - - str_context!(MappedError); - let err = Result::<(), _>::Err(io_error.into()) - .map_context(|e| MappedError(format!("Mapped: {}", e))) - .unwrap_err(); - - assert!(err.is_chain::()); - assert!(err.find_chain_cause::().is_some()); - } - - #[test] - fn test_error_downcasting() { - str_context!(OriginalError); - let original = Error::new(OriginalError("test".into()), None, None); - - let error: Box = Box::new(original); - - // Test downcast_chain_ref - assert!(error.is_chain::()); - assert!(error.downcast_chain_ref::().is_some()); - - // Test downcast_inner_ref - let inner = error.downcast_inner_ref::(); - assert!(inner.is_some()); - } - - #[derive(Debug, Clone)] - enum TestErrorKind { - Basic(String), - Complex { message: String }, - } - - impl Display for TestErrorKind { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - TestErrorKind::Basic(msg) => write!(f, "Basic error: {}", msg), - TestErrorKind::Complex { message } => write!(f, "Complex error: {}", message), - } - } - } - - #[test] - fn test_err_kind_macro() { - err_kind!(TestError, TestErrorKind); - - let err = TestError::from(TestErrorKind::Basic("test".into())); - assert!(matches!(err.kind(), TestErrorKind::Basic(_))); - // The annotated error should display "(passed error)" even in a chain - assert_eq!(format!("{}", err), "Basic error: test"); - assert_eq!(format!("{:?}", err), "Basic(\"test\")"); - - let complex_err = TestError::from(TestErrorKind::Complex { - message: "test".into(), - }); - assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. })); - // The annotated error should display "(passed error)" even in a chain - assert_eq!(format!("{}", complex_err), "Complex error: test"); - assert_eq!( - format!("{:?}", complex_err), - "Complex { message: \"test\" }" - ); - } - #[test] - fn test_annotated_error_display_and_debug() { - let annotated = AnnotatedError(()); - - // Test Display formatting - assert_eq!(format!("{}", annotated), "(passed error)"); - - // Test Debug formatting - assert_eq!(format!("{:?}", annotated), "(passed error)"); - - // Test with error chain - let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); - let err = Result::<(), _>::Err(io_error.into()) - .annotate() - .unwrap_err(); - - // The annotated error should display "(passed error)" even in a chain - assert_eq!(format!("{}", err), "(passed error)"); - assert!(format!("{:?}", err).contains("(passed error)")); - - // Verify the error chain is preserved - assert!(err.source().is_some()); - assert!(err.source().unwrap().is_chain::()); - } - - // Helper error types for testing - #[derive(Debug)] - struct TestError(String); - - impl std::fmt::Display for TestError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } - } - - impl std::error::Error for TestError {} - - #[test] - fn test_downcast_chain_operations() { - // Create a test error chain - let original_error = Error::new( - TestError("test message".to_string()), - None, - Some("test location".to_string()), - ); - - // Test is_chain - assert!(original_error.is_chain::()); - assert!(!original_error.is_chain::()); - - // Test downcast_chain_ref - let downcast_ref = original_error.downcast_chain_ref::(); - assert!(downcast_ref.is_some()); - let downcast_kind = downcast_ref.unwrap().kind(); - assert_eq!(format!("{}", downcast_kind), "test message"); - assert_eq!( - format!("{:?}", downcast_kind), - "TestError(\"test message\")" - ); - - // Test invalid downcast_chain_ref - let invalid_downcast = original_error.downcast_chain_ref::(); - assert!(invalid_downcast.is_none()); - - // Test downcast_chain_mut - let mut mutable_error = original_error; - let downcast_mut = mutable_error.downcast_chain_mut::(); - assert!(downcast_mut.is_some()); - assert_eq!(downcast_mut.unwrap().kind().0, "test message"); - - // Test invalid downcast_chain_mut - let invalid_downcast_mut = mutable_error.downcast_chain_mut::(); - assert!(invalid_downcast_mut.is_none()); - } - - #[test] - fn test_downcast_inner_operations() { - // Create a test error - let mut error = Error::new( - TestError("inner test".to_string()), - None, - Some("test location".to_string()), - ); - - // Test downcast_inner_ref - let inner_ref = error.downcast_inner_ref::(); - assert!(inner_ref.is_some()); - assert_eq!(inner_ref.unwrap().0, "inner test"); - // Test invalid downcast_inner_ref - let invalid_inner = error.downcast_inner_ref::(); - assert!(invalid_inner.is_none()); - - // Test downcast_inner_mut - let inner_mut = error.downcast_inner_mut::(); - assert!(inner_mut.is_some()); - assert_eq!(inner_mut.unwrap().0, "inner test"); - - // Test invalid downcast_inner_mut - let invalid_inner_mut = error.downcast_inner_mut::(); - assert!(invalid_inner_mut.is_none()); - } - - #[test] - fn test_error_down_for_dyn_error() { - // Create a boxed error - let error: Box = Box::new(Error::new( - TestError("dyn test".to_string()), - None, - Some("test location".to_string()), - )); - - // Test is_chain through trait object - assert!(error.is_chain::()); - assert!(!error.is_chain::()); - - // Test downcast_chain_ref through trait object - let chain_ref = error.downcast_chain_ref::(); - assert!(chain_ref.is_some()); - assert_eq!(chain_ref.unwrap().kind().0, "dyn test"); - - // Test downcast_inner_ref through trait object - let inner_ref = error.downcast_inner_ref::(); - assert!(inner_ref.is_some()); - assert_eq!(inner_ref.unwrap().0, "dyn test"); - } - - #[test] - fn test_error_down_with_sync_send() { - // Create a boxed error with Send + Sync - let error: Box = Box::new(Error::new( - TestError("sync test".to_string()), - None, - Some("test location".to_string()), - )); - - // Test operations on Send + Sync error - assert!(error.is_chain::()); - assert!(error.downcast_chain_ref::().is_some()); - assert!(error.downcast_inner_ref::().is_some()); - - // Test invalid downcasts - assert!(!error.is_chain::()); - assert!(error.downcast_chain_ref::().is_none()); - assert!(error.downcast_inner_ref::().is_none()); - } -}