Skip to content

Commit 5ab378f

Browse files
Add Config and Context JUnit extensions (#11076)
feat(junit): Add config extension feat(junit): Add context extension feat(junit): Move table tests into junit-utils module feat(core): Improve span links tests Co-authored-by: bruce.bujon <bruce.bujon@datadoghq.com>
1 parent f1608f5 commit 5ab378f

File tree

15 files changed

+538
-376
lines changed

15 files changed

+538
-376
lines changed

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

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import datadog.trace.core.DDSpan;
2121
import datadog.trace.core.PendingTrace;
2222
import datadog.trace.core.TraceCollector;
23-
import de.thetaphi.forbiddenapis.SuppressForbidden;
23+
import datadog.trace.junit.utils.context.AllowContextTestingExtension;
2424
import java.lang.instrument.ClassFileTransformer;
2525
import java.lang.instrument.Instrumentation;
2626
import java.util.List;
@@ -31,7 +31,6 @@
3131
import java.util.function.Predicate;
3232
import net.bytebuddy.agent.ByteBuddyAgent;
3333
import org.junit.jupiter.api.AfterEach;
34-
import org.junit.jupiter.api.BeforeAll;
3534
import org.junit.jupiter.api.BeforeEach;
3635
import org.junit.jupiter.api.extension.ExtendWith;
3736
import org.opentest4j.AssertionFailedError;
@@ -42,7 +41,7 @@
4241
* current implementation is inspired and kept close to it Groovy / Spock counterpart, the {@code
4342
* InstrumentationSpecification}.
4443
*/
45-
@ExtendWith(TestClassShadowingExtension.class)
44+
@ExtendWith({TestClassShadowingExtension.class, AllowContextTestingExtension.class})
4645
public abstract class AbstractInstrumentationTest {
4746
static final Instrumentation INSTRUMENTATION = ByteBuddyAgent.getInstrumentation();
4847

@@ -55,19 +54,6 @@ public abstract class AbstractInstrumentationTest {
5554
protected ClassFileTransformer activeTransformer;
5655
protected ClassFileTransformerListener transformerLister;
5756

58-
@SuppressForbidden // Class.forName() used to dynamically configure context if present
59-
@BeforeAll
60-
static void allowContextTesting() {
61-
// Allow re-registration of context managers so each test can use a fresh tracer.
62-
// This mirrors DDSpecification.allowContextTesting() for the Spock test framework.
63-
try {
64-
Class.forName("datadog.context.ContextManager").getMethod("allowTesting").invoke(null);
65-
Class.forName("datadog.context.ContextBinder").getMethod("allowTesting").invoke(null);
66-
} catch (Throwable ignore) {
67-
// don't block testing if context types aren't available
68-
}
69-
}
70-
7157
@BeforeEach
7258
public void init() {
7359
// If this fails, it's likely the result of another test loading Config before it can be

dd-trace-api/src/test/java/datadog/trace/api/DDTraceApiTableTestConverters.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package datadog.trace.api;
22

3-
import datadog.trace.test.util.TableTestTypeConverters;
3+
import datadog.trace.junit.utils.tabletest.TableTestTypeConverters;
44
import org.tabletest.junit.TypeConverter;
55

66
/** TableTest converters shared by dd-trace-api test classes for unparsable constants. */

dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CHttpCodec.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@
3232
class W3CHttpCodec {
3333
private static final Logger log = LoggerFactory.getLogger(W3CHttpCodec.class);
3434

35-
static final String TRACE_PARENT_KEY = "traceparent";
36-
static final String TRACE_STATE_KEY = "tracestate";
37-
static final String OT_BAGGAGE_PREFIX = "ot-baggage-";
38-
private static final String E2E_START_KEY = OT_BAGGAGE_PREFIX + DDTags.TRACE_START_TIME;
39-
4035
private static final int TRACE_PARENT_TID_START = 2 + 1;
4136
private static final int TRACE_PARENT_TID_END = TRACE_PARENT_TID_START + 32;
4237
private static final int TRACE_PARENT_SID_START = TRACE_PARENT_TID_END + 1;
@@ -45,6 +40,12 @@ class W3CHttpCodec {
4540
private static final int TRACE_PARENT_FLAGS_SAMPLED = 1;
4641
private static final int TRACE_PARENT_LENGTH = TRACE_PARENT_FLAGS_START + 2;
4742

43+
// Package-protected for testing
44+
static final String TRACE_PARENT_KEY = "traceparent";
45+
static final String TRACE_STATE_KEY = "tracestate";
46+
static final String OT_BAGGAGE_PREFIX = "ot-baggage-";
47+
static final String E2E_START_KEY = OT_BAGGAGE_PREFIX + DDTags.TRACE_START_TIME;
48+
4849
private W3CHttpCodec() {
4950
// This class should not be created. This also makes code coverage checks happy.
5051
}

dd-trace-core/src/test/java/datadog/trace/core/DDSpanLinkTest.java

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,35 @@
33
import static datadog.trace.api.DDTags.SPAN_LINKS;
44
import static datadog.trace.bootstrap.instrumentation.api.AgentSpanLink.DEFAULT_FLAGS;
55
import static datadog.trace.bootstrap.instrumentation.api.AgentSpanLink.SAMPLED_FLAG;
6+
import static datadog.trace.bootstrap.instrumentation.api.ContextVisitors.stringValuesMap;
67
import static datadog.trace.bootstrap.instrumentation.api.SpanAttributes.EMPTY;
78
import static datadog.trace.core.propagation.HttpCodecTestHelper.TRACE_PARENT_KEY;
89
import static datadog.trace.core.propagation.HttpCodecTestHelper.TRACE_STATE_KEY;
10+
import static datadog.trace.core.propagation.HttpCodecTestHelper.newW3cHttpCodecExtractor;
11+
import static java.util.Collections.emptyList;
12+
import static java.util.stream.Collectors.toList;
913
import static org.junit.jupiter.api.Assertions.assertEquals;
1014
import static org.junit.jupiter.api.Assertions.assertNull;
1115
import static org.junit.jupiter.api.Assertions.assertTrue;
1216

1317
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import com.fasterxml.jackson.databind.type.CollectionType;
1419
import datadog.trace.api.Config;
1520
import datadog.trace.api.DDSpanId;
1621
import datadog.trace.api.DDTraceId;
1722
import datadog.trace.api.DynamicConfig;
1823
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
19-
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
20-
import datadog.trace.bootstrap.instrumentation.api.ContextVisitors;
24+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer.SpanBuilder;
2125
import datadog.trace.bootstrap.instrumentation.api.SpanAttributes;
2226
import datadog.trace.bootstrap.instrumentation.api.SpanLink;
2327
import datadog.trace.common.writer.ListWriter;
2428
import datadog.trace.core.propagation.ExtractedContext;
2529
import datadog.trace.core.propagation.HttpCodec;
26-
import datadog.trace.core.propagation.HttpCodecTestHelper;
2730
import java.io.IOException;
2831
import java.util.ArrayList;
2932
import java.util.HashMap;
3033
import java.util.List;
3134
import java.util.Map;
32-
import java.util.stream.Collectors;
3335
import java.util.stream.IntStream;
3436
import org.junit.jupiter.api.AfterEach;
3537
import org.junit.jupiter.api.BeforeEach;
@@ -41,19 +43,21 @@ class DDSpanLinkTest extends DDCoreJavaSpecification {
4143

4244
private static final int SPAN_LINK_TAG_MAX_LENGTH = 25_000;
4345
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
46+
private static final CollectionType SPAN_LINK_LIST_TYPE =
47+
JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, SpanLinkAsTag.class);
4448

4549
private ListWriter writer;
4650
private CoreTracer tracer;
4751

4852
@BeforeEach
4953
void setup() {
50-
writer = new ListWriter();
51-
tracer = tracerBuilder().writer(writer).build();
54+
this.writer = new ListWriter();
55+
this.tracer = tracerBuilder().writer(this.writer).build();
5256
}
5357

5458
@AfterEach
5559
void cleanupTest() {
56-
writer.clear();
60+
this.writer.clear();
5761
}
5862

5963
@TableTest({
@@ -65,15 +69,15 @@ void createSpanLinkFromExtractedContext(boolean sampled, String traceFlags, Stri
6569
String traceId = "11223344556677889900aabbccddeeff";
6670
String spanId = "123456789abcdef0";
6771
String traceState = "dd=s:" + sample + ";o:some;t.dm:-4";
72+
6873
Map<String, String> headers = new HashMap<>();
6974
headers.put(TRACE_PARENT_KEY.toUpperCase(), "00-" + traceId + "-" + spanId + "-" + traceFlags);
7075
headers.put(TRACE_STATE_KEY.toUpperCase(), traceState);
7176
HttpCodec.Extractor extractor =
72-
HttpCodecTestHelper.W3CHttpCodecNewExtractor(
77+
newW3cHttpCodecExtractor(
7378
Config.get(), () -> DynamicConfig.create().apply().captureTraceConfig());
7479

75-
ExtractedContext context =
76-
(ExtractedContext) extractor.extract(headers, ContextVisitors.stringValuesMap());
80+
ExtractedContext context = (ExtractedContext) extractor.extract(headers, stringValuesMap());
7781
SpanLink link = DDSpanLink.from(context);
7882

7983
assertEquals(DDTraceId.fromHex(traceId), link.traceId());
@@ -85,19 +89,18 @@ void createSpanLinkFromExtractedContext(boolean sampled, String traceFlags, Stri
8589
@Test
8690
void testSpanLinkEncodingTagMaxSize() throws Exception {
8791
int tooManyLinkCount = 300;
88-
AgentTracer.SpanBuilder builder = tracer.buildSpan("test", "operation");
92+
SpanBuilder builder = tracer.buildSpan("test", "operation");
8993
List<SpanLink> links =
9094
IntStream.range(0, tooManyLinkCount)
9195
.mapToObj(this::createLink)
92-
.collect(Collectors.toList());
93-
94-
for (SpanLink link : links) {
95-
builder.withLink(link);
96-
}
96+
.peek(builder::withLink)
97+
.collect(toList());
9798
AgentSpan span = builder.start();
9899
span.finish();
99-
writer.waitForTraces(1);
100-
String spanLinksTag = (String) writer.get(0).get(0).getTag(SPAN_LINKS);
100+
this.writer.waitForTraces(1);
101+
102+
assertEquals(1, this.writer.get(0).size());
103+
String spanLinksTag = (String) this.writer.get(0).get(0).getTag(SPAN_LINKS);
101104
List<SpanLinkAsTag> decodedSpanLinks = deserializeSpanLinks(spanLinksTag);
102105

103106
assertTrue(spanLinksTag.length() < SPAN_LINK_TAG_MAX_LENGTH);
@@ -112,20 +115,18 @@ void testSpanLinkEncodingTagMaxSize() throws Exception {
112115

113116
@Test
114117
void testSpanLinksEncodingOmittedEmptyKeys() throws Exception {
115-
AgentTracer.SpanBuilder builder = tracer.buildSpan("test", "operation");
116118
SpanLink link =
117119
new DDSpanLink(
118120
DDTraceId.fromHex("11223344556677889900aabbccddeeff"),
119121
DDSpanId.fromHex("123456789abcdef0"),
120122
DEFAULT_FLAGS,
121123
"",
122124
EMPTY);
125+
this.tracer.buildSpan("test", "operation").withLink(link).start().finish();
126+
this.writer.waitForTraces(1);
123127

124-
AgentSpan span = builder.withLink(link).start();
125-
span.finish();
126-
writer.waitForTraces(1);
128+
assertEquals(1, this.writer.get(0).size());
127129
String spanLinksTag = (String) writer.get(0).get(0).getTag(SPAN_LINKS);
128-
129130
assertEquals(
130131
"[{\"span_id\":\"123456789abcdef0\",\"trace_id\":\"11223344556677889900aabbccddeeff\"}]",
131132
spanLinksTag);
@@ -140,7 +141,7 @@ void testSpanLinksEncodingOmittedEmptyKeys() throws Exception {
140141
})
141142
@ParameterizedTest(name = "add span link at any time [{index}]")
142143
void addSpanLinkAtAnyTime(boolean beforeStart, boolean afterStart) throws Exception {
143-
AgentTracer.SpanBuilder builder = tracer.buildSpan("test", "operation");
144+
SpanBuilder builder = this.tracer.buildSpan("test", "operation");
144145
List<SpanLink> links = new ArrayList<>();
145146

146147
if (beforeStart) {
@@ -155,12 +156,11 @@ void addSpanLinkAtAnyTime(boolean beforeStart, boolean afterStart) throws Except
155156
links.add(link);
156157
}
157158
span.finish();
158-
writer.waitForTraces(1);
159-
String spanLinksTag = (String) writer.get(0).get(0).getTag(SPAN_LINKS);
160-
List<SpanLinkAsTag> decodedSpanLinks =
161-
spanLinksTag == null
162-
? java.util.Collections.emptyList()
163-
: deserializeSpanLinks(spanLinksTag);
159+
this.writer.waitForTraces(1);
160+
161+
assertEquals(1, this.writer.get(0).size());
162+
String spanLinksTag = (String) this.writer.get(0).get(0).getTag(SPAN_LINKS);
163+
List<SpanLinkAsTag> decodedSpanLinks = deserializeSpanLinks(spanLinksTag);
164164

165165
int expectedLinkCount = (beforeStart ? 1 : 0) + (afterStart ? 1 : 0);
166166
assertEquals(expectedLinkCount, decodedSpanLinks.size());
@@ -171,14 +171,15 @@ void addSpanLinkAtAnyTime(boolean beforeStart, boolean afterStart) throws Except
171171

172172
@Test
173173
void filterNullLinks() throws Exception {
174-
AgentTracer.SpanBuilder builder = tracer.buildSpan("test", "operation");
174+
SpanBuilder builder = this.tracer.buildSpan("test", "operation");
175175

176176
AgentSpan span = builder.withLink(null).start();
177177
span.addLink(null);
178178
span.finish();
179-
writer.waitForTraces(1);
180-
String spanLinksTag = (String) writer.get(0).get(0).getTag(SPAN_LINKS);
179+
this.writer.waitForTraces(1);
181180

181+
assertEquals(1, this.writer.get(0).size());
182+
String spanLinksTag = (String) this.writer.get(0).get(0).getTag(SPAN_LINKS);
182183
assertNull(spanLinksTag);
183184
}
184185

@@ -215,9 +216,10 @@ private void assertLink(SpanLink expected, SpanLinkAsTag actual) {
215216
}
216217

217218
static List<SpanLinkAsTag> deserializeSpanLinks(String json) throws IOException {
218-
return JSON_MAPPER.readValue(
219-
json,
220-
JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, SpanLinkAsTag.class));
219+
if (json == null) {
220+
return emptyList();
221+
}
222+
return JSON_MAPPER.readValue(json, SPAN_LINK_LIST_TYPE);
221223
}
222224

223225
static class SpanLinkAsTag {

dd-trace-core/src/test/java/datadog/trace/core/propagation/HttpCodecTestHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class HttpCodecTestHelper {
1010
public static final String TRACE_PARENT_KEY = W3CHttpCodec.TRACE_PARENT_KEY;
1111
public static final String TRACE_STATE_KEY = W3CHttpCodec.TRACE_STATE_KEY;
1212

13-
public static HttpCodec.Extractor W3CHttpCodecNewExtractor(
13+
public static HttpCodec.Extractor newW3cHttpCodecExtractor(
1414
Config config, Supplier<TraceConfig> traceConfigSupplier) {
1515
return W3CHttpCodec.newExtractor(config, traceConfigSupplier);
1616
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ include(
159159
":dd-java-agent:testing",
160160
":utils:config-utils",
161161
":utils:container-utils",
162+
":utils:junit-utils",
162163
":utils:filesystem-utils",
163164
":utils:flare-utils",
164165
":utils:logging-utils",

utils/junit-utils/build.gradle.kts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
plugins {
2+
`java-library`
3+
}
4+
5+
apply(from = "$rootDir/gradle/java.gradle")
6+
7+
dependencies {
8+
api(libs.bytebuddy)
9+
api(libs.bytebuddyagent)
10+
api(libs.forbiddenapis)
11+
api(project(":components:environment"))
12+
13+
compileOnly(libs.junit.jupiter)
14+
compileOnly(libs.tabletest)
15+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package datadog.trace.junit.utils.config;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Repeatable;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
import org.junit.jupiter.api.extension.ExtendWith;
9+
10+
/**
11+
* Declares a configuration override for a test. Can be placed on a test class (applies to all
12+
* tests) or on individual test methods.
13+
*
14+
* <p>By default, injects a system property with the {@code dd.} prefix. Use {@code env = true} for
15+
* environment variables (prefix {@code DD_}).
16+
*
17+
* <p>Examples:
18+
*
19+
* <pre>{@code
20+
* @WithConfig(key = "service", value = "my_service")
21+
* @WithConfig(key = "trace.resolver.enabled", value = "false")
22+
* class MyTest extends DDJavaSpecification {
23+
*
24+
* @Test
25+
* @WithConfig(key = "AGENT_HOST", value = "localhost", env = true)
26+
* void testWithEnv() { ... }
27+
* }
28+
* }</pre>
29+
*/
30+
@Retention(RetentionPolicy.RUNTIME)
31+
@Target({ElementType.TYPE, ElementType.METHOD})
32+
@Repeatable(WithConfigs.class)
33+
@ExtendWith(WithConfigExtension.class)
34+
public @interface WithConfig {
35+
/**
36+
* Config key (e.g. {@code "trace.resolver.enabled"}). The {@code dd.}/{@code DD_} prefix is
37+
* auto-added unless {@link #addPrefix()} is {@code false}.
38+
*/
39+
String key();
40+
41+
/** Config value. */
42+
String value();
43+
44+
/** If {@code true}, sets an environment variable instead of a system property. */
45+
boolean env() default false;
46+
47+
/** If {@code false}, the key is used as-is without adding the {@code dd.}/{@code DD_} prefix. */
48+
boolean addPrefix() default true;
49+
}

0 commit comments

Comments
 (0)