88import datadog .trace .test .agent .decoder .DecodedSpan ;
99import datadog .trace .test .agent .decoder .DecodedTrace ;
1010import java .util .ArrayList ;
11+ import java .util .BitSet ;
1112import java .util .Comparator ;
1213import java .util .List ;
1314import java .util .function .UnaryOperator ;
2425 * assertTraces(traces, trace(span().operationName("servlet.request").resourceName("GET /greeting")));
2526 * }</pre>
2627 *
27- * <p>A single matching algorithm is tuned by three orthogonal, combinable {@link Options}:
28+ * <p>A single matching algorithm is tuned by three combinable {@link Options}:
2829 *
2930 * <ul>
30- * <li>{@link #SORT_BY_START_TIME} / {@link #SORT_BY_ROOT_SPAN_ID} (the {@code sorter}) — sort the
31- * received traces before matching.
31+ * <li>{@link #SORT_BY_START_TIME} (the {@code sorter}) — sort the received traces before matching,
32+ * making positional matching deterministic .
3233 * <li>{@link #UNORDERED} — a matcher may match <em>any</em> distinct (unconsumed) trace rather
33- * than the one at its position. Without a sorter this is fully order-independent;
34- * <em>with</em> a sorter it is forward-only (once a matcher takes the trace at sorted
35- * position <i>p</i>, the next matcher can only look at positions after <i>p</i>) — i.e. match
36- * as a subsequence in sorted order.
34+ * than the one at its position; the received order stops mattering (so a {@code sorter} has no
35+ * effect in this mode).
3736 * <li>{@link #IGNORE_ADDITIONAL_TRACES} — don't require every received trace to be matched; extra
3837 * traces are ignored (a subset assertion). Without it, the counts must be equal.
3938 * </ul>
4039 *
41- * <p>The default (no options) is thus count-exact positional matching (matcher <i>i</i> ↔ trace
42- * <i>i</i>, received order). Combine flags with the fluent {@link Options} — e.g. {@code o ->
43- * o.unordered().ignoreAdditionalTraces()} — to assert only the traces you care about in a
44- * collection that also holds unrelated or non-deterministically-ordered ones (e.g. a distributed
45- * test where connection-setup commands land as their own traces). The traces come from a {@code
46- * TraceBackend} (mock or test-agent), both decoded to {@link DecodedTrace}.
40+ * <p>The useful combinations are:
41+ *
42+ * <ul>
43+ * <li><b>default</b> (optionally with a {@code sorter}) — count-exact positional: matcher
44+ * <i>i</i> ↔ trace <i>i</i>.
45+ * <li><b>{@code sorter} + {@code ignoreAdditionalTraces}</b> — ordered subsequence: match in
46+ * sorted order, skipping the traces no matcher claims.
47+ * <li><b>{@code unordered} + {@code ignoreAdditionalTraces}</b> — any-order subset: match the
48+ * traces you care about and ignore the rest (e.g. a distributed test where connection-setup
49+ * commands land as their own traces alongside the request traces).
50+ * </ul>
51+ *
52+ * <p>Combine flags with the fluent {@link Options}, e.g. {@code o ->
53+ * o.unordered().ignoreAdditionalTraces()}. The traces come from a {@code TraceBackend} (mock or
54+ * test-agent), both decoded to {@link DecodedTrace}.
4755 */
4856public final class SmokeTraceAssertions {
4957 /** Sorts traces by the earliest span start time. */
@@ -55,11 +63,11 @@ public final class SmokeTraceAssertions {
5563 Options ::ignoreAdditionalTraces ;
5664
5765 /** Let each matcher match any distinct trace rather than the one at its position. */
58- public static final UnaryOperator <Options > UNORDERED = Options ::unordered ;
66+ public static final UnaryOperator <Options > UNORDERED = Options ::unorder ;
5967
6068 /** Sorts traces by earliest span start time before matching. */
6169 public static final UnaryOperator <Options > SORT_BY_START_TIME =
62- options -> options .sorter (TRACE_START_TIME_COMPARATOR );
70+ options -> options .sort (TRACE_START_TIME_COMPARATOR );
6371
6472 private SmokeTraceAssertions () {}
6573
@@ -107,31 +115,26 @@ public static void assertTraces(
107115 traces = new ArrayList <>(traces );
108116 traces .sort (opts .sorter );
109117 }
118+ // Assert traces
110119 boolean strictPositional = !opts .unordered && !opts .ignoreAdditionalTraces ;
111- // Record matched traces for unordered mode
112- boolean [] traceMatched = new boolean [traceCount ];
113- // Record last matching trace index for ignoreAdditionalTraces mode
114- int floor = 0 ;
115-
120+ BitSet skippedTraces = new BitSet (traceCount );
116121 for (int matcherIndex = 0 ; matcherIndex < matchers .length ; matcherIndex ++) {
117122 TraceMatcher matcher = matchers [matcherIndex ];
118123 if (strictPositional ) {
119124 matcher .assertTrace (traces .get (matcherIndex ).getSpans (), matcherIndex );
120125 continue ;
121126 }
122-
123127 boolean matched = false ;
124- for (int traceIndex = floor ; traceIndex < traceCount ; traceIndex ++) {
125- // Skipped already matched traces - unordered mode only
126- if (traceMatched [traceIndex ]) {
128+ for (int traceIndex = skippedTraces .nextClearBit (0 ); traceIndex < traceCount ; traceIndex ++) {
129+ if (skippedTraces .get (traceIndex )) {
127130 continue ;
128131 }
129132 if (matcher .matches (traces .get (traceIndex ).getSpans (), matcherIndex )) {
130133 matched = true ;
131134 if (opts .unordered ) {
132- traceMatched [ traceIndex ] = true ;
135+ skippedTraces . set ( traceIndex ) ;
133136 } else {
134- floor = traceIndex + 1 ;
137+ skippedTraces . set ( 0 , traceIndex ) ;
135138 }
136139 break ;
137140 }
@@ -150,25 +153,24 @@ private static long earliestStart(DecodedTrace trace) {
150153 return start == MAX_VALUE ? 0L : start ;
151154 }
152155
153- /** Trace-collection matching options; see the class doc for how they combine. */
154156 public static final class Options {
155157 boolean ignoreAdditionalTraces = false ;
156158 boolean unordered = false ;
157- Comparator <DecodedTrace > sorter = null ; // null => keep received order
159+ Comparator <DecodedTrace > sorter = null ;
158160
159- /** Ignore received traces beyond those the matchers consume (a subset assertion). */
160161 public Options ignoreAdditionalTraces () {
161162 this .ignoreAdditionalTraces = true ;
162163 return this ;
163164 }
164165
165- /** Let each matcher match any distinct trace (forward-only when a {@link #sorter} is set). */
166- public Options unordered () {
166+ public Options unorder () {
167167 this .unordered = true ;
168+ this .sorter = null ;
168169 return this ;
169170 }
170171
171- public Options sorter (Comparator <DecodedTrace > sorter ) {
172+ public Options sort (Comparator <DecodedTrace > sorter ) {
173+ this .unordered = false ;
172174 this .sorter = sorter ;
173175 return this ;
174176 }
0 commit comments