Remove implementations of TestAttemptContinuation.
Now TestStrategy.beginExecution() will always return a "done" continuation.
RELNOTES: None.
PiperOrigin-RevId: 491937994
Change-Id: I54f8256339b150d2912e84375578b3a5b9a0621f
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 0ebeaa3..ba16e4e 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
@@ -22,7 +22,6 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputHelper;
@@ -33,7 +32,6 @@
import com.google.devtools.build.lib.actions.ExecutionRequirements;
import com.google.devtools.build.lib.actions.SimpleSpawn;
import com.google.devtools.build.lib.actions.Spawn;
-import com.google.devtools.build.lib.actions.SpawnContinuation;
import com.google.devtools.build.lib.actions.SpawnMetrics;
import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.actions.TestExecException;
@@ -70,7 +68,6 @@
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
-import javax.annotation.Nullable;
/** Runs TestRunnerAction actions. */
// TODO(bazel-team): add tests for this strategy.
@@ -303,7 +300,7 @@
Spawn spawn,
ActionExecutionContext actionExecutionContext,
Path execRoot)
- throws IOException, InterruptedException {
+ throws ExecException, IOException, InterruptedException {
ResolvedPaths resolvedPaths = testAction.resolve(execRoot);
Path out = actionExecutionContext.getInputPath(testAction.getTestLog());
Path err = resolvedPaths.getTestStderr();
@@ -317,32 +314,16 @@
long startTimeMillis = actionExecutionContext.getClock().currentTimeMillis();
SpawnStrategyResolver resolver = actionExecutionContext.getContext(SpawnStrategyResolver.class);
- SpawnContinuation spawnContinuation;
- try {
- ImmutableList<SpawnResult> spawnResults =
- resolver.exec(spawn, actionExecutionContext.withFileOutErr(testOutErr));
- spawnContinuation = SpawnContinuation.immediate(spawnResults);
- } catch (InterruptedException e) {
- if (streamed != null) {
- streamed.close();
- }
- testOutErr.close();
- throw e;
- } catch (ExecException e) {
- spawnContinuation = SpawnContinuation.failedWithExecException(e);
- }
- return new BazelTestAttemptContinuation(
+ return runTestAttempt(
testAction,
actionExecutionContext,
spawn,
+ resolver,
resolvedPaths,
testOutErr,
streamed,
- startTimeMillis,
- spawnContinuation,
- /* testResultDataBuilder= */ null,
- /* spawnResults= */ null);
+ startTimeMillis);
}
private static void appendCoverageLog(FileOutErr coverageOutErr, FileOutErr outErr)
@@ -598,7 +579,8 @@
}
@Override
- public TestAttemptContinuation beginExecution() throws InterruptedException, IOException {
+ public TestAttemptContinuation beginExecution()
+ throws InterruptedException, IOException, ExecException {
prepareFileSystem(testAction, execRoot, tmpDir, workingDirectory);
return beginTestAttempt(testAction, spawn, actionExecutionContext, execRoot);
}
@@ -650,267 +632,214 @@
.build());
}
- private final class BazelTestAttemptContinuation extends TestAttemptContinuation {
- private final TestRunnerAction testAction;
- private final ActionExecutionContext actionExecutionContext;
- private final Spawn spawn;
- private final ResolvedPaths resolvedPaths;
- private final FileOutErr fileOutErr;
- private final Closeable streamed;
- private final long startTimeMillis;
- private final SpawnContinuation spawnContinuation;
- private TestResultData.Builder testResultDataBuilder;
- private ImmutableList<SpawnResult> spawnResults;
+ private TestAttemptContinuation runTestAttempt(
+ TestRunnerAction testAction,
+ ActionExecutionContext actionExecutionContext,
+ Spawn spawn,
+ SpawnStrategyResolver resolver,
+ ResolvedPaths resolvedPaths,
+ FileOutErr fileOutErr,
+ Closeable streamed,
+ long startTimeMillis)
+ throws InterruptedException, ExecException, IOException {
- BazelTestAttemptContinuation(
- TestRunnerAction testAction,
- ActionExecutionContext actionExecutionContext,
- Spawn spawn,
- ResolvedPaths resolvedPaths,
- FileOutErr fileOutErr,
- Closeable streamed,
- long startTimeMillis,
- SpawnContinuation spawnContinuation,
- TestResultData.Builder testResultDataBuilder,
- ImmutableList<SpawnResult> spawnResults) {
- this.testAction = testAction;
- this.actionExecutionContext = actionExecutionContext;
- this.spawn = spawn;
- this.resolvedPaths = resolvedPaths;
- this.fileOutErr = fileOutErr;
- this.streamed = streamed;
- this.startTimeMillis = startTimeMillis;
- this.spawnContinuation = spawnContinuation;
- this.testResultDataBuilder = testResultDataBuilder;
- this.spawnResults = spawnResults;
+ ImmutableList<SpawnResult> spawnResults;
+
+ // We have two protos to represent test attempts:
+ // 1. com.google.devtools.build.lib.view.test.TestStatus.TestResultData represents both
+ // failed attempts and finished tests. Bazel stores this to disk to persist cached test
+ // result information across server restarts.
+ // 2. com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestResult
+ // represents only individual attempts (failed or not). Bazel reports this as an event to
+ // the Build Event Protocol, but never saves it to disk.
+ //
+ // The TestResult proto is always constructed from a TestResultData instance, either one
+ // that is created right here, or one that is read back from disk.
+ TestResultData.Builder testResultDataBuilder;
+ try {
+ spawnResults = resolver.exec(spawn, actionExecutionContext.withFileOutErr(fileOutErr));
+ testResultDataBuilder = TestResultData.newBuilder();
+ testResultDataBuilder.setCachable(true).setTestPassed(true).setStatus(BlazeTestStatus.PASSED);
+ } catch (SpawnExecException e) {
+ if (e.isCatastrophic()) {
+ closeSuppressed(e, streamed);
+ closeSuppressed(e, fileOutErr);
+ throw e;
+ }
+ if (!e.getSpawnResult().setupSuccess()) {
+ closeSuppressed(e, streamed);
+ closeSuppressed(e, fileOutErr);
+ // Rethrow as the test could not be run and thus there's no point in retrying.
+ throw e;
+ }
+ spawnResults = ImmutableList.of(e.getSpawnResult());
+ testResultDataBuilder = TestResultData.newBuilder();
+ testResultDataBuilder
+ .setCachable(e.getSpawnResult().status().isConsideredUserError())
+ .setTestPassed(false)
+ .setStatus(e.hasTimedOut() ? BlazeTestStatus.TIMEOUT : BlazeTestStatus.FAILED);
+ } catch (InterruptedException e) {
+ closeSuppressed(e, streamed);
+ closeSuppressed(e, fileOutErr);
+ throw e;
}
+ long endTimeMillis = actionExecutionContext.getClock().currentTimeMillis();
- @Nullable
- @Override
- public ListenableFuture<?> getFuture() {
- return spawnContinuation.getFuture();
- }
+ // SpawnActionContext guarantees the first entry to correspond to the spawn passed in (there
+ // may be additional entries due to tree artifact handling).
+ SpawnResult primaryResult = spawnResults.get(0);
- @Override
- public TestAttemptContinuation execute()
- throws InterruptedException, ExecException, IOException {
+ // The SpawnResult of a remotely cached or remotely executed action may not have walltime
+ // set. We fall back to the time measured here for backwards compatibility.
+ long durationMillis = endTimeMillis - startTimeMillis;
+ durationMillis =
+ primaryResult.getWallTime().orElse(Duration.ofMillis(durationMillis)).toMillis();
- if (testResultDataBuilder == null) {
- // We have two protos to represent test attempts:
- // 1. com.google.devtools.build.lib.view.test.TestStatus.TestResultData represents both
- // failed attempts and finished tests. Bazel stores this to disk to persist cached test
- // result information across server restarts.
- // 2. com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestResult
- // represents only individual attempts (failed or not). Bazel reports this as an event to
- // the Build Event Protocol, but never saves it to disk.
- //
- // The TestResult proto is always constructed from a TestResultData instance, either one
- // that is created right here, or one that is read back from disk.
- TestResultData.Builder builder = null;
- ImmutableList<SpawnResult> spawnResults;
- try {
- SpawnContinuation nextContinuation = spawnContinuation.execute();
- if (!nextContinuation.isDone()) {
- return new BazelTestAttemptContinuation(
- testAction,
- actionExecutionContext,
- spawn,
- resolvedPaths,
- fileOutErr,
- streamed,
- startTimeMillis,
- nextContinuation,
- builder,
- /* spawnResults= */ null);
- }
- spawnResults = nextContinuation.get();
- builder = TestResultData.newBuilder();
- builder.setCachable(true).setTestPassed(true).setStatus(BlazeTestStatus.PASSED);
- } catch (SpawnExecException e) {
- if (e.isCatastrophic()) {
- closeSuppressed(e, streamed);
- closeSuppressed(e, fileOutErr);
- throw e;
- }
- if (!e.getSpawnResult().setupSuccess()) {
- closeSuppressed(e, streamed);
- closeSuppressed(e, fileOutErr);
- // Rethrow as the test could not be run and thus there's no point in retrying.
- throw e;
- }
- spawnResults = ImmutableList.of(e.getSpawnResult());
- builder = TestResultData.newBuilder();
- builder
- .setCachable(e.getSpawnResult().status().isConsideredUserError())
- .setTestPassed(false)
- .setStatus(e.hasTimedOut() ? BlazeTestStatus.TIMEOUT : BlazeTestStatus.FAILED);
- } catch (InterruptedException e) {
+ testResultDataBuilder
+ .setStartTimeMillisEpoch(startTimeMillis)
+ .addTestTimes(durationMillis)
+ .addTestProcessTimes(durationMillis)
+ .setRunDurationMillis(durationMillis)
+ .setHasCoverage(testAction.isCoverageMode());
+
+ if (testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing()) {
+ if (testAction.getCoverageDirectoryTreeArtifact() == null) {
+ // Otherwise we'll get a NPE https://github.com/bazelbuild/bazel/issues/13185
+ TestExecException e =
+ createTestExecException(
+ TestAction.Code.LOCAL_TEST_PREREQ_UNMET,
+ "coverageDirectoryTreeArtifact is null:"
+ + " --experimental_split_coverage_postprocessing depends on"
+ + " --experimental_fetch_all_coverage_outputs being enabled");
+ closeSuppressed(e, streamed);
+ closeSuppressed(e, fileOutErr);
+ throw e;
+ }
+ actionExecutionContext
+ .getMetadataHandler()
+ .getMetadata(testAction.getCoverageDirectoryTreeArtifact());
+ ImmutableSet<? extends ActionInput> expandedCoverageDir =
+ actionExecutionContext
+ .getMetadataHandler()
+ .getTreeArtifactChildren(
+ (SpecialArtifact) testAction.getCoverageDirectoryTreeArtifact());
+ Spawn coveragePostProcessingSpawn =
+ createCoveragePostProcessingSpawn(
+ actionExecutionContext,
+ testAction,
+ ImmutableList.copyOf(expandedCoverageDir),
+ tmpDirRoot,
+ executionOptions.splitXmlGeneration);
+ SpawnStrategyResolver spawnStrategyResolver =
+ actionExecutionContext.getContext(SpawnStrategyResolver.class);
+
+ Path testRoot =
+ actionExecutionContext.getInputPath(testAction.getTestLog()).getParentDirectory();
+
+ Path out = testRoot.getChild("coverage.log");
+ Path err = testRoot.getChild("coverage.err");
+ FileOutErr coverageOutErr = new FileOutErr(out, err);
+ ActionExecutionContext actionExecutionContextWithCoverageFileOutErr =
+ actionExecutionContext.withFileOutErr(coverageOutErr);
+
+ writeOutFile(coverageOutErr.getErrorPath(), coverageOutErr.getOutputPath());
+ appendCoverageLog(coverageOutErr, fileOutErr);
+ try {
+ spawnStrategyResolver.exec(
+ coveragePostProcessingSpawn, actionExecutionContextWithCoverageFileOutErr);
+ } catch (SpawnExecException e) {
+ if (e.isCatastrophic()) {
closeSuppressed(e, streamed);
closeSuppressed(e, fileOutErr);
throw e;
}
- long endTimeMillis = actionExecutionContext.getClock().currentTimeMillis();
-
- // SpawnActionContext guarantees the first entry to correspond to the spawn passed in (there
- // may be additional entries due to tree artifact handling).
- SpawnResult primaryResult = spawnResults.get(0);
-
- // The SpawnResult of a remotely cached or remotely executed action may not have walltime
- // set. We fall back to the time measured here for backwards compatibility.
- long durationMillis = endTimeMillis - startTimeMillis;
- durationMillis =
- primaryResult.getWallTime().orElse(Duration.ofMillis(durationMillis)).toMillis();
-
- builder
- .setStartTimeMillisEpoch(startTimeMillis)
- .addTestTimes(durationMillis)
- .addTestProcessTimes(durationMillis)
- .setRunDurationMillis(durationMillis)
- .setHasCoverage(testAction.isCoverageMode());
-
- if (testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing()) {
- if (testAction.getCoverageDirectoryTreeArtifact() == null) {
- // Otherwise we'll get a NPE https://github.com/bazelbuild/bazel/issues/13185
- TestExecException e =
- createTestExecException(
- TestAction.Code.LOCAL_TEST_PREREQ_UNMET,
- "coverageDirectoryTreeArtifact is null:"
- + " --experimental_split_coverage_postprocessing depends on"
- + " --experimental_fetch_all_coverage_outputs being enabled");
- closeSuppressed(e, streamed);
- closeSuppressed(e, fileOutErr);
- throw e;
- }
- actionExecutionContext
- .getMetadataHandler()
- .getMetadata(testAction.getCoverageDirectoryTreeArtifact());
- ImmutableSet<? extends ActionInput> expandedCoverageDir =
- actionExecutionContext
- .getMetadataHandler()
- .getTreeArtifactChildren(
- (SpecialArtifact) testAction.getCoverageDirectoryTreeArtifact());
- Spawn coveragePostProcessingSpawn =
- createCoveragePostProcessingSpawn(
- actionExecutionContext,
- testAction,
- ImmutableList.copyOf(expandedCoverageDir),
- tmpDirRoot,
- executionOptions.splitXmlGeneration);
- SpawnStrategyResolver spawnStrategyResolver =
- actionExecutionContext.getContext(SpawnStrategyResolver.class);
-
- Path testRoot =
- actionExecutionContext.getInputPath(testAction.getTestLog()).getParentDirectory();
-
- Path out = testRoot.getChild("coverage.log");
- Path err = testRoot.getChild("coverage.err");
- FileOutErr coverageOutErr = new FileOutErr(out, err);
- ActionExecutionContext actionExecutionContextWithCoverageFileOutErr =
- actionExecutionContext.withFileOutErr(coverageOutErr);
-
- writeOutFile(coverageOutErr.getErrorPath(), coverageOutErr.getOutputPath());
- appendCoverageLog(coverageOutErr, fileOutErr);
- try {
- spawnStrategyResolver.exec(
- coveragePostProcessingSpawn, actionExecutionContextWithCoverageFileOutErr);
- } catch (SpawnExecException e) {
- if (e.isCatastrophic()) {
- closeSuppressed(e, streamed);
- closeSuppressed(e, fileOutErr);
- throw e;
- }
- if (!e.getSpawnResult().setupSuccess()) {
- closeSuppressed(e, streamed);
- closeSuppressed(e, fileOutErr);
- // Rethrow as the test could not be run and thus there's no point in retrying.
- throw e;
- }
- testResultDataBuilder
- .setCachable(e.getSpawnResult().status().isConsideredUserError())
- .setTestPassed(false)
- .setStatus(e.hasTimedOut() ? BlazeTestStatus.TIMEOUT : BlazeTestStatus.FAILED);
- } catch (ExecException | InterruptedException e) {
- closeSuppressed(e, streamed);
- closeSuppressed(e, fileOutErr);
- throw e;
- }
- }
-
- this.spawnResults = spawnResults;
- this.testResultDataBuilder = builder;
- }
-
- Verify.verify(
- !(testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing())
- || testAction.getCoverageData().getPath().exists());
- Verify.verifyNotNull(spawnResults);
- Verify.verifyNotNull(testResultDataBuilder);
-
- try {
- if (!fileOutErr.hasRecordedOutput()) {
- // Make sure that the test.log exists.Spaw
- FileSystemUtils.touchFile(fileOutErr.getOutputPath());
- }
- // Append any error output to the test.log. This is very rare.
- writeOutFile(fileOutErr.getErrorPath(), fileOutErr.getOutputPath());
- fileOutErr.close();
- if (streamed != null) {
- streamed.close();
- }
- } catch (IOException e) {
- throw new EnvironmentalExecException(e, Code.TEST_OUT_ERR_IO_EXCEPTION);
- }
-
- Path xmlOutputPath = resolvedPaths.getXmlOutputPath();
-
- // If the test did not create a test.xml, and --experimental_split_xml_generation is enabled,
- // then we run a separate action to create a test.xml from test.log. We do this as a spawn
- // rather than doing it locally in-process, as the test.log file may only exist remotely (when
- // remote execution is enabled), and we do not want to have to download it.
- if (executionOptions.splitXmlGeneration
- && fileOutErr.getOutputPath().exists()
- && !xmlOutputPath.exists()) {
- Spawn xmlGeneratingSpawn =
- createXmlGeneratingSpawn(testAction, spawn.getEnvironment(), spawnResults.get(0));
- SpawnStrategyResolver spawnStrategyResolver =
- actionExecutionContext.getContext(SpawnStrategyResolver.class);
- // We treat all failures to generate the test.xml here as catastrophic, and won't rerun
- // the test if this fails. We redirect the output to a temporary file.
- FileOutErr xmlSpawnOutErr = actionExecutionContext.getFileOutErr().childOutErr();
- try {
-
- ImmutableList<SpawnResult> xmlSpawnResults =
- spawnStrategyResolver.exec(
- xmlGeneratingSpawn, actionExecutionContext.withFileOutErr(xmlSpawnOutErr));
- this.spawnResults =
- ImmutableList.<SpawnResult>builder()
- .addAll(spawnResults)
- .addAll(xmlSpawnResults)
- .build();
- } catch (InterruptedException | ExecException e) {
- closeSuppressed(e, xmlSpawnOutErr);
+ if (!e.getSpawnResult().setupSuccess()) {
+ closeSuppressed(e, streamed);
+ closeSuppressed(e, fileOutErr);
+ // Rethrow as the test could not be run and thus there's no point in retrying.
throw e;
}
+ testResultDataBuilder
+ .setCachable(e.getSpawnResult().status().isConsideredUserError())
+ .setTestPassed(false)
+ .setStatus(e.hasTimedOut() ? BlazeTestStatus.TIMEOUT : BlazeTestStatus.FAILED);
+ } catch (ExecException | InterruptedException e) {
+ closeSuppressed(e, streamed);
+ closeSuppressed(e, fileOutErr);
+ throw e;
}
-
- TestCase details = parseTestResult(xmlOutputPath);
- if (details != null) {
- testResultDataBuilder.setTestCase(details);
- }
-
- BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo =
- extractExecutionInfo(spawnResults.get(0), testResultDataBuilder);
- StandaloneTestResult standaloneTestResult =
- StandaloneTestResult.builder()
- .setSpawnResults(spawnResults)
- // We return the TestResultData.Builder rather than the finished TestResultData
- // instance, as we may have to rename the output files in case the test needs to be
- // rerun (if it failed here _and_ is marked flaky _and_ the number of flaky attempts
- // is larger than 1).
- .setTestResultDataBuilder(testResultDataBuilder)
- .setExecutionInfo(executionInfo)
- .build();
- return TestAttemptContinuation.of(standaloneTestResult);
}
+
+ Verify.verify(
+ !(testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing())
+ || testAction.getCoverageData().getPath().exists());
+ Verify.verifyNotNull(spawnResults);
+ Verify.verifyNotNull(testResultDataBuilder);
+
+ try {
+ if (!fileOutErr.hasRecordedOutput()) {
+ // Make sure that the test.log exists.Spaw
+ FileSystemUtils.touchFile(fileOutErr.getOutputPath());
+ }
+ // Append any error output to the test.log. This is very rare.
+ writeOutFile(fileOutErr.getErrorPath(), fileOutErr.getOutputPath());
+ fileOutErr.close();
+ if (streamed != null) {
+ streamed.close();
+ }
+ } catch (IOException e) {
+ throw new EnvironmentalExecException(e, Code.TEST_OUT_ERR_IO_EXCEPTION);
+ }
+
+ Path xmlOutputPath = resolvedPaths.getXmlOutputPath();
+
+ // If the test did not create a test.xml, and --experimental_split_xml_generation is enabled,
+ // then we run a separate action to create a test.xml from test.log. We do this as a spawn
+ // rather than doing it locally in-process, as the test.log file may only exist remotely (when
+ // remote execution is enabled), and we do not want to have to download it.
+ if (executionOptions.splitXmlGeneration
+ && fileOutErr.getOutputPath().exists()
+ && !xmlOutputPath.exists()) {
+ Spawn xmlGeneratingSpawn =
+ createXmlGeneratingSpawn(testAction, spawn.getEnvironment(), spawnResults.get(0));
+ SpawnStrategyResolver spawnStrategyResolver =
+ actionExecutionContext.getContext(SpawnStrategyResolver.class);
+ // We treat all failures to generate the test.xml here as catastrophic, and won't rerun
+ // the test if this fails. We redirect the output to a temporary file.
+ FileOutErr xmlSpawnOutErr = actionExecutionContext.getFileOutErr().childOutErr();
+ try {
+
+ ImmutableList<SpawnResult> xmlSpawnResults =
+ spawnStrategyResolver.exec(
+ xmlGeneratingSpawn, actionExecutionContext.withFileOutErr(xmlSpawnOutErr));
+ spawnResults =
+ ImmutableList.<SpawnResult>builder()
+ .addAll(spawnResults)
+ .addAll(xmlSpawnResults)
+ .build();
+ } catch (InterruptedException | ExecException e) {
+ closeSuppressed(e, xmlSpawnOutErr);
+ throw e;
+ }
+ }
+
+ TestCase details = parseTestResult(xmlOutputPath);
+ if (details != null) {
+ testResultDataBuilder.setTestCase(details);
+ }
+
+ BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo =
+ extractExecutionInfo(spawnResults.get(0), testResultDataBuilder);
+ StandaloneTestResult standaloneTestResult =
+ StandaloneTestResult.builder()
+ .setSpawnResults(spawnResults)
+ // We return the TestResultData.Builder rather than the finished TestResultData
+ // instance, as we may have to rename the output files in case the test needs to be
+ // rerun (if it failed here _and_ is marked flaky _and_ the number of flaky attempts
+ // is larger than 1).
+ .setTestResultDataBuilder(testResultDataBuilder)
+ .setExecutionInfo(executionInfo)
+ .build();
+ return TestAttemptContinuation.of(standaloneTestResult);
}
}