diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/execution/EarlyFlakeDetection.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/execution/EarlyFlakeDetection.java index 53dc2e603b3..ca04e0619b8 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/execution/EarlyFlakeDetection.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/execution/EarlyFlakeDetection.java @@ -71,6 +71,12 @@ public boolean suppressFailures() { return suppressFailures; } + @Override + public boolean propagateFailure() { + // used to bypass TestNG's RetryAnalyzer marking `fail` + `pass` test executions as passed + return !suppressFailures && results == ExecutionAggregation.MIXED; + } + private int getExecutions(long durationMillis) { for (ExecutionsByDuration e : executionsByDuration) { if (durationMillis <= e.getDurationMillis()) { diff --git a/dd-java-agent/instrumentation/testng/testng-7.0/src/main/java/datadog/trace/instrumentation/testng7/TestNGExecutionInstrumentation.java b/dd-java-agent/instrumentation/testng/testng-7.0/src/main/java/datadog/trace/instrumentation/testng7/TestNGExecutionInstrumentation.java index 093d801e234..ad251a978d1 100644 --- a/dd-java-agent/instrumentation/testng/testng-7.0/src/main/java/datadog/trace/instrumentation/testng7/TestNGExecutionInstrumentation.java +++ b/dd-java-agent/instrumentation/testng/testng-7.0/src/main/java/datadog/trace/instrumentation/testng7/TestNGExecutionInstrumentation.java @@ -110,20 +110,26 @@ public static class SuppressFailuresAdvice { @SuppressWarnings("bytebuddy-exception-suppression") @Advice.OnMethodEnter public static void suppressFailures(@Advice.Argument(0) final ITestResult result) { - if (result.getStatus() != ITestResult.FAILURE) { - // nothing to suppress - return; - } - IRetryAnalyzer retryAnalyzer = TestNGUtils.getRetryAnalyzer(result); if (!(retryAnalyzer instanceof RetryAnalyzer)) { // test execution policies not injected return; } RetryAnalyzer ddRetryAnalyzer = (RetryAnalyzer) retryAnalyzer; - if (ddRetryAnalyzer.getAndResetSuppressFailures()) { + + if (result.getStatus() == ITestResult.FAILURE + && ddRetryAnalyzer.getAndResetSuppressFailures()) { // "failed but within success percentage" result.setStatus(ITestResult.SUCCESS_PERCENTAGE_FAILURE); + } else if (result.isSuccess() && ddRetryAnalyzer.shouldPropagateFailure()) { + // Aligns session status with DD's reported result on EFD. Without this, + // the session status would depend on the result order: + // - pass + fail -> fail (correct) + // - fail + pass -> pass (incorrect, EFD should fail on flaky) + result.setStatus(ITestResult.FAILURE); + result.setThrowable( + new AssertionError( + "Datadog Early Flake Detection: test has flaky results (mixed pass/fail)")); } } } diff --git a/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-fail-pass/coverages.ftl b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-fail-pass/coverages.ftl new file mode 100644 index 00000000000..8878e547a79 --- /dev/null +++ b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-fail-pass/coverages.ftl @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-fail-pass/events.ftl b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-fail-pass/events.ftl new file mode 100644 index 00000000000..eed9810cc14 --- /dev/null +++ b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-fail-pass/events.ftl @@ -0,0 +1,207 @@ +[ { + "content" : { + "duration" : ${content_duration}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "library_version" : ${content_meta_library_version}, + "span.kind" : "test_suite_end", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "testng-7.0", + "test.source.file" : "dummy_source_path", + "test.status" : "fail", + "test.suite" : "org.example.TestFailAndThenSucceed", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count}, + "test.source.end" : 19, + "test.source.start" : 11 + }, + "name" : "testng.test_suite", + "resource" : "org.example.TestFailAndThenSucceed", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id} + }, + "type" : "test_suite_end", + "version" : 1 +}, { + "content" : { + "duration" : ${content_duration_2}, + "error" : 1, + "meta" : { + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "error.message" : ${content_meta_error_message}, + "error.stack" : ${content_meta_error_stack}, + "error.type" : "java.lang.AssertionError", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.is_new" : "true", + "test.module" : "testng-7.0", + "test.name" : "test_fail_and_then_succeed", + "test.source.file" : "dummy_source_path", + "test.source.method" : "test_fail_and_then_succeed()V", + "test.status" : "fail", + "test.suite" : "org.example.TestFailAndThenSucceed", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_2}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id}, + "test.source.end" : 18, + "test.source.start" : 12 + }, + "name" : "testng.test", + "parent_id" : ${content_parent_id}, + "resource" : "org.example.TestFailAndThenSucceed.test_fail_and_then_succeed", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "span_id" : ${content_span_id}, + "start" : ${content_start_2}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id}, + "trace_id" : ${content_trace_id} + }, + "type" : "test", + "version" : 2 +}, { + "content" : { + "duration" : ${content_duration_3}, + "error" : 0, + "meta" : { + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.final_status" : "fail", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.is_new" : "true", + "test.is_retry" : "true", + "test.module" : "testng-7.0", + "test.name" : "test_fail_and_then_succeed", + "test.retry_reason" : "early_flake_detection", + "test.source.file" : "dummy_source_path", + "test.source.method" : "test_fail_and_then_succeed()V", + "test.status" : "pass", + "test.suite" : "org.example.TestFailAndThenSucceed", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_3}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id}, + "test.source.end" : 18, + "test.source.start" : 12 + }, + "name" : "testng.test", + "parent_id" : ${content_parent_id}, + "resource" : "org.example.TestFailAndThenSucceed.test_fail_and_then_succeed", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "span_id" : ${content_span_id_2}, + "start" : ${content_start_3}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id}, + "trace_id" : ${content_trace_id_2} + }, + "type" : "test", + "version" : 2 +}, { + "content" : { + "duration" : ${content_duration_4}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid_2}, + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test_session_end", + "test.command" : "testng-7.0", + "test.early_flake.enabled" : "true", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.status" : "fail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_4}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id} + }, + "name" : "testng.test_session", + "resource" : "testng-7.0", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start_4}, + "test_session_id" : ${content_test_session_id} + }, + "type" : "test_session_end", + "version" : 1 +}, { + "content" : { + "duration" : ${content_duration_5}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid_3}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "library_version" : ${content_meta_library_version}, + "span.kind" : "test_module_end", + "test.early_flake.enabled" : "true", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "testng-7.0", + "test.status" : "fail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_5} + }, + "name" : "testng.test_module", + "resource" : "testng-7.0", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start_5}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id} + }, + "type" : "test_module_end", + "version" : 1 +} ] \ No newline at end of file diff --git a/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-pass-fail/coverages.ftl b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-pass-fail/coverages.ftl new file mode 100644 index 00000000000..8878e547a79 --- /dev/null +++ b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-pass-fail/coverages.ftl @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-pass-fail/events.ftl b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-pass-fail/events.ftl new file mode 100644 index 00000000000..8af78a81cde --- /dev/null +++ b/dd-java-agent/instrumentation/testng/testng-7.0/src/test/resources/test-efd-new-flaky-test-pass-fail/events.ftl @@ -0,0 +1,207 @@ +[ { + "content" : { + "duration" : ${content_duration}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "library_version" : ${content_meta_library_version}, + "span.kind" : "test_suite_end", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "testng-7.0", + "test.source.file" : "dummy_source_path", + "test.status" : "fail", + "test.suite" : "org.example.TestSucceedAndThenFail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count}, + "test.source.end" : 19, + "test.source.start" : 11 + }, + "name" : "testng.test_suite", + "resource" : "org.example.TestSucceedAndThenFail", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id} + }, + "type" : "test_suite_end", + "version" : 1 +}, { + "content" : { + "duration" : ${content_duration_2}, + "error" : 0, + "meta" : { + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.is_new" : "true", + "test.module" : "testng-7.0", + "test.name" : "test_succeed_and_then_fail", + "test.source.file" : "dummy_source_path", + "test.source.method" : "test_succeed_and_then_fail()V", + "test.status" : "pass", + "test.suite" : "org.example.TestSucceedAndThenFail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_2}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id}, + "test.source.end" : 18, + "test.source.start" : 12 + }, + "name" : "testng.test", + "parent_id" : ${content_parent_id}, + "resource" : "org.example.TestSucceedAndThenFail.test_succeed_and_then_fail", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "span_id" : ${content_span_id}, + "start" : ${content_start_2}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id}, + "trace_id" : ${content_trace_id} + }, + "type" : "test", + "version" : 2 +}, { + "content" : { + "duration" : ${content_duration_3}, + "error" : 1, + "meta" : { + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "error.message" : ${content_meta_error_message}, + "error.stack" : ${content_meta_error_stack}, + "error.type" : "java.lang.AssertionError", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.final_status" : "fail", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.is_new" : "true", + "test.is_retry" : "true", + "test.module" : "testng-7.0", + "test.name" : "test_succeed_and_then_fail", + "test.retry_reason" : "early_flake_detection", + "test.source.file" : "dummy_source_path", + "test.source.method" : "test_succeed_and_then_fail()V", + "test.status" : "fail", + "test.suite" : "org.example.TestSucceedAndThenFail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_3}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id}, + "test.source.end" : 18, + "test.source.start" : 12 + }, + "name" : "testng.test", + "parent_id" : ${content_parent_id}, + "resource" : "org.example.TestSucceedAndThenFail.test_succeed_and_then_fail", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "span_id" : ${content_span_id_2}, + "start" : ${content_start_3}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id}, + "trace_id" : ${content_trace_id_2} + }, + "type" : "test", + "version" : 2 +}, { + "content" : { + "duration" : ${content_duration_4}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid_2}, + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test_session_end", + "test.command" : "testng-7.0", + "test.early_flake.enabled" : "true", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.status" : "fail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_4}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id} + }, + "name" : "testng.test_session", + "resource" : "testng-7.0", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start_4}, + "test_session_id" : ${content_test_session_id} + }, + "type" : "test_session_end", + "version" : 1 +}, { + "content" : { + "duration" : ${content_duration_5}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid_3}, + "component" : "testng", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "library_version" : ${content_meta_library_version}, + "span.kind" : "test_module_end", + "test.early_flake.enabled" : "true", + "test.framework" : "testng", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "testng-7.0", + "test.status" : "fail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_5} + }, + "name" : "testng.test_module", + "resource" : "testng-7.0", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start_5}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id} + }, + "type" : "test_module_end", + "version" : 1 +} ] \ No newline at end of file diff --git a/dd-java-agent/instrumentation/testng/testng-common/src/main/java/datadog/trace/instrumentation/testng/execution/RetryAnalyzer.java b/dd-java-agent/instrumentation/testng/testng-common/src/main/java/datadog/trace/instrumentation/testng/execution/RetryAnalyzer.java index 0abdb781f84..c066a75f1fc 100644 --- a/dd-java-agent/instrumentation/testng/testng-common/src/main/java/datadog/trace/instrumentation/testng/execution/RetryAnalyzer.java +++ b/dd-java-agent/instrumentation/testng/testng-common/src/main/java/datadog/trace/instrumentation/testng/execution/RetryAnalyzer.java @@ -49,6 +49,10 @@ public void setSuppressFailures(ITestResult result) { suppressFailures = executionPolicy.suppressFailures(); } + public boolean shouldPropagateFailure() { + return executionPolicy != null && executionPolicy.propagateFailure(); + } + public boolean getAndResetSuppressFailures() { boolean suppressFailures = this.suppressFailures; this.suppressFailures = false; diff --git a/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/groovy/datadog/trace/instrumentation/testng/TestNGTest.groovy b/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/groovy/datadog/trace/instrumentation/testng/TestNGTest.groovy index c20783d5260..0c6f20e547d 100644 --- a/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/groovy/datadog/trace/instrumentation/testng/TestNGTest.groovy +++ b/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/groovy/datadog/trace/instrumentation/testng/TestNGTest.groovy @@ -134,6 +134,8 @@ abstract class TestNGTest extends CiVisibilityInstrumentationTest { "test-efd-new-very-slow-test" | true | [TestSucceedVerySlow] | [] // is executed only once "test-efd-faulty-session-threshold" | false | [TestFailedAndSucceed] | [] "test-efd-skip-new-test" | true | [TestSucceedSkipEfd] | [] + "test-efd-new-flaky-test-pass-fail" | false | [TestSucceedAndThenFail] | [] + "test-efd-new-flaky-test-fail-pass" | false | [TestFailAndThenSucceed] | [] } def "test impacted tests detection #testcaseName"() { diff --git a/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/java/org/example/TestFailAndThenSucceed.java b/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/java/org/example/TestFailAndThenSucceed.java new file mode 100644 index 00000000000..8645f127ad4 --- /dev/null +++ b/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/java/org/example/TestFailAndThenSucceed.java @@ -0,0 +1,15 @@ +package org.example; + +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +public class TestFailAndThenSucceed { + + private int executions; + + @Test + public void test_fail_and_then_succeed() { + assertTrue(++executions >= 2); + } +} diff --git a/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/java/org/example/TestSucceedAndThenFail.java b/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/java/org/example/TestSucceedAndThenFail.java new file mode 100644 index 00000000000..9e7fa41e414 --- /dev/null +++ b/dd-java-agent/instrumentation/testng/testng-common/src/testFixtures/java/org/example/TestSucceedAndThenFail.java @@ -0,0 +1,15 @@ +package org.example; + +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +public class TestSucceedAndThenFail { + + private int executions; + + @Test + public void test_succeed_and_then_fail() { + assertTrue(++executions < 2); + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/civisibility/execution/TestExecutionPolicy.java b/internal-api/src/main/java/datadog/trace/api/civisibility/execution/TestExecutionPolicy.java index c55b052ce93..0b8c27408e8 100644 --- a/internal-api/src/main/java/datadog/trace/api/civisibility/execution/TestExecutionPolicy.java +++ b/internal-api/src/main/java/datadog/trace/api/civisibility/execution/TestExecutionPolicy.java @@ -28,4 +28,15 @@ public interface TestExecutionPolicy extends TestExecutionTracker { * result */ boolean suppressFailures(); + + /** + * Must be called after the execution is registered by {@link + * TestExecutionTracker#registerExecution(TestStatus, long)}. + * + * @return {@code true} if a passing test result should be converted to failure because the + * aggregated results indicate the test should fail the build + */ + default boolean propagateFailure() { + return false; + } }