Skip to content

Commit dccd70a

Browse files
committed
feat(test): Add instrumentation test for JUnit
1 parent 1bf9c7f commit dccd70a

4 files changed

Lines changed: 182 additions & 6 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public void tearDown() {
112112
}
113113

114114
/**
115-
* Checks the structure of the traces captured from the test agent.
115+
* Checks the structure of the traces captured from the test tracer.
116116
*
117117
* @param matchers The matchers to verify the trace collection, one matcher by expected trace.
118118
*/
@@ -121,7 +121,7 @@ protected void assertTraces(TraceMatcher... matchers) {
121121
}
122122

123123
/**
124-
* Checks the structure of the traces captured from the test agent.
124+
* Checks the structure of the traces captured from the test tracer.
125125
*
126126
* @param options The {@link TraceAssertions.Options} to configure the checks.
127127
* @param matchers The matchers to verify the trace collection, one matcher by expected trace.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* - introduce as few as possible matchers
1111
* - only have matchers for generic purpose, don't introduce feature / produce / use-case specific matchers
1212
* - name "ignores" as "any"?
13+
* - think about extensibility? Open matchers for inheritance
1314
*/
1415

1516
public class Matchers {
@@ -21,7 +22,7 @@ public static <T> Matcher<T> isNull() {
2122
return new IsNull<>();
2223
}
2324

24-
public static <T> Matcher<T> nonNull() {
25+
public static <T> Matcher<T> isNonNull() {
2526
return new IsNonNull<>();
2627
}
2728

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import static datadog.trace.agent.test.assertions.Matchers.assertValue;
44
import static datadog.trace.agent.test.assertions.Matchers.is;
55
import static datadog.trace.agent.test.assertions.Matchers.isFalse;
6+
import static datadog.trace.agent.test.assertions.Matchers.isNonNull;
67
import static datadog.trace.agent.test.assertions.Matchers.isNull;
78
import static datadog.trace.agent.test.assertions.Matchers.isTrue;
89
import static datadog.trace.agent.test.assertions.Matchers.matches;
9-
import static datadog.trace.agent.test.assertions.Matchers.nonNull;
1010
import static datadog.trace.agent.test.assertions.Matchers.validates;
1111
import static datadog.trace.core.DDSpanAccessor.spanLinks;
1212
import static java.time.Duration.ofNanos;
@@ -15,7 +15,11 @@
1515
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
1616
import datadog.trace.core.DDSpan;
1717
import java.time.Duration;
18+
import java.util.ArrayList;
19+
import java.util.Collection;
20+
import java.util.HashMap;
1821
import java.util.List;
22+
import java.util.Map;
1923
import java.util.function.Predicate;
2024
import java.util.regex.Pattern;
2125
import org.opentest4j.AssertionFailedError;
@@ -35,6 +39,7 @@ public final class SpanMatcher {
3539
private Matcher<Duration> durationMatcher;
3640
private Matcher<String> typeMatcher;
3741
private Matcher<Boolean> errorMatcher;
42+
private TagsMatcher[] tagMatchers;
3843
private SpanLinkMatcher[] linkMatchers;
3944

4045
private static final Matcher<Long> CHILD_OF_PREVIOUS_MATCHER = is(0L);
@@ -69,7 +74,7 @@ public SpanMatcher childOfPrevious() {
6974
}
7075

7176
public SpanMatcher hasServiceName() {
72-
this.serviceNameMatcher = nonNull();
77+
this.serviceNameMatcher = isNonNull();
7378
return this;
7479
}
7580

@@ -133,6 +138,11 @@ public SpanMatcher withoutError() {
133138
return this;
134139
}
135140

141+
public SpanMatcher tags(TagsMatcher... matchers) {
142+
this.tagMatchers = matchers;
143+
return this;
144+
}
145+
136146
public SpanMatcher links(SpanLinkMatcher... matchers) {
137147
this.linkMatchers = matchers;
138148
return this;
@@ -157,7 +167,38 @@ void assertSpan(DDSpan span, DDSpan previousSpan) {
157167
}
158168

159169
private void assertSpanTags(TagMap tags) {
160-
// TODO Implement span tag assertions
170+
// Check if tags should be asserted at all
171+
if (this.tagMatchers == null) {
172+
return;
173+
}
174+
// Collect all matchers
175+
Map<String, Matcher<?>> matchers = new HashMap<>();
176+
for (TagsMatcher tagMatcher : this.tagMatchers) {
177+
matchers.putAll(tagMatcher.tagMatchers);
178+
}
179+
// Assert all tags
180+
List<String> uncheckedTagNames = new ArrayList<>();
181+
tags.forEach(
182+
(key, value) -> {
183+
Matcher<Object> matcher = (Matcher) matchers.remove(key);
184+
if (matcher == null) {
185+
uncheckedTagNames.add(key);
186+
} else {
187+
assertValue(matcher, value, "Unexpected " + key + " tag value.");
188+
}
189+
});
190+
// Remove matchers that accept missing tags
191+
Collection<Matcher<?>> values = matchers.values();
192+
values.removeIf(matcher -> matcher instanceof Any);
193+
values.removeIf(matcher -> matcher instanceof IsNull);
194+
// Fails if any tags are missing
195+
if (!matchers.isEmpty()) {
196+
throw new AssertionFailedError("Missing tags: " + String.join(", ", matchers.keySet()));
197+
}
198+
// Fails if any unexpected tags are present
199+
if (!uncheckedTagNames.isEmpty()) {
200+
throw new AssertionFailedError("Unexpected tags: " + String.join(", ", uncheckedTagNames));
201+
}
161202
}
162203

163204
private void assertSpanLinks(List<AgentSpanLink> links) {
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package datadog.trace.agent.test.assertions;
2+
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;
6+
import static datadog.trace.api.DDTags.ERROR_MSG;
7+
import static datadog.trace.api.DDTags.ERROR_STACK;
8+
import static datadog.trace.api.DDTags.ERROR_TYPE;
9+
import static datadog.trace.api.DDTags.LANGUAGE_TAG_KEY;
10+
import static datadog.trace.api.DDTags.REQUIRED_CODE_ORIGIN_TAGS;
11+
import static datadog.trace.api.DDTags.RUNTIME_ID_TAG;
12+
import static datadog.trace.api.DDTags.THREAD_ID;
13+
import static datadog.trace.api.DDTags.THREAD_NAME;
14+
import static datadog.trace.common.sampling.RateByServiceTraceSampler.SAMPLING_AGENT_RATE;
15+
import static datadog.trace.common.writer.ddagent.TraceMapper.SAMPLING_PRIORITY_KEY;
16+
17+
import datadog.trace.api.DDTags;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
public final class TagsMatcher {
22+
final Map<String, Matcher<?>> tagMatchers;
23+
24+
private TagsMatcher(Map<String, Matcher<?>> tagMatchers) {
25+
this.tagMatchers = tagMatchers;
26+
}
27+
28+
public static TagsMatcher defaultTags() {
29+
Map<String, Matcher<?>> tagMatchers = new HashMap<>();
30+
tagMatchers.put(THREAD_NAME, isNonNull());
31+
tagMatchers.put(THREAD_ID, isNonNull());
32+
tagMatchers.put(RUNTIME_ID_TAG, any());
33+
tagMatchers.put(LANGUAGE_TAG_KEY, any());
34+
tagMatchers.put(SAMPLING_AGENT_RATE, any());
35+
tagMatchers.put(SAMPLING_PRIORITY_KEY.toString(), any());
36+
tagMatchers.put("_sample_rate", any());
37+
tagMatchers.put(DDTags.PID_TAG, any());
38+
tagMatchers.put(DDTags.SCHEMA_VERSION_TAG_KEY, any());
39+
tagMatchers.put(DDTags.PROFILING_ENABLED, any());
40+
tagMatchers.put(DDTags.PROFILING_CONTEXT_ENGINE, any());
41+
tagMatchers.put(DDTags.BASE_SERVICE, any());
42+
tagMatchers.put(DDTags.DSM_ENABLED, any());
43+
tagMatchers.put(DDTags.DJM_ENABLED, any());
44+
tagMatchers.put(DDTags.PARENT_ID, any());
45+
tagMatchers.put(DDTags.SPAN_LINKS, any()); // this is checked by LinksAsserter
46+
47+
for (String tagName : REQUIRED_CODE_ORIGIN_TAGS) {
48+
tagMatchers.put(tagName, any());
49+
}
50+
// TODO Keep porting default tag logic
51+
// TODO Dev notes:
52+
// - it seems there is way too many logic there
53+
// - need to check if its related to tracing only
54+
55+
return new TagsMatcher(tagMatchers);
56+
}
57+
58+
/**
59+
* Requires the following tag to match the given matcher.
60+
*
61+
* @param tagName The tag name to match.
62+
* @param matcher The matcher to apply to the tag value.
63+
* @return A tag matcher that requires the following tag to match the given matcher.
64+
*/
65+
public static TagsMatcher tag(String tagName, Matcher<?> matcher) {
66+
Map<String, Matcher<?>> tagMatchers = new HashMap<>();
67+
tagMatchers.put(tagName, matcher);
68+
return new TagsMatcher(tagMatchers);
69+
}
70+
71+
/**
72+
* Requires the following tags to be present.
73+
*
74+
* @param tagNames The tag names to match.
75+
* @return A tag matcher that requires the following tags to be present.
76+
*/
77+
public static TagsMatcher includes(String... tagNames) {
78+
Map<String, Matcher<?>> tagMatchers = new HashMap<>();
79+
for (String tagName : tagNames) {
80+
tagMatchers.put(tagName, any());
81+
}
82+
return new TagsMatcher(tagMatchers);
83+
}
84+
85+
/**
86+
* Requires the error tags to match the given error.
87+
*
88+
* @param error The error to match.
89+
* @return A tag matcher that requires the error tags to match the given error.
90+
*/
91+
public static TagsMatcher error(Throwable error) {
92+
return error(error.getClass(), error.getMessage());
93+
}
94+
95+
/**
96+
* Requires the error tags to match the given error type.
97+
*
98+
* @param errorType The error type to match.
99+
* @return A tag matcher that requires the error tags to match the given error type.
100+
*/
101+
public static TagsMatcher error(Class<? extends Throwable> errorType) {
102+
return error(errorType, null);
103+
}
104+
105+
/**
106+
* Requires the error tags to match the given error type and message.
107+
*
108+
* @param errorType The error type to match.
109+
* @param message The error message to match.
110+
* @return A tag matcher that requires the error tags to match the given error type and message.
111+
*/
112+
public static TagsMatcher error(Class<? extends Throwable> errorType, String message) {
113+
Map<String, Matcher<?>> tagMatchers = new HashMap<>();
114+
tagMatchers.put(ERROR_TYPE, Matchers.<String>validates(s -> testErrorType(errorType, s)));
115+
tagMatchers.put(ERROR_STACK, isNonNull());
116+
if (message != null) {
117+
tagMatchers.put(ERROR_MSG, is(message));
118+
}
119+
return new TagsMatcher(tagMatchers);
120+
}
121+
122+
static boolean testErrorType(Class<? extends Throwable> errorType, String actual) {
123+
if (errorType.getName().equals(actual)) {
124+
return true;
125+
}
126+
try {
127+
// also accept type names which are subclasses of the given error type
128+
return errorType.isAssignableFrom(
129+
Class.forName(actual, false, TagsMatcher.class.getClassLoader()));
130+
} catch (Throwable ignore) {
131+
return false;
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)