facet_testhelpers/
lib.rs

1#![warn(missing_docs)]
2#![warn(clippy::std_instead_of_core)]
3#![warn(clippy::std_instead_of_alloc)]
4#![forbid(unsafe_code)]
5#![doc = include_str!("../README.md")]
6
7pub use color_eyre::eyre;
8pub use facet_testhelpers_macros::test;
9
10use log::{Level, LevelFilter, Log, Metadata, Record};
11use owo_colors::{OwoColorize, Style};
12use std::io::Write;
13
14struct SimpleLogger;
15
16impl Log for SimpleLogger {
17    fn enabled(&self, _metadata: &Metadata) -> bool {
18        true
19    }
20
21    fn log(&self, record: &Record) {
22        // Create style based on log level
23        let level_style = match record.level() {
24            Level::Error => Style::new().fg_rgb::<243, 139, 168>(), // Catppuccin red (Maroon)
25            Level::Warn => Style::new().fg_rgb::<249, 226, 175>(),  // Catppuccin yellow (Peach)
26            Level::Info => Style::new().fg_rgb::<166, 227, 161>(),  // Catppuccin green (Green)
27            Level::Debug => Style::new().fg_rgb::<137, 180, 250>(), // Catppuccin blue (Blue)
28            Level::Trace => Style::new().fg_rgb::<148, 226, 213>(), // Catppuccin teal (Teal)
29        };
30
31        // Convert level to styled display
32        eprintln!(
33            "{} - {}: {}",
34            record.level().style(level_style),
35            record
36                .target()
37                .style(Style::new().fg_rgb::<137, 180, 250>()), // Blue for the target
38            record.args()
39        );
40    }
41
42    fn flush(&self) {
43        let _ = std::io::stderr().flush();
44    }
45}
46
47/// Installs color-backtrace (except on miri), and sets up a simple logger.
48pub fn setup() {
49    #[cfg(not(miri))]
50    {
51        use color_eyre::config::HookBuilder;
52        use regex::Regex;
53        use std::sync::LazyLock;
54
55        /// This regex is used to filter out unwanted frames in error backtraces.
56        /// It ignores panic frames, test runners, and a few threading details.
57        ///
58        /// Regex: ^(std::panic|core::panic|test::run_test|__pthread_cond_wait|Thread::new::thread_start)
59        static IGNORE_FRAMES: LazyLock<Regex> = LazyLock::new(|| {
60            Regex::new(r"^(std::panic|core::panic|test::run_test|__pthread_cond_wait|std::sys::(pal|backtrace)|std::thread::Builder|core::ops::function|test::__rust_begin_short_backtrace|<core::panic::|<alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once)")
61                .unwrap()
62        });
63
64        // color-eyre filter
65        let eyre_filter = {
66            move |frames: &mut Vec<&color_eyre::config::Frame>| {
67                frames.retain(|frame| {
68                    frame
69                        .name
70                        .as_ref()
71                        .map(|n| !IGNORE_FRAMES.is_match(&n.to_string()))
72                        .unwrap_or(true)
73                });
74            }
75        };
76
77        HookBuilder::default()
78            .add_frame_filter(Box::new(eyre_filter))
79            .install()
80            .expect("Failed to set up color-eyre");
81
82        // color-backtrace filter
83        {
84            use color_backtrace::{BacktracePrinter, Frame};
85
86            // The frame filter must be Fn(&mut Vec<&Frame>)
87            let filter = move |frames: &mut Vec<&Frame>| {
88                frames.retain(|frame| {
89                    frame
90                        .name
91                        .as_ref()
92                        .map(|name| !IGNORE_FRAMES.is_match(name))
93                        .unwrap_or(true)
94                });
95            };
96
97            // Build and install custom BacktracePrinter with our filter.
98            // Use StandardStream to provide a WriteColor.
99            let stderr = color_backtrace::termcolor::StandardStream::stderr(
100                color_backtrace::termcolor::ColorChoice::Auto,
101            );
102            let printer = BacktracePrinter::new().add_frame_filter(Box::new(filter));
103            printer.install(Box::new(stderr));
104        }
105    }
106
107    let logger = Box::new(SimpleLogger);
108    log::set_boxed_logger(logger).unwrap();
109    log::set_max_level(LevelFilter::Trace);
110}