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 facet_testhelpers_macros::test;
8
9use log::{Level, LevelFilter, Log, Metadata, Record};
10use owo_colors::{OwoColorize, Style};
11use std::io::Write;
12use std::sync::LazyLock;
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/// Lazy initialization of the global logger.
48///
49/// This ensures the logger is set up exactly once, regardless of how many
50/// tests run in the same process.
51static LOGGER_INIT: LazyLock<()> = LazyLock::new(|| {
52    let logger = Box::new(SimpleLogger);
53    let _ = log::set_boxed_logger(logger);
54    log::set_max_level(LevelFilter::Trace);
55});
56
57/// Set up a simple logger for tests.
58///
59/// This function ensures the logger is initialized exactly once using
60/// [`LazyLock`], making it safe to use with both `cargo test` and
61/// `cargo nextest run`.
62///
63/// # Recommendation
64///
65/// While this works with regular `cargo test`, we recommend using
66/// `cargo nextest run` for:
67/// - Process-per-test isolation
68/// - Faster parallel test execution
69/// - Better test output and reporting
70///
71/// Install nextest with: `cargo install cargo-nextest`
72///
73/// For more information, visit: <https://nexte.st>
74pub fn setup() {
75    // Print a helpful message if not using nextest
76    let is_nextest = std::env::var("NEXTEST").as_deref() == Ok("1");
77    if !is_nextest {
78        static NEXTEST_WARNING: LazyLock<()> = LazyLock::new(|| {
79            eprintln!(
80                "💡 Tip: Consider using `cargo nextest run` for better test output and performance."
81            );
82            eprintln!("   Install with: cargo install cargo-nextest");
83            eprintln!("   More info: https://nexte.st");
84            eprintln!();
85        });
86        *NEXTEST_WARNING;
87    }
88
89    // Ensure the logger is initialized
90    *LOGGER_INIT;
91}
92
93/// An error type that panics when it's built (such as when you use `?`
94/// to coerce to it)
95#[derive(Debug)]
96pub struct IPanic;
97
98impl<E> From<E> for IPanic
99where
100    E: core::error::Error + Send + Sync,
101{
102    #[track_caller]
103    fn from(value: E) -> Self {
104        panic!("from: {}: {value}", core::panic::Location::caller())
105    }
106}