Skip to content

Commit

Permalink
Tracing of setup and teardown actions in JUnit 4 (#8030)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-mohedano authored Dec 2, 2024
1 parent 626485f commit cdee1c8
Show file tree
Hide file tree
Showing 34 changed files with 2,829 additions and 0 deletions.
28 changes: 28 additions & 0 deletions dd-java-agent/instrumentation/junit-4.10/junit-4.13/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apply from: "$rootDir/gradle/java.gradle"

muzzle {
pass {
group = 'junit'
module = 'junit'
versions = '[4.13,5)'
}
}

addTestSuiteForDir('latestDepTest', 'test')

dependencies {
implementation project(':dd-java-agent:instrumentation:junit-4.10')
compileOnly group: 'junit', name: 'junit', version: '4.13'

testImplementation testFixtures(project(':dd-java-agent:agent-ci-visibility'))

// version used below is not the minimum one that we support,
// but the tests need to use it in order to be compliant with Spock 2.x
testImplementation(group: 'junit', name: 'junit') {
version {
strictly '4.13.2'
}
}

latestDepTestImplementation group: 'junit', name: 'junit', version: '4.+'
}
228 changes: 228 additions & 0 deletions dd-java-agent/instrumentation/junit-4.10/junit-4.13/gradle.lockfile

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package datadog.trace.instrumentation.junit4;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import net.bytebuddy.asm.Advice;
import org.junit.runner.manipulation.InvalidOrderingException;
import org.junit.runner.manipulation.Ordering;
import org.junit.runners.model.FrameworkMethod;

@AutoService(InstrumenterModule.class)
public class JUnit4BeforeAfterInstrumentation extends InstrumenterModule.CiVisibility
implements Instrumenter.ForKnownTypes {

public JUnit4BeforeAfterInstrumentation() {
super("ci-visibility", "junit-4", "setup-teardown");
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"org.junit.internal.runners.statements.RunBefores",
"org.junit.internal.runners.statements.RunAfters",
"org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters$RunBeforeParams",
"org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters$RunAfterParams",
};
}

@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".JUnit4BeforeAfterOperationsTracer",
};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("invokeMethod")
.and(takesArgument(0, named("org.junit.runners.model.FrameworkMethod"))),
JUnit4BeforeAfterInstrumentation.class.getName() + "$RunBeforesAftersAdvice");
}

public static class RunBeforesAftersAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope startCallSpan(@Advice.Argument(0) final FrameworkMethod method) {
return JUnit4BeforeAfterOperationsTracer.startTrace(method.getMethod());
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void finishCallSpan(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
JUnit4BeforeAfterOperationsTracer.endTrace(scope, throwable);
}

// JUnit 4.13 and above
public static void muzzleCheck(final Ordering ord) {
try {
ord.apply(null);
} catch (InvalidOrderingException e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package datadog.trace.instrumentation.junit4;

import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import java.lang.reflect.Method;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runners.Parameterized;

public class JUnit4BeforeAfterOperationsTracer {
public static AgentScope startTrace(final Method method) {
final AgentSpan span = AgentTracer.startSpan("junit", method.getName());
if (method.isAnnotationPresent(Before.class)) {
span.setTag(Tags.TEST_CALLBACK, "Before");
} else if (method.isAnnotationPresent(After.class)) {
span.setTag(Tags.TEST_CALLBACK, "After");
} else if (method.isAnnotationPresent(BeforeClass.class)) {
span.setTag(Tags.TEST_CALLBACK, "BeforeClass");
} else if (method.isAnnotationPresent(AfterClass.class)) {
span.setTag(Tags.TEST_CALLBACK, "AfterClass");
} else if (method.isAnnotationPresent(Parameterized.BeforeParam.class)) {
span.setTag(Tags.TEST_CALLBACK, "BeforeParam");
} else if (method.isAnnotationPresent(Parameterized.AfterParam.class)) {
span.setTag(Tags.TEST_CALLBACK, "AfterParam");
}
return AgentTracer.activateSpan(span);
}

public static void endTrace(final AgentScope scope, final Throwable throwable) {
final AgentSpan span = scope.span();
if (throwable != null) {
span.addThrowable(throwable);
}
scope.close();
span.finish();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import datadog.trace.api.DisableTestTrace
import datadog.trace.civisibility.CiVisibilityInstrumentationTest
import datadog.trace.instrumentation.junit4.TestEventsHandlerHolder
import junit.runner.Version
import org.example.TestFailedAfter
import org.example.TestFailedAfterClass
import org.example.TestFailedAfterParam
import org.example.TestFailedBefore
import org.example.TestFailedBeforeClass
import org.example.TestFailedBeforeParam
import org.example.TestSucceedBeforeAfter
import org.example.TestSucceedBeforeClassAfterClass
import org.example.TestSucceedBeforeParamAfterParam
import org.junit.runner.JUnitCore

@DisableTestTrace(reason = "avoid self-tracing")
class JUnit413Test extends CiVisibilityInstrumentationTest {

def runner = new JUnitCore()

def "test #testcaseName"() {
runTests(tests)

assertSpansData(testcaseName, expectedTracesCount)

where:
testcaseName | tests | expectedTracesCount
"test-succeed-before-after" | [TestSucceedBeforeAfter] | 3
"test-succeed-before-class-after-class" | [TestSucceedBeforeClassAfterClass] | 3
"test-succeed-before-param-after-param" | [TestSucceedBeforeParamAfterParam] | 2
"test-failed-before-class" | [TestFailedBeforeClass] | 1
"test-failed-after-class" | [TestFailedAfterClass] | 3
"test-failed-before" | [TestFailedBefore] | 3
"test-failed-after" | [TestFailedAfter] | 3
"test-failed-before-param" | [TestFailedBeforeParam] | 2
"test-failed-after-param" | [TestFailedAfterParam] | 2
}

private void runTests(Collection<Class<?>> tests) {
TestEventsHandlerHolder.start()
try {
Class[] array = tests.toArray(new Class[0])
runner.run(array)
} catch (Throwable ignored) {
// Ignored
}
TestEventsHandlerHolder.stop()
}

@Override
String instrumentedLibraryName() {
return "junit4"
}

@Override
String instrumentedLibraryVersion() {
return Version.id()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.example;

import static org.junit.Assert.assertTrue;

import org.junit.After;
import org.junit.Test;

public class TestFailedAfter {
@After
public void tearDown() {
throw new RuntimeException("testcase teardown failed");
}

@Test
public void test_succeed() {
assertTrue(true);
}

@Test
public void another_test_succeed() {
assertTrue(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.example;

import static org.junit.Assert.assertTrue;

import org.junit.AfterClass;
import org.junit.Test;

public class TestFailedAfterClass {
@AfterClass
public static void tearDown() {
throw new RuntimeException("suite teardown failed");
}

@Test
public void test_succeed() {
assertTrue(true);
}

@Test
public void another_test_succeed() {
assertTrue(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.example;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class TestFailedAfterParam {
private final int num1;
private final int num2;
private final int sum;

public TestFailedAfterParam(final int num1, final int num2, final int sum) {
this.num1 = num1;
this.num2 = num2;
this.sum = sum;
}

@Parameterized.BeforeParam
public static void setup() {}

@Parameterized.AfterParam
public static void tearDown() {
throw new RuntimeException("after param setup failed");
}

@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {{0, 0, 0}, {1, 1, 2}});
}

@Test
public void parameterized_test_succeed() {
assertEquals(num1 + num2, sum);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.example;

import static org.junit.Assert.assertTrue;

import org.junit.Before;
import org.junit.Test;

public class TestFailedBefore {
@Before
public void setup() {
throw new RuntimeException("testcase setup failed");
}

@Test
public void test_succeed() {
assertTrue(true);
}

@Test
public void another_test_succeed() {
assertTrue(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.example;

import static org.junit.Assert.assertTrue;

import org.junit.BeforeClass;
import org.junit.Test;

public class TestFailedBeforeClass {
@BeforeClass
public static void setup() {
throw new RuntimeException("suite setup failed");
}

@Test
public void test_succeed() {
assertTrue(true);
}

@Test
public void another_test_succeed() {
assertTrue(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.example;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class TestFailedBeforeParam {
private final int num1;
private final int num2;
private final int sum;

public TestFailedBeforeParam(final int num1, final int num2, final int sum) {
this.num1 = num1;
this.num2 = num2;
this.sum = sum;
}

@Parameterized.BeforeParam
public static void setup() {
throw new RuntimeException("before param setup failed");
}

@Parameterized.AfterParam
public static void tearDown() {}

@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {{0, 0, 0}, {1, 1, 2}});
}

@Test
public void parameterized_test_succeed() {
assertEquals(num1 + num2, sum);
}
}
Loading

0 comments on commit cdee1c8

Please sign in to comment.