Skip to content

Commit 2305f07

Browse files
Trace setup and teardown operations in JUnit 5 (#7714)
1 parent 9db260f commit 2305f07

File tree

19 files changed

+821
-106
lines changed

19 files changed

+821
-106
lines changed

dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner {
199199
}
200200
}
201201

202+
@Override
203+
protected String idGenerationStrategyName() {
204+
"RANDOM"
205+
}
206+
202207
@Override
203208
void setup() {
204209
skippableTests.clear()

dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ abstract class CiVisibilityTestUtils {
4141
// Regardless, the values of these fields should be treated as different
4242
path("content.start", false),
4343
path("content.duration", false),
44+
path("content.metrics.['_dd.host.vcpu_count']", false),
4445
path("content.meta.['_dd.p.tid']", false),
4546
path("content.meta.['error.stack']", false),
4647
]
@@ -83,15 +84,15 @@ abstract class CiVisibilityTestUtils {
8384
replacementMap.put(labelGenerator.forKey(e.key), "\"$e.value\"")
8485
}
8586

86-
def expectedEvents = getFreemarkerTemplate(baseTemplatesPath + "/events.ftl", replacementMap)
87+
def expectedEvents = getFreemarkerTemplate(baseTemplatesPath + "/events.ftl", replacementMap, events)
8788
def actualEvents = JSON_MAPPER.writeValueAsString(events)
8889
try {
8990
JSONAssert.assertEquals(expectedEvents, actualEvents, JSONCompareMode.LENIENT)
9091
} catch (AssertionError e) {
9192
throw new org.opentest4j.AssertionFailedError("Events mismatch", expectedEvents, actualEvents, e)
9293
}
9394

94-
def expectedCoverages = getFreemarkerTemplate(baseTemplatesPath + "/coverages.ftl", replacementMap)
95+
def expectedCoverages = getFreemarkerTemplate(baseTemplatesPath + "/coverages.ftl", replacementMap, coverages)
9596
def actualCoverages = JSON_MAPPER.writeValueAsString(coverages)
9697
try {
9798
JSONAssert.assertEquals(expectedCoverages, actualCoverages, JSONCompareMode.LENIENT)
@@ -122,11 +123,15 @@ abstract class CiVisibilityTestUtils {
122123
}
123124
}
124125

125-
private static String getFreemarkerTemplate(String templatePath, Map<String, Object> replacements) {
126-
Template coveragesTemplate = FREEMARKER.getTemplate(templatePath)
127-
StringWriter coveragesOut = new StringWriter()
128-
coveragesTemplate.process(replacements, coveragesOut)
129-
return coveragesOut.toString()
126+
private static String getFreemarkerTemplate(String templatePath, Map<String, Object> replacements, List<Map<?, ?>> replacementsSource) {
127+
try {
128+
Template coveragesTemplate = FREEMARKER.getTemplate(templatePath)
129+
StringWriter coveragesOut = new StringWriter()
130+
coveragesTemplate.process(replacements, coveragesOut)
131+
return coveragesOut.toString()
132+
} catch (Exception e) {
133+
throw new RuntimeException("Could not get Freemarker template " + templatePath + "; replacements map: " + replacements + "; replacements source: " + replacementsSource, e)
134+
}
130135
}
131136

132137
private static final class TemplateGenerator {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
apply from: "$rootDir/gradle/java.gradle"
2+
3+
def jupiterVersion = '5.8.0'
4+
def platformVersion = '1.8.0'
5+
6+
muzzle {
7+
pass {
8+
group = 'org.junit.platform'
9+
module = 'junit-platform-launcher'
10+
versions = "[$platformVersion,)"
11+
extraDependency "org.junit.jupiter:junit-jupiter-engine:$jupiterVersion"
12+
}
13+
pass {
14+
group = 'org.junit.jupiter'
15+
module = 'junit-jupiter-engine'
16+
versions = "[$jupiterVersion,)"
17+
}
18+
pass {
19+
group = 'org.junit.jupiter'
20+
module = 'junit-jupiter-api'
21+
versions = "[$jupiterVersion,)"
22+
extraDependency "org.junit.jupiter:junit-jupiter-engine:$jupiterVersion"
23+
}
24+
}
25+
26+
addTestSuiteForDir('latestDepTest', 'test')
27+
28+
dependencies {
29+
implementation project(':dd-java-agent:instrumentation:junit-5.3')
30+
31+
compileOnly group: 'org.junit.platform', name: 'junit-platform-launcher', version: "$platformVersion"
32+
compileOnly group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: "$jupiterVersion"
33+
compileOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "$jupiterVersion"
34+
35+
testImplementation testFixtures(project(':dd-java-agent:agent-ci-visibility'))
36+
37+
// versions used below are not the minimum ones that we support,
38+
// but the tests need to use them in order to be compliant with Spock 2.x
39+
testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.8.2'
40+
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.2'
41+
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.2'
42+
43+
latestDepTestImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '+'
44+
latestDepTestImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '+'
45+
latestDepTestImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '+'
46+
}
47+
48+
configurations.matching({ it.name.startsWith('test') }).each({
49+
it.resolutionStrategy {
50+
force group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.8.2'
51+
force group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.2'
52+
force group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.2'
53+
}
54+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package datadog.trace.instrumentation.junit5;
2+
3+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
4+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
5+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
6+
import datadog.trace.bootstrap.instrumentation.api.Tags;
7+
import java.lang.reflect.Method;
8+
import org.junit.jupiter.api.extension.ExtensionContext;
9+
import org.junit.jupiter.api.extension.InvocationInterceptor;
10+
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
11+
12+
public class BeforeAfterOperationsTracer implements InvocationInterceptor {
13+
14+
@Override
15+
public void interceptBeforeAllMethod(
16+
Invocation<Void> invocation,
17+
ReflectiveInvocationContext<Method> invocationContext,
18+
ExtensionContext extensionContext)
19+
throws Throwable {
20+
traceInvocation(invocation, invocationContext.getExecutable(), "BeforeAll");
21+
}
22+
23+
@Override
24+
public void interceptBeforeEachMethod(
25+
Invocation<Void> invocation,
26+
ReflectiveInvocationContext<Method> invocationContext,
27+
ExtensionContext extensionContext)
28+
throws Throwable {
29+
traceInvocation(invocation, invocationContext.getExecutable(), "BeforeEach");
30+
}
31+
32+
@Override
33+
public void interceptAfterEachMethod(
34+
Invocation<Void> invocation,
35+
ReflectiveInvocationContext<Method> invocationContext,
36+
ExtensionContext extensionContext)
37+
throws Throwable {
38+
traceInvocation(invocation, invocationContext.getExecutable(), "AfterEach");
39+
}
40+
41+
@Override
42+
public void interceptAfterAllMethod(
43+
Invocation<Void> invocation,
44+
ReflectiveInvocationContext<Method> invocationContext,
45+
ExtensionContext extensionContext)
46+
throws Throwable {
47+
traceInvocation(invocation, invocationContext.getExecutable(), "AfterAll");
48+
}
49+
50+
private static void traceInvocation(
51+
Invocation<Void> invocation, Method executable, String operationName) throws Throwable {
52+
AgentSpan agentSpan = AgentTracer.startSpan("junit", executable.getName());
53+
agentSpan.setTag(Tags.TEST_CALLBACK, operationName);
54+
try (AgentScope agentScope = AgentTracer.activateSpan(agentSpan)) {
55+
invocation.proceed();
56+
} finally {
57+
agentSpan.finish();
58+
}
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package datadog.trace.instrumentation.junit5;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
5+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
6+
7+
import com.google.auto.service.AutoService;
8+
import datadog.trace.agent.tooling.Instrumenter;
9+
import datadog.trace.agent.tooling.InstrumenterModule;
10+
import net.bytebuddy.asm.Advice;
11+
import net.bytebuddy.description.type.TypeDescription;
12+
import net.bytebuddy.matcher.ElementMatcher;
13+
import org.junit.jupiter.engine.extension.ExtensionRegistrar;
14+
15+
@AutoService(InstrumenterModule.class)
16+
public class JUnit5BeforeAfterInstrumentation extends InstrumenterModule.CiVisibility
17+
implements Instrumenter.ForTypeHierarchy {
18+
19+
public JUnit5BeforeAfterInstrumentation() {
20+
super("ci-visibility", "junit-5", "setup-teardown");
21+
}
22+
23+
@Override
24+
public String hierarchyMarkerType() {
25+
return "org.junit.jupiter.engine.extension.ExtensionRegistrar";
26+
}
27+
28+
@Override
29+
public ElementMatcher<TypeDescription> hierarchyMatcher() {
30+
return implementsInterface(named(hierarchyMarkerType()));
31+
}
32+
33+
@Override
34+
public String[] helperClassNames() {
35+
return new String[] {
36+
packageName + ".BeforeAfterOperationsTracer",
37+
};
38+
}
39+
40+
@Override
41+
public void methodAdvice(MethodTransformer transformer) {
42+
transformer.applyAdvice(
43+
isConstructor(),
44+
JUnit5BeforeAfterInstrumentation.class.getName() + "$RegisterExtensionAdvice");
45+
}
46+
47+
public static class RegisterExtensionAdvice {
48+
@Advice.OnMethodExit
49+
public static void registerExtension(@Advice.This ExtensionRegistrar extensionRegistrar) {
50+
extensionRegistrar.registerExtension(BeforeAfterOperationsTracer.class);
51+
}
52+
}
53+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import datadog.trace.api.DisableTestTrace
2+
import datadog.trace.civisibility.CiVisibilityInstrumentationTest
3+
import datadog.trace.instrumentation.junit5.TestEventsHandlerHolder
4+
import org.example.*
5+
import org.junit.jupiter.engine.JupiterTestEngine
6+
import org.junit.platform.engine.DiscoverySelector
7+
import org.junit.platform.launcher.core.LauncherConfig
8+
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder
9+
import org.junit.platform.launcher.core.LauncherFactory
10+
11+
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass
12+
13+
@DisableTestTrace(reason = "avoid self-tracing")
14+
class JUnit58Test extends CiVisibilityInstrumentationTest {
15+
16+
def "test #testcaseName"() {
17+
runTests(tests)
18+
19+
assertSpansData(testcaseName, expectedTracesCount)
20+
21+
where:
22+
testcaseName | tests | expectedTracesCount
23+
"test-before-each-after-each" | [TestSucceedBeforeEachAfterEach] | 2
24+
"test-before-all-after-all" | [TestSucceedBeforeAllAfterAll] | 2
25+
}
26+
27+
private static void runTests(List<Class<?>> tests) {
28+
TestEventsHandlerHolder.startForcefully()
29+
30+
DiscoverySelector[] selectors = new DiscoverySelector[tests.size()]
31+
for (i in 0..<tests.size()) {
32+
selectors[i] = selectClass(tests[i])
33+
}
34+
35+
def launcherReq = LauncherDiscoveryRequestBuilder.request()
36+
.selectors(selectors)
37+
.build()
38+
39+
def launcherConfig = LauncherConfig
40+
.builder()
41+
.enableTestEngineAutoRegistration(false)
42+
.addTestEngines(new JupiterTestEngine())
43+
.build()
44+
45+
def launcher = LauncherFactory.create(launcherConfig)
46+
try {
47+
launcher.execute(launcherReq)
48+
} catch (Throwable ignored) {
49+
}
50+
51+
TestEventsHandlerHolder.stop()
52+
}
53+
54+
@Override
55+
String instrumentedLibraryName() {
56+
return "junit5"
57+
}
58+
59+
@Override
60+
String instrumentedLibraryVersion() {
61+
return JupiterTestEngine.getPackage().getImplementationVersion()
62+
}
63+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.example;
2+
3+
import static org.junit.jupiter.api.Assertions.assertTrue;
4+
5+
import org.junit.jupiter.api.*;
6+
7+
public class TestSucceedBeforeAllAfterAll {
8+
9+
@BeforeAll
10+
public static void setUp() {}
11+
12+
@AfterAll
13+
public static void tearDown() {}
14+
15+
@Test
16+
public void test_succeed() {
17+
assertTrue(true);
18+
}
19+
20+
@Test
21+
public void another_test_succeed() {
22+
assertTrue(true);
23+
}
24+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.example;
2+
3+
import static org.junit.jupiter.api.Assertions.assertTrue;
4+
5+
import org.junit.jupiter.api.AfterEach;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class TestSucceedBeforeEachAfterEach {
10+
11+
@BeforeEach
12+
public void setUp() {}
13+
14+
@AfterEach
15+
public void tearDown() {}
16+
17+
@Test
18+
public void test_succeed() {
19+
assertTrue(true);
20+
}
21+
22+
@Test
23+
public void another_test_succeed() {
24+
assertTrue(true);
25+
}
26+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[ ]

0 commit comments

Comments
 (0)