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;
12
13struct SimpleLogger;
14
15impl Log for SimpleLogger {
16    fn enabled(&self, _metadata: &Metadata) -> bool {
17        true
18    }
19
20    fn log(&self, record: &Record) {
21        // Create style based on log level
22        let level_style = match record.level() {
23            Level::Error => Style::new().fg_rgb::<243, 139, 168>(), // Catppuccin red (Maroon)
24            Level::Warn => Style::new().fg_rgb::<249, 226, 175>(),  // Catppuccin yellow (Peach)
25            Level::Info => Style::new().fg_rgb::<166, 227, 161>(),  // Catppuccin green (Green)
26            Level::Debug => Style::new().fg_rgb::<137, 180, 250>(), // Catppuccin blue (Blue)
27            Level::Trace => Style::new().fg_rgb::<148, 226, 213>(), // Catppuccin teal (Teal)
28        };
29
30        // Convert level to styled display
31        eprintln!(
32            "{} - {}: {}",
33            record.level().style(level_style),
34            record
35                .target()
36                .style(Style::new().fg_rgb::<137, 180, 250>()), // Blue for the target
37            record.args()
38        );
39    }
40
41    fn flush(&self) {
42        let _ = std::io::stderr().flush();
43    }
44}
45
46/// Set up a simple logger.
47///
48/// # Panics
49///
50/// Panics if not running under `cargo-nextest`. This crate requires nextest
51/// for proper test isolation and logger setup.
52pub fn setup() {
53    let is_nextest = std::env::var("NEXTEST").as_deref() == Ok("1");
54    if !is_nextest {
55        let command = if cfg!(miri) {
56            "cargo miri nextest run"
57        } else {
58            "cargo nextest run"
59        };
60        let message = format!(
61            "This test suite requires cargo-nextest to run.\n\
62            \n\
63            cargo-nextest provides:\n\
64              • Process-per-test isolation (required for our logger setup)\n\
65              • Faster parallel test execution\n\
66              • Better test output and reporting\n\
67            \n\
68            Install it with:\n\
69              cargo install cargo-nextest\n\
70            \n\
71            Then run tests with:\n\
72              {command}\n\
73            \n\
74            For more information, visit: https://nexte.st"
75        );
76        let boxed = boxen::builder()
77            .border_style(boxen::BorderStyle::Round)
78            .padding(1)
79            .border_color("red")
80            .render(&message)
81            .unwrap();
82        panic!("\n{boxed}");
83    }
84
85    let logger = Box::new(SimpleLogger);
86    // Ignore SetLoggerError - logger may already be set if running multiple tests
87    // in the same process (e.g., under valgrind with --test-threads=1)
88    let _ = log::set_boxed_logger(logger);
89    log::set_max_level(LevelFilter::Trace);
90}
91
92/// An error type that panics when it's built (such as when you use `?`
93/// to coerce to it)
94#[derive(Debug)]
95pub struct IPanic;
96
97impl<E> From<E> for IPanic
98where
99    E: core::error::Error + Send + Sync,
100{
101    #[track_caller]
102    fn from(value: E) -> Self {
103        panic!("from: {}: {value}", core::panic::Location::caller())
104    }
105}