Skip to content

Commit 64dca18

Browse files
Implement tests quarantining (#8320)
1 parent b24d153 commit 64dca18

File tree

54 files changed

+4271
-52
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+4271
-52
lines changed

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettings.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class ExecutionSettings {
3030
Collections.emptyMap(),
3131
Collections.emptyList(),
3232
null,
33+
null,
3334
LineDiff.EMPTY);
3435

3536
private final boolean itrEnabled;
@@ -41,6 +42,7 @@ public class ExecutionSettings {
4142
@Nullable private final String itrCorrelationId;
4243
@Nonnull private final Map<TestIdentifier, TestMetadata> skippableTests;
4344
@Nonnull private final Map<String, BitSet> skippableTestsCoverage;
45+
@Nonnull private final Collection<TestIdentifier> quarantinedTests;
4446
@Nullable private final Collection<TestIdentifier> flakyTests;
4547
@Nullable private final Collection<TestIdentifier> knownTests;
4648
@Nonnull private final Diff pullRequestDiff;
@@ -55,6 +57,7 @@ public ExecutionSettings(
5557
@Nullable String itrCorrelationId,
5658
@Nonnull Map<TestIdentifier, TestMetadata> skippableTests,
5759
@Nonnull Map<String, BitSet> skippableTestsCoverage,
60+
@Nonnull Collection<TestIdentifier> quarantinedTests,
5861
@Nullable Collection<TestIdentifier> flakyTests,
5962
@Nullable Collection<TestIdentifier> knownTests,
6063
@Nonnull Diff pullRequestDiff) {
@@ -67,6 +70,7 @@ public ExecutionSettings(
6770
this.itrCorrelationId = itrCorrelationId;
6871
this.skippableTests = skippableTests;
6972
this.skippableTestsCoverage = skippableTestsCoverage;
73+
this.quarantinedTests = quarantinedTests;
7074
this.flakyTests = flakyTests;
7175
this.knownTests = knownTests;
7276
this.pullRequestDiff = pullRequestDiff;
@@ -117,6 +121,11 @@ public Map<TestIdentifier, TestMetadata> getSkippableTests() {
117121
return skippableTests;
118122
}
119123

124+
@Nonnull
125+
public Collection<TestIdentifier> getQuarantinedTests() {
126+
return quarantinedTests;
127+
}
128+
120129
/**
121130
* @return the list of known tests for the given module (can be empty), or {@code null} if known
122131
* tests could not be obtained
@@ -156,6 +165,7 @@ public boolean equals(Object o) {
156165
&& Objects.equals(itrCorrelationId, that.itrCorrelationId)
157166
&& Objects.equals(skippableTests, that.skippableTests)
158167
&& Objects.equals(skippableTestsCoverage, that.skippableTestsCoverage)
168+
&& Objects.equals(quarantinedTests, that.quarantinedTests)
159169
&& Objects.equals(flakyTests, that.flakyTests)
160170
&& Objects.equals(knownTests, that.knownTests)
161171
&& Objects.equals(pullRequestDiff, that.pullRequestDiff);
@@ -171,6 +181,7 @@ public int hashCode() {
171181
itrCorrelationId,
172182
skippableTests,
173183
skippableTestsCoverage,
184+
quarantinedTests,
174185
flakyTests,
175186
knownTests,
176187
pullRequestDiff);
@@ -207,6 +218,7 @@ public static ByteBuffer serialize(ExecutionSettings settings) {
207218
TestMetadataSerializer::serialize);
208219

209220
s.write(settings.skippableTestsCoverage, Serializer::write, Serializer::write);
221+
s.write(settings.quarantinedTests, TestIdentifierSerializer::serialize);
210222
s.write(settings.flakyTests, TestIdentifierSerializer::serialize);
211223
s.write(settings.knownTests, TestIdentifierSerializer::serialize);
212224

@@ -234,6 +246,8 @@ public static ExecutionSettings deserialize(ByteBuffer buffer) {
234246

235247
Map<String, BitSet> skippableTestsCoverage =
236248
Serializer.readMap(buffer, Serializer::readString, Serializer::readBitSet);
249+
Collection<TestIdentifier> quarantinedTests =
250+
Serializer.readSet(buffer, TestIdentifierSerializer::deserialize);
237251
Collection<TestIdentifier> flakyTests =
238252
Serializer.readSet(buffer, TestIdentifierSerializer::deserialize);
239253
Collection<TestIdentifier> knownTests =
@@ -251,6 +265,7 @@ public static ExecutionSettings deserialize(ByteBuffer buffer) {
251265
itrCorrelationId,
252266
skippableTests,
253267
skippableTestsCoverage,
268+
quarantinedTests,
254269
flakyTests,
255270
knownTests,
256271
diff);

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettingsFactoryImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ private Map<String, ExecutionSettings> doCreate(
233233
.getIdentifiersByModule()
234234
.getOrDefault(moduleName, Collections.emptyMap()),
235235
skippableTests.getCoveredLinesByRelativeSourcePath(),
236+
Collections.emptyList(), // FIXME implement retrieving quarantined tests
236237
flakyTestsByModule != null
237238
? flakyTestsByModule.getOrDefault(moduleName, Collections.emptyList())
238239
: null,

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/execution/RetryUntilSuccessful.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,34 @@
99
public class RetryUntilSuccessful implements TestExecutionPolicy {
1010

1111
private final int maxExecutions;
12+
private final boolean suppressFailures;
1213
private int executions;
1314

1415
/** Total execution counter that is shared by all retry policies */
1516
private final AtomicInteger totalExecutions;
1617

17-
public RetryUntilSuccessful(int maxExecutions, AtomicInteger totalExecutions) {
18+
public RetryUntilSuccessful(
19+
int maxExecutions, boolean suppressFailures, AtomicInteger totalExecutions) {
1820
this.maxExecutions = maxExecutions;
21+
this.suppressFailures = suppressFailures;
1922
this.totalExecutions = totalExecutions;
2023
this.executions = 0;
2124
}
2225

2326
@Override
2427
public boolean applicable() {
25-
// the last execution is not altered by the policy
26-
// (no retries, no exceptions suppressing)
27-
return executions < maxExecutions - 1;
28+
return currentExecutionIsNotLast() || suppressFailures;
2829
}
2930

3031
@Override
3132
public boolean suppressFailures() {
32-
// if this isn't the last execution,
33-
// possible failures should be suppressed
34-
return applicable();
33+
// do not suppress failures for last execution
34+
// (unless flag to suppress all failures is set)
35+
return currentExecutionIsNotLast() || suppressFailures;
36+
}
37+
38+
private boolean currentExecutionIsNotLast() {
39+
return executions < maxExecutions - 1;
3540
}
3641

3742
@Override

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/execution/RunNTimes.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,30 @@
99
public class RunNTimes implements TestExecutionPolicy {
1010

1111
private final EarlyFlakeDetectionSettings earlyFlakeDetectionSettings;
12+
private final boolean suppressFailures;
1213
private int executions;
1314
private int maxExecutions;
1415

15-
public RunNTimes(EarlyFlakeDetectionSettings earlyFlakeDetectionSettings) {
16+
public RunNTimes(
17+
EarlyFlakeDetectionSettings earlyFlakeDetectionSettings, boolean suppressFailures) {
1618
this.earlyFlakeDetectionSettings = earlyFlakeDetectionSettings;
19+
this.suppressFailures = suppressFailures;
1720
this.executions = 0;
1821
this.maxExecutions = earlyFlakeDetectionSettings.getExecutions(0);
1922
}
2023

2124
@Override
2225
public boolean applicable() {
23-
// the last execution is not altered by the policy
24-
// (no retries, no exceptions suppressing)
26+
return currentExecutionIsNotLast() || suppressFailures();
27+
}
28+
29+
private boolean currentExecutionIsNotLast() {
2530
return executions < maxExecutions - 1;
2631
}
2732

2833
@Override
2934
public boolean suppressFailures() {
30-
return false;
35+
return suppressFailures;
3136
}
3237

3338
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package datadog.trace.civisibility.execution;
2+
3+
import datadog.trace.api.civisibility.execution.TestExecutionPolicy;
4+
import datadog.trace.api.civisibility.telemetry.tag.RetryReason;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
/**
8+
* Runs a test case once. If it fails - suppresses the failure so that the build status is not
9+
* affected.
10+
*/
11+
public class RunOnceIgnoreOutcome implements TestExecutionPolicy {
12+
13+
private boolean testExecuted;
14+
15+
@Override
16+
public boolean applicable() {
17+
return !testExecuted;
18+
}
19+
20+
@Override
21+
public boolean suppressFailures() {
22+
return true;
23+
}
24+
25+
@Override
26+
public boolean retry(boolean successful, long durationMillis) {
27+
testExecuted = true;
28+
return false;
29+
}
30+
31+
@Nullable
32+
@Override
33+
public RetryReason currentExecutionRetryReason() {
34+
return null;
35+
}
36+
}

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/test/ExecutionStrategy.java

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import datadog.trace.civisibility.execution.Regular;
1212
import datadog.trace.civisibility.execution.RetryUntilSuccessful;
1313
import datadog.trace.civisibility.execution.RunNTimes;
14+
import datadog.trace.civisibility.execution.RunOnceIgnoreOutcome;
1415
import datadog.trace.civisibility.source.LinesResolver;
1516
import datadog.trace.civisibility.source.SourcePathResolver;
1617
import java.lang.reflect.Method;
@@ -60,6 +61,11 @@ public boolean isFlaky(TestIdentifier test) {
6061
return flakyTests != null && flakyTests.contains(test.withoutParameters());
6162
}
6263

64+
public boolean isQuarantined(TestIdentifier test) {
65+
Collection<TestIdentifier> quarantinedTests = executionSettings.getQuarantinedTests();
66+
return quarantinedTests.contains(test.withoutParameters());
67+
}
68+
6369
@Nullable
6470
public SkipReason skipReason(TestIdentifier test) {
6571
if (test == null) {
@@ -85,29 +91,44 @@ public TestExecutionPolicy executionPolicy(TestIdentifier test, TestSourceData t
8591
return Regular.INSTANCE;
8692
}
8793

88-
EarlyFlakeDetectionSettings efdSettings = executionSettings.getEarlyFlakeDetectionSettings();
89-
if (efdSettings.isEnabled() && !isEFDLimitReached()) {
90-
if (isNew(test) || isModified(testSource)) {
91-
// check-then-act with "earlyFlakeDetectionsUsed" is not atomic here,
92-
// but we don't care if we go "a bit" over the limit, it does not have to be precise
93-
earlyFlakeDetectionsUsed.incrementAndGet();
94-
return new RunNTimes(efdSettings);
95-
}
94+
if (isEFDApplicable(test, testSource)) {
95+
// check-then-act with "earlyFlakeDetectionsUsed" is not atomic here,
96+
// but we don't care if we go "a bit" over the limit, it does not have to be precise
97+
earlyFlakeDetectionsUsed.incrementAndGet();
98+
return new RunNTimes(executionSettings.getEarlyFlakeDetectionSettings(), isQuarantined(test));
9699
}
97100

98-
if (executionSettings.isFlakyTestRetriesEnabled()) {
99-
Collection<TestIdentifier> flakyTests = executionSettings.getFlakyTests();
100-
if ((flakyTests == null || flakyTests.contains(test.withoutParameters()))
101-
&& autoRetriesUsed.get() < config.getCiVisibilityTotalFlakyRetryCount()) {
102-
// check-then-act with "autoRetriesUsed" is not atomic here,
103-
// but we don't care if we go "a bit" over the limit, it does not have to be precise
104-
return new RetryUntilSuccessful(config.getCiVisibilityFlakyRetryCount(), autoRetriesUsed);
105-
}
101+
if (isAutoRetryApplicable(test)) {
102+
// check-then-act with "autoRetriesUsed" is not atomic here,
103+
// but we don't care if we go "a bit" over the limit, it does not have to be precise
104+
return new RetryUntilSuccessful(
105+
config.getCiVisibilityFlakyRetryCount(), isQuarantined(test), autoRetriesUsed);
106+
}
107+
108+
if (isQuarantined(test)) {
109+
return new RunOnceIgnoreOutcome();
106110
}
107111

108112
return Regular.INSTANCE;
109113
}
110114

115+
private boolean isAutoRetryApplicable(TestIdentifier test) {
116+
if (!executionSettings.isFlakyTestRetriesEnabled()) {
117+
return false;
118+
}
119+
120+
Collection<TestIdentifier> flakyTests = executionSettings.getFlakyTests();
121+
return (flakyTests == null || flakyTests.contains(test.withoutParameters()))
122+
&& autoRetriesUsed.get() < config.getCiVisibilityTotalFlakyRetryCount();
123+
}
124+
125+
private boolean isEFDApplicable(TestIdentifier test, TestSourceData testSource) {
126+
EarlyFlakeDetectionSettings efdSettings = executionSettings.getEarlyFlakeDetectionSettings();
127+
return efdSettings.isEnabled()
128+
&& !isEFDLimitReached()
129+
&& (isNew(test) || isModified(testSource));
130+
}
131+
111132
public boolean isEFDLimitReached() {
112133
Collection<TestIdentifier> knownTests = executionSettings.getKnownTests();
113134
if (knownTests == null) {

dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ExecutionSettingsTest.groovy

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class ExecutionSettingsTest extends Specification {
2929
null,
3030
[:],
3131
[:],
32+
new HashSet<>([]),
3233
null,
3334
new HashSet<>([]),
3435
LineDiff.EMPTY),
@@ -41,8 +42,9 @@ class ExecutionSettingsTest extends Specification {
4142
true,
4243
new EarlyFlakeDetectionSettings(true, [], 10),
4344
"",
44-
[new TestIdentifier("bc", "def", "g"): new TestMetadata(true), new TestIdentifier("de", "f", null): new TestMetadata(false)],
45+
[(new TestIdentifier("bc", "def", "g")): new TestMetadata(true), (new TestIdentifier("de", "f", null)): new TestMetadata(false)],
4546
[:],
47+
new HashSet<>([new TestIdentifier("suite", "quarantined", null)]),
4648
new HashSet<>([new TestIdentifier("name", null, null)]),
4749
new HashSet<>([new TestIdentifier("b", "c", "g")]),
4850
new LineDiff(["path": lines()])
@@ -62,6 +64,7 @@ class ExecutionSettingsTest extends Specification {
6264
}), "cov2": BitSet.valueOf(new byte[]{
6365
4, 5, 6
6466
})],
67+
new HashSet<>([new TestIdentifier("suite", "quarantined", null), new TestIdentifier("another", "another-quarantined", null)]),
6568
new HashSet<>([new TestIdentifier("name", null, "g"), new TestIdentifier("b", "c", null)]),
6669
new HashSet<>([new TestIdentifier("b", "c", null), new TestIdentifier("bb", "cc", null)]),
6770
new LineDiff(["path": lines(1, 2, 3)]),
@@ -75,12 +78,13 @@ class ExecutionSettingsTest extends Specification {
7578
true,
7679
new EarlyFlakeDetectionSettings(true, [new EarlyFlakeDetectionSettings.ExecutionsByDuration(10, 20), new EarlyFlakeDetectionSettings.ExecutionsByDuration(30, 40)], 10),
7780
"itrCorrelationId",
78-
[new TestIdentifier("bc", "def", null): new TestMetadata(true), new TestIdentifier("de", "f", null): new TestMetadata(true)],
81+
[(new TestIdentifier("bc", "def", null)): new TestMetadata(true), (new TestIdentifier("de", "f", null)): new TestMetadata(true)],
7982
["cov" : BitSet.valueOf(new byte[]{
8083
1, 2, 3
8184
}), "cov2": BitSet.valueOf(new byte[]{
8285
4, 5, 6
8386
})],
87+
new HashSet<>([new TestIdentifier("suite", "quarantined", null), new TestIdentifier("another", "another-quarantined", null)]),
8488
new HashSet<>([]),
8589
new HashSet<>([new TestIdentifier("b", "c", null), new TestIdentifier("bb", "cc", "g")]),
8690
new LineDiff(["path": lines(1, 2, 3), "path-b": lines(1, 2, 128, 257, 999)]),

dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner {
7171
private static Path agentKeyFile
7272

7373
private static final List<TestIdentifier> skippableTests = new ArrayList<>()
74+
private static final List<TestIdentifier> quarantinedTests = new ArrayList<>()
7475
private static final List<TestIdentifier> flakyTests = new ArrayList<>()
7576
private static final List<TestIdentifier> knownTests = new ArrayList<>()
7677
private static volatile Diff diff = LineDiff.EMPTY
@@ -79,6 +80,7 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner {
7980
private static volatile boolean flakyRetryEnabled = false
8081
private static volatile boolean earlyFlakinessDetectionEnabled = false
8182
private static volatile boolean impactedTestsDetectionEnabled = false
83+
private static volatile boolean quarantineEnabled = false
8284

8385
public static final int SLOW_TEST_THRESHOLD_MILLIS = 1000
8486
public static final int VERY_SLOW_TEST_THRESHOLD_MILLIS = 2000
@@ -130,6 +132,7 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner {
130132
itrEnabled ? "itrCorrelationId" : null,
131133
skippableTestsWithMetadata,
132134
[:],
135+
quarantinedTests,
133136
flakyTests,
134137
earlyFlakinessDetectionEnabled || CIConstants.FAIL_FAST_TEST_ORDER.equalsIgnoreCase(Config.get().ciVisibilityTestOrder) ? knownTests : null,
135138
diff)
@@ -251,10 +254,12 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner {
251254
@Override
252255
void setup() {
253256
skippableTests.clear()
257+
quarantinedTests.clear()
254258
flakyTests.clear()
255259
knownTests.clear()
256260
diff = LineDiff.EMPTY
257261
itrEnabled = false
262+
quarantineEnabled = false
258263
flakyRetryEnabled = false
259264
earlyFlakinessDetectionEnabled = false
260265
impactedTestsDetectionEnabled = false
@@ -275,10 +280,18 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner {
275280
knownTests.addAll(tests)
276281
}
277282

283+
def givenQuarantinedTests(List<TestIdentifier> tests) {
284+
quarantinedTests.addAll(tests)
285+
}
286+
278287
def givenDiff(Diff diff) {
279288
this.diff = diff
280289
}
281290

291+
def givenQuarantineEnabled(boolean quarantineEnabled) {
292+
this.quarantineEnabled = quarantineEnabled
293+
}
294+
282295
def givenFlakyRetryEnabled(boolean flakyRetryEnabled) {
283296
this.flakyRetryEnabled = flakyRetryEnabled
284297
}

0 commit comments

Comments
 (0)