Move execute implementation up to TestRunnerAction
Instead, add methods to TestActionContext to construct a TestRunnerSpawn
and inform about test_keep_going.
As of this point, the high-level test execution process is now shared
between Bazel and Blaze. This is in preparation for implementing async
test execution.
PiperOrigin-RevId: 236107062
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java
index f36b4e0..928d170 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionContext.java
@@ -26,16 +26,13 @@
* A context for the execution of test actions ({@link TestRunnerAction}).
*/
public interface TestActionContext extends ActionContext {
-
- /**
- * Executes the test command, directing standard out / err to {@code outErr}. The status of the
- * test should be communicated by posting a {@link TestResult} object to the eventbus.
- *
- * @return a list of SpawnResults created during execution of the test command, if any
- */
- List<SpawnResult> exec(TestRunnerAction action, ActionExecutionContext actionExecutionContext)
+ TestRunnerSpawn createTestRunnerSpawn(
+ TestRunnerAction testRunnerAction, ActionExecutionContext actionExecutionContext)
throws ExecException, InterruptedException;
+ /** Returns whether test_keep_going is enabled. */
+ boolean isTestKeepGoing();
+
/**
* Creates a cached test result.
*/
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
index 718133e..88e7861 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
@@ -31,15 +31,22 @@
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactPathResolver;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
+import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionInfoSpecifier;
import com.google.devtools.build.lib.actions.NotifyOnActionCacheHit;
+import com.google.devtools.build.lib.actions.SpawnResult;
+import com.google.devtools.build.lib.actions.TestExecException;
import com.google.devtools.build.lib.analysis.RunfilesSupplierImpl;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.RunUnder;
+import com.google.devtools.build.lib.analysis.test.TestActionContext.FailedAttemptResult;
+import com.google.devtools.build.lib.analysis.test.TestActionContext.TestAttemptResult;
+import com.google.devtools.build.lib.analysis.test.TestActionContext.TestRunnerSpawn;
import com.google.devtools.build.lib.buildeventstream.TestFileNameConstants;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.ImmutableIterable;
+import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.util.LoggingUtil;
import com.google.devtools.build.lib.util.Pair;
@@ -742,8 +749,19 @@
public ActionResult execute(ActionExecutionContext actionExecutionContext)
throws ActionExecutionException, InterruptedException {
TestActionContext context = actionExecutionContext.getContext(TestActionContext.class);
+ return execute(actionExecutionContext, context);
+ }
+
+ @VisibleForTesting
+ public ActionResult execute(
+ ActionExecutionContext actionExecutionContext, TestActionContext testActionContext)
+ throws ActionExecutionException, InterruptedException {
try {
- return ActionResult.create(context.exec(this, actionExecutionContext));
+ TestRunnerSpawn testRunnerSpawn =
+ testActionContext.createTestRunnerSpawn(this, actionExecutionContext);
+ return ActionResult.create(
+ runAttempts(
+ testRunnerSpawn, actionExecutionContext, testActionContext.isTestKeepGoing()));
} catch (ExecException e) {
throw e.toActionExecutionException(this);
} finally {
@@ -751,6 +769,67 @@
}
}
+ private List<SpawnResult> runAttempts(
+ TestRunnerSpawn testRunnerSpawn,
+ ActionExecutionContext actionExecutionContext,
+ boolean keepGoing)
+ throws ExecException, InterruptedException {
+ List<SpawnResult> spawnResults = new ArrayList<>();
+ runAttempts(
+ testRunnerSpawn, actionExecutionContext, keepGoing, spawnResults, new ArrayList<>());
+ return spawnResults;
+ }
+
+ private void runAttempts(
+ TestRunnerSpawn testRunnerSpawn,
+ ActionExecutionContext actionExecutionContext,
+ boolean keepGoing,
+ List<SpawnResult> spawnResults,
+ List<FailedAttemptResult> failedAttempts)
+ throws ExecException, InterruptedException {
+ try {
+ TestAttemptResult testProcessResult = testRunnerSpawn.execute();
+ spawnResults.addAll(testProcessResult.spawnResults());
+ int maxAttempts = testRunnerSpawn.getMaxAttempts(testProcessResult);
+ // Failed test retry loop.
+ for (int attempt = 1; attempt < maxAttempts && !testProcessResult.hasPassed(); attempt++) {
+ failedAttempts.add(
+ testRunnerSpawn.finalizeFailedTestAttempt(
+ testProcessResult, failedAttempts.size() + 1));
+
+ testProcessResult = testRunnerSpawn.execute();
+ spawnResults.addAll(testProcessResult.spawnResults());
+ }
+
+ TestRunnerSpawn fallbackRunner;
+ if (!testProcessResult.hasPassed()
+ && (fallbackRunner = testRunnerSpawn.getFallbackRunner()) != null) {
+ // All attempts failed and fallback is enabled.
+ failedAttempts.add(
+ testRunnerSpawn.finalizeFailedTestAttempt(
+ testProcessResult, failedAttempts.size() + 1));
+ runAttempts(
+ fallbackRunner, actionExecutionContext, keepGoing, spawnResults, failedAttempts);
+ } else {
+ testRunnerSpawn.finalizeTest(testProcessResult, failedAttempts);
+
+ if (!keepGoing && !testProcessResult.hasPassed()) {
+ throw new TestExecException("Test failed: aborting");
+ }
+ }
+ } catch (IOException e) {
+ // Print the stack trace, otherwise the unexpected I/O error is hard to diagnose.
+ // A stack trace could help with bugs like https://github.com/bazelbuild/bazel/issues/4924
+ StringBuilder sb = new StringBuilder();
+ sb.append("Caught I/O exception: ").append(e.getMessage());
+ for (Object s : e.getStackTrace()) {
+ sb.append("\n\t").append(s);
+ }
+ actionExecutionContext.getEventHandler().handle(Event.error(sb.toString()));
+ throw new EnvironmentalExecException("unexpected I/O exception", e);
+ }
+ }
+
@Override
public String getMnemonic() {
return MNEMONIC;
diff --git a/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java b/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java
index 6885355..63c384d 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java
@@ -91,14 +91,7 @@
}
@Override
- public List<SpawnResult> exec(
- TestRunnerAction action, ActionExecutionContext actionExecutionContext)
- throws ExecException, InterruptedException {
- TestRunnerSpawn testRunnerSpawn = createTestRunnerSpawn(action, actionExecutionContext);
- return runAttempts(testRunnerSpawn, actionExecutionContext);
- }
-
- protected TestRunnerSpawn createTestRunnerSpawn(
+ public TestRunnerSpawn createTestRunnerSpawn(
TestRunnerAction action, ActionExecutionContext actionExecutionContext)
throws ExecException, InterruptedException {
Path execRoot = actionExecutionContext.getExecRoot();
diff --git a/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java b/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java
index efb31ea..c936640 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java
@@ -23,9 +23,7 @@
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
-import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecException;
-import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.actions.TestExecException;
import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -52,7 +50,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -128,64 +125,8 @@
}
@Override
- public abstract List<SpawnResult> exec(
- TestRunnerAction action, ActionExecutionContext actionExecutionContext)
- throws ExecException, InterruptedException;
-
- protected List<SpawnResult> runAttempts(
- TestRunnerSpawn testRunnerSpawn, ActionExecutionContext actionExecutionContext)
- throws ExecException, InterruptedException {
- List<SpawnResult> spawnResults = new ArrayList<>();
- runAttempts(testRunnerSpawn, actionExecutionContext, spawnResults, new ArrayList<>());
- return spawnResults;
- }
-
- private void runAttempts(
- TestRunnerSpawn testRunnerSpawn,
- ActionExecutionContext actionExecutionContext,
- List<SpawnResult> spawnResults,
- List<FailedAttemptResult> failedAttempts)
- throws ExecException, InterruptedException {
- try {
- TestAttemptResult testProcessResult = testRunnerSpawn.execute();
- spawnResults.addAll(testProcessResult.spawnResults());
- int maxAttempts = testRunnerSpawn.getMaxAttempts(testProcessResult);
- // Failed test retry loop.
- for (int attempt = 1; attempt < maxAttempts && !testProcessResult.hasPassed(); attempt++) {
- failedAttempts.add(
- testRunnerSpawn.finalizeFailedTestAttempt(
- testProcessResult, failedAttempts.size() + 1));
-
- testProcessResult = testRunnerSpawn.execute();
- spawnResults.addAll(testProcessResult.spawnResults());
- }
-
- TestRunnerSpawn fallbackRunner;
- if (!testProcessResult.hasPassed()
- && (fallbackRunner = testRunnerSpawn.getFallbackRunner()) != null) {
- // All attempts failed and fallback is enabled.
- failedAttempts.add(
- testRunnerSpawn.finalizeFailedTestAttempt(
- testProcessResult, failedAttempts.size() + 1));
- runAttempts(fallbackRunner, actionExecutionContext, spawnResults, failedAttempts);
- } else {
- testRunnerSpawn.finalizeTest(testProcessResult, failedAttempts);
-
- if (!executionOptions.testKeepGoing && !testProcessResult.hasPassed()) {
- throw new TestExecException("Test failed: aborting");
- }
- }
- } catch (IOException e) {
- // Print the stack trace, otherwise the unexpected I/O error is hard to diagnose.
- // A stack trace could help with bugs like https://github.com/bazelbuild/bazel/issues/4924
- StringBuilder sb = new StringBuilder();
- sb.append("Caught I/O exception: ").append(e.getMessage());
- for (Object s : e.getStackTrace()) {
- sb.append("\n\t").append(s);
- }
- actionExecutionContext.getEventHandler().handle(Event.error(sb.toString()));
- throw new EnvironmentalExecException("unexpected I/O exception", e);
- }
+ public final boolean isTestKeepGoing() {
+ return executionOptions.testKeepGoing;
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/ExclusiveTestStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/test/ExclusiveTestStrategy.java
index 66e2576..99963e3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/test/ExclusiveTestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/test/ExclusiveTestStrategy.java
@@ -16,14 +16,12 @@
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
-import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.analysis.test.TestActionContext;
import com.google.devtools.build.lib.analysis.test.TestResult;
import com.google.devtools.build.lib.analysis.test.TestRunnerAction;
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;
/**
* Test strategy wrapper called 'exclusive'. It should delegate to a test strategy for local
@@ -41,10 +39,15 @@
}
@Override
- public List<SpawnResult> exec(
- TestRunnerAction action, ActionExecutionContext actionExecutionContext)
+ public TestRunnerSpawn createTestRunnerSpawn(
+ TestRunnerAction testRunnerAction, ActionExecutionContext actionExecutionContext)
throws ExecException, InterruptedException {
- return parent.exec(action, actionExecutionContext);
+ return parent.createTestRunnerSpawn(testRunnerAction, actionExecutionContext);
+ }
+
+ @Override
+ public boolean isTestKeepGoing() {
+ return parent.isTestKeepGoing();
}
@Override