Skip to content

Commit ab61baa

Browse files
Avoid final field modifications in Karate instrumentation (#11753)
feat: avoid final field modifications in karate instrumentation fix: revert changes feat: propagate failed results to original fix: spotless nit: comment Merge branch 'master' into daniel.mohedano/jep-500-karate Merge branch 'master' into daniel.mohedano/jep-500-karate Co-authored-by: daniel.mohedano <daniel.mohedano@datadoghq.com>
1 parent e1617c7 commit ab61baa

10 files changed

Lines changed: 575 additions & 18 deletions

File tree

‎buildSrc/src/main/kotlin/dd-trace-java.instrumentation.testing-framework-tests.gradle.kts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ logger.info("Avoid executing classes used to test testing frameworks instrumenta
66

77
tasks.withType<Test>().configureEach {
88
exclude("**/TestAssumption*", "**/TestSuiteSetUpAssumption*")
9+
exclude("**/TestContinueOnStepFailure*")
910
exclude("**/TestDisableTestTrace*")
1011
exclude("**/TestError*")
1112
exclude("**/TestFactory*")

‎dd-java-agent/instrumentation/karate-1.0/src/main/java/datadog/trace/instrumentation/karate/ExecutionContext.java‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ public void setSuppressFailures(boolean suppressFailures) {
1919
this.suppressFailures = suppressFailures;
2020
}
2121

22-
public boolean getAndResetSuppressFailures() {
23-
boolean suppressFailures = this.suppressFailures;
24-
this.suppressFailures = false;
22+
public boolean shouldSuppressFailures() {
2523
return suppressFailures;
2624
}
2725

‎dd-java-agent/instrumentation/karate-1.0/src/main/java/datadog/trace/instrumentation/karate/KarateExecutionInstrumentation.java‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ public static void afterExecute(@Advice.This ScenarioRuntime scenarioRuntime) {
103103
return;
104104
}
105105

106-
ScenarioResult finalResult = scenarioRuntime.result;
106+
ScenarioResult originalResult = scenarioRuntime.result;
107+
ScenarioResult finalResult = originalResult;
107108

108109
TestExecutionPolicy executionPolicy = context.getExecutionPolicy();
109110
while (executionPolicy.applicable()) {
@@ -115,7 +116,12 @@ public static void afterExecute(@Advice.This ScenarioRuntime scenarioRuntime) {
115116
finalResult = retry.result;
116117
}
117118

118-
KarateUtils.setResult(scenarioRuntime, finalResult);
119+
// When the scenario is retried, the original runtime's result must reflect the final
120+
// attempt's outcome. To avoid final field modifications, the final attempt's failure is
121+
// reflected onto the original result via addStepResult
122+
if (finalResult.isFailed() && !originalResult.isFailed()) {
123+
originalResult.addStepResult(finalResult.getFailedStep());
124+
}
119125

120126
CallDepthThreadLocalMap.reset(ScenarioRuntime.class);
121127
}
@@ -140,7 +146,10 @@ public static void onAddingStepResult(
140146
return;
141147
}
142148

143-
if (executionContext.getAndResetSuppressFailures()) {
149+
// Suppress every failing step of a to-be-retried attempt (not just the first): with
150+
// continueOnStepFailure a single attempt can add multiple failing steps, and any leak would
151+
// mark the original runtime's result failed
152+
if (executionContext.shouldSuppressFailures()) {
144153
stepResult = new StepResult(stepResult.getStep(), KarateUtils.abortedResult());
145154
stepResult.setFailedReason(result.getError());
146155
stepResult.setErrorIgnored(true);

‎dd-java-agent/instrumentation/karate-1.0/src/main/java/datadog/trace/instrumentation/karate/KarateUtils.java‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.intuit.karate.core.FeatureRuntime;
88
import com.intuit.karate.core.Result;
99
import com.intuit.karate.core.Scenario;
10-
import com.intuit.karate.core.ScenarioResult;
1110
import com.intuit.karate.core.ScenarioRuntime;
1211
import com.intuit.karate.core.Tag;
1312
import datadog.trace.api.civisibility.config.LibraryCapability;
@@ -46,8 +45,6 @@ private KarateUtils() {}
4645
// static method to create aborted result has a different signature starting with Karate 1.4.1
4746
private static final MethodHandle ABORTED_RESULT_STARTTIME_DURATION_NANOS =
4847
METHOD_HANDLES.method(Result.class, "aborted", long.class, long.class);
49-
private static final MethodHandle SCENARIO_RUNTIME_RESULT_SETTER =
50-
METHOD_HANDLES.privateFieldSetter(ScenarioRuntime.class, "result");
5148

5249
private static final ComparableVersion karateV12 = new ComparableVersion("1.2.0");
5350
private static final ComparableVersion karateV13 = new ComparableVersion("1.3.0");
@@ -153,10 +150,6 @@ public static void resetBeforeHook(FeatureRuntime featureRuntime) {
153150
METHOD_HANDLES.invoke(FEATURE_RUNTIME_BEFORE_HOOK_DONE_SETTER, featureRuntime, false);
154151
}
155152

156-
public static void setResult(ScenarioRuntime runtime, ScenarioResult result) {
157-
METHOD_HANDLES.invoke(SCENARIO_RUNTIME_RESULT_SETTER, runtime, result);
158-
}
159-
160153
public static String getKarateVersion() {
161154
return FileUtils.KARATE_VERSION;
162155
}

‎dd-java-agent/instrumentation/karate-1.0/src/test/groovy/KarateTest.groovy‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,14 @@ class KarateTest extends CiVisibilityInstrumentationTest {
7272
assertSpansData(testcaseName)
7373

7474
where:
75-
testcaseName | success | tests | retriedTests
76-
"test-failed" | false | [TestFailedKarate] | []
77-
"test-retry-failed" | false | [TestFailedKarate] | [new TestFQN("[org/example/test_failed] test failed", "second scenario")]
78-
"test-failed-then-succeed" | true | [TestFailedThenSucceedKarate] | [new TestFQN("[org/example/test_failed_then_succeed] test failed", "flaky scenario")]
79-
"test-retry-parameterized" | false | [TestFailedParameterizedKarate] | [
75+
testcaseName | success | tests | retriedTests
76+
"test-failed" | false | [TestFailedKarate] | []
77+
"test-retry-failed" | false | [TestFailedKarate] | [new TestFQN("[org/example/test_failed] test failed", "second scenario")]
78+
"test-failed-then-succeed" | true | [TestFailedThenSucceedKarate] | [new TestFQN("[org/example/test_failed_then_succeed] test failed", "flaky scenario")]
79+
"test-retry-continue-on-step-failure" | true | [TestContinueOnStepFailureKarate] | [
80+
new TestFQN("[org/example/test_continue_on_step_failure] test continue on step failure", "flaky scenario")
81+
]
82+
"test-retry-parameterized" | false | [TestFailedParameterizedKarate] | [
8083
new TestFQN("[org/example/test_failed_parameterized] test parameterized", "first scenario as an outline")
8184
]
8285
}

‎dd-java-agent/instrumentation/karate-1.0/src/test/java/org/example/Flaky.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,16 @@
55
public class Flaky {
66

77
private static int counter = 0;
8+
private static int stepCounter = 0;
89

10+
// Fails the first two attempts, passes from the third onwards.
911
public static void flake() {
1012
assertTrue(++counter >= 3);
1113
}
14+
15+
// Same flaky behavior exposed as a value, for continueOnStepFailure scenarios that assert it in
16+
// several steps. Uses a separate counter because both helpers run in the same test JVM.
17+
public static boolean shouldPass() {
18+
return ++stepCounter >= 3;
19+
}
1220
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.example;
2+
3+
import com.intuit.karate.junit5.Karate;
4+
5+
public class TestContinueOnStepFailureKarate {
6+
7+
@Karate.Test
8+
public Karate test() {
9+
return Karate.run("classpath:org/example/test_continue_on_step_failure.feature");
10+
}
11+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Feature: test continue on step failure
2+
3+
Scenario: flaky scenario
4+
* configure continueOnStepFailure = { enabled: true, continueAfter: true }
5+
* def pass = Java.type('org.example.Flaky').shouldPass()
6+
* match pass == true
7+
* match pass == true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[ ]

0 commit comments

Comments
 (0)