| #![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!("{:?}", ¤t)); |
| } else { |
| s.field("current", &format_args!("<locked>")); |
| } |
| |
| s.finish() |
| } |
| } |