Skip to main content

oopsie_core/
test_utils.rs

1//! Shared helpers for the integration-test suites of the consumer crates.
2//!
3//! Backtrace and spantrace snapshots are platform- and toolchain-specific, so
4//! each snapshot is blessed and asserted only on the toolchain that produced
5//! it; the variance lives in the snapshot *filename* (channel + target triple)
6//! rather than in a cross-toolchain normalization battery. The filters here are
7//! therefore deliberately light: they only erase build-to-build noise
8//! (compilation hashes, line/column numbers, machine-specific paths).
9
10#[cfg(feature = "tracing")]
11use tracing_subscriber::prelude::*;
12
13#[doc(hidden)]
14pub mod __private {
15    pub use konst;
16    pub use target_triple::TARGET;
17}
18
19/// The toolchain channel (`stable` or `unstable`) embedded in snapshot file
20/// names so toolchain-specific snapshots stay in separate files.
21// Keyed on the granular feature (not the `unstable` umbrella) because the
22// Provider-API trace surfacing it gates is what actually changes snapshot
23// content.
24pub const CHANNEL: &str = if cfg!(feature = "unstable-error-generic-member-access") {
25    "unstable"
26} else {
27    "stable"
28};
29
30#[cfg(feature = "unstable-error-generic-member-access")]
31const _: () = assert!(matches!(CHANNEL.as_bytes(), b"unstable"));
32
33/// Build a snapshot name from a base label plus the channel and target triple,
34/// keeping platform- and toolchain-specific snapshots in separate files.
35#[macro_export]
36macro_rules! snap_name {
37    ($name:literal) => {{
38        $crate::test_utils::__private::konst::string::str_join!(
39            "_",
40            &[
41                $name,
42                $crate::test_utils::CHANNEL,
43                $crate::test_utils::__private::TARGET
44            ]
45        )
46    }};
47}
48
49/// Install a test subscriber with an `ErrorLayer` so span traces are captured
50/// for the duration of the returned guard.
51#[cfg(feature = "tracing")]
52#[must_use]
53pub fn init_test_subscriber() -> tracing::subscriber::DefaultGuard {
54    #[cfg(feature = "serde")]
55    let error_layer = crate::tracing::json_error_layer();
56    #[cfg(not(feature = "serde"))]
57    let error_layer = tracing_error::ErrorLayer::default();
58    let subscriber = tracing_subscriber::registry().with(error_layer);
59    tracing::subscriber::set_default(subscriber)
60}
61
62/// Force backtrace capture on the current thread so snapshots are deterministic
63/// regardless of the ambient `RUST_BACKTRACE` environment.
64pub fn force_backtrace() {
65    crate::set_rust_backtrace_override(crate::RustBacktrace::Enabled);
66}
67
68/// `insta` snapshot redaction profiles that erase build-to-build noise
69/// (compilation hashes, line/column numbers, machine-specific paths) from
70/// backtrace and spantrace snapshots.
71pub mod settings {
72    use std::{
73        env,
74        path::{Path, PathBuf},
75        process::Command,
76        sync::LazyLock,
77    };
78
79    /// `insta::Settings` carrying the light, build-to-build normalization filters
80    /// shared by every backtrace/spantrace snapshot. Bind with [`insta::Settings::bind`].
81    #[must_use]
82    pub fn backtrace() -> insta::Settings {
83        static RUSTC_SYSROOT: LazyLock<String> = LazyLock::new(|| {
84            String::from_utf8(
85                Command::new("rustc")
86                    .arg("--print")
87                    .arg("sysroot")
88                    .output()
89                    .expect("failed to run rustc")
90                    .stdout,
91            )
92            .expect("invalid UTF-8 in rustc sysroot")
93            .trim()
94            .to_owned()
95        });
96
97        static WORKSPACE_ROOT: LazyLock<String> = LazyLock::new(|| {
98            Path::new(env!("CARGO_MANIFEST_DIR"))
99                .parent()
100                .unwrap()
101                .parent()
102                .unwrap()
103                .to_string_lossy()
104                .into_owned()
105        });
106
107        static CARGO_HOME: LazyLock<String> = LazyLock::new(|| {
108            env::var("CARGO_HOME")
109                .map_or_else(
110                    |_| {
111                        PathBuf::from(env::var("HOME").expect("HOME environment variable not set"))
112                            .join(".cargo")
113                    },
114                    PathBuf::from,
115                )
116                .to_string_lossy()
117                .into_owned()
118        });
119
120        let mut settings = insta::Settings::clone_current();
121        settings.add_filter(r"\[[0-9a-f]{7,16}\]", "[[HASH]]");
122        settings.add_filter(r"::h[0-9a-f]{7,16}\b", "::h[HASH]");
123        settings.add_filter(r"\/[a-f0-9]+\/", "/[HASH]/");
124        settings.add_filter(r"rs:\d+(:\d+)?", "rs:[LOC]");
125        settings.add_filter(&WORKSPACE_ROOT, "[WORKSPACE]");
126        settings.add_filter(&RUSTC_SYSROOT, "[SYS_ROOT]");
127        settings.add_filter(&CARGO_HOME, "[CARGO_HOME]/");
128        // Stdlib path normalization: local `[SYS_ROOT]/lib/rustlib/src/rust/library/`
129        // and CI `/rustc/[HASH]/library/` both → `[STDLIB]/library/`.
130        settings.add_filter(
131            r"\[SYS_ROOT\]/lib/rustlib/src/rust/library/",
132            "[STDLIB]/library/",
133        );
134        settings.add_filter(r"/rustc/\[HASH\]/library/", "[STDLIB]/library/");
135        // JSON snapshots carry line/column as numeric fields rather than `rs:N:C`.
136        settings.add_filter(r#""line":\s*\d+"#, r#""line": 42"#);
137        settings.add_filter(r#""column":\s*\d+"#, r#""column": 69"#);
138        // macOS test threads bottom out in a libc frame whose exact symbol
139        // varies between runs; the render path peels this OS tail, but the
140        // erased path serializes raw frames, so normalize it here.
141        settings.add_filter(r"__pthread\w*", "[OS_TAIL]");
142        settings
143    }
144}
145
146/// Run a block with a named [`settings`] redaction profile bound, so the
147/// snapshots asserted inside it use the shared normalization filters.
148#[macro_export]
149macro_rules! redact {
150    ($name:ident, $bl:block) => {
151        $crate::test_utils::settings::$name().bind(|| $bl)
152    };
153}