Skip to content

Commit 53e0f60

Browse files
Improve testing framework (#11909)
fix(testing): Fix span link matchers fix(testing): Use constant rather than literal feat(testing): Add measured and top-level status matchers feat(testing): Split out converter to avoid dependency on dd-trace-api and internal-api feat(testing): Move generic matchers to utils for later reuse fix(testing): Fix package name fix(testing): Add missing Javadoc feat(testing): Improve API and prevent altering order If sorters are applied, they alter the original reception order, making any following assertion fails if it does not come up with another sorter. feat(testing): Add span sort by ancestry Co-authored-by: bruce.bujon <bruce.bujon@datadoghq.com>
1 parent 6c8dfc8 commit 53e0f60

120 files changed

Lines changed: 427 additions & 198 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import datadog.trace.core.DDSpan;
2222
import datadog.trace.core.PendingTrace;
2323
import datadog.trace.core.TraceCollector;
24-
import datadog.trace.junit.utils.config.WithConfig;
25-
import datadog.trace.junit.utils.context.AllowContextTestingExtension;
26-
import datadog.trace.junit.utils.context.LegacyContextTestingExtension;
24+
import datadog.trace.test.junit.utils.config.WithConfig;
25+
import datadog.trace.test.junit.utils.context.AllowContextTestingExtension;
26+
import datadog.trace.test.junit.utils.context.LegacyContextTestingExtension;
2727
import java.lang.instrument.ClassFileTransformer;
2828
import java.lang.instrument.Instrumentation;
2929
import java.util.List;

dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/SpanLinkMatcher.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package datadog.trace.agent.test.assertions;
22

3-
import static datadog.trace.agent.test.assertions.Matchers.assertValue;
4-
import static datadog.trace.agent.test.assertions.Matchers.is;
53
import static datadog.trace.bootstrap.instrumentation.api.AgentSpanLink.DEFAULT_FLAGS;
64
import static datadog.trace.bootstrap.instrumentation.api.SpanAttributes.EMPTY;
5+
import static datadog.trace.test.junit.utils.assertions.Matchers.assertValue;
6+
import static datadog.trace.test.junit.utils.assertions.Matchers.is;
77

88
import datadog.trace.api.DDTraceId;
99
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
1010
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
1111
import datadog.trace.bootstrap.instrumentation.api.SpanAttributes;
1212
import datadog.trace.core.DDSpan;
13+
import datadog.trace.test.junit.utils.assertions.Matcher;
14+
import datadog.trace.test.junit.utils.assertions.Matchers;
1315

1416
/**
1517
* Provides matchers for span links based on their properties such as trace and span IDs links refer

dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/SpanMatcher.java

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
package datadog.trace.agent.test.assertions;
22

3-
import static datadog.trace.agent.test.assertions.Matchers.assertValue;
4-
import static datadog.trace.agent.test.assertions.Matchers.is;
5-
import static datadog.trace.agent.test.assertions.Matchers.isFalse;
6-
import static datadog.trace.agent.test.assertions.Matchers.isNonNull;
7-
import static datadog.trace.agent.test.assertions.Matchers.isNull;
8-
import static datadog.trace.agent.test.assertions.Matchers.isTrue;
9-
import static datadog.trace.agent.test.assertions.Matchers.matches;
10-
import static datadog.trace.agent.test.assertions.Matchers.validates;
113
import static datadog.trace.core.DDSpanAccessor.spanLinks;
4+
import static datadog.trace.test.junit.utils.assertions.Matchers.assertValue;
5+
import static datadog.trace.test.junit.utils.assertions.Matchers.is;
6+
import static datadog.trace.test.junit.utils.assertions.Matchers.isFalse;
7+
import static datadog.trace.test.junit.utils.assertions.Matchers.isNonNull;
8+
import static datadog.trace.test.junit.utils.assertions.Matchers.isNull;
9+
import static datadog.trace.test.junit.utils.assertions.Matchers.isTrue;
10+
import static datadog.trace.test.junit.utils.assertions.Matchers.matches;
11+
import static datadog.trace.test.junit.utils.assertions.Matchers.validates;
1212
import static java.time.Duration.ofNanos;
1313
import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure;
1414

1515
import datadog.trace.api.DDTraceId;
1616
import datadog.trace.api.TagMap;
1717
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
1818
import datadog.trace.core.DDSpan;
19+
import datadog.trace.test.junit.utils.assertions.Any;
20+
import datadog.trace.test.junit.utils.assertions.IsNull;
21+
import datadog.trace.test.junit.utils.assertions.Matcher;
22+
import datadog.trace.test.junit.utils.assertions.Matchers;
1923
import java.time.Duration;
2024
import java.util.ArrayList;
2125
import java.util.Collection;
@@ -45,6 +49,8 @@
4549
* #durationLongerThan(Duration)}
4650
* <li>span type with {@link #type(String)}
4751
* <li>span error status with {@link #error()} and {@link #error(boolean)}
52+
* <li>span measured status with {@link #measured()} and {@link #measured(boolean)}
53+
* <li>span top-level status with {@link #topLevel()} and {@link #topLevel(boolean)}
4854
* <li>span tags with {@link #tags(TagsMatcher...)}
4955
* <li>span links with {@link #links(SpanLinkMatcher...)}
5056
* </ul>
@@ -60,6 +66,8 @@ public final class SpanMatcher {
6066
private Matcher<Duration> durationMatcher;
6167
private Matcher<String> typeMatcher;
6268
private Matcher<Boolean> errorMatcher;
69+
private Matcher<Boolean> measuredMatcher;
70+
private Matcher<Boolean> topLevelMatcher;
6371
private TagsMatcher[] tagMatchers;
6472
private SpanLinkMatcher[] linkMatchers;
6573

@@ -300,6 +308,50 @@ public SpanMatcher error(boolean errored) {
300308
return this;
301309
}
302310

311+
/**
312+
* Checks the span is measured.
313+
*
314+
* @return The current {@link SpanMatcher} instance updated with the specified measured
315+
* constraint.
316+
*/
317+
public SpanMatcher measured() {
318+
return measured(true);
319+
}
320+
321+
/**
322+
* Checks the span measured status matches the given value.
323+
*
324+
* @param measured The expected measured status.
325+
* @return The current {@link SpanMatcher} instance updated with the specified measured
326+
* constraint.
327+
*/
328+
public SpanMatcher measured(boolean measured) {
329+
this.measuredMatcher = measured ? isTrue() : isFalse();
330+
return this;
331+
}
332+
333+
/**
334+
* Checks the span is a top-level span.
335+
*
336+
* @return The current {@link SpanMatcher} instance updated with the specified top-level
337+
* constraint.
338+
*/
339+
public SpanMatcher topLevel() {
340+
return topLevel(true);
341+
}
342+
343+
/**
344+
* Checks the span top-level status matches the given value.
345+
*
346+
* @param topLevel The expected top-level status.
347+
* @return The current {@link SpanMatcher} instance updated with the specified top-level
348+
* constraint.
349+
*/
350+
public SpanMatcher topLevel(boolean topLevel) {
351+
this.topLevelMatcher = topLevel ? isTrue() : isFalse();
352+
return this;
353+
}
354+
303355
public SpanMatcher tags(TagsMatcher... matchers) {
304356
this.tagMatchers = matchers;
305357
return this;
@@ -341,6 +393,8 @@ else if (this.parentIdMatcher == CHILD_OF_PREVIOUS_MATCHER) {
341393
assertValue(this.durationMatcher, ofNanos(span.getDurationNano()), "Unexpected duration");
342394
assertValue(this.typeMatcher, span.getSpanType(), "Unexpected span type");
343395
assertValue(this.errorMatcher, span.isError(), "Unexpected error status");
396+
assertValue(this.measuredMatcher, span.isMeasured(), "Unexpected measured status");
397+
assertValue(this.topLevelMatcher, span.isTopLevel(), "Unexpected top-level status");
344398
assertSpanTags(span.getTags());
345399
assertSpanLinks(spanLinks(span));
346400
}
@@ -399,7 +453,7 @@ private void assertSpanLinks(List<AgentSpanLink> links) {
399453
.buildAndThrow();
400454
}
401455
for (int i = 0; i < expectedLinkCount; i++) {
402-
SpanLinkMatcher linkMatcher = this.linkMatchers[expectedLinkCount];
456+
SpanLinkMatcher linkMatcher = this.linkMatchers[i];
403457
AgentSpanLink link = links.get(i);
404458
linkMatcher.assertLink(link);
405459
}

dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package datadog.trace.agent.test.assertions;
22

3-
import static datadog.trace.agent.test.assertions.Matchers.any;
4-
import static datadog.trace.agent.test.assertions.Matchers.is;
5-
import static datadog.trace.agent.test.assertions.Matchers.isNonNull;
63
import static datadog.trace.api.DDTags.BASE_SERVICE;
74
import static datadog.trace.api.DDTags.DD_INTEGRATION;
85
import static datadog.trace.api.DDTags.DJM_ENABLED;
@@ -24,7 +21,13 @@
2421
import static datadog.trace.api.DDTags.TRACER_HOST;
2522
import static datadog.trace.common.sampling.RateByServiceTraceSampler.SAMPLING_AGENT_RATE;
2623
import static datadog.trace.common.writer.ddagent.TraceMapper.SAMPLING_PRIORITY_KEY;
24+
import static datadog.trace.core.DDSpanContext.SAMPLE_RATE_KEY;
25+
import static datadog.trace.test.junit.utils.assertions.Matchers.any;
26+
import static datadog.trace.test.junit.utils.assertions.Matchers.is;
27+
import static datadog.trace.test.junit.utils.assertions.Matchers.isNonNull;
2728

29+
import datadog.trace.test.junit.utils.assertions.Matcher;
30+
import datadog.trace.test.junit.utils.assertions.Matchers;
2831
import java.util.HashMap;
2932
import java.util.Map;
3033

@@ -43,7 +46,7 @@ public static TagsMatcher defaultTags() {
4346
tagMatchers.put(LANGUAGE_TAG_KEY, any());
4447
tagMatchers.put(SAMPLING_AGENT_RATE, any());
4548
tagMatchers.put(SAMPLING_PRIORITY_KEY.toString(), any());
46-
tagMatchers.put("_sample_rate", any());
49+
tagMatchers.put(SAMPLE_RATE_KEY, any());
4750
tagMatchers.put(PID_TAG, any());
4851
tagMatchers.put(SCHEMA_VERSION_TAG_KEY, any());
4952
tagMatchers.put(PROFILING_ENABLED, any());

dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TraceAssertions.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure;
55

66
import datadog.trace.core.DDSpan;
7+
import java.util.ArrayList;
78
import java.util.Comparator;
89
import java.util.List;
910
import java.util.function.UnaryOperator;
@@ -26,6 +27,9 @@
2627
* </ul>
2728
*/
2829
public final class TraceAssertions {
30+
/*
31+
* Trace comparators.
32+
*/
2933
/** Trace comparator to sort by start time. */
3034
public static final Comparator<List<DDSpan>> TRACE_START_TIME_COMPARATOR =
3135
Comparator.comparingLong(
@@ -37,19 +41,19 @@ public final class TraceAssertions {
3741
trace -> trace.isEmpty() ? 0L : trace.get(0).getLocalRootSpan().getSpanId());
3842

3943
/*
40-
* Trace assertions options.
44+
* Trace assertion options.
4145
*/
42-
/** Ignores addition traces. If there are more traces than expected, do not fail. */
46+
/** Ignores additional traces. If there are more traces than expected, do not fail. */
4347
public static final UnaryOperator<Options> IGNORE_ADDITIONAL_TRACES =
44-
Options::ignoredAdditionalTraces;
48+
Options::ignoreAdditionalTraces;
4549

4650
/** Sorts traces by start time. */
4751
public static final UnaryOperator<Options> SORT_BY_START_TIME =
48-
options -> options.sorter(TRACE_START_TIME_COMPARATOR);
52+
options -> options.sort(TRACE_START_TIME_COMPARATOR);
4953

5054
/** Sorts traces by their root span identifier. */
5155
public static final UnaryOperator<Options> SORT_BY_ROOT_SPAN_ID =
52-
options -> options.sorter(TRACE_ROOT_SPAN_ID_COMPARATOR);
56+
options -> options.sort(TRACE_ROOT_SPAN_ID_COMPARATOR);
5357

5458
private TraceAssertions() {}
5559

@@ -102,26 +106,27 @@ public static void assertTraces(
102106
.buildAndThrow();
103107
}
104108
}
105-
if (opts.sorter != null) {
106-
traces.sort(opts.sorter);
109+
if (opts.comparator != null) {
110+
traces = new ArrayList<>(traces);
111+
traces.sort(opts.comparator);
107112
}
108113
for (int i = 0; i < expectedTraceCount; i++) {
109114
List<DDSpan> trace = traces.get(i);
110115
matchers[i].assertTrace(trace, i);
111116
}
112117
}
113118

114-
public static class Options {
115-
boolean ignoredAdditionalTraces = false;
116-
Comparator<List<DDSpan>> sorter = TRACE_START_TIME_COMPARATOR;
119+
public static final class Options {
120+
private boolean ignoredAdditionalTraces = false;
121+
private Comparator<List<DDSpan>> comparator = TRACE_START_TIME_COMPARATOR;
117122

118-
public Options ignoredAdditionalTraces() {
123+
public Options ignoreAdditionalTraces() {
119124
this.ignoredAdditionalTraces = true;
120125
return this;
121126
}
122127

123-
public Options sorter(Comparator<List<DDSpan>> sorter) {
124-
this.sorter = sorter;
128+
public Options sort(Comparator<List<DDSpan>> comparator) {
129+
this.comparator = comparator;
125130
return this;
126131
}
127132
}

dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TraceMatcher.java

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package datadog.trace.agent.test.assertions;
22

33
import static java.util.Comparator.comparingLong;
4+
import static java.util.stream.Collectors.toSet;
45

56
import datadog.trace.core.DDSpan;
7+
import java.util.ArrayList;
68
import java.util.Comparator;
9+
import java.util.HashMap;
710
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Set;
813
import java.util.function.UnaryOperator;
914
import org.opentest4j.AssertionFailedError;
1015

@@ -15,17 +20,37 @@
1520
* with the expected {@link SpanMatcher}s (one per expected span), or {@link #trace(UnaryOperator,
1621
* SpanMatcher...)} to configure the checks with a {@link Options} object.
1722
*
18-
* <p>{@link #SORT_BY_START_TIME} can be used as predefined configuration to sort spans by start
19-
* time.
23+
* <p>The following predefined configurations:
24+
*
25+
* <ul>
26+
* <li>{@link #SORT_BY_START_TIME} sorts spans by start time,
27+
* <li>{@link #SORT_BY_ANCESTRY} sorts spans by ancestry, root spans (or which parents are not
28+
* present in the trace chunk) first, followed by their children by start time, depth-first *
29+
* </ul>
2030
*
2131
* @see TraceAssertions
2232
* @see SpanMatcher
2333
*/
2434
public final class TraceMatcher {
35+
/*
36+
* Span comparators.
37+
*/
38+
/** Span comparator to sort by start time. */
2539
public static final Comparator<DDSpan> START_TIME_COMPARATOR =
26-
comparingLong(DDSpan::getStartTime);
40+
comparingLong(DDSpan::getStartTime).thenComparingLong(DDSpan::getSpanId);
41+
42+
/*
43+
* Span assertion options.
44+
*/
45+
/** Sorts spans by start time. */
2746
public static UnaryOperator<Options> SORT_BY_START_TIME =
28-
options -> options.sorter(START_TIME_COMPARATOR);
47+
options -> options.sort(START_TIME_COMPARATOR);
48+
49+
/**
50+
* Sorts spans by ancestry, root spans (or which parents are absent from the trace chunk) first,
51+
* followed by their children by start time, depth-first.
52+
*/
53+
public static final UnaryOperator<Options> SORT_BY_ANCESTRY = Options::sortByAncestry;
2954

3055
private final Options options;
3156
private final SpanMatcher[] matchers;
@@ -65,19 +90,58 @@ void assertTrace(List<DDSpan> trace, int traceIndex) {
6590
this.matchers.length,
6691
spanCount);
6792
}
68-
if (this.options.sorter != null) {
69-
trace.sort(this.options.sorter);
93+
if (this.options.sortByAncestry) {
94+
trace = sortByAncestry(trace);
95+
} else if (this.options.comparator != null) {
96+
trace = new ArrayList<>(trace);
97+
trace.sort(this.options.comparator);
7098
}
7199
for (int spanIndex = 0; spanIndex < spanCount; spanIndex++) {
72100
this.matchers[spanIndex].assertSpan(trace, spanIndex);
73101
}
74102
}
75103

76-
public static class Options {
77-
Comparator<DDSpan> sorter = null;
104+
private static List<DDSpan> sortByAncestry(List<DDSpan> spans) {
105+
Set<Long> spanIds = spans.stream().map(DDSpan::getSpanId).collect(toSet());
106+
Map<Long, List<DDSpan>> spansByParentId = new HashMap<>();
107+
for (DDSpan span : spans) {
108+
long parentId = span.getParentId();
109+
if (parentId != 0 && !spanIds.contains(parentId)) {
110+
parentId = 0;
111+
}
112+
spansByParentId.computeIfAbsent(parentId, k -> new ArrayList<>()).add(span);
113+
}
114+
spansByParentId.forEach((k, v) -> v.sort(START_TIME_COMPARATOR));
115+
116+
List<DDSpan> ordered = new ArrayList<>(spans.size());
117+
appendChildren(ordered, spansByParentId.get(0L), spansByParentId);
118+
return ordered;
119+
}
120+
121+
private static void appendChildren(
122+
List<DDSpan> orderedSpan, List<DDSpan> children, Map<Long, List<DDSpan>> spansByParentId) {
123+
for (DDSpan child : children) {
124+
orderedSpan.add(child);
125+
List<DDSpan> grandChildren = spansByParentId.get(child.getSpanId());
126+
if (grandChildren != null) {
127+
appendChildren(orderedSpan, grandChildren, spansByParentId);
128+
}
129+
}
130+
}
131+
132+
public static final class Options {
133+
private Comparator<DDSpan> comparator = null;
134+
private boolean sortByAncestry = false;
135+
136+
public Options sort(Comparator<DDSpan> comparator) {
137+
this.comparator = comparator;
138+
this.sortByAncestry = false;
139+
return this;
140+
}
78141

79-
public Options sorter(Comparator<DDSpan> sorter) {
80-
this.sorter = sorter;
142+
private Options sortByAncestry() {
143+
this.comparator = null;
144+
this.sortByAncestry = true;
81145
return this;
82146
}
83147
}

0 commit comments

Comments
 (0)