//! Test-only helpers shared across the crate's unit tests. //! //! Compiled only under `cfg(test)`, so nothing here reaches a consumer. /// One `tracing` event a [`events_from`] run saw. pub(crate) struct CapturedEvent { /// The event's message, which `tracing` carries as a field named `message`. pub(crate) message: Option, /// Every other field, in the order they were recorded. pub(crate) fields: Vec<(String, String)>, } impl CapturedEvent { /// The value recorded for `name`, if the event carried it. pub(crate) fn field(&self, name: &str) -> Option<&str> { self.fields .iter() .find(|(k, _)| k == name) .map(|(_, v)| v.as_str()) } } /// Collects every field of one event. /// /// A `%value` field arrives through `record_debug` as a `format_args`, whose /// `Debug` rendering is the displayed text with no quotes around it, so both /// recorders below produce the bare string. #[derive(Default)] struct EventVisitor { message: Option, fields: Vec<(String, String)>, } impl EventVisitor { fn put(&mut self, name: &str, value: String) { if name == "message" { self.message = Some(value); } else { self.fields.push((name.to_string(), value)); } } } impl tracing::field::Visit for EventVisitor { fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { self.put(field.name(), format!("{value:?}")); } fn record_str(&mut self, field: &tracing::field::Field, value: &str) { self.put(field.name(), value.to_string()); } fn record_bool(&mut self, field: &tracing::field::Field, value: bool) { self.put(field.name(), value.to_string()); } } /// The buffer one [`events_from`] call is filling, and the thread it belongs to. struct Sink { thread: std::thread::ThreadId, events: Vec, } /// Where the installed subscriber puts what it sees. `None` between captures. static SINK: std::sync::Mutex> = std::sync::Mutex::new(None); /// One capture at a time, since there is one sink. static CAPTURING: std::sync::Mutex<()> = std::sync::Mutex::new(()); /// The process-wide subscriber, installed once and never removed. /// /// Global rather than thread-scoped, which is the opposite of what it looks /// like it should be. `tracing::subscriber::with_default` is thread-local, but /// the interest cache it feeds is process-wide and is rebuilt whenever any /// dispatcher is registered or dropped. Under a test harness running dozens of /// threads, another test dropping its dispatcher can reset a callsite to /// "never" between this one registering and emitting, and the event is silently /// lost. Measured: passes on an 8-core box, fails on a 96-core one. /// /// Installing once and leaving it there makes the cached interest stable. The /// sink is what moves, and it records only the thread that installed it, so a /// capture still sees exactly its own events. struct Recorder; impl tracing::Subscriber for Recorder { fn register_callsite( &self, _: &'static tracing::Metadata<'static>, ) -> tracing::subscriber::Interest { // `sometimes` so `enabled` is consulted per event rather than the // verdict being cached; the sink comes and goes. tracing::subscriber::Interest::sometimes() } fn enabled(&self, _: &tracing::Metadata<'_>) -> bool { true } fn new_span(&self, _: &tracing::span::Attributes<'_>) -> tracing::span::Id { tracing::span::Id::from_u64(1) } fn record(&self, _: &tracing::span::Id, _: &tracing::span::Record<'_>) {} fn record_follows_from(&self, _: &tracing::span::Id, _: &tracing::span::Id) {} fn event(&self, event: &tracing::Event<'_>) { let mut sink = SINK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let Some(sink) = sink.as_mut() else { return }; if sink.thread != std::thread::current().id() { return; } let mut visitor = EventVisitor::default(); event.record(&mut visitor); sink.events.push(CapturedEvent { message: visitor.message, fields: visitor.fields, }); } fn enter(&self, _: &tracing::span::Id) {} fn exit(&self, _: &tracing::span::Id) {} } /// Every `tracing` event `f` emitted on this thread, in order. /// /// What it is for: a branch whose only effect is a log line, which no assertion /// on a return value can reach. pub(crate) fn events_from(f: impl FnOnce()) -> Vec { static INSTALL: std::sync::Once = std::sync::Once::new(); INSTALL.call_once(|| { // Err would mean something else claimed the global default; nothing in // this crate's tests does, and a capture that recorded nothing would // fail its own assertions rather than pass quietly. let _ = tracing::subscriber::set_global_default(Recorder); }); let _capturing = CAPTURING .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); *SINK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(Sink { thread: std::thread::current().id(), events: Vec::new(), }); f(); SINK.lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .take() .expect("the sink this call installed") .events }