1313import java .util .HashMap ;
1414import java .util .List ;
1515import java .util .Map ;
16+ import java .util .Map .Entry ;
1617import java .util .function .Predicate ;
1718import java .util .regex .Pattern ;
1819
2425 * datadog.trace.agent.test.assertions}, which matches on {@code DDSpan}); the two share only the
2526 * generic value matchers in {@code datadog.trace.junit.utils.assertions}. Only what a span carries
2627 * once serialized is matchable here: name/service/resource/type, error status, {@code meta} (string
27- * tags), {@code metrics} (numeric tags), and parent linkage.
28+ * tags), {@code metrics} (numeric tags), {@code meta_struct} (nested structured data), and parent
29+ * linkage.
2830 *
2931 * <p>Parent linkage can be expressed by {@link #root()}, {@link #childOf(long) explicit id}, or —
3032 * for structural assertions within a sorted trace — by position with {@link #childOfIndex(int)} /
@@ -40,8 +42,10 @@ public final class SpanMatcher {
4042 private Long parentId ; // explicit expected parent id (childOf/root); null => not checked by id
4143 private int parentSpanIndex = -1 ; // >= 0 => parent is the span at this index in the sorted trace
4244 private boolean childOfPrevious ; // parent is the preceding span in the sorted trace
43- private final Map <String , Matcher <?>> metaMatchers = new HashMap <>();
44- private final Map <String , Matcher <?>> metricMatchers = new HashMap <>();
45+ private final Map <String , Matcher <String >> metaMatchers = new HashMap <>();
46+ private final Map <String , Matcher <Number >> metricMatchers = new HashMap <>();
47+ // meta_struct values are nested (map/list/scalar), so matched as Object.
48+ private final Map <String , Matcher <?>> metaStructMatchers = new HashMap <>();
4549
4650 private SpanMatcher () {}
4751
@@ -124,7 +128,7 @@ public SpanMatcher childOfPrevious() {
124128 }
125129
126130 /** Matches a {@code meta} (string) tag against the given matcher. */
127- public SpanMatcher tag (String name , Matcher <? > matcher ) {
131+ public SpanMatcher tag (String name , Matcher <String > matcher ) {
128132 this .metaMatchers .put (name , matcher );
129133 return this ;
130134 }
@@ -136,43 +140,54 @@ public SpanMatcher tag(String name, String value) {
136140 }
137141
138142 /** Matches a {@code metrics} (numeric) tag against the given matcher. */
139- public SpanMatcher metric (String name , Matcher <? > matcher ) {
143+ public SpanMatcher metric (String name , Matcher <Number > matcher ) {
140144 this .metricMatchers .put (name , matcher );
141145 return this ;
142146 }
143147
144- // TODO thin: exhaustive tag coverage (fail on unexpected meta/metrics) and span-links.
148+ public SpanMatcher metaStruct (String name , Matcher <?> matcher ) {
149+ this .metaStructMatchers .put (name , matcher );
150+ return this ;
151+ }
145152
146- /** Positional (count-exact) assertion: the span at {@code spanIndex} matches this matcher. */
147153 void assertSpan (List <DecodedSpan > trace , int spanIndex ) {
148154 assertValue (
149155 parentIdMatcher (trace , spanIndex ),
150156 trace .get (spanIndex ).getParentId (),
151157 "Unexpected parent id" );
152- assertFields (trace .get (spanIndex ));
153- }
154-
155- /** Asserts a span's own fields (service/operation/resource/type/error/tags), ignoring linkage. */
156- @ SuppressWarnings ("unchecked" )
157- private void assertFields (DecodedSpan span ) {
158+ DecodedSpan span = trace .get (spanIndex );
158159 assertValue (this .serviceMatcher , span .getService (), "Unexpected service name" );
159160 assertValue (this .operationNameMatcher , span .getName (), "Unexpected operation name" );
160161 assertValue (this .resourceNameMatcher , span .getResource (), "Unexpected resource name" );
161162 assertValue (this .typeMatcher , span .getType (), "Unexpected span type" );
162163 assertValue (this .errorMatcher , span .getError () != 0 , "Unexpected error status" );
163- Map <String , String > meta = span .getMeta ();
164- for (Map .Entry <String , Matcher <?>> entry : this .metaMatchers .entrySet ()) {
165- Object value = meta == null ? null : meta .get (entry .getKey ());
164+ assertSpanTags (span .getMeta ());
165+ assertSpanMetrics (span .getMetrics ());
166+ assertSpanMetaStruct (span .getMetaStruct ());
167+ }
168+
169+ private void assertSpanTags (Map <String , String > meta ) {
170+ for (Entry <String , Matcher <String >> entry : this .metaMatchers .entrySet ()) {
171+ String key = entry .getKey ();
172+ assertValue (entry .getValue (), meta .get (key ), "Unexpected meta tag '" + key + "'" );
173+ }
174+ }
175+
176+ private void assertSpanMetrics (Map <String , Number > metrics ) {
177+ for (Entry <String , Matcher <Number >> entry : this .metricMatchers .entrySet ()) {
166178 assertValue (
167- ( Matcher < Object >) entry .getValue (),
168- value ,
169- "Unexpected meta tag '" + entry .getKey () + "'" );
179+ entry .getValue (),
180+ metrics . get ( entry . getKey ()) ,
181+ "Unexpected metric '" + entry .getKey () + "'" );
170182 }
171- Map <String , Number > metrics = span .getMetrics ();
172- for (Map .Entry <String , Matcher <?>> entry : this .metricMatchers .entrySet ()) {
173- Object value = metrics == null ? null : metrics .get (entry .getKey ());
183+ }
184+
185+ @ SuppressWarnings ("unchecked" )
186+ private void assertSpanMetaStruct (Map <String , Object > metaStruct ) {
187+ for (Entry <String , Matcher <?>> entry : this .metaStructMatchers .entrySet ()) {
188+ String key = entry .getKey ();
174189 assertValue (
175- (Matcher <Object >) entry .getValue (), value , "Unexpected metric '" + entry . getKey () + "'" );
190+ (Matcher <Object >) entry .getValue (), metaStruct . get ( key ) , "Unexpected meta_struct '" + key + "'" );
176191 }
177192 }
178193
0 commit comments