#![cfg(feature = "tracing")]
#![cfg_attr(
feature = "unstable-error-generic-member-access",
feature(error_generic_member_access)
)]
#![allow(
clippy::all,
reason = "integration test fixtures intentionally trip style lints"
)]
mod common;
use oopsie::{Contextual as _, Oopsie, SpanTrace, oopsie};
use std::io;
use tracing::instrument;
#[derive(Debug, Oopsie)]
#[oopsie("Boxed spantrace error")]
#[oopsie(suffix, module(false))]
struct BoxedSpantraceError {
#[oopsie(spantrace)]
span: Box<SpanTrace>,
}
#[test]
fn test_extract_boxed_spantrace_via_provide_ref() {
use oopsie::Diagnostic as _;
let err = BoxedSpantraceOopsie.build();
let extracted = err.oopsie_spantrace();
assert!(extracted.is_some());
}
#[oopsie(traced)]
#[oopsie("captured content")]
pub struct CapturedContentError {
info: String,
}
#[test]
fn captured_backtrace_has_real_frames() {
use oopsie::Diagnostic as _;
common::force_backtrace();
let err = CapturedContentOopsie { info: "x" }.build();
let bt = err
.oopsie_backtrace()
.expect("traced error must expose a backtrace");
assert!(
!bt.frames().is_empty(),
"forced backtrace capture must record frames, got 0"
);
let has_this_fn = bt
.frames()
.iter()
.flat_map(backtrace::BacktraceFrame::symbols)
.filter_map(backtrace::BacktraceSymbol::name)
.any(|n| format!("{n}").contains("captured_backtrace_has_real_frames"));
assert!(
has_this_fn,
"captured backtrace should contain this test's frame"
);
}
#[test]
fn captured_spantrace_status_is_captured_within_span() {
use oopsie::Diagnostic as _;
#[instrument(target = "test")]
fn make() -> CapturedContentError {
CapturedContentOopsie { info: "x" }.build()
}
let _guard = common::init_test_subscriber();
let err = make();
let st = err
.oopsie_spantrace()
.expect("traced error must expose a spantrace");
let rendered = st.to_string();
assert!(
!rendered.is_empty(),
"spantrace captured inside an instrumented span must render non-empty content"
);
assert!(
rendered.contains("make"),
"spantrace content should name the instrumented span, got: {rendered}"
);
}
#[oopsie(traced(spantrace(false)))]
enum ExtractSrcError {
#[oopsie("extract src")]
Src { info: String },
}
#[oopsie(traced(spantrace(false)))]
enum ExtractWrapDiagError {
#[oopsie("wrap diag source")]
WrapDiag { source: ExtractSrcError },
}
#[oopsie(traced(spantrace(false)))]
enum ExtractWrapIoError {
#[oopsie("wrap io source")]
WrapIo { source: io::Error },
}
fn symbol_names(bt: &oopsie::Backtrace) -> Vec<String> {
bt.frames()
.iter()
.flat_map(backtrace::BacktraceFrame::symbols)
.filter_map(|s| s.name().map(|n| format!("{n}")))
.collect()
}
#[inline(never)]
fn build_extract_src() -> ExtractSrcError {
extract_src_oopsies::Src { info: "x" }.build()
}
#[test]
fn diagnostic_source_extracts_backtrace_from_source() {
use oopsie::Diagnostic as _;
common::force_backtrace();
let src = build_extract_src();
let src_names = symbol_names(src.oopsie_backtrace().expect("src backtrace"));
let wrapped: ExtractWrapDiagError = extract_wrap_diag_oopsies::WrapDiag.build_error(src);
let wrap_names = symbol_names(wrapped.oopsie_backtrace().expect("wrap backtrace"));
assert_eq!(
wrap_names, src_names,
"Diagnostic source must extract (reuse) the source's backtrace content"
);
}
#[test]
fn non_diagnostic_source_captures_fresh_backtrace() {
use oopsie::Diagnostic as _;
common::force_backtrace();
let src = build_extract_src();
let src_top_user = symbol_names(src.oopsie_backtrace().expect("src backtrace"))
.into_iter()
.find(|n| n.contains("build_extract_src"));
assert!(
src_top_user.is_some(),
"sanity: source backtrace should name its builder fn"
);
let wrapped: ExtractWrapIoError =
extract_wrap_io_oopsies::WrapIo.build_error(io::Error::other("disk full"));
let wrap_names = symbol_names(wrapped.oopsie_backtrace().expect("wrap backtrace"));
assert!(
!wrap_names.iter().any(|n| n.contains("build_extract_src")),
"fresh capture must not contain the already-returned source builder frame"
);
assert!(
wrap_names
.iter()
.any(|n| n.contains("non_diagnostic_source_captures_fresh_backtrace")),
"fresh capture must contain the wrap-site frame"
);
}
#[oopsie(traced)]
enum ChainInnerError {
#[oopsie("chain inner")]
Inner { info: String },
}
#[oopsie(traced)]
enum ChainOuterError {
#[oopsie("chain outer")]
Wrap { source: ChainInnerError },
}
#[test]
fn derive_chain_skips_empty_source_spantrace() {
use oopsie::Diagnostic as _;
let inner: ChainInnerError = chain_inner_oopsies::Inner { info: "x" }.build();
assert!(!inner.oopsie_spantrace().unwrap().is_captured());
let _guard = common::init_test_subscriber();
let outer: ChainOuterError =
tracing::info_span!("wrap").in_scope(|| chain_outer_oopsies::Wrap.build_error(inner));
assert!(
outer.oopsie_spantrace().unwrap().is_captured(),
"the source's empty spantrace must not shadow the captured wrap-site one"
);
}
#[cfg(feature = "unstable-error-generic-member-access")]
mod provide_test {
use super::*;
#[derive(Debug, Oopsie)]
#[oopsie("Boxed spantrace error via provide")]
#[oopsie(suffix, module(false))]
#[oopsie(provide(ref, oopsie::SpanTrace => prov_span.as_ref()))]
struct BoxedSpantraceProvideError {
#[oopsie(spantrace)]
prov_span: Box<SpanTrace>,
}
#[test]
fn test_extract_boxed_spantrace_via_nightly_provide() {
let err = BoxedSpantraceProvideOopsie.build();
let extracted = core::error::request_ref::<SpanTrace>(&err);
assert!(extracted.is_some());
}
}