Skip to content

Commit 205255c

Browse files
fmeumcopybara-github
authored andcommitted
Add --experimental_cancel_concurrent_tests=on_failed
The existing `--experimental_cancel_concurrent_tests` flag can be used to let Bazel cancel concurrent runs of the same test as soon as one run passes with `--runs_per_test_detects_flakes`. This change turns the boolean-valued into an enum-valued flag that also accepts the value `on_failed` to instead cancel on the first failed run. This can be used to avoid reruns of flaky tests when the intention is to only detect flakes, not their probability of being flaky. Also fix the quirk that `--experimental_cancel_concurrent_tests` has no effect with default Bazel settings since the `exclusive` test strategy is registered last and thus always wins. Perhaps surprisingly, this is the only effect of that incorrect registration order - all other `exclusive` logic is explicitly gated behind the value of `--test_strategy`. RELNOTES[NEW]: The `--experimental_cancel_concurrent_tests` option now accepts the values `on_passed`, `on_failed` and `never` and cancels concurrent test runs on the first matching result. If enabled, it's now effective by default and no longer requires `--test_strategy=standalone` to be passed explicitly. Closes bazelbuild#26630. PiperOrigin-RevId: 789203343 Change-Id: I5e7838fa3562a1137987efbf2235d22b122e3e51
1 parent 40567c4 commit 205255c

File tree

9 files changed

+197
-108
lines changed

9 files changed

+197
-108
lines changed

src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.google.devtools.build.lib.analysis.actions.LazyWriteNestedSetOfTupleAction;
4040
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
4141
import com.google.devtools.build.lib.analysis.config.CoreOptions;
42+
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions.CancelConcurrentTests;
4243
import com.google.devtools.build.lib.analysis.test.TestProvider.TestParams;
4344
import com.google.devtools.build.lib.analysis.test.TestProvider.TestParams.CoverageParams;
4445
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -403,9 +404,10 @@ private TestParams createTestAction()
403404
Artifact undeclaredOutputsDir =
404405
ruleContext.getPackageRelativeTreeArtifact(dir.getRelative("test.outputs"), root);
405406

406-
boolean cancelConcurrentTests =
407+
CancelConcurrentTests cancelConcurrentTests =
407408
testConfiguration.runsPerTestDetectsFlakes()
408-
&& testConfiguration.cancelConcurrentTests();
409+
? testConfiguration.cancelConcurrentTests()
410+
: CancelConcurrentTests.NEVER;
409411

410412
boolean splitCoveragePostProcessing = testConfiguration.splitCoveragePostProcessing();
411413
TestRunnerAction testRunnerAction =

src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,17 @@
2828
import java.util.List;
2929
import javax.annotation.Nullable;
3030

31-
/**
32-
* A context for the execution of test actions ({@link TestRunnerAction}).
33-
*/
31+
/** A context for the execution of test actions ({@link TestRunnerAction}). */
3432
public interface TestActionContext extends ActionContext {
3533

3634
/**
3735
* A group of attempts for a single test shard, ran either sequentially or in parallel.
3836
*
39-
* <p>When one attempt succeeds, threads running the other attempts get an {@link
40-
* InterruptedException} and {@link #cancelled()} will in the future return true. When a thread
41-
* joins an attempt group that is already cancelled, {@link InterruptedException} will be thrown
42-
* on the call to {@link #register()}.
37+
* <p>When one attempt matches the result specified by {@link
38+
* com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions.CancelConcurrentTests},
39+
* threads running the other attempts get an {@link InterruptedException} and {@link #cancelled()}
40+
* will in the future return true. When a thread joins an attempt group that is already cancelled,
41+
* {@link InterruptedException} will be thrown on the call to {@link #register()}.
4342
*/
4443
interface AttemptGroup {
4544

@@ -53,7 +52,9 @@ interface AttemptGroup {
5352
/** Unregisters a thread from the attempt group. */
5453
void unregister();
5554

56-
/** Signal that the attempt run by this thread has succeeded and cancel all the others. */
55+
/**
56+
* Signal that the attempt run by this thread has the desired result and cancel all the others.
57+
*/
5758
void cancelOthers();
5859

5960
/** Whether the attempt group has been cancelled. */

src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
2929
import com.google.devtools.build.lib.analysis.config.RunUnder;
3030
import com.google.devtools.build.lib.analysis.test.CoverageConfiguration.CoverageOptions;
31+
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions.CancelConcurrentTests;
3132
import com.google.devtools.build.lib.analysis.test.TestShardingStrategy.ShardingStrategyConverter;
3233
import com.google.devtools.build.lib.cmdline.Label;
3334
import com.google.devtools.build.lib.packages.TestSize;
3435
import com.google.devtools.build.lib.packages.TestTimeout;
3536
import com.google.devtools.build.lib.util.Pair;
3637
import com.google.devtools.build.lib.util.RegexFilter;
38+
import com.google.devtools.common.options.BoolOrEnumConverter;
3739
import com.google.devtools.common.options.Converters;
3840
import com.google.devtools.common.options.Option;
3941
import com.google.devtools.common.options.OptionDefinition;
@@ -269,16 +271,32 @@ public static class TestOptions extends FragmentOptions {
269271
+ "run/attempt fails gets a FLAKY status.")
270272
public boolean runsPerTestDetectsFlakes;
271273

274+
/** When to cancel concurrently running tests. */
275+
public enum CancelConcurrentTests {
276+
NEVER,
277+
ON_FAILED,
278+
ON_PASSED;
279+
280+
/** Converts to {@link CancelConcurrentTests}. */
281+
static class Converter extends BoolOrEnumConverter<CancelConcurrentTests> {
282+
public Converter() {
283+
super(CancelConcurrentTests.class, "when to cancel concurrent tests", ON_PASSED, NEVER);
284+
}
285+
}
286+
}
287+
272288
@Option(
273289
name = "experimental_cancel_concurrent_tests",
274-
defaultValue = "false",
290+
defaultValue = "never",
291+
converter = CancelConcurrentTests.Converter.class,
275292
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
276293
effectTags = {OptionEffectTag.AFFECTS_OUTPUTS, OptionEffectTag.LOADING_AND_ANALYSIS},
277294
metadataTags = {OptionMetadataTag.EXPERIMENTAL},
278295
help =
279-
"If true, then Blaze will cancel concurrently running tests on the first successful "
280-
+ "run. This is only useful in combination with --runs_per_test_detects_flakes.")
281-
public boolean cancelConcurrentTests;
296+
"If 'on_failed' or 'on_passed, then Blaze will cancel concurrently running tests on "
297+
+ "the first run with that result. This is only useful in combination with "
298+
+ "--runs_per_test_detects_flakes.")
299+
public CancelConcurrentTests cancelConcurrentTests;
282300

283301
@Option(
284302
name = "coverage_support",
@@ -446,7 +464,7 @@ public boolean runsPerTestDetectsFlakes() {
446464
return options.runsPerTestDetectsFlakes;
447465
}
448466

449-
public boolean cancelConcurrentTests() {
467+
public CancelConcurrentTests cancelConcurrentTests() {
450468
return options.cancelConcurrentTests;
451469
}
452470

src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import com.google.devtools.build.lib.analysis.test.TestActionContext.TestAttemptResult;
5656
import com.google.devtools.build.lib.analysis.test.TestActionContext.TestAttemptResult.Result;
5757
import com.google.devtools.build.lib.analysis.test.TestActionContext.TestRunnerSpawn;
58+
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions.CancelConcurrentTests;
5859
import com.google.devtools.build.lib.buildeventstream.TestFileNameConstants;
5960
import com.google.devtools.build.lib.cmdline.Label;
6061
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -163,7 +164,7 @@ public class TestRunnerAction extends AbstractAction
163164
*/
164165
private final Collection<String> requiredClientEnvVariables;
165166

166-
private final boolean cancelConcurrentTestsOnSuccess;
167+
private final CancelConcurrentTests cancelConcurrentTests;
167168

168169
private final boolean splitCoveragePostProcessing;
169170
private final NestedSetBuilder<Artifact> lcovMergerFilesToRun;
@@ -210,7 +211,7 @@ private static ImmutableSet<Artifact> nonNullAsSet(Artifact... artifacts) {
210211
BuildConfigurationValue configuration,
211212
String workspaceName,
212213
@Nullable PathFragment shExecutable,
213-
boolean cancelConcurrentTestsOnSuccess,
214+
CancelConcurrentTests cancelConcurrentTests,
214215
boolean splitCoveragePostProcessing,
215216
NestedSetBuilder<Artifact> lcovMergerFilesToRun,
216217
@Nullable Artifact lcovMergerRunfilesTree,
@@ -268,7 +269,7 @@ private static ImmutableSet<Artifact> nonNullAsSet(Artifact... artifacts) {
268269
configuration.getActionEnvironment().getInheritedEnv(),
269270
configuration.getTestActionEnvironment().getInheritedEnv(),
270271
this.extraTestEnv.getInheritedEnv());
271-
this.cancelConcurrentTestsOnSuccess = cancelConcurrentTestsOnSuccess;
272+
this.cancelConcurrentTests = cancelConcurrentTests;
272273
this.splitCoveragePostProcessing = splitCoveragePostProcessing;
273274
this.lcovMergerFilesToRun = lcovMergerFilesToRun;
274275
this.lcovMergerRunfilesTree = lcovMergerRunfilesTree;
@@ -1011,15 +1012,22 @@ public ActionResult execute(
10111012
try {
10121013
testRunnerSpawn = testActionContext.createTestRunnerSpawn(this, actionExecutionContext);
10131014
attemptGroup =
1014-
cancelConcurrentTestsOnSuccess
1015+
cancelConcurrentTests != CancelConcurrentTests.NEVER
10151016
? testActionContext.getAttemptGroup(getOwner(), shardNum)
10161017
: AttemptGroup.NOOP;
1018+
var cancelOnResult =
1019+
switch (cancelConcurrentTests) {
1020+
case NEVER -> null;
1021+
case ON_FAILED -> Result.FAILED_CAN_RETRY;
1022+
case ON_PASSED -> Result.PASSED;
1023+
};
10171024
try {
10181025
attemptGroup.register();
10191026
var result =
10201027
executeAllAttempts(
10211028
testRunnerSpawn,
10221029
testActionContext.isTestKeepGoing(),
1030+
cancelOnResult,
10231031
attemptGroup,
10241032
spawnResults,
10251033
failedAttempts);
@@ -1198,6 +1206,7 @@ public Path getCoverageDataPath() {
11981206
public ActionResult executeAllAttempts(
11991207
TestRunnerSpawn testRunnerSpawn,
12001208
boolean keepGoing,
1209+
@Nullable Result cancelOnResult,
12011210
final AttemptGroup attemptGroup,
12021211
List<SpawnResult> spawnResults,
12031212
List<ProcessedAttemptResult> failedAttempts)
@@ -1212,9 +1221,10 @@ public ActionResult executeAllAttempts(
12121221

12131222
spawnResults.addAll(result.spawnResults());
12141223
TestAttemptResult.Result testResult = result.result();
1215-
if (testResult == TestAttemptResult.Result.PASSED) {
1224+
if (testResult == cancelOnResult) {
12161225
attemptGroup.cancelOthers();
1217-
} else {
1226+
}
1227+
if (testResult != TestAttemptResult.Result.PASSED) {
12181228
TestRunnerSpawnAndMaxAttempts nextRunnerAndAttempts =
12191229
computeNextRunnerAndMaxAttempts(
12201230
testResult,

src/main/java/com/google/devtools/build/lib/rules/test/ExclusiveTestStrategy.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@
3131
* <p>This strategy should be registered with a command line identifier of 'exclusive' which will
3232
* trigger behavior in SkyframeExecutor to schedule test execution sequentially after non-test
3333
* actions. This ensures streamed test output is not polluted by other action output.
34+
*
35+
* <p>Note: It's expected that this strategy is largely identical to the one it wraps. Most of the
36+
* behavior specific to the 'exclusive' strategy is enabled based on the value of the <code>
37+
* --test_strategy</code> flag, not instance methods of this class.
3438
*/
3539
public class ExclusiveTestStrategy implements TestActionContext {
36-
private TestActionContext parent;
40+
private final TestActionContext parent;
3741

3842
public ExclusiveTestStrategy(TestActionContext parent) {
3943
this.parent = parent;

src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ public void registerActionContexts(
6262
TestStrategy.getTmpRoot(env.getWorkspace(), env.getExecRoot(), executionOptions);
6363
TestActionContext testStrategy =
6464
new StandaloneTestStrategy(executionOptions, testSummaryOptions, testTmpRoot);
65-
registryBuilder.register(TestActionContext.class, testStrategy, "standalone");
65+
// Keep the standalone test strategy last so that it is the default one.
6666
registryBuilder.register(
6767
TestActionContext.class, new ExclusiveTestStrategy(testStrategy), "exclusive");
68+
registryBuilder.register(TestActionContext.class, testStrategy, "standalone");
6869
registryBuilder.register(FileWriteActionContext.class, new FileWriteStrategy(), "local");
6970
registryBuilder.register(
7071
TemplateExpansionContext.class, new LocalTemplateExpansionStrategy(), "local");

src/test/java/com/google/devtools/build/lib/exec/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ java_library(
3737
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_directories",
3838
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
3939
"//src/main/java/com/google/devtools/build/lib/analysis:server_directories",
40+
"//src/main/java/com/google/devtools/build/lib/analysis:test/test_configuration",
4041
"//src/main/java/com/google/devtools/build/lib/analysis/config:build_configuration",
4142
"//src/main/java/com/google/devtools/build/lib/analysis/config:build_options",
4243
"//src/main/java/com/google/devtools/build/lib/analysis/config:core_options",

0 commit comments

Comments
 (0)