#[cfg(feature = "std")]
use std::{
collections::VecDeque,
sync::{Mutex, OnceLock},
};
#[cfg(feature = "std")]
const CAPACITY: usize = 256;
#[cfg(feature = "std")]
fn ring() -> &'static Mutex<VecDeque<String>> {
static RING: OnceLock<Mutex<VecDeque<String>>> = OnceLock::new();
RING.get_or_init(|| Mutex::new(VecDeque::with_capacity(CAPACITY)))
}
#[cfg(feature = "std")]
pub type DiagnosticSink = fn(&str);
#[cfg(feature = "std")]
fn default_sink(message: &str) {
eprintln!("{message}");
}
#[cfg(feature = "std")]
fn sink() -> &'static Mutex<DiagnosticSink> {
static SINK: OnceLock<Mutex<DiagnosticSink>> = OnceLock::new();
SINK.get_or_init(|| Mutex::new(default_sink))
}
#[cfg(feature = "std")]
pub fn set_sink(new_sink: DiagnosticSink) {
if let Ok(mut s) = sink().lock() {
*s = new_sink;
}
}
#[cfg(feature = "std")]
fn scope() -> &'static Mutex<Option<String>> {
static SCOPE: OnceLock<Mutex<Option<String>>> = OnceLock::new();
SCOPE.get_or_init(|| Mutex::new(None))
}
#[cfg(feature = "std")]
pub fn set_scope(name: Option<String>) {
if let Ok(mut sc) = scope().lock() {
*sc = name;
}
}
#[cfg(feature = "std")]
#[must_use]
pub fn current_scope() -> Option<String> {
scope().lock().ok().and_then(|sc| sc.clone())
}
#[cfg(feature = "std")]
pub fn emit(message: String) {
let tagged = match current_scope() {
Some(test) => format!("[test={test}] {message}"),
None => message,
};
let f = sink().lock().map(|s| *s).unwrap_or(default_sink);
f(&tagged);
record(tagged);
}
#[cfg(feature = "std")]
pub fn record(message: String) {
let Ok(mut r) = ring().lock() else {
return; };
if r.len() == CAPACITY {
r.pop_front();
}
r.push_back(message);
}
#[cfg(feature = "std")]
#[must_use]
pub fn recorded() -> Vec<String> {
ring()
.lock()
.map(|r| r.iter().cloned().collect())
.unwrap_or_default()
}
#[cfg(feature = "std")]
#[must_use]
pub fn any_contains(needle: &str) -> bool {
ring()
.lock()
.map(|r| r.iter().any(|m| m.contains(needle)))
.unwrap_or(false)
}
#[cfg(feature = "std")]
#[must_use]
pub fn test_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
#[cfg(feature = "std")]
pub fn clear() {
if let Ok(mut r) = ring().lock() {
r.clear();
}
}
#[cfg(not(feature = "std"))]
pub fn emit(_message: alloc::string::String) {}
#[cfg(not(feature = "std"))]
pub fn record(_message: alloc::string::String) {}
#[cfg(not(feature = "std"))]
#[must_use]
pub fn recorded() -> alloc::vec::Vec<alloc::string::String> {
alloc::vec::Vec::new()
}
#[cfg(not(feature = "std"))]
#[must_use]
pub fn any_contains(_needle: &str) -> bool {
false
}
#[cfg(not(feature = "std"))]
pub fn clear() {}
#[cfg(test)]
#[path = "diagnostics_test.rs"]
mod diagnostics_test;