Skip to content

Commit 21c5ce0

Browse files
authored
mock: match parent span on ExpectedSpan (#3098)
## Motivation The `with_ancestry` methods on `NewSpan` and `ExpectedEvent` provide a way to match whether the span or event is a contextual or explicit root or if it has a contextual or explicit parent span. However, in the case of matching on a contextual or explicit parent span, only the span name could be used for matching. This is sufficiently precise when testing tracing instrumentation in other libraries or applications as opposed to testing tracing itself. It is likely that a user would like to test that some span or event has a specific span as a parent, and not just any span with a specific name, in many cases, all the possible parent spans may have the same name. This is the case when testing tracing instrumentation in Tokio. ## Solution To solve this problem, the `Ancestry` struct was renamed to `ExpectedAncestry` and in the case of expecting an explicit or conextual parent, an `ExpectedSpan` object can be passed in. This provides the maximum possible flexibility. The convenience functions in the `expect` module now take `Into<ExpectedSpan>` so that existing tests that pass a string type object for the parent will see the same behaviour as previously and shorthand use for expected Ids is also available. Additionally, the span checking code has been unified between the `MockCollector` and `MockSubscriber` cases and the assertion descriptions have been improved to make them more readable.
1 parent 2fdbf09 commit 21c5ce0

File tree

9 files changed

+574
-311
lines changed

9 files changed

+574
-311
lines changed

tracing-mock/src/ancestry.rs

Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,102 @@
11
//! Define the ancestry of an event or span.
22
//!
3-
//! See the documentation on the [`Ancestry`] enum for further details.
3+
//! See the documentation on the [`ExpectedAncestry`] enum for further details.
44
55
use tracing_core::{
66
span::{self, Attributes},
77
Event,
88
};
99

10+
use crate::span::{ActualSpan, ExpectedSpan};
11+
1012
/// The ancestry of an event or span.
1113
///
1214
/// An event or span can have an explicitly assigned parent, or be an explicit root. Otherwise,
1315
/// an event or span may have a contextually assigned parent or in the final case will be a
1416
/// contextual root.
1517
#[derive(Debug, Eq, PartialEq)]
16-
pub enum Ancestry {
17-
/// The event or span has an explicitly assigned parent (created with `parent: span_id`) with
18-
/// the specified name.
19-
HasExplicitParent(String),
18+
pub enum ExpectedAncestry {
19+
/// The event or span has an explicitly assigned parent (created with `parent: span_id`) span.
20+
HasExplicitParent(ExpectedSpan),
2021
/// The event or span is an explicitly defined root. It was created with `parent: None` and
2122
/// has no parent.
2223
IsExplicitRoot,
23-
/// The event or span has a contextually assigned parent with the specified name. It has no
24-
/// explicitly assigned parent, nor has it been explicitly defined as a root (it was created
25-
/// without the `parent:` directive). There was a span in context when this event or span was
26-
/// created.
27-
HasContextualParent(String),
24+
/// The event or span has a contextually assigned parent span. It has no explicitly assigned
25+
/// parent span, nor has it been explicitly defined as a root (it was created without the
26+
/// `parent:` directive). There was a span in context when this event or span was created.
27+
HasContextualParent(ExpectedSpan),
2828
/// The event or span is a contextual root. It has no explicitly assigned parent, nor has it
2929
/// been explicitly defined as a root (it was created without the `parent:` directive).
3030
/// Additionally, no span was in context when this event or span was created.
3131
IsContextualRoot,
3232
}
3333

34-
impl Ancestry {
34+
pub(crate) enum ActualAncestry {
35+
HasExplicitParent(ActualSpan),
36+
IsExplicitRoot,
37+
HasContextualParent(ActualSpan),
38+
IsContextualRoot,
39+
}
40+
41+
impl ExpectedAncestry {
3542
#[track_caller]
3643
pub(crate) fn check(
3744
&self,
38-
actual_ancestry: &Ancestry,
45+
actual_ancestry: &ActualAncestry,
3946
ctx: impl std::fmt::Display,
4047
collector_name: &str,
4148
) {
42-
let expected_description = |ancestry: &Ancestry| match ancestry {
43-
Self::IsExplicitRoot => "be an explicit root".to_string(),
44-
Self::HasExplicitParent(name) => format!("have an explicit parent with name='{name}'"),
45-
Self::IsContextualRoot => "be a contextual root".to_string(),
46-
Self::HasContextualParent(name) => {
47-
format!("have a contextual parent with name='{name}'")
49+
match (self, actual_ancestry) {
50+
(Self::IsExplicitRoot, ActualAncestry::IsExplicitRoot) => {}
51+
(Self::IsContextualRoot, ActualAncestry::IsContextualRoot) => {}
52+
(
53+
Self::HasExplicitParent(expected_parent),
54+
ActualAncestry::HasExplicitParent(actual_parent),
55+
) => {
56+
expected_parent.check(
57+
actual_parent,
58+
format_args!("{ctx} to have an explicit parent span"),
59+
collector_name,
60+
);
4861
}
49-
};
50-
51-
let actual_description = |ancestry: &Ancestry| match ancestry {
52-
Self::IsExplicitRoot => "was actually an explicit root".to_string(),
53-
Self::HasExplicitParent(name) => {
54-
format!("actually has an explicit parent with name='{name}'")
62+
(
63+
Self::HasContextualParent(expected_parent),
64+
ActualAncestry::HasContextualParent(actual_parent),
65+
) => {
66+
println!("----> [{collector_name}] check {expected_parent:?} against actual parent with Id={id:?}", id = actual_parent.id());
67+
expected_parent.check(
68+
actual_parent,
69+
format_args!("{ctx} to have a contextual parent span"),
70+
collector_name,
71+
);
5572
}
56-
Self::IsContextualRoot => "was actually a contextual root".to_string(),
57-
Self::HasContextualParent(name) => {
58-
format!("actually has a contextual parent with name='{name}'")
73+
_ => {
74+
// Ancestry types don't match at all.
75+
let expected_description = match self {
76+
Self::IsExplicitRoot => "be an explicit root",
77+
Self::HasExplicitParent(_) => "have an explicit parent span",
78+
Self::IsContextualRoot => "be a contextual root",
79+
Self::HasContextualParent(_) => "have a contextual parent span",
80+
};
81+
82+
let actual_description = match actual_ancestry {
83+
ActualAncestry::IsExplicitRoot => "is actually an explicit root",
84+
ActualAncestry::HasExplicitParent(_) => "actually has an explicit parent span",
85+
ActualAncestry::IsContextualRoot => "is actually a contextual root",
86+
ActualAncestry::HasContextualParent(_) => {
87+
"actually has a contextual parent span"
88+
}
89+
};
90+
91+
panic!(
92+
"{}",
93+
format!(
94+
"[{collector_name}] expected {ctx} to {expected_description}, \
95+
but it {actual_description}"
96+
)
97+
);
5998
}
60-
};
61-
62-
assert_eq!(
63-
self,
64-
actual_ancestry,
65-
"[{collector_name}] expected {ctx} to {expected_description}, but {actual_description}",
66-
expected_description = expected_description(self),
67-
actual_description = actual_description(actual_ancestry)
68-
);
99+
}
69100
}
70101
}
71102

@@ -120,29 +151,29 @@ impl HasAncestry for &Attributes<'_> {
120151
pub(crate) fn get_ancestry(
121152
item: impl HasAncestry,
122153
lookup_current: impl FnOnce() -> Option<span::Id>,
123-
span_name: impl FnOnce(&span::Id) -> Option<&str>,
124-
) -> Ancestry {
154+
actual_span: impl FnOnce(&span::Id) -> Option<ActualSpan>,
155+
) -> ActualAncestry {
125156
if item.is_contextual() {
126157
if let Some(parent_id) = lookup_current() {
127-
let contextual_parent_name = span_name(&parent_id).expect(
158+
let contextual_parent_span = actual_span(&parent_id).expect(
128159
"tracing-mock: contextual parent cannot \
129160
be looked up by ID. Was it recorded correctly?",
130161
);
131-
Ancestry::HasContextualParent(contextual_parent_name.to_string())
162+
ActualAncestry::HasContextualParent(contextual_parent_span)
132163
} else {
133-
Ancestry::IsContextualRoot
164+
ActualAncestry::IsContextualRoot
134165
}
135166
} else if item.is_root() {
136-
Ancestry::IsExplicitRoot
167+
ActualAncestry::IsExplicitRoot
137168
} else {
138169
let parent_id = item.parent().expect(
139170
"tracing-mock: is_contextual=false is_root=false \
140171
but no explicit parent found. This is a bug!",
141172
);
142-
let explicit_parent_name = span_name(parent_id).expect(
173+
let explicit_parent_span = actual_span(parent_id).expect(
143174
"tracing-mock: explicit parent cannot be looked \
144175
up by ID. Is the provided Span ID valid: {parent_id}",
145176
);
146-
Ancestry::HasExplicitParent(explicit_parent_name.to_string())
177+
ActualAncestry::HasExplicitParent(explicit_parent_span)
147178
}
148179
}

tracing-mock/src/collector.rs

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ use crate::{
142142
event::ExpectedEvent,
143143
expect::Expect,
144144
field::ExpectedFields,
145-
span::{ExpectedSpan, NewSpan},
145+
span::{ActualSpan, ExpectedSpan, NewSpan},
146146
};
147147
use std::{
148148
collections::{HashMap, VecDeque},
@@ -160,19 +160,15 @@ use tracing::{
160160
};
161161

162162
pub(crate) struct SpanState {
163-
id: u64,
163+
id: Id,
164164
name: &'static str,
165165
refs: usize,
166166
meta: &'static Metadata<'static>,
167167
}
168168

169-
impl SpanState {
170-
pub(crate) fn id(&self) -> u64 {
171-
self.id
172-
}
173-
174-
pub(crate) fn metadata(&self) -> &'static Metadata<'static> {
175-
self.meta
169+
impl From<&SpanState> for ActualSpan {
170+
fn from(span_state: &SpanState) -> Self {
171+
Self::new(span_state.id.clone(), Some(span_state.meta))
176172
}
177173
}
178174

@@ -1069,7 +1065,7 @@ where
10691065
.lock()
10701066
.unwrap()
10711067
.get(span_id)
1072-
.map(|span| span.name)
1068+
.map(|span| span.into())
10731069
},
10741070
)
10751071
};
@@ -1140,7 +1136,7 @@ where
11401136
get_ancestry(
11411137
span,
11421138
|| self.lookup_current(),
1143-
|span_id| spans.get(span_id).map(|span| span.name),
1139+
|span_id| spans.get(span_id).map(|span| span.into()),
11441140
)
11451141
},
11461142
&self.name,
@@ -1150,7 +1146,7 @@ where
11501146
spans.insert(
11511147
id.clone(),
11521148
SpanState {
1153-
id: id.into_u64(),
1149+
id: id.clone(),
11541150
name: meta.name(),
11551151
refs: 1,
11561152
meta,
@@ -1166,7 +1162,7 @@ where
11661162
match self.expected.lock().unwrap().pop_front() {
11671163
None => {}
11681164
Some(Expect::Enter(ref expected_span)) => {
1169-
expected_span.check(span, &self.name);
1165+
expected_span.check(&span.into(), "to enter a span", &self.name);
11701166
}
11711167
Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name)),
11721168
}
@@ -1189,7 +1185,7 @@ where
11891185
match self.expected.lock().unwrap().pop_front() {
11901186
None => {}
11911187
Some(Expect::Exit(ref expected_span)) => {
1192-
expected_span.check(span, &self.name);
1188+
expected_span.check(&span.into(), "to exit a span", &self.name);
11931189
let curr = self.current.lock().unwrap().pop();
11941190
assert_eq!(
11951191
Some(id),
@@ -1205,27 +1201,34 @@ where
12051201
}
12061202

12071203
fn clone_span(&self, id: &Id) -> Id {
1208-
let name = self.spans.lock().unwrap().get_mut(id).map(|span| {
1209-
let name = span.name;
1210-
println!(
1211-
"[{}] clone_span: {}; id={:?}; refs={:?};",
1212-
self.name, name, id, span.refs
1213-
);
1214-
span.refs += 1;
1215-
name
1216-
});
1217-
if name.is_none() {
1218-
println!("[{}] clone_span: id={:?};", self.name, id);
1204+
let mut spans = self.spans.lock().unwrap();
1205+
let mut span = spans.get_mut(id);
1206+
match span.as_deref_mut() {
1207+
Some(span) => {
1208+
println!(
1209+
"[{}] clone_span: {}; id={:?}; refs={:?};",
1210+
self.name, span.name, id, span.refs,
1211+
);
1212+
span.refs += 1;
1213+
}
1214+
None => {
1215+
println!(
1216+
"[{}] clone_span: id={:?} (not found in span list);",
1217+
self.name, id
1218+
);
1219+
}
12191220
}
1221+
12201222
let mut expected = self.expected.lock().unwrap();
1221-
let was_expected = if let Some(Expect::CloneSpan(ref span)) = expected.front() {
1222-
assert_eq!(
1223-
name,
1224-
span.name(),
1225-
"[{}] expected to clone a span named {:?}",
1226-
self.name,
1227-
span.name()
1228-
);
1223+
let was_expected = if let Some(Expect::CloneSpan(ref expected_span)) = expected.front() {
1224+
match span {
1225+
Some(actual_span) => {
1226+
let actual_span: &_ = actual_span;
1227+
expected_span.check(&actual_span.into(), "to clone a span", &self.name);
1228+
}
1229+
// Check only by Id
1230+
None => expected_span.check(&id.into(), "to clone a span", &self.name),
1231+
}
12291232
true
12301233
} else {
12311234
false

0 commit comments

Comments
 (0)