Skip to content

Commit 731b447

Browse files
Finish Karate tests after scenario post-processing (#12014)
fix: avoid retrying untracked karate scenarios fix: finish karate tests after scenario post-processing fix: merge test scenarios Merge branch 'daniel.mohedano/karate-ignore-untracked-scenarios' into daniel.mohedano/karate-finish-after-post-processing fix: honor expected karate failures Merge branch 'master' into daniel.mohedano/karate-finish-after-post-processing # Conflicts: # dd-java-agent/instrumentation/karate/karate-2.0/src/main/java21/datadog/trace/instrumentation/karate2/KarateScenarioAdvice.java Co-authored-by: daniel.mohedano <daniel.mohedano@datadoghq.com>
1 parent 507d52c commit 731b447

21 files changed

Lines changed: 1405 additions & 21 deletions

File tree

dd-java-agent/instrumentation/karate/karate-2.0/src/main/java/datadog/trace/instrumentation/karate2/KarateExecutionInstrumentation.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import com.google.auto.service.AutoService;
1212
import datadog.trace.agent.tooling.Instrumenter;
1313
import datadog.trace.agent.tooling.InstrumenterModule;
14-
import datadog.trace.api.Config;
1514
import java.util.Collections;
1615
import java.util.Map;
1716

@@ -37,11 +36,6 @@ public KarateExecutionInstrumentation() {
3736
super("ci-visibility", "karate", "test-retry");
3837
}
3938

40-
@Override
41-
public boolean isEnabled() {
42-
return super.isEnabled() && Config.get().isCiVisibilityExecutionPoliciesEnabled();
43-
}
44-
4539
@Override
4640
public String[] knownMatchingTypes() {
4741
return new String[] {"io.karatelabs.core.ScenarioRuntime", "io.karatelabs.core.ScenarioResult"};

dd-java-agent/instrumentation/karate/karate-2.0/src/main/java21/datadog/trace/instrumentation/karate2/KarateScenarioAdvice.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,20 @@ public static void afterExecute(
3939
return;
4040
}
4141

42+
Scenario scenario = scenarioRuntime.getScenario();
43+
ExecutionContext context =
44+
InstrumentationContext.get(Scenario.class, ExecutionContext.class).get(scenario);
45+
if (context == null) {
46+
return;
47+
}
48+
KarateTracingListener.afterScenario(scenarioRuntime, result, context);
49+
4250
if (CallDepthThreadLocalMap.incrementCallDepth(ScenarioRuntime.class) > 0) {
43-
// nested call (a retry invoked below, or a called scenario)
51+
// retry invoked below
4452
return;
4553
}
4654

4755
try {
48-
Scenario scenario = scenarioRuntime.getScenario();
49-
ExecutionContext context =
50-
InstrumentationContext.get(Scenario.class, ExecutionContext.class).get(scenario);
51-
if (context == null) {
52-
return;
53-
}
54-
5556
ScenarioResult finalResult = result;
5657
TestExecutionPolicy executionPolicy = context.getExecutionPolicy();
5758
while (executionPolicy.applicable()) {
@@ -79,7 +80,8 @@ public static void onAddingStepResult(
7980
@Advice.Argument(value = 0, readOnly = false) StepResult stepResult,
8081
@Advice.FieldValue("scenario") Scenario scenario) {
8182

82-
if (stepResult.isFailed()) {
83+
// Keep expected failures intact so Karate can apply the @fail result inversion.
84+
if (stepResult.isFailed() && !scenario.isFail()) {
8385
ExecutionContext executionContext =
8486
InstrumentationContext.get(Scenario.class, ExecutionContext.class).get(scenario);
8587
if (executionContext == null) {

dd-java-agent/instrumentation/karate/karate-2.0/src/main/java21/datadog/trace/instrumentation/karate2/KarateTracingListener.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ public boolean onEvent(RunEvent event) {
6161
return true;
6262
}
6363

64+
private void afterScenario(ScenarioRunEvent event) {
65+
ScenarioRuntime sr = event.source();
66+
Scenario scenario = sr.getScenario();
67+
// A context means call advice will finish the test after scenario post-processing.
68+
if (scenarioContext.get(scenario) == null) {
69+
afterScenario(sr, event.result(), null);
70+
}
71+
}
72+
6473
private boolean beforeFeature(FeatureRunEvent event) {
6574
FeatureRuntime fr = event.source();
6675
if (skipTracking(fr)) {
@@ -149,20 +158,22 @@ private boolean beforeScenario(ScenarioRunEvent event) {
149158
return true;
150159
}
151160

152-
private void afterScenario(ScenarioRunEvent event) {
153-
ScenarioRuntime sr = event.source();
161+
public static void afterScenario(
162+
ScenarioRuntime sr, ScenarioResult result, ExecutionContext context) {
154163
if (skipTracking(sr)) {
155164
return;
156165
}
157166
Scenario scenario = sr.getScenario();
158-
ScenarioResult result = event.result();
159167
TestDescriptor testDescriptor = KarateUtils.toTestDescriptor(sr);
160168

161-
ExecutionContext context = scenarioContext.get(scenario);
162169
Throwable suppressedError = context != null ? context.getAndClearSuppressedError() : null;
163-
Throwable failedReason = getFailedReason(result, suppressedError);
170+
// @fail retains the expected step error even when the final result passes, so only inspect
171+
// failed results for an error
172+
boolean resultFailed = result != null && result.isFailed();
173+
Throwable failedReason =
174+
resultFailed ? getFailedReason(result, suppressedError) : suppressedError;
164175

165-
if ((result != null && result.isFailed()) || failedReason != null) {
176+
if (resultFailed || failedReason != null) {
166177
TestEventsHandlerHolder.TEST_EVENTS_HANDLER.onTestFailure(testDescriptor, failedReason);
167178
} else if (result == null || result.getStepResults().isEmpty()) {
168179
TestEventsHandlerHolder.TEST_EVENTS_HANDLER.onTestSkip(testDescriptor, null);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import datadog.trace.api.DisableTestTrace
2+
import datadog.trace.civisibility.CiVisibilityInstrumentationTest
3+
import datadog.trace.instrumentation.karate2.KarateUtils
4+
import datadog.trace.instrumentation.karate2.TestEventsHandlerHolder
5+
import org.example.TestSucceedKarate
6+
7+
@DisableTestTrace(reason = "avoid self-tracing")
8+
class KarateV2RetryDisabledForkedTest extends CiVisibilityInstrumentationTest {
9+
10+
@Override
11+
void configurePreAgent() {
12+
super.configurePreAgent()
13+
injectSysConfig("trace.test-retry.enabled", "false")
14+
}
15+
16+
def "finishes scenarios without execution policies when retry instrumentation is disabled"() {
17+
setup:
18+
givenEarlyFlakinessDetectionEnabled(true)
19+
givenKnownTests([])
20+
TestEventsHandlerHolder.start()
21+
22+
when:
23+
new TestSucceedKarate().test()
24+
assertSpansData("test-retry-disabled")
25+
26+
then:
27+
noExceptionThrown()
28+
29+
cleanup:
30+
TestEventsHandlerHolder.stop()
31+
}
32+
33+
@Override
34+
String instrumentedLibraryName() {
35+
return "karate"
36+
}
37+
38+
@Override
39+
String instrumentedLibraryVersion() {
40+
return KarateUtils.getKarateVersion()
41+
}
42+
}

dd-java-agent/instrumentation/karate/karate-2.0/src/test/groovy/KarateV2Test.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class KarateV2Test extends CiVisibilityInstrumentationTest {
3333
"test-with-setup" | true | [TestWithSetupKarate]
3434
"test-parameterized" | true | [TestParameterizedKarate]
3535
"test-failed" | false | [TestFailedKarate]
36+
"test-fail-expected" | true | [TestFailedExpectedKarate]
37+
"test-fail-unexpected" | false | [TestFailedUnexpectedKarate]
3638
"test-skipped-feature" | true | [TestSkippedFeatureKarate]
3739
}
3840

@@ -67,6 +69,10 @@ class KarateV2Test extends CiVisibilityInstrumentationTest {
6769
"test-failed" | false | [TestFailedKarate] | []
6870
"test-retry-failed" | false | [TestFailedKarate] | [new TestFQN("[org/example/test_failed] test failed", "second scenario")]
6971
"test-failed-then-succeed" | true | [TestFailedThenSucceedKarate] | [new TestFQN("[org/example/test_failed_then_succeed] test failed", "flaky scenario")]
72+
"test-retry-after-scenario-failed" | false | [TestFailedAfterScenarioKarate] | [
73+
new TestFQN("[org/example/test_after_scenario_failed] test after scenario failed", "after scenario failed")
74+
]
75+
"test-retry-fail-expected" | true | [TestFailedExpectedKarate] | [new TestFQN("[org/example/test_fail_expected] test fail expected", "expected failure")]
7076
"test-retry-parameterized" | false | [TestFailedParameterizedKarate] | [
7177
new TestFQN("[org/example/test_failed_parameterized] test parameterized", "first scenario as an outline")
7278
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.example;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import com.intuit.karate.Results;
6+
import com.intuit.karate.Runner;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class TestFailedAfterScenarioKarate {
10+
11+
@Test
12+
public void test() {
13+
Results results =
14+
Runner.path("classpath:org/example/test_after_scenario_failed.feature").parallel(1);
15+
assertEquals(0, results.getFailCount(), results.getErrorMessages());
16+
}
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.example;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import com.intuit.karate.Results;
6+
import com.intuit.karate.Runner;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class TestFailedExpectedKarate {
10+
11+
@Test
12+
public void test() {
13+
Results results = Runner.path("classpath:org/example/test_fail_expected.feature").parallel(1);
14+
assertEquals(0, results.getFailCount(), results.getErrorMessages());
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.example;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import com.intuit.karate.Results;
6+
import com.intuit.karate.Runner;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class TestFailedUnexpectedKarate {
10+
11+
@Test
12+
public void test() {
13+
Results results = Runner.path("classpath:org/example/test_fail_unexpected.feature").parallel(1);
14+
assertEquals(0, results.getFailCount(), results.getErrorMessages());
15+
}
16+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Feature: test after scenario failed
2+
3+
Scenario: after scenario failed
4+
* configure afterScenario = function() { karate.fail('after scenario failed') }
5+
* def value = true
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Feature: test fail expected
2+
3+
@fail
4+
Scenario: expected failure
5+
* match 1 == 2

0 commit comments

Comments
 (0)