Compare commits

..

No commits in common. "28eb28e47d0a08e880bc6c0041a462562648f5f9" and "24382fbaed2d2acd97076f1f1a46c854da6789e1" have entirely different histories.

2 changed files with 36 additions and 314 deletions

View file

@ -13,26 +13,31 @@ on:
types: types:
- created - created
jobs: jobs:
coverage: test:
name: coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
toolchain: nightly toolchain: nightly
components: llvm-tools-preview components: llvm-tools-preview
- name: Install cargo-llvm-cov - name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov run: >
- name: Generate code coverage 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'
run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json | 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 - name: Upload coverage to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos directory: ./
files: codecov.json fail_ci_if_error: false
fail_ci_if_error: true files: ./lcov.info
verbose: true

View file

@ -338,8 +338,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline] #[inline]
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> {
if self.is_chain::<T>() { if self.is_chain::<T>() {
// Use transmute when we've verified the types match #[allow(clippy::cast_ptr_alignment)]
unsafe { Some(std::mem::transmute::<&Error<U>, &Error<T>>(self)) } unsafe {
#[allow(trivial_casts)]
Some(*(self as *const dyn StdError as *const &Error<T>))
}
} else { } else {
None None
} }
@ -348,8 +351,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline] #[inline]
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> {
if self.is_chain::<T>() { if self.is_chain::<T>() {
// Use transmute when we've verified the types match #[allow(clippy::cast_ptr_alignment)]
unsafe { Some(std::mem::transmute::<&mut Error<U>, &mut Error<T>>(self)) } unsafe {
#[allow(trivial_casts)]
Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>))
}
} else { } else {
None None
} }
@ -357,8 +363,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline] #[inline]
fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> {
if self.is_chain::<T>() { if self.is_chain::<T>() {
// Use transmute when we've verified the types match #[allow(clippy::cast_ptr_alignment)]
unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) } unsafe {
#[allow(trivial_casts)]
Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind)
}
} else { } else {
None None
} }
@ -367,8 +376,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline] #[inline]
fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> {
if self.is_chain::<T>() { if self.is_chain::<T>() {
// Use transmute when we've verified the types match #[allow(clippy::cast_ptr_alignment)]
unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) } unsafe {
#[allow(trivial_casts)]
Some(&mut (*(self as *mut dyn StdError as *mut &mut Error<T>)).kind)
}
} else { } else {
None None
} }
@ -595,7 +607,6 @@ macro_rules! str_context {
#[derive(Clone)] #[derive(Clone)]
pub struct $e(pub String); pub struct $e(pub String);
impl $e { impl $e {
#[allow(dead_code)]
pub fn new<S: Into<String>>(s: S) -> Self { pub fn new<S: Into<String>>(s: S) -> Self {
$e(s.into()) $e(s.into())
} }
@ -677,12 +688,6 @@ macro_rules! str_context {
/// do_some_io(filename).map_context(|e| ErrorKind::from(e))?; /// do_some_io(filename).map_context(|e| ErrorKind::from(e))?;
/// Ok(()) /// Ok(())
/// } /// }
///
/// # fn main() {
/// # if let Err(e) = func1() {
/// # eprintln!("Error:\n{:?}", e);
/// # }
/// # }
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! err_kind { 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::<Level1Error>());
assert!(err.find_chain_cause::<Level2Error>().is_some());
assert!(err.find_chain_cause::<Level3Error>().is_some());
assert!(err.find_chain_cause::<io::Error>().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::<io::Error>());
}
#[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::<io::Error>()
.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::<MappedError>());
assert!(err.find_chain_cause::<io::Error>().is_some());
}
#[test]
fn test_error_downcasting() {
str_context!(OriginalError);
let original = Error::new(OriginalError("test".into()), None, None);
let error: Box<dyn StdError + Send + Sync> = Box::new(original);
// Test downcast_chain_ref
assert!(error.is_chain::<OriginalError>());
assert!(error.downcast_chain_ref::<OriginalError>().is_some());
// Test downcast_inner_ref
let inner = error.downcast_inner_ref::<OriginalError>();
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::<io::Error>());
}
// 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::<TestError>());
assert!(!original_error.is_chain::<io::Error>());
// Test downcast_chain_ref
let downcast_ref = original_error.downcast_chain_ref::<TestError>();
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::<io::Error>();
assert!(invalid_downcast.is_none());
// Test downcast_chain_mut
let mut mutable_error = original_error;
let downcast_mut = mutable_error.downcast_chain_mut::<TestError>();
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::<io::Error>();
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::<TestError>();
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::<io::Error>();
assert!(invalid_inner.is_none());
// Test downcast_inner_mut
let inner_mut = error.downcast_inner_mut::<TestError>();
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::<io::Error>();
assert!(invalid_inner_mut.is_none());
}
#[test]
fn test_error_down_for_dyn_error() {
// Create a boxed error
let error: Box<dyn std::error::Error + 'static> = 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::<TestError>());
assert!(!error.is_chain::<io::Error>());
// Test downcast_chain_ref through trait object
let chain_ref = error.downcast_chain_ref::<TestError>();
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::<TestError>();
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<dyn std::error::Error + Send + Sync> = 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::<TestError>());
assert!(error.downcast_chain_ref::<TestError>().is_some());
assert!(error.downcast_inner_ref::<TestError>().is_some());
// Test invalid downcasts
assert!(!error.is_chain::<io::Error>());
assert!(error.downcast_chain_ref::<io::Error>().is_none());
assert!(error.downcast_inner_ref::<io::Error>().is_none());
}
}