diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/tracing/tests/support | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/tracing/tests/support')
-rw-r--r-- | third_party/rust/tracing/tests/support/event.rs | 99 | ||||
-rw-r--r-- | third_party/rust/tracing/tests/support/field.rs | 226 | ||||
-rw-r--r-- | third_party/rust/tracing/tests/support/metadata.rs | 64 | ||||
-rw-r--r-- | third_party/rust/tracing/tests/support/mod.rs | 14 | ||||
-rw-r--r-- | third_party/rust/tracing/tests/support/span.rs | 167 | ||||
-rw-r--r-- | third_party/rust/tracing/tests/support/subscriber.rs | 569 |
6 files changed, 1139 insertions, 0 deletions
diff --git a/third_party/rust/tracing/tests/support/event.rs b/third_party/rust/tracing/tests/support/event.rs new file mode 100644 index 0000000000..7033d8a134 --- /dev/null +++ b/third_party/rust/tracing/tests/support/event.rs @@ -0,0 +1,99 @@ +#![allow(missing_docs)] +use super::{field, metadata, Parent}; + +use std::fmt; + +/// A mock event. +/// +/// This is intended for use with the mock subscriber API in the +/// `subscriber` module. +#[derive(Debug, Default, Eq, PartialEq)] +pub struct MockEvent { + pub fields: Option<field::Expect>, + pub(in crate::support) parent: Option<Parent>, + metadata: metadata::Expect, +} + +pub fn mock() -> MockEvent { + MockEvent { + ..Default::default() + } +} + +impl MockEvent { + pub fn named<I>(self, name: I) -> Self + where + I: Into<String>, + { + Self { + metadata: metadata::Expect { + name: Some(name.into()), + ..self.metadata + }, + ..self + } + } + + pub fn with_fields<I>(self, fields: I) -> Self + where + I: Into<field::Expect>, + { + Self { + fields: Some(fields.into()), + ..self + } + } + + pub fn at_level(self, level: tracing::Level) -> Self { + Self { + metadata: metadata::Expect { + level: Some(level), + ..self.metadata + }, + ..self + } + } + + pub fn with_target<I>(self, target: I) -> Self + where + I: Into<String>, + { + Self { + metadata: metadata::Expect { + target: Some(target.into()), + ..self.metadata + }, + ..self + } + } + + pub fn with_explicit_parent(self, parent: Option<&str>) -> MockEvent { + let parent = match parent { + Some(name) => Parent::Explicit(name.into()), + None => Parent::ExplicitRoot, + }; + Self { + parent: Some(parent), + ..self + } + } + + pub(in crate::support) fn check(&mut self, event: &tracing::Event<'_>) { + let meta = event.metadata(); + let name = meta.name(); + self.metadata + .check(meta, format_args!("event \"{}\"", name)); + assert!(meta.is_event(), "expected {}, but got {:?}", self, event); + if let Some(ref mut expected_fields) = self.fields { + let mut checker = expected_fields.checker(name.to_string()); + event.record(&mut checker); + checker.finish(); + } + } +} + +impl fmt::Display for MockEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "an event{}", self.metadata) + } +} diff --git a/third_party/rust/tracing/tests/support/field.rs b/third_party/rust/tracing/tests/support/field.rs new file mode 100644 index 0000000000..3667cf08b4 --- /dev/null +++ b/third_party/rust/tracing/tests/support/field.rs @@ -0,0 +1,226 @@ +use tracing::{ + callsite, + callsite::Callsite, + field::{self, Field, Value, Visit}, + metadata::Kind, +}; + +use std::{collections::HashMap, fmt}; + +#[derive(Default, Debug, Eq, PartialEq)] +pub struct Expect { + fields: HashMap<String, MockValue>, + only: bool, +} + +#[derive(Debug)] +pub struct MockField { + name: String, + value: MockValue, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum MockValue { + I64(i64), + U64(u64), + Bool(bool), + Str(String), + Debug(String), + Any, +} + +pub fn mock<K>(name: K) -> MockField +where + String: From<K>, +{ + MockField { + name: name.into(), + value: MockValue::Any, + } +} + +impl MockField { + /// Expect a field with the given name and value. + pub fn with_value(self, value: &dyn Value) -> Self { + Self { + value: MockValue::from(value), + ..self + } + } + + pub fn and(self, other: MockField) -> Expect { + Expect { + fields: HashMap::new(), + only: false, + } + .and(self) + .and(other) + } + + pub fn only(self) -> Expect { + Expect { + fields: HashMap::new(), + only: true, + } + .and(self) + } +} + +impl Into<Expect> for MockField { + fn into(self) -> Expect { + Expect { + fields: HashMap::new(), + only: false, + } + .and(self) + } +} + +impl Expect { + pub fn and(mut self, field: MockField) -> Self { + self.fields.insert(field.name, field.value); + self + } + + /// Indicates that no fields other than those specified should be expected. + pub fn only(self) -> Self { + Self { only: true, ..self } + } + + fn compare_or_panic(&mut self, name: &str, value: &dyn Value, ctx: &str) { + let value = value.into(); + match self.fields.remove(name) { + Some(MockValue::Any) => {} + Some(expected) => assert!( + expected == value, + "\nexpected `{}` to contain:\n\t`{}{}`\nbut got:\n\t`{}{}`", + ctx, + name, + expected, + name, + value + ), + None if self.only => panic!( + "\nexpected `{}` to contain only:\n\t`{}`\nbut got:\n\t`{}{}`", + ctx, self, name, value + ), + _ => {} + } + } + + pub fn checker(&mut self, ctx: String) -> CheckVisitor<'_> { + CheckVisitor { expect: self, ctx } + } + + pub fn is_empty(&self) -> bool { + self.fields.is_empty() + } +} + +impl fmt::Display for MockValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MockValue::I64(v) => write!(f, "i64 = {:?}", v), + MockValue::U64(v) => write!(f, "u64 = {:?}", v), + MockValue::Bool(v) => write!(f, "bool = {:?}", v), + MockValue::Str(v) => write!(f, "&str = {:?}", v), + MockValue::Debug(v) => write!(f, "&fmt::Debug = {:?}", v), + MockValue::Any => write!(f, "_ = _"), + } + } +} + +pub struct CheckVisitor<'a> { + expect: &'a mut Expect, + ctx: String, +} + +impl<'a> Visit for CheckVisitor<'a> { + fn record_i64(&mut self, field: &Field, value: i64) { + self.expect + .compare_or_panic(field.name(), &value, &self.ctx[..]) + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.expect + .compare_or_panic(field.name(), &value, &self.ctx[..]) + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.expect + .compare_or_panic(field.name(), &value, &self.ctx[..]) + } + + fn record_str(&mut self, field: &Field, value: &str) { + self.expect + .compare_or_panic(field.name(), &value, &self.ctx[..]) + } + + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + self.expect + .compare_or_panic(field.name(), &field::debug(value), &self.ctx) + } +} + +impl<'a> CheckVisitor<'a> { + pub fn finish(self) { + assert!( + self.expect.fields.is_empty(), + "{}missing {}", + self.expect, + self.ctx + ); + } +} + +impl<'a> From<&'a dyn Value> for MockValue { + fn from(value: &'a dyn Value) -> Self { + struct MockValueBuilder { + value: Option<MockValue>, + } + + impl Visit for MockValueBuilder { + fn record_i64(&mut self, _: &Field, value: i64) { + self.value = Some(MockValue::I64(value)); + } + + fn record_u64(&mut self, _: &Field, value: u64) { + self.value = Some(MockValue::U64(value)); + } + + fn record_bool(&mut self, _: &Field, value: bool) { + self.value = Some(MockValue::Bool(value)); + } + + fn record_str(&mut self, _: &Field, value: &str) { + self.value = Some(MockValue::Str(value.to_owned())); + } + + fn record_debug(&mut self, _: &Field, value: &dyn fmt::Debug) { + self.value = Some(MockValue::Debug(format!("{:?}", value))); + } + } + + let fake_field = callsite!(name: "fake", kind: Kind::EVENT, fields: fake_field) + .metadata() + .fields() + .field("fake_field") + .unwrap(); + let mut builder = MockValueBuilder { value: None }; + value.record(&fake_field, &mut builder); + builder + .value + .expect("finish called before a value was recorded") + } +} + +impl fmt::Display for Expect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "fields ")?; + let entries = self + .fields + .iter() + .map(|(k, v)| (field::display(k), field::display(v))); + f.debug_map().entries(entries).finish() + } +} diff --git a/third_party/rust/tracing/tests/support/metadata.rs b/third_party/rust/tracing/tests/support/metadata.rs new file mode 100644 index 0000000000..2c3606b05e --- /dev/null +++ b/third_party/rust/tracing/tests/support/metadata.rs @@ -0,0 +1,64 @@ +use std::fmt; +use tracing::Metadata; + +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct Expect { + pub name: Option<String>, + pub level: Option<tracing::Level>, + pub target: Option<String>, +} + +impl Expect { + pub(in crate::support) fn check(&self, actual: &Metadata<'_>, ctx: fmt::Arguments<'_>) { + if let Some(ref expected_name) = self.name { + let name = actual.name(); + assert!( + expected_name == name, + "expected {} to be named `{}`, but got one named `{}`", + ctx, + expected_name, + name + ) + } + + if let Some(ref expected_level) = self.level { + let level = actual.level(); + assert!( + expected_level == level, + "expected {} to be at level `{:?}`, but it was at level `{:?}` instead", + ctx, + expected_level, + level, + ) + } + + if let Some(ref expected_target) = self.target { + let target = actual.target(); + assert!( + expected_target == target, + "expected {} to have target `{}`, but it had target `{}` instead", + ctx, + expected_target, + target, + ) + } + } +} + +impl fmt::Display for Expect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref name) = self.name { + write!(f, " named `{}`", name)?; + } + + if let Some(ref level) = self.level { + write!(f, " at the `{:?}` level", level)?; + } + + if let Some(ref target) = self.target { + write!(f, " with target `{}`", target)?; + } + + Ok(()) + } +} diff --git a/third_party/rust/tracing/tests/support/mod.rs b/third_party/rust/tracing/tests/support/mod.rs new file mode 100644 index 0000000000..7900f38c76 --- /dev/null +++ b/third_party/rust/tracing/tests/support/mod.rs @@ -0,0 +1,14 @@ +#![allow(dead_code)] +pub mod event; +pub mod field; +mod metadata; +pub mod span; +pub mod subscriber; + +#[derive(Debug, Eq, PartialEq)] +pub(in crate::support) enum Parent { + ContextualRoot, + Contextual(String), + ExplicitRoot, + Explicit(String), +} diff --git a/third_party/rust/tracing/tests/support/span.rs b/third_party/rust/tracing/tests/support/span.rs new file mode 100644 index 0000000000..023e5b7079 --- /dev/null +++ b/third_party/rust/tracing/tests/support/span.rs @@ -0,0 +1,167 @@ +#![allow(missing_docs)] +use super::{field, metadata, Parent}; +use std::fmt; + +/// A mock span. +/// +/// This is intended for use with the mock subscriber API in the +/// `subscriber` module. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct MockSpan { + pub(in crate::support) metadata: metadata::Expect, +} + +#[derive(Debug, Default, Eq, PartialEq)] +pub struct NewSpan { + pub(in crate::support) span: MockSpan, + pub(in crate::support) fields: field::Expect, + pub(in crate::support) parent: Option<Parent>, +} + +pub fn mock() -> MockSpan { + MockSpan { + ..Default::default() + } +} + +impl MockSpan { + pub fn named<I>(self, name: I) -> Self + where + I: Into<String>, + { + Self { + metadata: metadata::Expect { + name: Some(name.into()), + ..self.metadata + }, + } + } + + pub fn at_level(self, level: tracing::Level) -> Self { + Self { + metadata: metadata::Expect { + level: Some(level), + ..self.metadata + }, + } + } + + pub fn with_target<I>(self, target: I) -> Self + where + I: Into<String>, + { + Self { + metadata: metadata::Expect { + target: Some(target.into()), + ..self.metadata + }, + } + } + + pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan { + let parent = match parent { + Some(name) => Parent::Explicit(name.into()), + None => Parent::ExplicitRoot, + }; + NewSpan { + parent: Some(parent), + span: self, + ..Default::default() + } + } + + pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { + let parent = match parent { + Some(name) => Parent::Contextual(name.into()), + None => Parent::ContextualRoot, + }; + NewSpan { + parent: Some(parent), + span: self, + ..Default::default() + } + } + + pub fn name(&self) -> Option<&str> { + self.metadata.name.as_ref().map(String::as_ref) + } + + pub fn with_field<I>(self, fields: I) -> NewSpan + where + I: Into<field::Expect>, + { + NewSpan { + span: self, + fields: fields.into(), + ..Default::default() + } + } + + pub(in crate::support) fn check_metadata(&self, actual: &tracing::Metadata<'_>) { + self.metadata.check(actual, format_args!("span {}", self)); + assert!(actual.is_span(), "expected a span but got {:?}", actual); + } +} + +impl fmt::Display for MockSpan { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.metadata.name.is_some() { + write!(f, "a span{}", self.metadata) + } else { + write!(f, "any span{}", self.metadata) + } + } +} + +impl Into<NewSpan> for MockSpan { + fn into(self) -> NewSpan { + NewSpan { + span: self, + ..Default::default() + } + } +} + +impl NewSpan { + pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan { + let parent = match parent { + Some(name) => Parent::Explicit(name.into()), + None => Parent::ExplicitRoot, + }; + NewSpan { + parent: Some(parent), + ..self + } + } + + pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { + let parent = match parent { + Some(name) => Parent::Contextual(name.into()), + None => Parent::ContextualRoot, + }; + NewSpan { + parent: Some(parent), + ..self + } + } + + pub fn with_field<I>(self, fields: I) -> NewSpan + where + I: Into<field::Expect>, + { + NewSpan { + fields: fields.into(), + ..self + } + } +} + +impl fmt::Display for NewSpan { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "a new span{}", self.span.metadata)?; + if !self.fields.is_empty() { + write!(f, " with {}", self.fields)?; + } + Ok(()) + } +} diff --git a/third_party/rust/tracing/tests/support/subscriber.rs b/third_party/rust/tracing/tests/support/subscriber.rs new file mode 100644 index 0000000000..83f1139849 --- /dev/null +++ b/third_party/rust/tracing/tests/support/subscriber.rs @@ -0,0 +1,569 @@ +#![allow(missing_docs)] +use super::{ + event::MockEvent, + field as mock_field, + span::{MockSpan, NewSpan}, + Parent, +}; +use std::{ + collections::{HashMap, VecDeque}, + fmt, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, + thread, +}; +use tracing::{ + level_filters::LevelFilter, + span::{self, Attributes, Id}, + subscriber::Interest, + Event, Metadata, Subscriber, +}; + +#[derive(Debug, Eq, PartialEq)] +enum Expect { + Event(MockEvent), + Enter(MockSpan), + Exit(MockSpan), + CloneSpan(MockSpan), + DropSpan(MockSpan), + Visit(MockSpan, mock_field::Expect), + NewSpan(NewSpan), + Nothing, +} + +struct SpanState { + name: &'static str, + refs: usize, +} + +struct Running<F: Fn(&Metadata<'_>) -> bool> { + spans: Mutex<HashMap<Id, SpanState>>, + expected: Arc<Mutex<VecDeque<Expect>>>, + current: Mutex<Vec<Id>>, + ids: AtomicUsize, + max_level: Option<LevelFilter>, + filter: F, + name: String, +} + +pub struct MockSubscriber<F: Fn(&Metadata<'_>) -> bool> { + expected: VecDeque<Expect>, + max_level: Option<LevelFilter>, + filter: F, + name: String, +} + +pub struct MockHandle(Arc<Mutex<VecDeque<Expect>>>, String); + +pub fn mock() -> MockSubscriber<fn(&Metadata<'_>) -> bool> { + MockSubscriber { + expected: VecDeque::new(), + filter: (|_: &Metadata<'_>| true) as for<'r, 's> fn(&'r Metadata<'s>) -> _, + max_level: None, + name: thread::current() + .name() + .unwrap_or("mock_subscriber") + .to_string(), + } +} + +impl<F> MockSubscriber<F> +where + F: Fn(&Metadata<'_>) -> bool + 'static, +{ + /// 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(self, name: impl ToString) -> Self { + 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 clone_span(mut self, span: MockSpan) -> Self { + self.expected.push_back(Expect::CloneSpan(span)); + self + } + + #[allow(deprecated)] + pub fn drop_span(mut self, span: MockSpan) -> Self { + self.expected.push_back(Expect::DropSpan(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<mock_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 with_filter<G>(self, filter: G) -> MockSubscriber<G> + where + G: Fn(&Metadata<'_>) -> bool + 'static, + { + MockSubscriber { + expected: self.expected, + filter, + max_level: self.max_level, + name: self.name, + } + } + + pub fn with_max_level_hint(self, hint: impl Into<LevelFilter>) -> Self { + Self { + max_level: Some(hint.into()), + ..self + } + } + + pub fn run(self) -> impl Subscriber { + let (subscriber, _) = self.run_with_handle(); + subscriber + } + + pub fn run_with_handle(self) -> (impl Subscriber, MockHandle) { + let expected = Arc::new(Mutex::new(self.expected)); + let handle = MockHandle(expected.clone(), self.name.clone()); + let subscriber = Running { + spans: Mutex::new(HashMap::new()), + expected, + current: Mutex::new(Vec::new()), + ids: AtomicUsize::new(1), + filter: self.filter, + max_level: self.max_level, + name: self.name, + }; + (subscriber, handle) + } +} + +impl<F> Subscriber for Running<F> +where + F: Fn(&Metadata<'_>) -> bool + 'static, +{ + fn enabled(&self, meta: &Metadata<'_>) -> bool { + println!("[{}] enabled: {:#?}", self.name, meta); + let enabled = (self.filter)(meta); + println!("[{}] enabled -> {}", self.name, enabled); + enabled + } + + fn register_callsite(&self, meta: &'static Metadata<'static>) -> Interest { + println!("[{}] register_callsite: {:#?}", self.name, meta); + if self.enabled(meta) { + Interest::always() + } else { + Interest::never() + } + } + fn max_level_hint(&self) -> Option<LevelFilter> { + self.max_level + } + + fn record(&self, id: &Id, values: &span::Record<'_>) { + let spans = self.spans.lock().unwrap(); + let mut expected = self.expected.lock().unwrap(); + let span = spans + .get(id) + .unwrap_or_else(|| panic!("[{}] no span for ID {:?}", self.name, id)); + println!( + "[{}] record: {}; id={:?}; values={:?};", + self.name, span.name, id, values + ); + let was_expected = if let Some(Expect::Visit(_, _)) = expected.front() { + true + } else { + false + }; + if was_expected { + if let Expect::Visit(expected_span, mut expected_values) = expected.pop_front().unwrap() + { + if let Some(name) = expected_span.name() { + assert_eq!(name, span.name); + } + let mut checker = expected_values.checker(format!("span {}: ", span.name)); + values.record(&mut checker); + checker.finish(); + } + } + } + + fn event(&self, event: &Event<'_>) { + let name = event.metadata().name(); + println!("[{}] event: {};", self.name, name); + match self.expected.lock().unwrap().pop_front() { + None => {} + Some(Expect::Event(mut expected)) => { + let spans = self.spans.lock().unwrap(); + expected.check(event); + match expected.parent { + Some(Parent::ExplicitRoot) => { + assert!( + event.is_root(), + "[{}] expected {:?} to be an explicit root event", + self.name, + name + ); + } + Some(Parent::Explicit(expected_parent)) => { + let actual_parent = + event.parent().and_then(|id| spans.get(id)).map(|s| s.name); + assert_eq!( + Some(expected_parent.as_ref()), + actual_parent, + "[{}] expected {:?} to have explicit parent {:?}", + self.name, + name, + expected_parent, + ); + } + Some(Parent::ContextualRoot) => { + assert!( + event.is_contextual(), + "[{}] expected {:?} to have a contextual parent", + self.name, + name + ); + assert!( + self.current.lock().unwrap().last().is_none(), + "[{}] expected {:?} to be a root, but we were inside a span", + self.name, + name + ); + } + Some(Parent::Contextual(expected_parent)) => { + assert!( + event.is_contextual(), + "[{}] expected {:?} to have a contextual parent", + self.name, + name + ); + let stack = self.current.lock().unwrap(); + let actual_parent = + stack.last().and_then(|id| spans.get(id)).map(|s| s.name); + assert_eq!( + Some(expected_parent.as_ref()), + actual_parent, + "[{}] expected {:?} to have contextual parent {:?}", + self.name, + name, + expected_parent, + ); + } + None => {} + } + } + Some(ex) => ex.bad( + &self.name, + format_args!("[{}] observed event {:?}", self.name, event), + ), + } + } + + fn record_follows_from(&self, _span: &Id, _follows: &Id) { + // TODO: it should be possible to expect spans to follow from other spans + } + + fn new_span(&self, span: &Attributes<'_>) -> Id { + let meta = span.metadata(); + let id = self.ids.fetch_add(1, Ordering::SeqCst); + let id = Id::from_u64(id as u64); + println!( + "[{}] new_span: name={:?}; target={:?}; id={:?};", + self.name, + meta.name(), + meta.target(), + id + ); + let mut expected = self.expected.lock().unwrap(); + let was_expected = match expected.front() { + Some(Expect::NewSpan(_)) => true, + _ => false, + }; + let mut spans = self.spans.lock().unwrap(); + if was_expected { + if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() { + let name = meta.name(); + expected + .span + .metadata + .check(meta, format_args!("span `{}`", name)); + let mut checker = expected.fields.checker(name.to_string()); + span.record(&mut checker); + checker.finish(); + match expected.parent { + Some(Parent::ExplicitRoot) => { + assert!( + span.is_root(), + "[{}] expected {:?} to be an explicit root span", + self.name, + name + ); + } + Some(Parent::Explicit(expected_parent)) => { + let actual_parent = + span.parent().and_then(|id| spans.get(id)).map(|s| s.name); + assert_eq!( + Some(expected_parent.as_ref()), + actual_parent, + "[{}] expected {:?} to have explicit parent {:?}", + self.name, + name, + expected_parent, + ); + } + Some(Parent::ContextualRoot) => { + assert!( + span.is_contextual(), + "[{}] expected {:?} to have a contextual parent", + self.name, + name + ); + assert!( + self.current.lock().unwrap().last().is_none(), + "[{}] expected {:?} to be a root, but we were inside a span", + self.name, + name + ); + } + Some(Parent::Contextual(expected_parent)) => { + assert!( + span.is_contextual(), + "[{}] expected {:?} to have a contextual parent", + self.name, + name + ); + let stack = self.current.lock().unwrap(); + let actual_parent = + stack.last().and_then(|id| spans.get(id)).map(|s| s.name); + assert_eq!( + Some(expected_parent.as_ref()), + actual_parent, + "[{}] expected {:?} to have contextual parent {:?}", + self.name, + name, + expected_parent, + ); + } + None => {} + } + } + } + spans.insert( + id.clone(), + SpanState { + name: meta.name(), + refs: 1, + }, + ); + id + } + + fn enter(&self, id: &Id) { + let spans = self.spans.lock().unwrap(); + if let Some(span) = spans.get(id) { + println!("[{}] enter: {}; id={:?};", self.name, span.name, id); + match self.expected.lock().unwrap().pop_front() { + None => {} + Some(Expect::Enter(ref expected_span)) => { + if let Some(name) = expected_span.name() { + assert_eq!(name, span.name); + } + } + Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name)), + } + }; + self.current.lock().unwrap().push(id.clone()); + } + + fn exit(&self, id: &Id) { + 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 spans = self.spans.lock().unwrap(); + let span = spans + .get(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)) => { + if let Some(name) = expected_span.name() { + assert_eq!(name, span.name); + } + 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| spans.get(id)).map(|s| s.name) + ); + } + Some(ex) => ex.bad(&self.name, format_args!("exited span {:?}", span.name)), + }; + } + + fn clone_span(&self, id: &Id) -> Id { + let name = self.spans.lock().unwrap().get_mut(id).map(|span| { + let name = span.name; + println!( + "[{}] clone_span: {}; id={:?}; refs={:?};", + self.name, name, id, span.refs + ); + span.refs += 1; + name + }); + if name.is_none() { + println!("[{}] clone_span: id={:?};", self.name, id); + } + let mut expected = self.expected.lock().unwrap(); + let was_expected = if let Some(Expect::CloneSpan(ref span)) = expected.front() { + assert_eq!( + name, + span.name(), + "[{}] expected to clone a span named {:?}", + self.name, + span.name() + ); + true + } else { + false + }; + if was_expected { + expected.pop_front(); + } + id.clone() + } + + fn drop_span(&self, id: Id) { + let mut is_event = false; + let name = if let Ok(mut spans) = self.spans.try_lock() { + spans.get_mut(&id).map(|span| { + let name = span.name; + if name.contains("event") { + is_event = true; + } + println!( + "[{}] drop_span: {}; id={:?}; refs={:?};", + self.name, name, id, span.refs + ); + span.refs -= 1; + name + }) + } else { + None + }; + 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 span)) => { + // Don't assert if this function was called while panicking, + // as failing the assertion can cause a double panic. + if !::std::thread::panicking() { + assert_eq!(name, span.name()); + } + true + } + Some(Expect::Event(_)) => { + if !::std::thread::panicking() { + assert!(is_event, "[{}] expected an event", self.name); + } + true + } + _ => false, + }; + if was_expected { + expected.pop_front(); + } + } + } +} + +impl MockHandle { + pub fn assert_finished(&self) { + if let Ok(ref expected) = self.0.lock() { + assert!( + !expected.iter().any(|thing| thing != &Expect::Nothing), + "[{}] more notifications expected: {:?}", + self.1, + **expected + ); + } + } +} + +impl Expect { + fn bad<'a>(&self, name: impl AsRef<str>, what: fmt::Arguments<'a>) { + let name = name.as_ref(); + match self { + Expect::Event(e) => panic!("[{}] expected event {}, but {} instead", name, e, what,), + Expect::Enter(e) => panic!("[{}] expected to enter {} but {} instead", name, e, what,), + Expect::Exit(e) => panic!("[{}] expected to exit {} but {} instead", name, e, what,), + Expect::CloneSpan(e) => { + panic!("[{}] expected to clone {} but {} instead", name, e, what,) + } + Expect::DropSpan(e) => { + panic!("[{}] expected to drop {} but {} instead", name, e, what,) + } + Expect::Visit(e, fields) => panic!( + "[{}] expected {} to record {} but {} instead", + name, e, fields, what, + ), + Expect::NewSpan(e) => panic!("[{}] expected {} but {} instead", name, e, what), + Expect::Nothing => panic!( + "[{}] expected nothing else to happen, but {} instead", + name, what, + ), + } + } +} |