summaryrefslogtreecommitdiffstats
path: root/third_party/rust/tracing/tests/support
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/tracing/tests/support
parentInitial commit. (diff)
downloadfirefox-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.rs99
-rw-r--r--third_party/rust/tracing/tests/support/field.rs226
-rw-r--r--third_party/rust/tracing/tests/support/metadata.rs64
-rw-r--r--third_party/rust/tracing/tests/support/mod.rs14
-rw-r--r--third_party/rust/tracing/tests/support/span.rs167
-rw-r--r--third_party/rust/tracing/tests/support/subscriber.rs569
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,
+ ),
+ }
+ }
+}