Skip to content

Commit 8bc4640

Browse files
Implement ITR tests skipping for TestNG (#5453)
1 parent d46868a commit 8bc4640

File tree

6 files changed

+192
-5
lines changed

6 files changed

+192
-5
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package datadog.trace.instrumentation.testng;
2+
3+
import datadog.trace.api.Config;
4+
import datadog.trace.api.civisibility.config.SkippableTest;
5+
import java.lang.reflect.Method;
6+
import java.util.Set;
7+
8+
public class ItrFilter {
9+
10+
public static final ItrFilter INSTANCE = new ItrFilter();
11+
12+
private volatile boolean testsSkipped;
13+
14+
private ItrFilter() {}
15+
16+
public boolean skip(Method method, Object instance, Object[] parameters) {
17+
SkippableTest test = toSkippableTest(method, instance, parameters);
18+
Set<SkippableTest> skippableTests = Config.get().getCiVisibilitySkippableTests();
19+
if (skippableTests.contains(test)) {
20+
testsSkipped = true;
21+
return true;
22+
} else {
23+
return false;
24+
}
25+
}
26+
27+
private SkippableTest toSkippableTest(Method method, Object instance, Object[] parameters) {
28+
String testSuiteName = instance.getClass().getName();
29+
String testName = method.getName();
30+
String testParameters = TestNGUtils.getParameters(parameters);
31+
return new SkippableTest(testSuiteName, testName, testParameters, null);
32+
}
33+
34+
public boolean testsSkipped() {
35+
return testsSkipped;
36+
}
37+
}

dd-java-agent/instrumentation/testng/src/main/java/datadog/trace/instrumentation/testng/TestNGInstrumentation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public String[] helperClassNames() {
3535
packageName + ".TestNGUtils",
3636
packageName + ".TestNGSuiteListener",
3737
packageName + ".TestNGClassListener",
38+
packageName + ".ItrFilter",
3839
packageName + ".TracingListener"
3940
};
4041
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package datadog.trace.instrumentation.testng;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
5+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
6+
7+
import com.google.auto.service.AutoService;
8+
import datadog.trace.agent.tooling.Instrumenter;
9+
import datadog.trace.api.Config;
10+
import java.lang.reflect.Method;
11+
import java.util.Set;
12+
import net.bytebuddy.asm.Advice;
13+
import org.testng.SkipException;
14+
import org.testng.annotations.DataProvider;
15+
16+
@AutoService(Instrumenter.class)
17+
public class TestNGItrInstrumentation extends Instrumenter.CiVisibility
18+
implements Instrumenter.ForKnownTypes {
19+
public TestNGItrInstrumentation() {
20+
super("testng", "testng-itr");
21+
}
22+
23+
@Override
24+
public boolean isApplicable(Set<TargetSystem> enabledSystems) {
25+
return super.isApplicable(enabledSystems) && Config.get().isCiVisibilityItrEnabled();
26+
}
27+
28+
@Override
29+
public String[] knownMatchingTypes() {
30+
return new String[] {
31+
"org.testng.internal.MethodInvocationHelper",
32+
"org.testng.internal.invokers.MethodInvocationHelper"
33+
};
34+
}
35+
36+
@Override
37+
public void adviceTransformations(AdviceTransformation transformation) {
38+
transformation.applyAdvice(
39+
named("invokeMethod")
40+
.and(takesArguments(3))
41+
.and(takesArgument(0, Method.class))
42+
.and(takesArgument(1, Object.class))
43+
.and(takesArgument(2, Object[].class)),
44+
TestNGItrInstrumentation.class.getName() + "$InvokeMethodAdvice");
45+
}
46+
47+
@Override
48+
public String[] helperClassNames() {
49+
return new String[] {
50+
packageName + ".TestNGUtils",
51+
packageName + ".TestNGClassListener",
52+
packageName + ".ItrFilter",
53+
};
54+
}
55+
56+
public static class InvokeMethodAdvice {
57+
@Advice.OnMethodEnter
58+
public static void invokeMethod(
59+
@Advice.Argument(0) final Method method,
60+
@Advice.Argument(1) final Object instance,
61+
@Advice.Argument(2) final Object[] parameters) {
62+
if (ItrFilter.INSTANCE.skip(method, instance, parameters)) {
63+
throw new SkipException("Skipped by Datadog Intelligent Test Runner");
64+
}
65+
}
66+
67+
// TestNG 6.4 and above
68+
public static void muzzleCheck(final DataProvider dataProvider) {
69+
dataProvider.name();
70+
}
71+
}
72+
}

dd-java-agent/instrumentation/testng/src/main/java/datadog/trace/instrumentation/testng/TestNGUtils.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,24 @@ public static Method getTestMethod(final ITestResult result) {
4040
}
4141

4242
public static String getParameters(final ITestResult result) {
43-
if (result.getParameters() == null || result.getParameters().length == 0) {
43+
return getParameters(result.getParameters());
44+
}
45+
46+
public static String getParameters(Object[] parameters) {
47+
if (parameters == null || parameters.length == 0) {
4448
return null;
4549
}
4650

4751
// We build manually the JSON for test.parameters tag.
4852
// Example: {"arguments":{"0":"param1","1":"param2"}}
4953
final StringBuilder sb = new StringBuilder("{\"arguments\":{");
50-
for (int i = 0; i < result.getParameters().length; i++) {
54+
for (int i = 0; i < parameters.length; i++) {
5155
sb.append("\"")
5256
.append(i)
5357
.append("\":\"")
54-
.append(Strings.escapeToJson(String.valueOf(result.getParameters()[i])))
58+
.append(Strings.escapeToJson(String.valueOf(parameters[i])))
5559
.append("\"");
56-
if (i != result.getParameters().length - 1) {
60+
if (i != parameters.length - 1) {
5761
sb.append(",");
5862
}
5963
}

dd-java-agent/instrumentation/testng/src/main/java/datadog/trace/instrumentation/testng/TracingListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void onExecutionStart() {
4141

4242
@Override
4343
public void onExecutionFinish() {
44-
testEventsHandler.onTestModuleFinish(false);
44+
testEventsHandler.onTestModuleFinish(ItrFilter.INSTANCE.testsSkipped());
4545
}
4646

4747
@Override

dd-java-agent/instrumentation/testng/src/testFixtures/groovy/datadog/trace/instrumentation/testng/TestNGTest.groovy

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package datadog.trace.instrumentation.testng
22

33
import datadog.trace.agent.test.asserts.ListWriterAssert
4+
import datadog.trace.api.civisibility.config.SkippableTest
5+
import datadog.trace.api.civisibility.config.SkippableTestsSerializer
6+
import datadog.trace.api.config.CiVisibilityConfig
47
import datadog.trace.civisibility.CiVisibilityTest
58
import datadog.trace.api.civisibility.CIConstants
69
import datadog.trace.bootstrap.instrumentation.api.Tags
@@ -659,6 +662,76 @@ abstract class TestNGTest extends CiVisibilityTest {
659662
})
660663
}
661664

665+
def "test ITR skipping"() {
666+
setup:
667+
injectSysConfig(CiVisibilityConfig.CIVISIBILITY_SKIPPABLE_TESTS, SkippableTestsSerializer.serialize([
668+
new SkippableTest("org.example.TestFailedAndSucceed", "test_another_succeed", null, null),
669+
new SkippableTest("org.example.TestFailedAndSucceed", "test_failed", null, null),
670+
]))
671+
672+
def testNG = new TestNG()
673+
testNG.setTestClasses(TestFailedAndSucceed)
674+
testNG.setOutputDirectory(testOutputDir)
675+
testNG.run()
676+
677+
expect:
678+
ListWriterAssert.assertTraces(TEST_WRITER, 4, false, SORT_TRACES_BY_DESC_SIZE_THEN_BY_NAMES, {
679+
long testModuleId
680+
long testSuiteId
681+
trace(2, true) {
682+
testModuleId = testModuleSpan(it, 0, CIConstants.TEST_PASS)
683+
testSuiteId = testSuiteSpan(it, 1, testModuleId, "org.example.TestFailedAndSucceed", CIConstants.TEST_PASS)
684+
}
685+
trace(1) {
686+
testSpan(it, 0, testModuleId, testSuiteId, "org.example.TestFailedAndSucceed", "test_another_succeed", CIConstants.TEST_SKIP, testTags)
687+
}
688+
trace(1) {
689+
testSpan(it, 0, testModuleId, testSuiteId, "org.example.TestFailedAndSucceed", "test_failed", CIConstants.TEST_SKIP, testTags)
690+
}
691+
trace(1) {
692+
testSpan(it, 0, testModuleId, testSuiteId, "org.example.TestFailedAndSucceed", "test_succeed", CIConstants.TEST_PASS)
693+
}
694+
})
695+
696+
where:
697+
testTags = [(Tags.TEST_SKIP_REASON): "Skipped by Datadog Intelligent Test Runner"]
698+
}
699+
700+
def "test ITR skipping for parameterized tests"() {
701+
setup:
702+
injectSysConfig(CiVisibilityConfig.CIVISIBILITY_SKIPPABLE_TESTS, SkippableTestsSerializer.serialize([
703+
new SkippableTest("org.example.TestParameterized", "parameterized_test_succeed", testTags_0[Tags.TEST_PARAMETERS], null),
704+
]))
705+
706+
def testNG = new TestNG()
707+
testNG.setTestClasses(TestParameterized)
708+
testNG.setOutputDirectory(testOutputDir)
709+
testNG.run()
710+
711+
expect:
712+
ListWriterAssert.assertTraces(TEST_WRITER, 3, false, SORT_TRACES_BY_DESC_SIZE_THEN_BY_NAMES, {
713+
long testModuleId
714+
long testSuiteId
715+
trace(2, true) {
716+
testModuleId = testModuleSpan(it, 0, CIConstants.TEST_PASS)
717+
testSuiteId = testSuiteSpan(it, 1, testModuleId, "org.example.TestParameterized", CIConstants.TEST_PASS)
718+
}
719+
trace(1) {
720+
testSpan(it, 0, testModuleId, testSuiteId, "org.example.TestParameterized", "parameterized_test_succeed", CIConstants.TEST_SKIP, testTags_0)
721+
}
722+
trace(1) {
723+
testSpan(it, 0, testModuleId, testSuiteId, "org.example.TestParameterized", "parameterized_test_succeed", CIConstants.TEST_PASS, testTags_1)
724+
}
725+
})
726+
727+
where:
728+
testTags_0 = [
729+
(Tags.TEST_PARAMETERS): '{"arguments":{"0":"hello","1":"true"}}',
730+
(Tags.TEST_SKIP_REASON): "Skipped by Datadog Intelligent Test Runner"
731+
]
732+
testTags_1 = [(Tags.TEST_PARAMETERS): '{"arguments":{"0":"\\\"goodbye\\\"","1":"false"}}']
733+
}
734+
662735
@Override
663736
String expectedOperationPrefix() {
664737
return "testng"

0 commit comments

Comments
 (0)