diff --git a/.cirrus.yml b/.cirrus.yml index 2402a204..d33f774d 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -16,8 +16,6 @@ stable_test_task: primary_test_script: - rustc --version - cargo test --all - backtrace_shim_test_script: - - cargo test --manifest-path compatibility-tests/backtrace-shim/Cargo.toml backtraces_impl_backtrace_crate_test_script: - cargo test --manifest-path compatibility-tests/backtraces-impl-backtrace-crate/Cargo.toml context_selectors_have_documentation_test_script: @@ -63,8 +61,6 @@ doc_test_task: - rustc +nightly --version docs_script: - cargo +nightly doc - backtraces_docs_script: - - cargo +nightly doc --features=backtraces backtraces_impl_backtrace_crate_docs_script: - cargo +nightly doc --features=backtraces-impl-backtrace-crate futures_docs_script: @@ -82,8 +78,6 @@ doc_tests_task: fingerprint_script: cat Cargo.toml version_information_script: - rustc +nightly --version - backtraces_doctests_script: - - cargo +nightly test --doc --features=backtraces backtraces_impl_backtrace_crate_doctests_script: - cargo +nightly test --doc --features=backtraces-impl-backtrace-crate futures_doctests_script: @@ -171,27 +165,27 @@ nightly_test_task: - cargo test before_cache_script: rm -rf $CARGO_HOME/registry/index -std_backtraces_test_task: - name: "Rust Beta (backtraces)" +# Our Minimal Supported Rust Version (MSRV) +v1_56_test_task: + name: "Rust 1.56" container: - image: rust:latest + image: rust:1.56 cpu: 1 memory: 2Gi cargo_cache: folder: $CARGO_HOME/registry fingerprint_script: cat Cargo.toml - setup_script: - - rustup toolchain install beta - backtraces_impl_std_test_script: - - cd compatibility-tests/backtraces-impl-std && RUST_BACKTRACE=1 cargo test - backtraces_impl_std_docs_script: - - cargo +beta doc --features=backtraces-impl-std + primary_test_script: + - rustup self update + - cd compatibility-tests/v1_56/ + - rustc --version + - cargo test before_cache_script: rm -rf $CARGO_HOME/registry/index -v1_56_test_task: - name: "Rust 1.56" +v1_61_test_task: + name: "Rust 1.61" container: - image: rust:1.56 + image: rust:1.61 cpu: 1 memory: 2Gi cargo_cache: @@ -199,7 +193,7 @@ v1_56_test_task: fingerprint_script: cat Cargo.toml primary_test_script: - rustup self update - - cd compatibility-tests/v1_56/ + - cd compatibility-tests/v1_61/ - rustc --version - cargo test before_cache_script: rm -rf $CARGO_HOME/registry/index diff --git a/Cargo.toml b/Cargo.toml index d2c10635..df000bf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,14 @@ exclude = [ ] [package.metadata.docs.rs] -# The backtraces-impl-* features are incompatible with each other -features = [ "std", "backtraces", "futures", "guide" ] +# We don't use `all-features` for a few reasons: +# - `backtraces-impl-backtrace-crate` replaces the default stdlib implementation +# - `unstable-*` features might break with little warning +# - `internal-dev-dependencies` is internal +features = ["futures", "guide"] [features] -default = ["std", "rust_1_61"] +default = ["std", "rust_1_65"] # Implement the `std::error::Error` trait. std = [] @@ -37,14 +40,11 @@ unstable-core-error = [] # Add support for `Termination` for `Report` rust_1_61 = ["snafu-derive/rust_1_61"] -# Makes the backtrace type live -backtraces = ["std", "backtrace"] +# `Backtrace` was stabilized +rust_1_65 = ["rust_1_61"] # The backtrace type becomes `backtrace::Backtrace` -backtraces-impl-backtrace-crate = ["backtraces"] - -# The backtrace type becomes `std::backtrace::Backtrace` -backtraces-impl-std = [] +backtraces-impl-backtrace-crate = ["backtrace"] # The std::error::Error provider API will be implemented. unstable-provider-api = ["snafu-derive/unstable-provider-api"] diff --git a/compatibility-tests/backtrace-shim/src/lib.rs b/compatibility-tests/backtrace-shim/src/lib.rs deleted file mode 100644 index 8b137891..00000000 --- a/compatibility-tests/backtrace-shim/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/compatibility-tests/backtrace-shim/tests/backtrace.rs b/compatibility-tests/backtrace-shim/tests/backtrace.rs deleted file mode 100644 index 6b0ef54b..00000000 --- a/compatibility-tests/backtrace-shim/tests/backtrace.rs +++ /dev/null @@ -1,35 +0,0 @@ -use snafu::{prelude::*, Backtrace, ErrorCompat}; - -#[derive(Debug, Snafu)] -enum Error { - InvalidUser { user_id: i32, backtrace: Backtrace }, -} - -type Result = std::result::Result; - -fn check_less_than(user_id: i32) -> Result<()> { - ensure!(user_id >= 42, InvalidUserSnafu { user_id }); - Ok(()) -} - -fn check_greater_than(user_id: i32) -> Result<()> { - ensure!(user_id <= 42, InvalidUserSnafu { user_id }); - Ok(()) -} - -fn example(user_id: i32) -> Result<()> { - check_less_than(user_id)?; - check_greater_than(user_id)?; - - Ok(()) -} - -#[test] -fn backtrace_contains_function_names() { - let e = example(0).unwrap_err(); - let text = ErrorCompat::backtrace(&e) - .map(ToString::to_string) - .unwrap_or_default(); - assert!(text.contains("check_less_than")); - assert!(text.contains("example")); -} diff --git a/compatibility-tests/backtrace-shim/tests/backtrace_delegation.rs b/compatibility-tests/backtrace-shim/tests/backtrace_delegation.rs deleted file mode 100644 index 48ceb3fe..00000000 --- a/compatibility-tests/backtrace-shim/tests/backtrace_delegation.rs +++ /dev/null @@ -1,64 +0,0 @@ -use snafu::{prelude::*, ErrorCompat}; - -mod house { - use snafu::{prelude::*, Backtrace}; - - #[derive(Debug, Snafu)] - pub enum Error { - Fatal { backtrace: Backtrace }, - } - - pub fn answer_telephone() -> Result<(), Error> { - FatalSnafu.fail() - } -} - -#[derive(Debug, Snafu)] -enum Error { - MovieTrope { - #[snafu(backtrace)] - source: house::Error, - }, - SourceAndBacktraceAttrs { - // Testing source and backtrace attributes; the field should be recognized as a source, - // and allow us to get a backtrace delegated from the source error - #[snafu(source, backtrace)] - cause: house::Error, - }, -} - -fn delegate_example() -> Result<(), Error> { - house::answer_telephone().context(MovieTropeSnafu)?; - - Ok(()) -} - -#[test] -fn backtrace_comes_from_delegated_error() { - let e = delegate_example().unwrap_err(); - let text = ErrorCompat::backtrace(&e) - .map(ToString::to_string) - .unwrap_or_default(); - assert!( - text.contains("answer_telephone"), - "{:?} does not contain `answer_telephone`", - text - ); -} - -fn delegate_and_rename_example() -> Result<(), Error> { - house::answer_telephone().context(SourceAndBacktraceAttrsSnafu) -} - -#[test] -fn backtrace_comes_from_renamed_delegated_error() { - let e = delegate_and_rename_example().unwrap_err(); - let text = ErrorCompat::backtrace(&e) - .map(ToString::to_string) - .unwrap_or_default(); - assert!( - text.contains("answer_telephone"), - "{:?} does not contain `answer_telephone`", - text - ); -} diff --git a/compatibility-tests/backtrace-shim/tests/whatever_nested.rs b/compatibility-tests/backtrace-shim/tests/whatever_nested.rs deleted file mode 100644 index 4e5cb78a..00000000 --- a/compatibility-tests/backtrace-shim/tests/whatever_nested.rs +++ /dev/null @@ -1,20 +0,0 @@ -use snafu::{prelude::*, Whatever}; - -fn inner_outer() -> Result<(), Whatever> { - not_a_whatever().with_whatever_context(|_| format!("Outer failure")) -} - -fn not_a_whatever() -> Result<(), Box> { - inner_whatever().map_err(Into::into) -} - -fn inner_whatever() -> Result<(), Whatever> { - whatever!("Inner failure"); -} - -#[test] -fn backtrace_method_delegates_to_nested_whatever() { - let e = inner_outer().unwrap_err(); - let bt = e.backtrace().expect("Must have a backtrace"); - assert!(bt.to_string().contains("::inner_whatever::")); -} diff --git a/compatibility-tests/backtraces-impl-std/Cargo.toml b/compatibility-tests/backtraces-impl-std/Cargo.toml deleted file mode 100644 index 75b49893..00000000 --- a/compatibility-tests/backtraces-impl-std/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "backtraces-impl-std" -version = "0.1.0" -authors = ["Jake Goulding "] -edition = "2018" - -[dependencies] -snafu = { path = "../..", features = ["backtraces-impl-std"] } diff --git a/compatibility-tests/backtraces-impl-std/rust-toolchain b/compatibility-tests/backtraces-impl-std/rust-toolchain deleted file mode 100644 index 65b2df87..00000000 --- a/compatibility-tests/backtraces-impl-std/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -beta diff --git a/compatibility-tests/backtraces-impl-std/src/lib.rs b/compatibility-tests/backtraces-impl-std/src/lib.rs deleted file mode 100644 index d3d7bb6b..00000000 --- a/compatibility-tests/backtraces-impl-std/src/lib.rs +++ /dev/null @@ -1,37 +0,0 @@ -#![cfg(test)] - -use snafu::{prelude::*, Backtrace, ErrorCompat}; - -#[derive(Debug, Snafu)] -enum Error { - WithBacktrace { backtrace: Backtrace }, -} - -type Result = std::result::Result; - -fn example() -> Result<()> { - WithBacktraceSnafu.fail() -} - -#[test] -fn is_compatible_with_std_error_trait() { - fn expects_std_trait() {} - - expects_std_trait::(); -} - -#[test] -fn is_compatible_with_std_backtrace_type() { - fn expects_std_type(_: &std::backtrace::Backtrace) {} - - let error = example().unwrap_err(); - let backtrace = ErrorCompat::backtrace(&error).unwrap(); - expects_std_type(&backtrace); -} - -#[test] -fn backtrace_contains_function_names() { - let error = example().unwrap_err(); - let backtrace = ErrorCompat::backtrace(&error).unwrap(); - assert!(backtrace.to_string().contains("::example")); -} diff --git a/compatibility-tests/report-provider-api/Cargo.toml b/compatibility-tests/report-provider-api/Cargo.toml index 2c7250d8..f6b46255 100644 --- a/compatibility-tests/report-provider-api/Cargo.toml +++ b/compatibility-tests/report-provider-api/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -snafu = { path = "../..", features = ["backtraces-impl-std", "unstable-provider-api"] } +snafu = { path = "../..", features = ["unstable-provider-api"] } diff --git a/compatibility-tests/v1_56/Cargo.toml b/compatibility-tests/v1_56/Cargo.toml index 61aadea4..7e3882f9 100644 --- a/compatibility-tests/v1_56/Cargo.toml +++ b/compatibility-tests/v1_56/Cargo.toml @@ -5,4 +5,4 @@ authors = ["Jake Goulding "] edition = "2018" [dependencies] -snafu = { path = "../..", default-features = false, features = ["backtraces"] } +snafu = { path = "../..", default-features = false, features = ["std"] } diff --git a/compatibility-tests/backtrace-shim/Cargo.toml b/compatibility-tests/v1_61/Cargo.toml similarity index 53% rename from compatibility-tests/backtrace-shim/Cargo.toml rename to compatibility-tests/v1_61/Cargo.toml index 94478d9e..c31c52ca 100644 --- a/compatibility-tests/backtrace-shim/Cargo.toml +++ b/compatibility-tests/v1_61/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "backtrace-shim" +name = "v1_61" version = "0.1.0" authors = ["Jake Goulding "] edition = "2018" [dependencies] -snafu = { path = "../..", features = ["backtraces"] } +snafu = { path = "../..", default-features = false, features = ["std", "rust_1_61"] } diff --git a/compatibility-tests/v1_61/rust-toolchain b/compatibility-tests/v1_61/rust-toolchain new file mode 100644 index 00000000..4213d88d --- /dev/null +++ b/compatibility-tests/v1_61/rust-toolchain @@ -0,0 +1 @@ +1.61 diff --git a/compatibility-tests/v1_61/src/lib.rs b/compatibility-tests/v1_61/src/lib.rs new file mode 100644 index 00000000..31d87bb1 --- /dev/null +++ b/compatibility-tests/v1_61/src/lib.rs @@ -0,0 +1,58 @@ +#![cfg(test)] + +use snafu::{prelude::*, Backtrace, ErrorCompat}; + +type AnotherError = Box; + +#[derive(Debug, Snafu)] +enum Error { + #[snafu(display("Invalid user {}:\n{}", user_id, backtrace))] + InvalidUser { user_id: i32, backtrace: Backtrace }, + WithSource { + source: AnotherError, + backtrace: Backtrace, + }, + WithSourceAndOtherInfo { + user_id: i32, + source: AnotherError, + backtrace: Backtrace, + }, +} + +type Result = std::result::Result; + +fn example(user_id: i32) -> Result<()> { + ensure!(user_id >= 42, InvalidUserSnafu { user_id }); + Ok(()) +} + +#[test] +fn display_can_access_backtrace() { + let e = example(0).unwrap_err(); + let text = e.to_string(); + assert!( + text.contains("disabled backtrace"), + "{:?} does not contain expected text", + text + ); +} + +fn trigger() -> Result<(), AnotherError> { + Err("boom".into()) +} + +#[test] +fn errors_with_sources_can_have_backtraces() { + let e = trigger().context(WithSourceSnafu).unwrap_err(); + let backtrace = ErrorCompat::backtrace(&e).unwrap(); + assert!(backtrace.to_string().contains("disabled backtrace")); +} + +#[test] +fn errors_with_sources_and_other_info_can_have_backtraces() { + let e = trigger() + .context(WithSourceAndOtherInfoSnafu { user_id: 42 }) + .unwrap_err(); + let backtrace = ErrorCompat::backtrace(&e).unwrap(); + assert!(backtrace.to_string().contains("disabled backtrace")); +} diff --git a/netlify.toml b/netlify.toml index 403f226c..f884ddca 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,7 +1,7 @@ [build] command = """ rustup install nightly --profile minimal && \ -cargo +nightly doc --no-deps --features=std,backtraces,futures,guide +cargo +nightly doc --no-deps --features=futures,guide """ publish = "target/doc" diff --git a/src/backtrace_impl_backtrace_crate.rs b/src/backtrace_impl_backtrace_crate.rs new file mode 100644 index 00000000..44f4a8f2 --- /dev/null +++ b/src/backtrace_impl_backtrace_crate.rs @@ -0,0 +1,13 @@ +pub use backtrace::Backtrace; + +impl crate::GenerateImplicitData for Backtrace { + fn generate() -> Self { + Backtrace::new() + } +} + +impl crate::AsBacktrace for Backtrace { + fn as_backtrace(&self) -> Option<&Backtrace> { + Some(self) + } +} diff --git a/src/backtrace_inert.rs b/src/backtrace_impl_inert.rs similarity index 100% rename from src/backtrace_inert.rs rename to src/backtrace_impl_inert.rs diff --git a/src/backtrace_impl_std.rs b/src/backtrace_impl_std.rs new file mode 100644 index 00000000..4fdc1be7 --- /dev/null +++ b/src/backtrace_impl_std.rs @@ -0,0 +1,13 @@ +pub use std::backtrace::Backtrace; + +impl crate::GenerateImplicitData for Backtrace { + fn generate() -> Self { + Backtrace::force_capture() + } +} + +impl crate::AsBacktrace for Backtrace { + fn as_backtrace(&self) -> Option<&Backtrace> { + Some(self) + } +} diff --git a/src/backtrace_shim.rs b/src/backtrace_shim.rs deleted file mode 100644 index 5ae96f03..00000000 --- a/src/backtrace_shim.rs +++ /dev/null @@ -1,112 +0,0 @@ -use backtrace; -use std::{fmt, path}; - -/// A backtrace starting from the beginning of the thread. -/// -/// Backtrace functionality is currently **enabled**. Please review -/// [the feature flags](crate::guide::feature_flags) to disable it. -#[derive(Debug)] -pub struct Backtrace(backtrace::Backtrace); - -impl crate::GenerateImplicitData for Backtrace { - // Inlining in an attempt to remove this function from the backtrace - #[inline(always)] - fn generate() -> Self { - Backtrace(backtrace::Backtrace::new()) - } -} - -impl crate::AsBacktrace for Backtrace { - fn as_backtrace(&self) -> Option<&Backtrace> { - Some(self) - } -} - -impl fmt::Display for Backtrace { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let frames = self.0.frames(); - let width = (frames.len() as f32).log10().floor() as usize + 1; - - for (index, frame) in frames.iter().enumerate() { - let mut symbols = frame.symbols().iter().map(SymbolDisplay); - - if let Some(symbol) = symbols.next() { - writeln!( - f, - "{index:width$} {name}", - index = index, - width = width, - name = symbol.name() - )?; - if let Some(location) = symbol.location() { - writeln!( - f, - "{index:width$} {location}", - index = "", - width = width, - location = location - )?; - } - - for symbol in symbols { - writeln!( - f, - "{index:width$} {name}", - index = "", - width = width, - name = symbol.name() - )?; - if let Some(location) = symbol.location() { - writeln!( - f, - "{index:width$} {location}", - index = "", - width = width, - location = location - )?; - } - } - } - } - - Ok(()) - } -} - -struct SymbolDisplay<'a>(&'a backtrace::BacktraceSymbol); - -impl<'a> SymbolDisplay<'a> { - fn name(&self) -> SymbolNameDisplay<'a> { - SymbolNameDisplay(self.0) - } - - fn location(&self) -> Option> { - self.0.filename().map(|f| SymbolLocationDisplay(self.0, f)) - } -} - -struct SymbolNameDisplay<'a>(&'a backtrace::BacktraceSymbol); - -impl<'a> fmt::Display for SymbolNameDisplay<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.0.name() { - Some(n) => write!(f, "{}", n)?, - None => write!(f, "")?, - } - - Ok(()) - } -} - -struct SymbolLocationDisplay<'a>(&'a backtrace::BacktraceSymbol, &'a path::Path); - -impl<'a> fmt::Display for SymbolLocationDisplay<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.1.display())?; - if let Some(l) = self.0.lineno() { - write!(f, ":{}", l)?; - } - - Ok(()) - } -} diff --git a/src/guide/compatibility.md b/src/guide/compatibility.md index 5831afcf..ee9ac7a9 100644 --- a/src/guide/compatibility.md +++ b/src/guide/compatibility.md @@ -26,3 +26,23 @@ and test functions. [`Termination`]: std::process::Termination [`Report`]: crate::Report + +## `rust_1_65` + +
+
Default
+
enabled
+
Implies
+
+ +[`rust_1_61`](#rust_1_61) + +
+
+ +When enabled, SNAFU will assume that it's safe to target features +available in Rust 1.65. Notably, the [`Backtrace`][] type is used when +the standard library is available and no other backtrace provider is +selected. + +[`Backtrace`]: std::backtrace::Backtrace diff --git a/src/guide/examples/backtrace.rs b/src/guide/examples/backtrace.rs index a2663c91..e0a94f9a 100644 --- a/src/guide/examples/backtrace.rs +++ b/src/guide/examples/backtrace.rs @@ -4,22 +4,19 @@ use crate::{Snafu, Backtrace, ErrorCompat, GenerateImplicitData}; -/// Backtraces aren't yet supported by the stable Rust compiler. SNAFU -/// provides a stable-compatible way of getting backtraces as well as -/// support for the backtrace support in the nightly compiler. **By -/// default, backtraces are disabled**. It is -/// expected that the final executable adds SNAFU as a dependency and -/// chooses the appropriate [feature -/// flag](crate::guide::feature_flags) to enable backtraces. +/// Rust 1.65 stabilized the [`std::backtrace::Backtrace`] type, but +/// there's not yet a stable abstraction for accessing a backtrace +/// from an arbitrary error value. SNAFU provides a stable-compatible +/// way of accessing backtraces on a SNAFU-created error type. SNAFU +/// also supports environments where backtraces are not available, +/// such as `no_std` projects. /// -/// When using SNAFU to define error types, it's recommended to start -/// with a [`Backtrace`] field on every leaf error variant (those -/// without a `source`). Backtraces are only captured on -/// failure. Since backtraces are disabled by default, adding them in -/// a library does not force any users to pay the price of backtraces -/// if they are not used; they can be zero cost. +/// When defining error types which include backtraces, it's +/// recommended to start with a [`Backtrace`] field on every leaf +/// error variant (those without a `source`). Backtraces are only +/// captured on failure. /// -/// Certain errors are used for flow control. Those don't need a +/// Certain errors are used for flow control. These don't need a /// backtrace as they don't represent actual failures. However, /// sometimes an error is *mostly* used for flow control but might /// also indicate an error. In those cases, you can use @@ -32,13 +29,13 @@ use crate::{Snafu, Backtrace, ErrorCompat, GenerateImplicitData}; /// SNAFU error, for example, you can *delegate* retrieval of the /// backtrace to the source error. If the source error doesn't provide /// its own backtrace, you should capture your own backtrace. This -/// backtrace would not be as useful as one captured by the source +/// backtrace will not be as useful as one captured by the source /// error, but it's as useful as you can get. /// /// When you wish to display the backtrace of an error, you can use /// the [`ErrorCompat::backtrace`] method. It's recommended to always /// use this in the fully-qualified form so it will be easy to find -/// and replace when Rust stabilizes backtraces. +/// and replace when there's a stable way to access backtraces. /// /// ``` /// # use snafu::guide::examples::backtrace::*; @@ -63,7 +60,8 @@ use crate::{Snafu, Backtrace, ErrorCompat, GenerateImplicitData}; // needed in most cases: #[snafu(crate_root(crate), visibility(pub))] pub enum Error { - /// The most common leaf error should always include a backtrace field. + /// The most common case: leaf errors should always include a + /// backtrace field. UsualCase { backtrace: Backtrace, }, diff --git a/src/guide/feature_flags.md b/src/guide/feature_flags.md index f853cf83..fafeef5e 100644 --- a/src/guide/feature_flags.md +++ b/src/guide/feature_flags.md @@ -7,9 +7,7 @@ cases: - [`std`](#std) - [`unstable-core-error`](#unstable-core-error) - [`guide`](#guide) -- [`backtraces`](#backtraces) - [`backtraces-impl-backtrace-crate`](#backtraces-impl-backtrace-crate) -- [`backtraces-impl-std`](#backtraces-impl-std) - [`unstable-provider-api`](#unstable-provider-api) - [`futures`](#futures) - [`unstable-try-trait`](#unstable-try-trait) @@ -63,38 +61,11 @@ built. Most usages of SNAFU will want this feature disabled. -## `backtraces` - -
-
Default
-
disabled
-
Implies
-
- -[`std`](#std) - -
-
- -When enabled, the [`Backtrace`] type in your enum variant will capture -a backtrace when the error is generated. If you never use backtraces, -you can omit this feature to speed up compilation a small amount. - -It is recommended that only applications make use of this feature. - -[`Backtrace`]: crate::Backtrace - ## `backtraces-impl-backtrace-crate`
Default
disabled
-
Implies
-
- -[`backtraces`](#backtraces) - -
When enabled, the SNAFU [`Backtrace`] type becomes an alias to the @@ -102,20 +73,11 @@ When enabled, the SNAFU [`Backtrace`] type becomes an alias to the crates that require this type. It is recommended that only applications make use of this -feature. When the standard library stabilizes its own backtrace type, -this feature will no longer be supported and will be removed. - -## `backtraces-impl-std` - -
-
Default
-
disabled
-
- -When enabled, the SNAFU [`Backtrace`] type becomes an alias to the -[`std::backtrace::Backtrace`] type. +feature. When SNAFU's minimum supported Rust version has a stable +`Backtrace` type, this feature will no longer be supported and will be +removed. -It is recommended that only applications make use of this feature. +[`Backtrace`]: crate::Backtrace ## `unstable-provider-api` diff --git a/src/lib.rs b/src/lib.rs index e1c2b18e..e20c23c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,41 +227,30 @@ pub mod prelude { pub use crate::futures::{TryFutureExt as _, TryStreamExt as _}; } -#[cfg(all( - not(feature = "backtraces"), - not(feature = "backtraces-impl-backtrace-crate"), - not(feature = "backtraces-impl-std"), -))] -mod backtrace_inert; -#[cfg(all( - not(feature = "backtraces"), - not(feature = "backtraces-impl-backtrace-crate"), - not(feature = "backtraces-impl-std"), -))] -pub use crate::backtrace_inert::*; +#[cfg(not(any( + all(feature = "std", feature = "rust_1_65"), + feature = "backtraces-impl-backtrace-crate" +)))] +#[path = "backtrace_impl_inert.rs"] +mod backtrace_impl; + +#[cfg(feature = "backtraces-impl-backtrace-crate")] +#[path = "backtrace_impl_backtrace_crate.rs"] +mod backtrace_impl; #[cfg(all( - feature = "backtraces", - not(feature = "backtraces-impl-backtrace-crate"), - not(feature = "backtraces-impl-std"), -))] -mod backtrace_shim; -#[cfg(all( - feature = "backtraces", - not(feature = "backtraces-impl-backtrace-crate"), - not(feature = "backtraces-impl-std"), + feature = "std", + feature = "rust_1_65", + not(feature = "backtraces-impl-backtrace-crate") ))] -pub use crate::backtrace_shim::*; +#[path = "backtrace_impl_std.rs"] +mod backtrace_impl; + +pub use backtrace_impl::*; #[cfg(any(feature = "std", test))] mod once_bool; -#[cfg(feature = "backtraces-impl-backtrace-crate")] -pub use backtrace::Backtrace; - -#[cfg(feature = "backtraces-impl-std")] -pub use std::backtrace::Backtrace; - #[cfg(feature = "futures")] pub mod futures; @@ -1369,34 +1358,6 @@ fn backtrace_collection_enabled() -> bool { }) } -#[cfg(feature = "backtraces-impl-backtrace-crate")] -impl GenerateImplicitData for Backtrace { - fn generate() -> Self { - Backtrace::new() - } -} - -#[cfg(feature = "backtraces-impl-backtrace-crate")] -impl AsBacktrace for Backtrace { - fn as_backtrace(&self) -> Option<&Backtrace> { - Some(self) - } -} - -#[cfg(feature = "backtraces-impl-std")] -impl GenerateImplicitData for Backtrace { - fn generate() -> Self { - Backtrace::force_capture() - } -} - -#[cfg(feature = "backtraces-impl-std")] -impl AsBacktrace for Backtrace { - fn as_backtrace(&self) -> Option<&Backtrace> { - Some(self) - } -} - /// The source code location where the error was reported. /// /// To use it, add a field of type `Location` to your error and diff --git a/tests/backtrace.rs b/tests/backtrace.rs index f830b4f6..b0fa7fed 100644 --- a/tests/backtrace.rs +++ b/tests/backtrace.rs @@ -5,52 +5,148 @@ type AnotherError = Box; #[derive(Debug, Snafu)] enum Error { #[snafu(display("Invalid user {user_id}:\n{backtrace}"))] - InvalidUser { user_id: i32, backtrace: Backtrace }, + InvalidUser { + user_id: i32, + backtrace: Backtrace, + }, + WithSource { source: AnotherError, backtrace: Backtrace, }, + WithSourceAndOtherInfo { user_id: i32, source: AnotherError, backtrace: Backtrace, }, + + WithBacktrace { + backtrace: Backtrace, + }, } type Result = std::result::Result; -fn example(user_id: i32) -> Result<()> { - ensure!(user_id >= 42, InvalidUserSnafu { user_id }); - Ok(()) +fn example() -> Result<()> { + WithBacktraceSnafu.fail() } #[test] -fn display_can_access_backtrace() { - let e = example(0).unwrap_err(); - let text = e.to_string(); - assert!( - text.contains("disabled backtrace"), - "{:?} does not contain expected text", - text - ); -} +fn is_compatible_with_std_error_trait() { + fn expects_std_trait() {} -fn trigger() -> Result<(), AnotherError> { - Err("boom".into()) + expects_std_trait::(); } #[test] -fn errors_with_sources_can_have_backtraces() { - let e = trigger().context(WithSourceSnafu).unwrap_err(); - let backtrace = ErrorCompat::backtrace(&e).unwrap(); - assert!(backtrace.to_string().contains("disabled backtrace")); +fn is_compatible_with_std_backtrace_type() { + fn expects_std_type(_: &std::backtrace::Backtrace) {} + + let error = example().unwrap_err(); + let backtrace = ErrorCompat::backtrace(&error).unwrap(); + expects_std_type(&backtrace); } #[test] -fn errors_with_sources_and_other_info_can_have_backtraces() { - let e = trigger() - .context(WithSourceAndOtherInfoSnafu { user_id: 42 }) - .unwrap_err(); - let backtrace = ErrorCompat::backtrace(&e).unwrap(); - assert!(backtrace.to_string().contains("disabled backtrace")); +fn backtrace_contains_function_names() { + let error = example().unwrap_err(); + let backtrace = ErrorCompat::backtrace(&error).unwrap(); + assert!(backtrace.to_string().contains("::example")); +} + +mod delegation { + use snafu::{prelude::*, ErrorCompat}; + + mod house { + use snafu::{prelude::*, Backtrace}; + + #[derive(Debug, Snafu)] + pub struct FatalError { + backtrace: Backtrace, + } + + pub fn answer_telephone() -> Result<(), FatalError> { + FatalSnafu.fail() + } + } + + #[derive(Debug, Snafu)] + enum Error { + MovieTrope { + #[snafu(backtrace)] + source: house::FatalError, + }, + + SourceAndBacktraceAttrs { + // Testing source and backtrace attributes; the field should be recognized as a source, + // and allow us to get a backtrace delegated from the source error + #[snafu(source, backtrace)] + cause: house::FatalError, + }, + } + + fn delegate_example() -> Result<(), Error> { + house::answer_telephone().context(MovieTropeSnafu)?; + + Ok(()) + } + + #[test] + fn backtrace_comes_from_delegated_error() { + let e = delegate_example().unwrap_err(); + let text = ErrorCompat::backtrace(&e) + .map(ToString::to_string) + .unwrap_or_default(); + assert!( + text.contains("answer_telephone"), + "{:?} does not contain `answer_telephone`", + text, + ); + } + + fn delegate_and_rename_example() -> Result<(), Error> { + house::answer_telephone().context(SourceAndBacktraceAttrsSnafu) + } + + #[test] + fn backtrace_comes_from_renamed_delegated_error() { + let e = delegate_and_rename_example().unwrap_err(); + let text = ErrorCompat::backtrace(&e) + .map(ToString::to_string) + .unwrap_or_default(); + assert!( + text.contains("answer_telephone"), + "{:?} does not contain `answer_telephone`", + text, + ); + } +} + +mod whatever_nested { + use snafu::{prelude::*, Whatever}; + + fn inner_outer() -> Result<(), Whatever> { + not_a_whatever().with_whatever_context(|_| format!("Outer failure")) + } + + fn not_a_whatever() -> Result<(), Box> { + inner_whatever().map_err(Into::into) + } + + fn inner_whatever() -> Result<(), Whatever> { + whatever!("Inner failure"); + } + + #[test] + fn backtrace_method_delegates_to_nested_whatever() { + let e = inner_outer().unwrap_err(); + let bt = e.backtrace().expect("Must have a backtrace"); + let text = bt.to_string(); + assert!( + text.contains("::inner_whatever"), + "{:?} does not contain `::inner_whatever`", + text, + ); + } } diff --git a/tests/premade_error.rs b/tests/premade_error.rs index ae16a1d0..9f350011 100644 --- a/tests/premade_error.rs +++ b/tests/premade_error.rs @@ -103,5 +103,5 @@ fn has_a_backtrace() { let e = exercise(false).unwrap_err(); let bt = ErrorCompat::backtrace(&e).expect("Must have a backtrace"); - assert_eq!("disabled backtrace", bt.to_string()); + assert!(bt.to_string().contains("has_a_backtrace")); } diff --git a/tests/stringly_typed.rs b/tests/stringly_typed.rs index 7bc63b8f..b05108e9 100644 --- a/tests/stringly_typed.rs +++ b/tests/stringly_typed.rs @@ -201,7 +201,7 @@ mod message_source_and_backtrace { let e = exercise(false).unwrap_err(); let bt = ErrorCompat::backtrace(&e).expect("Must have a backtrace"); - assert_eq!("disabled backtrace", bt.to_string()); + assert!(bt.to_string().contains("has_a_backtrace")); } }