Skip to content

Commit cdee1c8

Browse files
Tracing of setup and teardown actions in JUnit 4 (#8030)
1 parent 626485f commit cdee1c8

File tree

34 files changed

+2829
-0
lines changed

34 files changed

+2829
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apply from: "$rootDir/gradle/java.gradle"
2+
3+
muzzle {
4+
pass {
5+
group = 'junit'
6+
module = 'junit'
7+
versions = '[4.13,5)'
8+
}
9+
}
10+
11+
addTestSuiteForDir('latestDepTest', 'test')
12+
13+
dependencies {
14+
implementation project(':dd-java-agent:instrumentation:junit-4.10')
15+
compileOnly group: 'junit', name: 'junit', version: '4.13'
16+
17+
testImplementation testFixtures(project(':dd-java-agent:agent-ci-visibility'))
18+
19+
// version used below is not the minimum one that we support,
20+
// but the tests need to use it in order to be compliant with Spock 2.x
21+
testImplementation(group: 'junit', name: 'junit') {
22+
version {
23+
strictly '4.13.2'
24+
}
25+
}
26+
27+
latestDepTestImplementation group: 'junit', name: 'junit', version: '4.+'
28+
}

dd-java-agent/instrumentation/junit-4.10/junit-4.13/gradle.lockfile

+228
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package datadog.trace.instrumentation.junit4;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
5+
6+
import com.google.auto.service.AutoService;
7+
import datadog.trace.agent.tooling.Instrumenter;
8+
import datadog.trace.agent.tooling.InstrumenterModule;
9+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
10+
import net.bytebuddy.asm.Advice;
11+
import org.junit.runner.manipulation.InvalidOrderingException;
12+
import org.junit.runner.manipulation.Ordering;
13+
import org.junit.runners.model.FrameworkMethod;
14+
15+
@AutoService(InstrumenterModule.class)
16+
public class JUnit4BeforeAfterInstrumentation extends InstrumenterModule.CiVisibility
17+
implements Instrumenter.ForKnownTypes {
18+
19+
public JUnit4BeforeAfterInstrumentation() {
20+
super("ci-visibility", "junit-4", "setup-teardown");
21+
}
22+
23+
@Override
24+
public String[] knownMatchingTypes() {
25+
return new String[] {
26+
"org.junit.internal.runners.statements.RunBefores",
27+
"org.junit.internal.runners.statements.RunAfters",
28+
"org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters$RunBeforeParams",
29+
"org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters$RunAfterParams",
30+
};
31+
}
32+
33+
@Override
34+
public String[] helperClassNames() {
35+
return new String[] {
36+
packageName + ".JUnit4BeforeAfterOperationsTracer",
37+
};
38+
}
39+
40+
@Override
41+
public void methodAdvice(MethodTransformer transformer) {
42+
transformer.applyAdvice(
43+
named("invokeMethod")
44+
.and(takesArgument(0, named("org.junit.runners.model.FrameworkMethod"))),
45+
JUnit4BeforeAfterInstrumentation.class.getName() + "$RunBeforesAftersAdvice");
46+
}
47+
48+
public static class RunBeforesAftersAdvice {
49+
@Advice.OnMethodEnter(suppress = Throwable.class)
50+
public static AgentScope startCallSpan(@Advice.Argument(0) final FrameworkMethod method) {
51+
return JUnit4BeforeAfterOperationsTracer.startTrace(method.getMethod());
52+
}
53+
54+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
55+
public static void finishCallSpan(
56+
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
57+
JUnit4BeforeAfterOperationsTracer.endTrace(scope, throwable);
58+
}
59+
60+
// JUnit 4.13 and above
61+
public static void muzzleCheck(final Ordering ord) {
62+
try {
63+
ord.apply(null);
64+
} catch (InvalidOrderingException e) {
65+
throw new RuntimeException(e);
66+
}
67+
}
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package datadog.trace.instrumentation.junit4;
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.After;
9+
import org.junit.AfterClass;
10+
import org.junit.Before;
11+
import org.junit.BeforeClass;
12+
import org.junit.runners.Parameterized;
13+
14+
public class JUnit4BeforeAfterOperationsTracer {
15+
public static AgentScope startTrace(final Method method) {
16+
final AgentSpan span = AgentTracer.startSpan("junit", method.getName());
17+
if (method.isAnnotationPresent(Before.class)) {
18+
span.setTag(Tags.TEST_CALLBACK, "Before");
19+
} else if (method.isAnnotationPresent(After.class)) {
20+
span.setTag(Tags.TEST_CALLBACK, "After");
21+
} else if (method.isAnnotationPresent(BeforeClass.class)) {
22+
span.setTag(Tags.TEST_CALLBACK, "BeforeClass");
23+
} else if (method.isAnnotationPresent(AfterClass.class)) {
24+
span.setTag(Tags.TEST_CALLBACK, "AfterClass");
25+
} else if (method.isAnnotationPresent(Parameterized.BeforeParam.class)) {
26+
span.setTag(Tags.TEST_CALLBACK, "BeforeParam");
27+
} else if (method.isAnnotationPresent(Parameterized.AfterParam.class)) {
28+
span.setTag(Tags.TEST_CALLBACK, "AfterParam");
29+
}
30+
return AgentTracer.activateSpan(span);
31+
}
32+
33+
public static void endTrace(final AgentScope scope, final Throwable throwable) {
34+
final AgentSpan span = scope.span();
35+
if (throwable != null) {
36+
span.addThrowable(throwable);
37+
}
38+
scope.close();
39+
span.finish();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import datadog.trace.api.DisableTestTrace
2+
import datadog.trace.civisibility.CiVisibilityInstrumentationTest
3+
import datadog.trace.instrumentation.junit4.TestEventsHandlerHolder
4+
import junit.runner.Version
5+
import org.example.TestFailedAfter
6+
import org.example.TestFailedAfterClass
7+
import org.example.TestFailedAfterParam
8+
import org.example.TestFailedBefore
9+
import org.example.TestFailedBeforeClass
10+
import org.example.TestFailedBeforeParam
11+
import org.example.TestSucceedBeforeAfter
12+
import org.example.TestSucceedBeforeClassAfterClass
13+
import org.example.TestSucceedBeforeParamAfterParam
14+
import org.junit.runner.JUnitCore
15+
16+
@DisableTestTrace(reason = "avoid self-tracing")
17+
class JUnit413Test extends CiVisibilityInstrumentationTest {
18+
19+
def runner = new JUnitCore()
20+
21+
def "test #testcaseName"() {
22+
runTests(tests)
23+
24+
assertSpansData(testcaseName, expectedTracesCount)
25+
26+
where:
27+
testcaseName | tests | expectedTracesCount
28+
"test-succeed-before-after" | [TestSucceedBeforeAfter] | 3
29+
"test-succeed-before-class-after-class" | [TestSucceedBeforeClassAfterClass] | 3
30+
"test-succeed-before-param-after-param" | [TestSucceedBeforeParamAfterParam] | 2
31+
"test-failed-before-class" | [TestFailedBeforeClass] | 1
32+
"test-failed-after-class" | [TestFailedAfterClass] | 3
33+
"test-failed-before" | [TestFailedBefore] | 3
34+
"test-failed-after" | [TestFailedAfter] | 3
35+
"test-failed-before-param" | [TestFailedBeforeParam] | 2
36+
"test-failed-after-param" | [TestFailedAfterParam] | 2
37+
}
38+
39+
private void runTests(Collection<Class<?>> tests) {
40+
TestEventsHandlerHolder.start()
41+
try {
42+
Class[] array = tests.toArray(new Class[0])
43+
runner.run(array)
44+
} catch (Throwable ignored) {
45+
// Ignored
46+
}
47+
TestEventsHandlerHolder.stop()
48+
}
49+
50+
@Override
51+
String instrumentedLibraryName() {
52+
return "junit4"
53+
}
54+
55+
@Override
56+
String instrumentedLibraryVersion() {
57+
return Version.id()
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import org.junit.After;
6+
import org.junit.Test;
7+
8+
public class TestFailedAfter {
9+
@After
10+
public void tearDown() {
11+
throw new RuntimeException("testcase teardown failed");
12+
}
13+
14+
@Test
15+
public void test_succeed() {
16+
assertTrue(true);
17+
}
18+
19+
@Test
20+
public void another_test_succeed() {
21+
assertTrue(true);
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import org.junit.AfterClass;
6+
import org.junit.Test;
7+
8+
public class TestFailedAfterClass {
9+
@AfterClass
10+
public static void tearDown() {
11+
throw new RuntimeException("suite teardown failed");
12+
}
13+
14+
@Test
15+
public void test_succeed() {
16+
assertTrue(true);
17+
}
18+
19+
@Test
20+
public void another_test_succeed() {
21+
assertTrue(true);
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.example;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import java.util.Arrays;
6+
import java.util.Collection;
7+
import org.junit.Test;
8+
import org.junit.runner.RunWith;
9+
import org.junit.runners.Parameterized;
10+
11+
@RunWith(Parameterized.class)
12+
public class TestFailedAfterParam {
13+
private final int num1;
14+
private final int num2;
15+
private final int sum;
16+
17+
public TestFailedAfterParam(final int num1, final int num2, final int sum) {
18+
this.num1 = num1;
19+
this.num2 = num2;
20+
this.sum = sum;
21+
}
22+
23+
@Parameterized.BeforeParam
24+
public static void setup() {}
25+
26+
@Parameterized.AfterParam
27+
public static void tearDown() {
28+
throw new RuntimeException("after param setup failed");
29+
}
30+
31+
@Parameterized.Parameters
32+
public static Collection<Object[]> data() {
33+
return Arrays.asList(new Object[][] {{0, 0, 0}, {1, 1, 2}});
34+
}
35+
36+
@Test
37+
public void parameterized_test_succeed() {
38+
assertEquals(num1 + num2, sum);
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import org.junit.Before;
6+
import org.junit.Test;
7+
8+
public class TestFailedBefore {
9+
@Before
10+
public void setup() {
11+
throw new RuntimeException("testcase setup failed");
12+
}
13+
14+
@Test
15+
public void test_succeed() {
16+
assertTrue(true);
17+
}
18+
19+
@Test
20+
public void another_test_succeed() {
21+
assertTrue(true);
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import org.junit.BeforeClass;
6+
import org.junit.Test;
7+
8+
public class TestFailedBeforeClass {
9+
@BeforeClass
10+
public static void setup() {
11+
throw new RuntimeException("suite setup failed");
12+
}
13+
14+
@Test
15+
public void test_succeed() {
16+
assertTrue(true);
17+
}
18+
19+
@Test
20+
public void another_test_succeed() {
21+
assertTrue(true);
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.example;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import java.util.Arrays;
6+
import java.util.Collection;
7+
import org.junit.Test;
8+
import org.junit.runner.RunWith;
9+
import org.junit.runners.Parameterized;
10+
11+
@RunWith(Parameterized.class)
12+
public class TestFailedBeforeParam {
13+
private final int num1;
14+
private final int num2;
15+
private final int sum;
16+
17+
public TestFailedBeforeParam(final int num1, final int num2, final int sum) {
18+
this.num1 = num1;
19+
this.num2 = num2;
20+
this.sum = sum;
21+
}
22+
23+
@Parameterized.BeforeParam
24+
public static void setup() {
25+
throw new RuntimeException("before param setup failed");
26+
}
27+
28+
@Parameterized.AfterParam
29+
public static void tearDown() {}
30+
31+
@Parameterized.Parameters
32+
public static Collection<Object[]> data() {
33+
return Arrays.asList(new Object[][] {{0, 0, 0}, {1, 1, 2}});
34+
}
35+
36+
@Test
37+
public void parameterized_test_succeed() {
38+
assertEquals(num1 + num2, sum);
39+
}
40+
}

0 commit comments

Comments
 (0)