blob: 50e0e6669d80d8ea08f262e90c19b9131d8a7ee1 [file] [log] [blame]
#![allow(missing_docs, dead_code)]
pub use tracing_mock::{event, field, span, subscriber};
use tracing_core::{
span::{Attributes, Id, Record},
Event, Subscriber,
};
use tracing_mock::{
event::MockEvent,
span::{MockSpan, NewSpan},
subscriber::{Expect, MockHandle},
};
use tracing_subscriber::{
layer::{Context, Layer},
registry::{LookupSpan, SpanRef},
};
use std::{
collections::VecDeque,
fmt,
sync::{Arc, Mutex},
};
pub mod layer {
use super::ExpectLayerBuilder;
pub fn mock() -> ExpectLayerBuilder {
ExpectLayerBuilder {
expected: Default::default(),
name: std::thread::current()
.name()
.map(String::from)
.unwrap_or_default(),
}
}
pub fn named(name: impl std::fmt::Display) -> ExpectLayerBuilder {
mock().named(name)
}
}
pub struct ExpectLayerBuilder {
expected: VecDeque<Expect>,
name: String,
}
pub struct ExpectLayer {
expected: Arc<Mutex<VecDeque<Expect>>>,
current: Mutex<Vec<Id>>,
name: String,
}
impl ExpectLayerBuilder {
/// Overrides the name printed by the mock subscriber's debugging output.
///
/// The debugging output is displayed if the test panics, or if the test is
/// run with `--nocapture`.
///
/// By default, the mock subscriber's name is the name of the test
/// (*technically*, the name of the thread where it was created, which is
/// the name of the test unless tests are run with `--test-threads=1`).
/// When a test has only one mock subscriber, this is sufficient. However,
/// some tests may include multiple subscribers, in order to test
/// interactions between multiple subscribers. In that case, it can be
/// helpful to give each subscriber a separate name to distinguish where the
/// debugging output comes from.
pub fn named(mut self, name: impl fmt::Display) -> Self {
use std::fmt::Write;
if !self.name.is_empty() {
write!(&mut self.name, "::{}", name).unwrap();
} else {
self.name = name.to_string();
}
self
}
pub fn enter(mut self, span: MockSpan) -> Self {
self.expected.push_back(Expect::Enter(span));
self
}
pub fn event(mut self, event: MockEvent) -> Self {
self.expected.push_back(Expect::Event(event));
self
}
pub fn exit(mut self, span: MockSpan) -> Self {
self.expected.push_back(Expect::Exit(span));
self
}
pub fn done(mut self) -> Self {
self.expected.push_back(Expect::Nothing);
self
}
pub fn record<I>(mut self, span: MockSpan, fields: I) -> Self
where
I: Into<field::Expect>,
{
self.expected.push_back(Expect::Visit(span, fields.into()));
self
}
pub fn new_span<I>(mut self, new_span: I) -> Self
where
I: Into<NewSpan>,
{
self.expected.push_back(Expect::NewSpan(new_span.into()));
self
}
pub fn run(self) -> ExpectLayer {
ExpectLayer {
expected: Arc::new(Mutex::new(self.expected)),
name: self.name,
current: Mutex::new(Vec::new()),
}
}
pub fn run_with_handle(self) -> (ExpectLayer, MockHandle) {
let expected = Arc::new(Mutex::new(self.expected));
let handle = MockHandle::new(expected.clone(), self.name.clone());
let layer = ExpectLayer {
expected,
name: self.name,
current: Mutex::new(Vec::new()),
};
(layer, handle)
}
}
impl ExpectLayer {
fn check_span_ref<'spans, S>(
&self,
expected: &MockSpan,
actual: &SpanRef<'spans, S>,
what_happened: impl fmt::Display,
) where
S: LookupSpan<'spans>,
{
if let Some(exp_name) = expected.name() {
assert_eq!(
actual.name(),
exp_name,
"\n[{}] expected {} a span named {:?}\n\
[{}] but it was named {:?} instead (span {} {:?})",
self.name,
what_happened,
exp_name,
self.name,
actual.name(),
actual.name(),
actual.id()
);
}
if let Some(exp_level) = expected.level() {
let actual_level = actual.metadata().level();
assert_eq!(
actual_level,
&exp_level,
"\n[{}] expected {} a span at {:?}\n\
[{}] but it was at {:?} instead (span {} {:?})",
self.name,
what_happened,
exp_level,
self.name,
actual_level,
actual.name(),
actual.id(),
);
}
if let Some(exp_target) = expected.target() {
let actual_target = actual.metadata().target();
assert_eq!(
actual_target,
exp_target,
"\n[{}] expected {} a span with target {:?}\n\
[{}] but it had the target {:?} instead (span {} {:?})",
self.name,
what_happened,
exp_target,
self.name,
actual_target,
actual.name(),
actual.id(),
);
}
}
}
impl<S> Layer<S> for ExpectLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn register_callsite(
&self,
metadata: &'static tracing::Metadata<'static>,
) -> tracing_core::Interest {
println!("[{}] register_callsite {:#?}", self.name, metadata);
tracing_core::Interest::always()
}
fn on_record(&self, _: &Id, _: &Record<'_>, _: Context<'_, S>) {
unimplemented!(
"so far, we don't have any tests that need an `on_record` \
implementation.\nif you just wrote one that does, feel free to \
implement it!"
);
}
fn on_event(&self, event: &Event<'_>, cx: Context<'_, S>) {
let name = event.metadata().name();
println!(
"[{}] event: {}; level: {}; target: {}",
self.name,
name,
event.metadata().level(),
event.metadata().target(),
);
match self.expected.lock().unwrap().pop_front() {
None => {}
Some(Expect::Event(mut expected)) => {
let get_parent_name = || cx.event_span(event).map(|span| span.name().to_string());
expected.check(event, get_parent_name, &self.name);
let mut current_scope = cx.event_scope(event).into_iter().flatten();
let expected_scope = expected.scope_mut();
let mut i = 0;
for (expected, actual) in expected_scope.iter_mut().zip(&mut current_scope) {
println!(
"[{}] event_scope[{}] actual={} ({:?}); expected={}",
self.name,
i,
actual.name(),
actual.id(),
expected
);
self.check_span_ref(
expected,
&actual,
format_args!("the {}th span in the event's scope to be", i),
);
i += 1;
}
let remaining_expected = &expected_scope[i..];
assert!(
remaining_expected.is_empty(),
"\n[{}] did not observe all expected spans in event scope!\n[{}] missing: {:#?}",
self.name,
self.name,
remaining_expected,
);
assert!(
current_scope.next().is_none(),
"\n[{}] did not expect all spans in the actual event scope!",
self.name,
);
}
Some(ex) => ex.bad(&self.name, format_args!("observed event {:#?}", event)),
}
}
fn on_follows_from(&self, _span: &Id, _follows: &Id, _: Context<'_, S>) {
// TODO: it should be possible to expect spans to follow from other spans
}
fn on_new_span(&self, span: &Attributes<'_>, id: &Id, cx: Context<'_, S>) {
let meta = span.metadata();
println!(
"[{}] new_span: name={:?}; target={:?}; id={:?};",
self.name,
meta.name(),
meta.target(),
id
);
let mut expected = self.expected.lock().unwrap();
let was_expected = matches!(expected.front(), Some(Expect::NewSpan(_)));
if was_expected {
if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() {
let get_parent_name = || {
span.parent()
.and_then(|id| cx.span(id))
.or_else(|| cx.lookup_current())
.map(|span| span.name().to_string())
};
expected.check(span, get_parent_name, &self.name);
}
}
}
fn on_enter(&self, id: &Id, cx: Context<'_, S>) {
let span = cx
.span(id)
.unwrap_or_else(|| panic!("[{}] no span for ID {:?}", self.name, id));
println!("[{}] enter: {}; id={:?};", self.name, span.name(), id);
match self.expected.lock().unwrap().pop_front() {
None => {}
Some(Expect::Enter(ref expected_span)) => {
self.check_span_ref(expected_span, &span, "to enter");
}
Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name())),
}
self.current.lock().unwrap().push(id.clone());
}
fn on_exit(&self, id: &Id, cx: Context<'_, S>) {
if std::thread::panicking() {
// `exit()` can be called in `drop` impls, so we must guard against
// double panics.
println!("[{}] exit {:?} while panicking", self.name, id);
return;
}
let span = cx
.span(id)
.unwrap_or_else(|| panic!("[{}] no span for ID {:?}", self.name, id));
println!("[{}] exit: {}; id={:?};", self.name, span.name(), id);
match self.expected.lock().unwrap().pop_front() {
None => {}
Some(Expect::Exit(ref expected_span)) => {
self.check_span_ref(expected_span, &span, "to exit");
let curr = self.current.lock().unwrap().pop();
assert_eq!(
Some(id),
curr.as_ref(),
"[{}] exited span {:?}, but the current span was {:?}",
self.name,
span.name(),
curr.as_ref().and_then(|id| cx.span(id)).map(|s| s.name())
);
}
Some(ex) => ex.bad(&self.name, format_args!("exited span {:?}", span.name())),
};
}
fn on_close(&self, id: Id, cx: Context<'_, S>) {
if std::thread::panicking() {
// `try_close` can be called in `drop` impls, so we must guard against
// double panics.
println!("[{}] close {:?} while panicking", self.name, id);
return;
}
let span = cx.span(&id);
let name = span.as_ref().map(|span| {
println!("[{}] close_span: {}; id={:?};", self.name, span.name(), id,);
span.name()
});
if name.is_none() {
println!("[{}] drop_span: id={:?}", self.name, id);
}
if let Ok(mut expected) = self.expected.try_lock() {
let was_expected = match expected.front() {
Some(Expect::DropSpan(ref expected_span)) => {
// Don't assert if this function was called while panicking,
// as failing the assertion can cause a double panic.
if !::std::thread::panicking() {
if let Some(ref span) = span {
self.check_span_ref(expected_span, span, "to close");
}
}
true
}
Some(Expect::Event(_)) => {
if !::std::thread::panicking() {
panic!(
"[{}] expected an event, but dropped span {} (id={:?}) instead",
self.name,
name.unwrap_or("<unknown name>"),
id
);
}
true
}
_ => false,
};
if was_expected {
expected.pop_front();
}
}
}
fn on_id_change(&self, _old: &Id, _new: &Id, _ctx: Context<'_, S>) {
panic!("well-behaved subscribers should never do this to us, lol");
}
}
impl fmt::Debug for ExpectLayer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("ExpectLayer");
s.field("name", &self.name);
if let Ok(expected) = self.expected.try_lock() {
s.field("expected", &expected);
} else {
s.field("expected", &format_args!("<locked>"));
}
if let Ok(current) = self.current.try_lock() {
s.field("current", &format_args!("{:?}", &current));
} else {
s.field("current", &format_args!("<locked>"));
}
s.finish()
}
}