Skip to content

Commit bcb5de7

Browse files
Fix JUnit 5 instrumentation and tests to work with JUnit 6
1 parent 807f207 commit bcb5de7

File tree

14 files changed

+437
-406
lines changed

14 files changed

+437
-406
lines changed

dd-java-agent/instrumentation/build.gradle

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,28 +94,11 @@ subprojects { Project subProj ->
9494
instrumentPluginClasspath project(path: ':dd-java-agent:agent-tooling', configuration: 'instrumentPluginClasspath')
9595
}
9696

97-
subProj.testing {
98-
suites.configureEach {
99-
// SpockRunner that we use to run agent tests cannot be properly ported to JUnit 5,
100-
// since the framework does not provide the hooks / extension points
101-
// that can be used to shadow the tested class.
102-
103-
// In order to mitigate this, SpockRunner extends JUnitPlatform,
104-
// which is a JUnit 4 runner that allows executing JUnit 5 tests in a JUnit 4 environment
105-
// (i.e. running them as JUnit 4 tests).
106-
107-
// So even though Spock 2 tests run on top of JUnit 5,
108-
// we execute them in "compatibility mode" so that SpockRunner could shadow the test class
109-
// See https://junit.org/junit5/docs/current/user-guide/#running-tests-junit-platform-runner for more details.
110-
useJUnit()
111-
}
112-
}
113-
11497
subProj.tasks.withType(Test).configureEach { subTask ->
11598
// The `forkedTest` task is not a proper test suite, so it has to be configured directly
11699
if (subTask.name == 'forkedTest') {
117100
// See SpockRunner comment above
118-
useJUnit()
101+
useJUnitPlatform()
119102
}
120103

121104
if (subTask.name in ['latestDepTest', 'latestDepForkedTest']) {

dd-java-agent/instrumentation/junit-5.3/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ muzzle {
2222
}
2323
}
2424

25+
ext {
26+
latestDepTestMinJavaVersionForTests = JavaVersion.VERSION_17
27+
}
28+
2529
addTestSuiteForDir('latestDepTest', 'test')
2630

2731
dependencies {
@@ -48,3 +52,9 @@ configurations.matching({ it.name.startsWith('test') }).each({
4852
force group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.12.0'
4953
}
5054
})
55+
56+
tasks.named("compileLatestDepTestJava").configure {
57+
setJavaVersion(it, 17)
58+
sourceCompatibility = JavaVersion.VERSION_1_8
59+
targetCompatibility = JavaVersion.VERSION_1_8
60+
}

dd-java-agent/instrumentation/junit-5.3/gradle.lockfile

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,30 @@ org.jetbrains:annotations:13.0=latestDepTestCompileClasspath,latestDepTestRuntim
135135
org.jspecify:jspecify:1.0.0=latestDepTestCompileClasspath
136136
org.junit.jupiter:junit-jupiter-api:5.12.0=testCompileClasspath,testRuntimeClasspath
137137
org.junit.jupiter:junit-jupiter-api:5.3.0=compileClasspath
138-
org.junit.jupiter:junit-jupiter-api:5.13.4=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
138+
org.junit.jupiter:junit-jupiter-api:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
139139
org.junit.jupiter:junit-jupiter-engine:5.12.0=testCompileClasspath,testRuntimeClasspath
140-
org.junit.jupiter:junit-jupiter-engine:5.13.4=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
140+
org.junit.jupiter:junit-jupiter-engine:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
141141
org.junit.jupiter:junit-jupiter-params:5.12.0=testCompileClasspath,testRuntimeClasspath
142-
org.junit.jupiter:junit-jupiter-params:5.13.4=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
142+
org.junit.jupiter:junit-jupiter-params:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
143+
org.junit.jupiter:junit-jupiter:5.12.0=testCompileClasspath,testRuntimeClasspath
144+
org.junit.jupiter:junit-jupiter:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
143145
org.junit.platform:junit-platform-commons:1.12.0=testCompileClasspath,testRuntimeClasspath
144146
org.junit.platform:junit-platform-commons:1.3.0=compileClasspath
145-
org.junit.platform:junit-platform-commons:1.13.4=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
147+
org.junit.platform:junit-platform-commons:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
146148
org.junit.platform:junit-platform-engine:1.12.0=testCompileClasspath,testRuntimeClasspath
147149
org.junit.platform:junit-platform-engine:1.3.0=compileClasspath
148-
org.junit.platform:junit-platform-engine:1.13.4=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
150+
org.junit.platform:junit-platform-engine:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
149151
org.junit.platform:junit-platform-launcher:1.12.0=testCompileClasspath,testRuntimeClasspath
150152
org.junit.platform:junit-platform-launcher:1.3.0=compileClasspath
151-
org.junit.platform:junit-platform-launcher:1.13.4=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
153+
org.junit.platform:junit-platform-launcher:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
152154
org.junit.platform:junit-platform-runner:1.12.0=testRuntimeClasspath
153155
org.junit.platform:junit-platform-runner:1.9.0=latestDepTestRuntimeClasspath
154156
org.junit.platform:junit-platform-suite-api:1.12.0=testRuntimeClasspath
155-
org.junit.platform:junit-platform-suite-api:1.13.4=latestDepTestRuntimeClasspath
157+
org.junit.platform:junit-platform-suite-api:6.0.0-M2=latestDepTestRuntimeClasspath
156158
org.junit.platform:junit-platform-suite-commons:1.12.0=testRuntimeClasspath
157159
org.junit.platform:junit-platform-suite-commons:1.9.0=latestDepTestRuntimeClasspath
158160
org.junit:junit-bom:5.12.0=testCompileClasspath,testRuntimeClasspath
159-
org.junit:junit-bom:5.9.1=spotbugs
160-
org.junit:junit-bom:5.13.4=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
161+
org.junit:junit-bom:6.0.0-M2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath
161162
org.msgpack:jackson-dataformat-msgpack:0.9.6=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
162163
org.msgpack:msgpack-core:0.9.6=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
163164
org.objenesis:objenesis:3.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package datadog.trace.instrumentation.junit5;
2+
3+
import datadog.trace.util.MethodHandles;
4+
import java.lang.invoke.MethodHandle;
5+
import java.util.Arrays;
6+
import java.util.function.BiFunction;
7+
import org.junit.platform.commons.util.ClassLoaderUtils;
8+
import org.junit.platform.engine.ConfigurationParameters;
9+
import org.junit.platform.engine.EngineExecutionListener;
10+
import org.junit.platform.engine.ExecutionRequest;
11+
import org.junit.platform.engine.TestDescriptor;
12+
13+
public class ExecutionRequestFactory {
14+
15+
private static final MethodHandles METHOD_HANDLES =
16+
new MethodHandles(ClassLoaderUtils.getDefaultClassLoader());
17+
18+
/*
19+
* From 5.13.0-RC1 onwards ExecutionRequest requires two additional arguments on creation.
20+
* - OutputDirectoryProvider outputDirectoryProvider
21+
* - NamespacedHierarchicalStore<Namespace> requestLevelStore
22+
*/
23+
private static final MethodHandle GET_OUTPUT_DIRECTORY_PROVIDER =
24+
METHOD_HANDLES.method(ExecutionRequest.class, "getOutputDirectoryProvider");
25+
private static final MethodHandle GET_STORE =
26+
METHOD_HANDLES.method(ExecutionRequest.class, "getStore");
27+
/*
28+
* From 6.0.0-M2 onwards CancellationToken is also required.
29+
*/
30+
private static final MethodHandle GET_CANCELLATION_TOKEN =
31+
METHOD_HANDLES.method(ExecutionRequest.class, "getCancellationToken");
32+
33+
private static final String[] CREATE_PARAMETER_TYPES =
34+
new String[] {
35+
"org.junit.platform.engine.TestDescriptor",
36+
"org.junit.platform.engine.EngineExecutionListener",
37+
"org.junit.platform.engine.ConfigurationParameters",
38+
"org.junit.platform.engine.reporting.OutputDirectoryProvider",
39+
"org.junit.platform.engine.support.store.NamespacedHierarchicalStore",
40+
"org.junit.platform.engine.CancellationToken"
41+
};
42+
43+
private static final BiFunction<ExecutionRequest, EngineExecutionListener, ExecutionRequest>
44+
EXECUTION_REQUEST_CREATE = createExecutionRequestHandle();
45+
46+
private static BiFunction<ExecutionRequest, EngineExecutionListener, ExecutionRequest>
47+
createExecutionRequestHandle() {
48+
// 6.0.0-M2 and later
49+
if (GET_CANCELLATION_TOKEN != null) {
50+
MethodHandle createMethod =
51+
METHOD_HANDLES.method(
52+
ExecutionRequest.class,
53+
m ->
54+
"create".equals(m.getName())
55+
&& m.getParameterCount() == 6
56+
&& Arrays.equals(
57+
Arrays.stream(m.getParameterTypes()).map(Class::getName).toArray(),
58+
CREATE_PARAMETER_TYPES));
59+
60+
return (request, listener) -> {
61+
Object provider = METHOD_HANDLES.invoke(GET_OUTPUT_DIRECTORY_PROVIDER, request);
62+
Object store = METHOD_HANDLES.invoke(GET_STORE, request);
63+
Object cancellationToken = METHOD_HANDLES.invoke(GET_CANCELLATION_TOKEN, request);
64+
return METHOD_HANDLES.invoke(
65+
createMethod,
66+
request.getRootTestDescriptor(),
67+
listener,
68+
request.getConfigurationParameters(),
69+
provider,
70+
store,
71+
cancellationToken);
72+
};
73+
}
74+
75+
// 5.13.0-RC1 and later
76+
if (GET_STORE != null && GET_OUTPUT_DIRECTORY_PROVIDER != null) {
77+
MethodHandle createMethod =
78+
METHOD_HANDLES.method(
79+
ExecutionRequest.class,
80+
m ->
81+
"create".equals(m.getName())
82+
&& m.getParameterCount() == 5
83+
&& Arrays.equals(
84+
Arrays.stream(m.getParameterTypes()).map(Class::getName).toArray(),
85+
Arrays.copyOf(CREATE_PARAMETER_TYPES, 5)));
86+
87+
return (request, listener) -> {
88+
Object provider = METHOD_HANDLES.invoke(GET_OUTPUT_DIRECTORY_PROVIDER, request);
89+
Object store = METHOD_HANDLES.invoke(GET_STORE, request);
90+
return METHOD_HANDLES.invoke(
91+
createMethod,
92+
request.getRootTestDescriptor(),
93+
listener,
94+
request.getConfigurationParameters(),
95+
provider,
96+
store);
97+
};
98+
}
99+
100+
MethodHandle constructor =
101+
METHOD_HANDLES.constructor(
102+
ExecutionRequest.class,
103+
TestDescriptor.class,
104+
EngineExecutionListener.class,
105+
ConfigurationParameters.class);
106+
return (request, listener) ->
107+
METHOD_HANDLES.invoke(
108+
constructor,
109+
request.getRootTestDescriptor(),
110+
listener,
111+
request.getConfigurationParameters());
112+
}
113+
114+
public static ExecutionRequest createExecutionRequest(
115+
ExecutionRequest request, EngineExecutionListener listener) {
116+
return EXECUTION_REQUEST_CREATE.apply(request, listener);
117+
}
118+
}

dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/JUnit5Instrumentation.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public ElementMatcher<TypeDescription> hierarchyMatcher() {
4949
public String[] helperClassNames() {
5050
return new String[] {
5151
packageName + ".JUnitPlatformUtils",
52+
packageName + ".ExecutionRequestFactory",
5253
packageName + ".TestEventsHandlerHolder",
5354
packageName + ".TracingListener",
5455
packageName + ".CompositeEngineListener",
@@ -117,7 +118,7 @@ public static void addTracingListener(
117118
EngineExecutionListener compositeListener =
118119
new CompositeEngineListener(tracingListener, originalListener);
119120
executionRequest =
120-
JUnitPlatformUtils.createExecutionRequest(executionRequest, compositeListener);
121+
ExecutionRequestFactory.createExecutionRequest(executionRequest, compositeListener);
121122
}
122123

123124
// JUnit 5.3.0 and above

dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/JUnitPlatformUtils.java

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@
2222
import org.junit.platform.commons.JUnitException;
2323
import org.junit.platform.commons.util.ClassLoaderUtils;
2424
import org.junit.platform.commons.util.ReflectionUtils;
25-
import org.junit.platform.engine.ConfigurationParameters;
26-
import org.junit.platform.engine.EngineExecutionListener;
27-
import org.junit.platform.engine.ExecutionRequest;
2825
import org.junit.platform.engine.TestDescriptor;
2926
import org.junit.platform.engine.TestEngine;
3027
import org.junit.platform.engine.TestSource;
@@ -108,71 +105,16 @@ private JUnitPlatformUtils() {}
108105
private static final MethodHandle GET_JAVA_METHOD =
109106
METHOD_HANDLES.method(MethodSource.class, "getJavaMethod");
110107

111-
/*
112-
* From 5.13.0-RC1 onwards ExecutionRequest requires two additional arguments on creation.
113-
* - OutputDirectoryProvider outputDirectoryProvider
114-
* - NamespacedHierarchicalStore<Namespace> requestLevelStore
115-
*/
116-
private static final MethodHandle GET_OUTPUT_DIRECTORY_PROVIDER =
117-
METHOD_HANDLES.method(ExecutionRequest.class, "getOutputDirectoryProvider");
118-
private static final MethodHandle GET_STORE =
119-
METHOD_HANDLES.method(ExecutionRequest.class, "getStore");
120-
private static final String[] CREATE_PARAMETER_TYPES =
121-
new String[] {
122-
"org.junit.platform.engine.TestDescriptor",
123-
"org.junit.platform.engine.EngineExecutionListener",
124-
"org.junit.platform.engine.ConfigurationParameters",
125-
"org.junit.platform.engine.reporting.OutputDirectoryProvider",
126-
"org.junit.platform.engine.support.store.NamespacedHierarchicalStore"
127-
};
128-
private static final MethodHandle EXECUTION_REQUEST_CREATE = createExecutionRequestHandle();
129-
130-
private static MethodHandle createExecutionRequestHandle() {
131-
if (GET_STORE != null && GET_OUTPUT_DIRECTORY_PROVIDER != null) {
132-
return METHOD_HANDLES.method(
133-
ExecutionRequest.class,
134-
m ->
135-
"create".equals(m.getName())
136-
&& m.getParameterCount() == 5
137-
&& Arrays.equals(
138-
Arrays.stream(m.getParameterTypes()).map(Class::getName).toArray(),
139-
CREATE_PARAMETER_TYPES));
140-
} else {
141-
return METHOD_HANDLES.constructor(
142-
ExecutionRequest.class,
143-
TestDescriptor.class,
144-
EngineExecutionListener.class,
145-
ConfigurationParameters.class);
146-
}
147-
}
148-
149-
public static ExecutionRequest createExecutionRequest(
150-
ExecutionRequest request, EngineExecutionListener listener) {
151-
if (GET_STORE != null && GET_OUTPUT_DIRECTORY_PROVIDER != null) {
152-
Object provider = METHOD_HANDLES.invoke(GET_OUTPUT_DIRECTORY_PROVIDER, request);
153-
Object store = METHOD_HANDLES.invoke(GET_STORE, request);
154-
return METHOD_HANDLES.invoke(
155-
EXECUTION_REQUEST_CREATE,
156-
request.getRootTestDescriptor(),
157-
listener,
158-
request.getConfigurationParameters(),
159-
provider,
160-
store);
161-
} else {
162-
return METHOD_HANDLES.invoke(
163-
EXECUTION_REQUEST_CREATE,
164-
request.getRootTestDescriptor(),
165-
listener,
166-
request.getConfigurationParameters());
167-
}
168-
}
169-
170108
private static Class<?> getTestClass(MethodSource methodSource) {
171109
Class<?> javaClass = METHOD_HANDLES.invoke(GET_JAVA_CLASS, methodSource);
172110
if (javaClass != null) {
173111
return javaClass;
174112
}
175-
return ReflectionUtils.loadClass(methodSource.getClassName()).orElse(null);
113+
try {
114+
return ClassLoaderUtils.getDefaultClassLoader().loadClass(methodSource.getClassName());
115+
} catch (ClassNotFoundException e) {
116+
return null;
117+
}
176118
}
177119

178120
private static Method getTestMethod(MethodSource methodSource) {

dd-java-agent/instrumentation/junit-5.3/src/test/java/org/example/TestFlaky.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

dd-java-agent/testing/build.gradle

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,5 @@ shadowJar {
8888
}
8989

9090
tasks.withType(Test).configureEach {
91-
// SpockRunner that we use to run agent tests cannot be properly ported to JUnit 5,
92-
// since the framework does not provide the hooks / extension points
93-
// that can be used to shadow the tested class.
94-
95-
// In order to mitigate this, SpockRunner extends JUnitPlatform,
96-
// which is a JUnit 4 runner that allows executing JUnit 5 tests in a JUnit 4 environment
97-
// (i.e. running them as JUnit 4 tests).
98-
99-
// So even though Spock 2 tests run on top of JUnit 5,
100-
// we execute them in "compatibility mode" so that SpockRunner could shadow the test class
101-
// See https://junit.org/junit5/docs/current/user-guide/#running-tests-junit-platform-runner for more details.
102-
useJUnit()
91+
useJUnitPlatform()
10392
}

dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/AgentTestRunner.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ import net.bytebuddy.dynamic.DynamicType
6767
import net.bytebuddy.utility.JavaModule
6868
import okhttp3.HttpUrl
6969
import okhttp3.OkHttpClient
70-
import org.junit.runner.RunWith
70+
import org.junit.jupiter.api.extension.ExtendWith
7171
import org.slf4j.LoggerFactory
7272
import org.spockframework.mock.MockUtil
7373
import org.spockframework.mock.runtime.MockInvocation
@@ -107,7 +107,7 @@ import static datadog.trace.util.AgentThreadFactory.AgentThread.TASK_SCHEDULER
107107
*/
108108
// CodeNarc incorrectly thinks ".class" is unnecessary in @RunWith
109109
@SuppressWarnings('UnnecessaryDotClass')
110-
@RunWith(SpockRunner.class)
110+
@ExtendWith(SpockExtension.class)
111111
abstract class AgentTestRunner extends DDSpecification implements AgentBuilder.Listener {
112112
private static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20)
113113

0 commit comments

Comments
 (0)