blob: e2f5c048f5a84ec1466548d2041ee1c2d16687d6 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.analysis.test;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.ActionContext;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.actions.SpawnResult.Status;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.view.test.TestStatus.TestResultData;
import java.io.IOException;
import java.util.List;
import javax.annotation.Nullable;
/**
* A context for the execution of test actions ({@link TestRunnerAction}).
*/
public interface TestActionContext extends ActionContext {
/**
* A group of attempts for a single test shard, ran either sequentially or in parallel.
*
* <p>When one attempt succeeds, threads running the other attempts get an {@link
* InterruptedException} and {@link #cancelled()} will in the future return true. When a thread
* joins an attempt group that is already cancelled, {@link InterruptedException} will be thrown
* on the call to {@link #register()}.
*/
interface AttemptGroup {
/**
* Registers a thread to the attempt group.
*
* <p>If the attempt group is already cancelled, throw {@link InterruptedException}.
*/
void register() throws InterruptedException;
/** Unregisters a thread from the attempt group. */
void unregister();
/** Signal that the attempt run by this thread has succeeded and cancel all the others. */
void cancelOthers();
/** Whether the attempt group has been cancelled. */
boolean cancelled();
/** A dummy attempt group used when no flaky test attempt cancellation is done. */
AttemptGroup NOOP =
new AttemptGroup() {
@Override
public void register() {}
@Override
public void unregister() {}
@Override
public void cancelOthers() {}
@Override
public boolean cancelled() {
return false;
}
};
}
TestRunnerSpawn createTestRunnerSpawn(
TestRunnerAction testRunnerAction, ActionExecutionContext actionExecutionContext)
throws ExecException, InterruptedException;
/** Returns whether test_keep_going is enabled. */
boolean isTestKeepGoing();
/**
* Returns {@code true} to indicate that exclusive tests should be treated as regular parallel
* tests.
*
* <p>Returning {@code true} may make sense for certain forced remote test execution strategies
* where running tests in sequence would be wasteful.
*/
default boolean forceExclusiveTestsInParallel() {
return false;
}
/**
* Returns {@code true} to indicate that "exclusive-if-local" tests should be treated as regular
* parallel tests.
*
* <p>Returning {@code true} may make sense for certain remote test execution strategies where
* running tests in sequence would be wasteful.
*/
default boolean forceExclusiveIfLocalTestsInParallel() {
return false;
}
/** Creates a cached test result. */
TestResult newCachedTestResult(Path execRoot, TestRunnerAction action, TestResultData cached)
throws IOException;
/** Returns the attempt group associaed with the given shard. */
AttemptGroup getAttemptGroup(ActionOwner owner, int shardNum);
/** An individual test attempt result. */
interface TestAttemptResult {
/** Test attempt result classification, splitting failures into permanent vs retriable. */
enum Result {
/** Test attempt successful. */
PASSED,
/** Test failed, potentially due to test flakiness, can be retried. */
FAILED_CAN_RETRY,
/** Permanent failure. */
FAILED;
boolean canRetry() {
return this == FAILED_CAN_RETRY;
}
}
/** Returns the overall test result. */
Result result();
/** Returns a list of spawn results for this test attempt. */
ImmutableList<SpawnResult> spawnResults();
/**
* Returns a description of the system failure associated with the primary spawn result, if any.
*/
@Nullable
default DetailedExitCode primarySystemFailure() {
if (spawnResults().isEmpty()) {
return null;
}
SpawnResult primarySpawnResult = spawnResults().get(0);
if (primarySpawnResult.status() == Status.SUCCESS) {
return null;
}
if (primarySpawnResult.status().isConsideredUserError()) {
return null;
}
return DetailedExitCode.of(primarySpawnResult.failureDetail());
}
}
/**
* An object representing a failed non-final attempt. This is only used for tests that are run
* multiple times. At this time, Bazel retries tests until the first passed attempt, or until the
* number of retries is exhausted - whichever comes first. This interface represents the result
* from a previous attempt, but never the final attempt, even if unsuccessful.
*/
interface FailedAttemptResult {}
/** A delegate to run a test. This may include running multiple spawns, renaming outputs, etc. */
interface TestRunnerSpawn {
ActionExecutionContext getActionExecutionContext();
/** Run the test attempt. Blocks until the attempt is complete. */
TestAttemptResult execute() throws InterruptedException, IOException, ExecException;
/**
* After the first attempt has run, this method is called to determine the maximum number of
* attempts for this test.
*/
int getMaxAttempts(TestAttemptResult firstTestAttemptResult);
/** Rename the output files if the test attempt failed, and post the test attempt result. */
FailedAttemptResult finalizeFailedTestAttempt(TestAttemptResult testAttemptResult, int attempt)
throws IOException;
/** Post the final test result based on the last attempt and the list of failed attempts. */
void finalizeTest(
TestAttemptResult lastTestAttemptResult, List<FailedAttemptResult> failedAttempts)
throws IOException;
/** Post the final test result based on the last attempt and the list of failed attempts. */
void finalizeCancelledTest(List<FailedAttemptResult> failedAttempts) throws IOException;
/**
* Return a {@link TestRunnerSpawn} object if test fallback is enabled, or {@code null}
* otherwise. Test fallback is a feature to allow a test to run with one strategy until the max
* attempts are exhausted and then run with another strategy for another set of attempts. This
* is rarely used, and should ideally be removed.
*/
@Nullable
default TestRunnerSpawn getFallbackRunner() throws ExecException, InterruptedException {
return null;
}
/**
* Return a {@link TestRunnerSpawn} object that is used on flaky retries. Flaky retry runner
* allows a test to run with a different strategy on flaky retries (for example, enabling test
* fail-fast mode to save up resources).
*/
default TestRunnerSpawn getFlakyRetryRunner() throws ExecException, InterruptedException {
return this;
}
}
}