blob: 3f58e71dd237558e83c36d9625d3f11fa648028b [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Ulf Adamse11063a2016-12-15 17:33:32 +000015package com.google.devtools.build.lib.exec;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010016
George Gensurec53bdaf2020-06-04 01:32:39 -070017import com.google.common.annotations.VisibleForTesting;
Laszlo Csomor6b08ff12019-05-02 01:35:41 -070018import com.google.common.base.Preconditions;
plfa445cda2020-10-29 10:39:30 -070019import com.google.common.base.Verify;
Klaus Aehlig89512a72017-02-10 14:36:43 +000020import com.google.common.collect.ImmutableList;
Ulf Adamsb3c833f2017-02-01 14:47:02 +000021import com.google.common.collect.ImmutableMap;
ulfjacke4cca142020-01-08 04:44:40 -080022import com.google.common.collect.ImmutableSet;
Chi Wangf5cf8b02021-11-07 20:05:55 -080023import com.google.common.collect.Maps;
felly71480542019-02-06 07:55:51 -080024import com.google.common.io.ByteStreams;
ulfjack4a5e1b72019-03-18 06:56:57 -070025import com.google.common.util.concurrent.ListenableFuture;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.actions.ActionExecutionContext;
plfa445cda2020-10-29 10:39:30 -070027import com.google.devtools.build.lib.actions.ActionInput;
ulfjack0858ae12018-07-27 02:37:53 -070028import com.google.devtools.build.lib.actions.ActionInputHelper;
Chi Wang97fb2cf2021-06-21 23:23:19 -070029import com.google.devtools.build.lib.actions.Artifact;
30import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact;
plfa445cda2020-10-29 10:39:30 -070031import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
Chi Wang97fb2cf2021-06-21 23:23:19 -070032import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
buchgr580038e2019-09-02 02:16:19 -070033import com.google.devtools.build.lib.actions.ArtifactPathResolver;
ulfjack415165a2019-09-12 07:56:40 -070034import com.google.devtools.build.lib.actions.EnvironmentalExecException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import com.google.devtools.build.lib.actions.ExecException;
buchgr9f7edd72017-07-14 12:58:50 +020036import com.google.devtools.build.lib.actions.ExecutionRequirements;
Chi Wang97fb2cf2021-06-21 23:23:19 -070037import com.google.devtools.build.lib.actions.FileArtifactValue;
Ulf Adamsd345cf92017-03-07 10:04:53 +000038import com.google.devtools.build.lib.actions.SimpleSpawn;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010039import com.google.devtools.build.lib.actions.Spawn;
ulfjack4a5e1b72019-03-18 06:56:57 -070040import com.google.devtools.build.lib.actions.SpawnContinuation;
ruperts4050a892017-10-07 00:46:20 +020041import com.google.devtools.build.lib.actions.SpawnResult;
buchgr580038e2019-09-02 02:16:19 -070042import com.google.devtools.build.lib.actions.TestExecException;
Chi Wang97fb2cf2021-06-21 23:23:19 -070043import com.google.devtools.build.lib.actions.cache.MetadataHandler;
ulfjack0858ae12018-07-27 02:37:53 -070044import com.google.devtools.build.lib.analysis.actions.SpawnAction;
jcaterf057da42020-04-14 05:08:40 -070045import com.google.devtools.build.lib.analysis.test.TestAttempt;
ulfjackab21d182017-08-10 15:36:14 +020046import com.google.devtools.build.lib.analysis.test.TestResult;
47import com.google.devtools.build.lib.analysis.test.TestRunnerAction;
ulfjack4a5e1b72019-03-18 06:56:57 -070048import com.google.devtools.build.lib.analysis.test.TestRunnerAction.ResolvedPaths;
jcaterf057da42020-04-14 05:08:40 -070049import com.google.devtools.build.lib.analysis.test.TestStrategy;
Benjamin Peterson1bbeadc2018-04-26 05:27:10 -070050import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
ulfjack797325f2019-10-17 06:33:40 -070051import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestResult.ExecutionInfo;
Googler2f08a182017-09-21 18:51:20 +020052import com.google.devtools.build.lib.buildeventstream.TestFileNameConstants;
ulfjacke4cca142020-01-08 04:44:40 -080053import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
54import com.google.devtools.build.lib.collect.nestedset.Order;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010055import com.google.devtools.build.lib.events.Reporter;
mschaller45576672020-06-10 19:15:07 -070056import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
mschallerd322b722020-12-28 15:16:32 -080057import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
mschaller07933882020-06-24 14:38:23 -070058import com.google.devtools.build.lib.server.FailureDetails.TestAction;
Chi Wang97fb2cf2021-06-21 23:23:19 -070059import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
Klaus Aehlig89512a72017-02-10 14:36:43 +000060import com.google.devtools.build.lib.util.Pair;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061import com.google.devtools.build.lib.util.io.FileOutErr;
felly71480542019-02-06 07:55:51 -080062import com.google.devtools.build.lib.vfs.FileStatus;
Damien Martin-Guillerezcc49b662016-12-29 15:53:08 +000063import com.google.devtools.build.lib.vfs.FileSystemUtils;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010064import com.google.devtools.build.lib.vfs.Path;
Ulf Adamsb3c833f2017-02-01 14:47:02 +000065import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010066import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus;
67import com.google.devtools.build.lib.view.test.TestStatus.TestCase;
68import com.google.devtools.build.lib.view.test.TestStatus.TestResultData;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010069import java.io.Closeable;
70import java.io.IOException;
felly71480542019-02-06 07:55:51 -080071import java.io.InputStream;
72import java.io.OutputStream;
ulfjackcccd7182018-03-28 16:49:14 -070073import java.time.Duration;
ruperts7967f332017-11-21 16:37:13 -080074import java.util.List;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010075import java.util.Map;
ulfjack404a6702018-02-21 01:37:55 -080076import java.util.TreeMap;
Chi Wang97fb2cf2021-06-21 23:23:19 -070077import java.util.concurrent.ConcurrentHashMap;
78import java.util.concurrent.ConcurrentMap;
ulfjack4a5e1b72019-03-18 06:56:57 -070079import javax.annotation.Nullable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010080
Ulf Adamse11063a2016-12-15 17:33:32 +000081/** Runs TestRunnerAction actions. */
lberki037c9dc2018-02-09 06:06:49 -080082// TODO(bazel-team): add tests for this strategy.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010083public class StandaloneTestStrategy extends TestStrategy {
Ulf Adamsb3c833f2017-02-01 14:47:02 +000084 private static final ImmutableMap<String, String> ENV_VARS =
85 ImmutableMap.<String, String>builder()
86 .put("TZ", "UTC")
Ulf Adamsb3c833f2017-02-01 14:47:02 +000087 .put("TEST_SRCDIR", TestPolicy.RUNFILES_DIR)
88 // TODO(lberki): Remove JAVA_RUNFILES and PYTHON_RUNFILES.
89 .put("JAVA_RUNFILES", TestPolicy.RUNFILES_DIR)
90 .put("PYTHON_RUNFILES", TestPolicy.RUNFILES_DIR)
91 .put("RUNFILES_DIR", TestPolicy.RUNFILES_DIR)
92 .put("TEST_TMPDIR", TestPolicy.TEST_TMP_DIR)
Yun Peng62c31052017-06-07 15:06:51 -040093 .put("RUN_UNDER_RUNFILES", "1")
Ulf Adamsb3c833f2017-02-01 14:47:02 +000094 .build();
95
96 public static final TestPolicy DEFAULT_LOCAL_POLICY = new TestPolicy(ENV_VARS);
97
Ulf Adamsb21cfa12016-12-16 14:31:42 +000098 protected final Path tmpDirRoot;
Kristina Chodorow379f7c02015-04-21 18:50:50 +000099
Han-Wen Nienhuysa51b3f72015-09-28 10:15:15 +0000100 public StandaloneTestStrategy(
ulfjackacd291a2017-06-16 15:25:42 +0200101 ExecutionOptions executionOptions, BinTools binTools, Path tmpDirRoot) {
102 super(executionOptions, binTools);
Ulf Adamsb21cfa12016-12-16 14:31:42 +0000103 this.tmpDirRoot = tmpDirRoot;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100104 }
105
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100106 @Override
ulfjack645fe0f2019-02-28 05:58:36 -0800107 public TestRunnerSpawn createTestRunnerSpawn(
michajlob091d302020-11-17 14:29:42 -0800108 TestRunnerAction action, ActionExecutionContext actionExecutionContext)
109 throws ExecException, InterruptedException {
buchgr580038e2019-09-02 02:16:19 -0700110 if (action.getExecutionSettings().getInputManifest() == null) {
mschallerd322b722020-12-28 15:16:32 -0800111 String errorMessage = "cannot run local tests with --nobuild_runfile_manifests";
mschaller07933882020-06-24 14:38:23 -0700112 throw new TestExecException(
mschallerd322b722020-12-28 15:16:32 -0800113 errorMessage,
114 FailureDetail.newBuilder()
115 .setTestAction(
116 TestAction.newBuilder().setCode(TestAction.Code.LOCAL_TEST_PREREQ_UNMET))
117 .setMessage(errorMessage)
118 .build());
buchgr580038e2019-09-02 02:16:19 -0700119 }
plfa445cda2020-10-29 10:39:30 -0700120 Map<String, String> testEnvironment =
121 createEnvironment(
122 actionExecutionContext, action, tmpDirRoot, executionOptions.splitXmlGeneration);
Han-Wen Nienhuysb6f557b2015-06-23 12:39:03 +0000123
ulfjack404a6702018-02-21 01:37:55 -0800124 Map<String, String> executionInfo =
125 new TreeMap<>(action.getTestProperties().getExecutionInfo());
buchgr9f7edd72017-07-14 12:58:50 +0200126 if (!action.shouldCacheResult()) {
127 executionInfo.put(ExecutionRequirements.NO_CACHE, "");
128 }
ulfjack404a6702018-02-21 01:37:55 -0800129 executionInfo.put(ExecutionRequirements.TIMEOUT, "" + getTimeout(action).getSeconds());
Ulf Adams19948b02017-01-10 16:27:59 +0000130
janakr91d93f62022-01-31 15:31:57 -0800131 SimpleSpawn.LocalResourcesSupplier localResourcesSupplier =
132 () ->
133 action
134 .getTestProperties()
135 .getLocalResourceUsage(
136 action.getOwner().getLabel(), executionOptions.usingLocalTestJobs());
philwoda21ba72017-05-02 20:25:12 +0200137
Ulf Adams19948b02017-01-10 16:27:59 +0000138 Spawn spawn =
Ulf Adamsd345cf92017-03-07 10:04:53 +0000139 new SimpleSpawn(
140 action,
lberki037c9dc2018-02-09 06:06:49 -0800141 getArgs(action),
plfa445cda2020-10-29 10:39:30 -0700142 ImmutableMap.copyOf(testEnvironment),
ulfjack404a6702018-02-21 01:37:55 -0800143 ImmutableMap.copyOf(executionInfo),
buchgra30df552019-08-29 04:54:41 -0700144 action.getRunfilesSupplier(),
kush2ce45a22018-05-02 14:15:37 -0700145 ImmutableMap.of(),
ulfjacke4cca142020-01-08 04:44:40 -0800146 /*inputs=*/ action.getInputs(),
cushon1f335042022-03-21 09:18:42 -0700147 NestedSetBuilder.emptySet(Order.STABLE_ORDER),
Chi Wang97fb2cf2021-06-21 23:23:19 -0700148 createSpawnOutputs(action),
janakr82b38962022-02-01 07:12:10 -0800149 /*mandatoryOutputs=*/ ImmutableSet.of(),
janakr91d93f62022-01-31 15:31:57 -0800150 localResourcesSupplier);
plfa445cda2020-10-29 10:39:30 -0700151 Path execRoot = actionExecutionContext.getExecRoot();
152 ArtifactPathResolver pathResolver = actionExecutionContext.getPathResolver();
153 Path runfilesDir = pathResolver.convertPath(action.getExecutionSettings().getRunfilesDir());
154 Path tmpDir = pathResolver.convertPath(tmpDirRoot.getChild(TestStrategy.getTmpDirName(action)));
155 Path workingDirectory = runfilesDir.getRelative(action.getRunfilesPrefix());
ulfjackb2a92ad2019-02-26 06:48:27 -0800156 return new StandaloneTestRunnerSpawn(
ulfjackfb04c5b2019-04-26 00:15:05 -0700157 action, actionExecutionContext, spawn, tmpDir, workingDirectory, execRoot);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100158 }
159
Chi Wang97fb2cf2021-06-21 23:23:19 -0700160 private ImmutableSet<ActionInput> createSpawnOutputs(TestRunnerAction action) {
161 ImmutableSet.Builder<ActionInput> builder = ImmutableSet.builder();
162 for (ActionInput output : action.getSpawnOutputs()) {
163 if (output.getExecPath().equals(action.getXmlOutputPath())) {
164 // HACK: Convert type of test.xml from BasicActionInput to DerivedArtifact. We want to
165 // inject metadata of test.xml if it is generated remotely and it's currently only possible
166 // to inject Artifact.
167 builder.add(createArtifactOutput(action, output.getExecPath()));
168 } else {
169 builder.add(output);
170 }
171 }
172 return builder.build();
173 }
174
ajurkowskibd74a872020-01-23 13:51:33 -0800175 private static ImmutableList<Pair<String, Path>> renameOutputs(
ulfjack77c9f5e2017-06-19 14:17:52 +0200176 ActionExecutionContext actionExecutionContext,
Damien Martin-Guillerezcc49b662016-12-29 15:53:08 +0000177 TestRunnerAction action,
ulfjackeb8cfc12019-04-29 04:43:14 -0700178 ImmutableList<Pair<String, Path>> testOutputs,
179 int attemptId)
Damien Martin-Guillerezcc49b662016-12-29 15:53:08 +0000180 throws IOException {
181 // Rename outputs
182 String namePrefix =
183 FileSystemUtils.removeExtension(action.getTestLog().getExecPath().getBaseName());
shahan18726b72018-03-15 14:18:46 -0700184 Path testRoot = actionExecutionContext.getInputPath(action.getTestLog()).getParentDirectory();
Googler2f08a182017-09-21 18:51:20 +0200185 Path attemptsDir = testRoot.getChild(namePrefix + "_attempts");
Damien Martin-Guillerezcc49b662016-12-29 15:53:08 +0000186 attemptsDir.createDirectory();
ulfjackeb8cfc12019-04-29 04:43:14 -0700187 String attemptPrefix = "attempt_" + attemptId;
Damien Martin-Guillerezcc49b662016-12-29 15:53:08 +0000188 Path testLog = attemptsDir.getChild(attemptPrefix + ".log");
Googler2f08a182017-09-21 18:51:20 +0200189
190 // Get the normal test output paths, and then update them to use "attempt_N" names, and
191 // attemptDir, before adding them to the outputs.
ulfjackb2a92ad2019-02-26 06:48:27 -0800192 ImmutableList.Builder<Pair<String, Path>> testOutputsBuilder = new ImmutableList.Builder<>();
Googler2f08a182017-09-21 18:51:20 +0200193 for (Pair<String, Path> testOutput : testOutputs) {
194 // e.g. /testRoot/test.dir/file, an example we follow throughout this loop's comments.
195 Path testOutputPath = testOutput.getSecond();
ulfjack0c61edb2018-03-27 11:32:07 -0700196 Path destinationPath;
197 if (testOutput.getFirst().equals(TestFileNameConstants.TEST_LOG)) {
198 // The rename rules for the test log are different than for all the other files.
199 destinationPath = testLog;
200 } else {
201 // e.g. test.dir/file
202 PathFragment relativeToTestDirectory = testOutputPath.relativeTo(testRoot);
Googler2f08a182017-09-21 18:51:20 +0200203
ulfjack0c61edb2018-03-27 11:32:07 -0700204 // e.g. attempt_1.dir/file
205 String destinationPathFragmentStr =
206 relativeToTestDirectory.getSafePathString().replaceFirst("test", attemptPrefix);
207 PathFragment destinationPathFragment = PathFragment.create(destinationPathFragmentStr);
olaolac35fe502017-10-17 04:09:07 +0200208
ulfjack0c61edb2018-03-27 11:32:07 -0700209 // e.g. /attemptsDir/attempt_1.dir/file
210 destinationPath = attemptsDir.getRelative(destinationPathFragment);
211 destinationPath.getParentDirectory().createDirectory();
212 }
olaolac35fe502017-10-17 04:09:07 +0200213
ulfjack0c61edb2018-03-27 11:32:07 -0700214 // Move to the destination.
Googler2f08a182017-09-21 18:51:20 +0200215 testOutputPath.renameTo(destinationPath);
216
217 testOutputsBuilder.add(Pair.of(testOutput.getFirst(), destinationPath));
Damien Martin-Guillerezcc49b662016-12-29 15:53:08 +0000218 }
ulfjackeb8cfc12019-04-29 04:43:14 -0700219 return testOutputsBuilder.build();
220 }
221
222 private StandaloneFailedAttemptResult processFailedTestAttempt(
223 int attemptId,
224 ActionExecutionContext actionExecutionContext,
225 TestRunnerAction action,
226 StandaloneTestResult result)
227 throws IOException {
228 return processTestAttempt(
229 attemptId, /*isLastAttempt=*/ false, actionExecutionContext, action, result);
230 }
231
arostovtseva6bfab02022-02-14 22:15:58 -0800232 private void finalizeTest(
ulfjackeb8cfc12019-04-29 04:43:14 -0700233 TestRunnerAction action,
234 ActionExecutionContext actionExecutionContext,
235 StandaloneTestResult standaloneTestResult,
236 List<FailedAttemptResult> failedAttempts)
237 throws IOException {
238 processTestAttempt(
239 failedAttempts.size() + 1,
240 /*isLastAttempt=*/ true,
241 actionExecutionContext,
242 action,
243 standaloneTestResult);
244
245 TestResultData.Builder dataBuilder = standaloneTestResult.testResultDataBuilder();
246 for (FailedAttemptResult failedAttempt : failedAttempts) {
247 TestResultData failedAttemptData =
248 ((StandaloneFailedAttemptResult) failedAttempt).testResultData;
249 dataBuilder.addAllFailedLogs(failedAttemptData.getFailedLogsList());
250 dataBuilder.addTestTimes(failedAttemptData.getTestTimes(0));
251 dataBuilder.addAllTestProcessTimes(failedAttemptData.getTestProcessTimesList());
252 }
253 if (dataBuilder.getStatus() == BlazeTestStatus.PASSED && !failedAttempts.isEmpty()) {
254 dataBuilder.setStatus(BlazeTestStatus.FLAKY);
255 }
256 TestResultData data = dataBuilder.build();
mschallerd322b722020-12-28 15:16:32 -0800257 TestResult result =
258 new TestResult(action, data, false, standaloneTestResult.primarySystemFailure());
arostovtseva6bfab02022-02-14 22:15:58 -0800259 postTestResult(actionExecutionContext, result);
ulfjackeb8cfc12019-04-29 04:43:14 -0700260 }
261
262 private StandaloneFailedAttemptResult processTestAttempt(
263 int attemptId,
264 boolean isLastAttempt,
265 ActionExecutionContext actionExecutionContext,
266 TestRunnerAction action,
267 StandaloneTestResult result)
268 throws IOException {
269 ImmutableList<Pair<String, Path>> testOutputs =
270 action.getTestOutputsMapping(
271 actionExecutionContext.getPathResolver(), actionExecutionContext.getExecRoot());
272 if (!isLastAttempt) {
273 testOutputs = renameOutputs(actionExecutionContext, action, testOutputs, attemptId);
274 }
Googler2f08a182017-09-21 18:51:20 +0200275
ulfjackeb8cfc12019-04-29 04:43:14 -0700276 // Recover the test log path, which may have been renamed, and add it to the data builder.
277 Path renamedTestLog = null;
278 for (Pair<String, Path> pair : testOutputs) {
279 if (TestFileNameConstants.TEST_LOG.equals(pair.getFirst())) {
George Gensurec53bdaf2020-06-04 01:32:39 -0700280 Preconditions.checkState(renamedTestLog == null, "multiple test_log matches");
ulfjackeb8cfc12019-04-29 04:43:14 -0700281 renamedTestLog = pair.getSecond();
282 }
283 }
ulfjack797325f2019-10-17 06:33:40 -0700284
285 TestResultData.Builder dataBuilder = result.testResultDataBuilder();
George Gensurec53bdaf2020-06-04 01:32:39 -0700286 // If the test log path does not exist, mark the test as incomplete
287 if (renamedTestLog == null) {
288 dataBuilder.setStatus(BlazeTestStatus.INCOMPLETE);
289 }
290
ulfjackeb8cfc12019-04-29 04:43:14 -0700291 if (dataBuilder.getStatus() == BlazeTestStatus.PASSED) {
292 dataBuilder.setPassedLog(renamedTestLog.toString());
ulfjack797325f2019-10-17 06:33:40 -0700293 } else if (dataBuilder.getStatus() == BlazeTestStatus.INCOMPLETE) {
294 // Incomplete (cancelled) test runs don't have a log.
295 Preconditions.checkState(renamedTestLog == null);
ulfjackeb8cfc12019-04-29 04:43:14 -0700296 } else {
297 dataBuilder.addFailedLogs(renamedTestLog.toString());
298 }
ulfjackb2a92ad2019-02-26 06:48:27 -0800299
300 // Add the test log to the output
301 TestResultData data = dataBuilder.build();
ulfjack77c9f5e2017-06-19 14:17:52 +0200302 actionExecutionContext
Benjamin Peterson1bbeadc2018-04-26 05:27:10 -0700303 .getEventHandler()
Klaus Aehlig0aaa6dd2017-03-08 17:33:08 +0000304 .post(
ulfjackb22c7ee2018-05-14 05:32:03 -0700305 TestAttempt.forExecutedTestResult(
ulfjackeb8cfc12019-04-29 04:43:14 -0700306 action, data, attemptId, testOutputs, result.executionInfo(), isLastAttempt));
307 processTestOutput(actionExecutionContext, data, action.getTestName(), renamedTestLog);
ulfjackb70f0c12019-02-27 07:23:38 -0800308 return new StandaloneFailedAttemptResult(data);
Damien Martin-Guillerezcc49b662016-12-29 15:53:08 +0000309 }
310
plfa445cda2020-10-29 10:39:30 -0700311 private static Map<String, String> setupEnvironment(
312 TestRunnerAction action,
313 Map<String, String> clientEnv,
314 Path execRoot,
315 Path runfilesDir,
ulfjackd1c53292017-06-08 18:09:01 +0200316 Path tmpDir) {
Ulf Adamsb3c833f2017-02-01 14:47:02 +0000317 PathFragment relativeTmpDir;
Kristina Chodorow88de40e2016-08-18 14:24:58 +0000318 if (tmpDir.startsWith(execRoot)) {
Ulf Adamsb3c833f2017-02-01 14:47:02 +0000319 relativeTmpDir = tmpDir.relativeTo(execRoot);
Kristina Chodorow88de40e2016-08-18 14:24:58 +0000320 } else {
Ulf Adamsb3c833f2017-02-01 14:47:02 +0000321 relativeTmpDir = tmpDir.asFragment();
Kristina Chodorow88de40e2016-08-18 14:24:58 +0000322 }
Ulf Adamsb3c833f2017-02-01 14:47:02 +0000323 return DEFAULT_LOCAL_POLICY.computeTestEnvironment(
Chi Wang97fb2cf2021-06-21 23:23:19 -0700324 action, clientEnv, getTimeout(action), runfilesDir.relativeTo(execRoot), relativeTmpDir);
325 }
326
327 static class TestMetadataHandler implements MetadataHandler {
328 private final MetadataHandler metadataHandler;
329 private final ImmutableSet<Artifact> outputs;
330 private final ConcurrentMap<Artifact, FileArtifactValue> fileMetadataMap =
331 new ConcurrentHashMap<>();
332
333 TestMetadataHandler(MetadataHandler metadataHandler, ImmutableSet<Artifact> outputs) {
334 this.metadataHandler = metadataHandler;
335 this.outputs = outputs;
336 }
337
338 @Nullable
339 @Override
340 public ActionInput getInput(String execPath) {
341 return metadataHandler.getInput(execPath);
342 }
343
344 @Nullable
345 @Override
346 public FileArtifactValue getMetadata(ActionInput input) throws IOException {
347 return metadataHandler.getMetadata(input);
348 }
349
350 @Override
351 public void setDigestForVirtualArtifact(Artifact artifact, byte[] digest) {
352 metadataHandler.setDigestForVirtualArtifact(artifact, digest);
353 }
354
355 @Override
356 public FileArtifactValue constructMetadataForDigest(
357 Artifact output, FileStatus statNoFollow, byte[] injectedDigest) throws IOException {
358 return metadataHandler.constructMetadataForDigest(output, statNoFollow, injectedDigest);
359 }
360
361 @Override
362 public ImmutableSet<TreeFileArtifact> getTreeArtifactChildren(SpecialArtifact treeArtifact) {
363 return metadataHandler.getTreeArtifactChildren(treeArtifact);
364 }
365
366 @Override
367 public TreeArtifactValue getTreeArtifactValue(SpecialArtifact treeArtifact) throws IOException {
368 return metadataHandler.getTreeArtifactValue(treeArtifact);
369 }
370
371 @Override
372 public void markOmitted(Artifact output) {
373 metadataHandler.markOmitted(output);
374 }
375
376 @Override
377 public boolean artifactOmitted(Artifact artifact) {
378 return metadataHandler.artifactOmitted(artifact);
379 }
380
381 @Override
382 public void resetOutputs(Iterable<? extends Artifact> outputs) {
383 metadataHandler.resetOutputs(outputs);
384 }
385
386 @Override
387 public void injectFile(Artifact output, FileArtifactValue metadata) {
388 if (outputs.contains(output)) {
389 metadataHandler.injectFile(output, metadata);
390 }
391 fileMetadataMap.put(output, metadata);
392 }
393
394 @Override
395 public void injectTree(SpecialArtifact output, TreeArtifactValue tree) {
396 metadataHandler.injectTree(output, tree);
397 }
398
399 public boolean fileInjected(Artifact output) {
400 return fileMetadataMap.containsKey(output);
401 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100402 }
Kristina Chodorowb579b942015-03-02 15:51:58 +0000403
ulfjack4a5e1b72019-03-18 06:56:57 -0700404 private TestAttemptContinuation beginTestAttempt(
405 TestRunnerAction testAction,
406 Spawn spawn,
407 ActionExecutionContext actionExecutionContext,
408 Path execRoot)
ulfjack415165a2019-09-12 07:56:40 -0700409 throws IOException, InterruptedException {
ulfjack4a5e1b72019-03-18 06:56:57 -0700410 ResolvedPaths resolvedPaths = testAction.resolve(execRoot);
411 Path out = actionExecutionContext.getInputPath(testAction.getTestLog());
412 Path err = resolvedPaths.getTestStderr();
413 FileOutErr testOutErr = new FileOutErr(out, err);
414 Closeable streamed = null;
jcaterd990df52020-04-07 04:38:15 -0700415 if (executionOptions.testOutput.equals(ExecutionOptions.TestOutputFormat.STREAMED)) {
ulfjack4a5e1b72019-03-18 06:56:57 -0700416 streamed =
michajlo9885c2f2020-03-09 12:33:06 -0700417 createStreamedTestOutput(
ulfjack4a5e1b72019-03-18 06:56:57 -0700418 Reporter.outErrForReporter(actionExecutionContext.getEventHandler()), out);
419 }
Chi Wang97fb2cf2021-06-21 23:23:19 -0700420
421 // We use TestMetadataHandler here mainly because the one provided by actionExecutionContext
422 // doesn't allow to inject undeclared outputs and test.xml is undeclared by the test action.
423 TestMetadataHandler testMetadataHandler = null;
424 if (actionExecutionContext.getMetadataHandler() != null) {
425 testMetadataHandler =
426 new TestMetadataHandler(
427 actionExecutionContext.getMetadataHandler(), testAction.getOutputs());
428 }
429
ulfjack4a5e1b72019-03-18 06:56:57 -0700430 long startTimeMillis = actionExecutionContext.getClock().currentTimeMillis();
jcater56b9b182020-04-17 13:55:34 -0700431 SpawnStrategyResolver resolver = actionExecutionContext.getContext(SpawnStrategyResolver.class);
jmmv397f1722020-09-03 08:45:43 -0700432 SpawnContinuation spawnContinuation;
433 try {
434 spawnContinuation =
Chi Wang97fb2cf2021-06-21 23:23:19 -0700435 resolver.beginExecution(
436 spawn,
437 actionExecutionContext
438 .withFileOutErr(testOutErr)
439 .withMetadataHandler(testMetadataHandler));
jmmv397f1722020-09-03 08:45:43 -0700440 } catch (InterruptedException e) {
441 if (streamed != null) {
442 streamed.close();
443 }
444 testOutErr.close();
445 throw e;
446 }
ulfjack4a5e1b72019-03-18 06:56:57 -0700447 return new BazelTestAttemptContinuation(
ulfjack415165a2019-09-12 07:56:40 -0700448 testAction,
Chi Wang97fb2cf2021-06-21 23:23:19 -0700449 testMetadataHandler,
ulfjack415165a2019-09-12 07:56:40 -0700450 actionExecutionContext,
451 spawn,
452 resolvedPaths,
453 testOutErr,
454 streamed,
455 startTimeMillis,
plfa445cda2020-10-29 10:39:30 -0700456 spawnContinuation,
457 /* testResultDataBuilder= */ null,
458 /* spawnResults= */ null);
ulfjack4a5e1b72019-03-18 06:56:57 -0700459 }
460
plfa445cda2020-10-29 10:39:30 -0700461 private static void appendCoverageLog(FileOutErr coverageOutErr, FileOutErr outErr)
462 throws IOException {
463 writeOutFile(coverageOutErr.getErrorPath(), outErr.getOutputPath());
464 writeOutFile(coverageOutErr.getOutputPath(), outErr.getOutputPath());
465 }
466
467 private static void writeOutFile(Path inFilePath, Path outFilePath) throws IOException {
468 FileStatus stat = inFilePath.statNullable();
felly71480542019-02-06 07:55:51 -0800469 if (stat != null) {
470 try {
471 if (stat.getSize() > 0) {
plfa445cda2020-10-29 10:39:30 -0700472 if (outFilePath.exists()) {
473 outFilePath.setWritable(true);
felly71480542019-02-06 07:55:51 -0800474 }
plfa445cda2020-10-29 10:39:30 -0700475 try (OutputStream out = outFilePath.getOutputStream(true);
476 InputStream in = inFilePath.getInputStream()) {
felly71480542019-02-06 07:55:51 -0800477 ByteStreams.copy(in, out);
478 }
479 }
480 } finally {
plfa445cda2020-10-29 10:39:30 -0700481 inFilePath.delete();
felly71480542019-02-06 07:55:51 -0800482 }
483 }
484 }
485
ulfjackeb8cfc12019-04-29 04:43:14 -0700486 private static BuildEventStreamProtos.TestResult.ExecutionInfo extractExecutionInfo(
ulfjack4a5e1b72019-03-18 06:56:57 -0700487 SpawnResult spawnResult, TestResultData.Builder result) {
488 BuildEventStreamProtos.TestResult.ExecutionInfo.Builder executionInfo =
489 BuildEventStreamProtos.TestResult.ExecutionInfo.newBuilder();
Benjamin Peterson1bbeadc2018-04-26 05:27:10 -0700490 if (spawnResult.isCacheHit()) {
491 result.setRemotelyCached(true);
492 executionInfo.setCachedRemotely(true);
493 }
494 String strategy = spawnResult.getRunnerName();
495 if (strategy != null) {
496 executionInfo.setStrategy(strategy);
497 result.setIsRemoteStrategy(strategy.equals("remote"));
498 }
499 if (spawnResult.getExecutorHostName() != null) {
500 executionInfo.setHostname(spawnResult.getExecutorHostName());
501 }
ulfjackeb8cfc12019-04-29 04:43:14 -0700502 return executionInfo.build();
Benjamin Peterson1bbeadc2018-04-26 05:27:10 -0700503 }
504
Chi Wang97fb2cf2021-06-21 23:23:19 -0700505 private static Artifact.DerivedArtifact createArtifactOutput(
506 TestRunnerAction action, PathFragment outputPath) {
507 Artifact.DerivedArtifact testLog = (Artifact.DerivedArtifact) action.getTestLog();
jhorvitzcab340f2021-09-27 09:16:23 -0700508 return DerivedArtifact.create(testLog.getRoot(), outputPath, testLog.getArtifactOwner());
Chi Wang97fb2cf2021-06-21 23:23:19 -0700509 }
510
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100511 /**
ulfjack0858ae12018-07-27 02:37:53 -0700512 * A spawn to generate a test.xml file from the test log. This is only used if the test does not
513 * generate a test.xml file itself.
514 */
Yun Peng35518982020-12-15 06:04:28 -0800515 private static Spawn createXmlGeneratingSpawn(
516 TestRunnerAction action, ImmutableMap<String, String> testEnv, SpawnResult result) {
laszlocsomorba87ba92020-01-15 05:33:02 -0800517 ImmutableList<String> args =
518 ImmutableList.of(
519 action.getTestXmlGeneratorScript().getExecPath().getCallablePathString(),
520 action.getTestLog().getExecPathString(),
521 action.getXmlOutputPath().getPathString(),
522 Long.toString(result.getWallTime().orElse(Duration.ZERO).getSeconds()),
523 Integer.toString(result.exitCode()));
Yun Peng35518982020-12-15 06:04:28 -0800524 ImmutableMap.Builder<String, String> envBuilder = ImmutableMap.builder();
525 // "PATH" and "TEST_BINARY" are also required, they should always be set in testEnv.
526 Preconditions.checkArgument(testEnv.containsKey("PATH"));
527 Preconditions.checkArgument(testEnv.containsKey("TEST_BINARY"));
528 envBuilder.putAll(testEnv).put("TEST_NAME", action.getTestName());
529 // testEnv only contains TEST_SHARD_INDEX and TEST_TOTAL_SHARDS if the test action is sharded,
530 // we need to set the default value when the action isn't sharded.
531 if (!action.isSharded()) {
532 envBuilder.put("TEST_SHARD_INDEX", "0");
533 envBuilder.put("TEST_TOTAL_SHARDS", "0");
534 }
Chi Wangf5cf8b02021-11-07 20:05:55 -0800535 Map<String, String> executionInfo =
536 Maps.newHashMapWithExpectedSize(action.getExecutionInfo().size() + 1);
537 executionInfo.putAll(action.getExecutionInfo());
538 if (result.exitCode() != 0) {
539 // If the test is failed, the spawn shouldn't use remote cache since the test.xml file is
540 // renamed immediately after the spawn execution. If there is another test attempt, the async
541 // upload will fail because it cannot read the file at original position.
542 executionInfo.put(ExecutionRequirements.NO_REMOTE_CACHE, "");
543 }
ulfjack0858ae12018-07-27 02:37:53 -0700544 return new SimpleSpawn(
545 action,
laszlocsomorba87ba92020-01-15 05:33:02 -0800546 args,
Googler2fc3d3f2022-02-01 06:29:56 -0800547 envBuilder.buildOrThrow(),
Jakob Buchgraber803801d2019-03-21 09:22:58 -0700548 // Pass the execution info of the action which is identical to the supported tags set on the
549 // test target. In particular, this does not set the test timeout on the spawn.
Chi Wangf5cf8b02021-11-07 20:05:55 -0800550 ImmutableMap.copyOf(executionInfo),
ulfjack0858ae12018-07-27 02:37:53 -0700551 null,
552 ImmutableMap.of(),
ulfjacke4cca142020-01-08 04:44:40 -0800553 /*inputs=*/ NestedSetBuilder.create(
554 Order.STABLE_ORDER, action.getTestXmlGeneratorScript(), action.getTestLog()),
555 /*tools=*/ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
Chi Wang97fb2cf2021-06-21 23:23:19 -0700556 /*outputs=*/ ImmutableSet.of(createArtifactOutput(action, action.getXmlOutputPath())),
janakr82b38962022-02-01 07:12:10 -0800557 /*mandatoryOutputs=*/ null,
ulfjack0858ae12018-07-27 02:37:53 -0700558 SpawnAction.DEFAULT_RESOURCE_SET);
559 }
560
plfa445cda2020-10-29 10:39:30 -0700561 private static Spawn createCoveragePostProcessingSpawn(
562 ActionExecutionContext actionExecutionContext,
563 TestRunnerAction action,
564 List<ActionInput> expandedCoverageDir,
565 Path tmpDirRoot,
566 boolean splitXmlGeneration) {
567 ImmutableList<String> args =
568 ImmutableList.of(action.getCollectCoverageScript().getExecPathString());
569
570 Map<String, String> testEnvironment =
571 createEnvironment(actionExecutionContext, action, tmpDirRoot, splitXmlGeneration);
572
573 testEnvironment.put("TEST_SHARD_INDEX", Integer.toString(action.getShardNum()));
574 testEnvironment.put(
575 "TEST_TOTAL_SHARDS", Integer.toString(action.getExecutionSettings().getTotalShards()));
576 testEnvironment.put("TEST_NAME", action.getTestName());
577 testEnvironment.put("IS_COVERAGE_SPAWN", "1");
578 return new SimpleSpawn(
579 action,
580 args,
581 ImmutableMap.copyOf(testEnvironment),
jcater285b17f2021-12-16 07:16:28 -0800582 action.getExecutionInfo(),
plfa445cda2020-10-29 10:39:30 -0700583 action.getLcovMergerRunfilesSupplier(),
janakr82b38962022-02-01 07:12:10 -0800584 /*filesetMappings=*/ ImmutableMap.of(),
585 /*inputs=*/ NestedSetBuilder.<ActionInput>compileOrder()
Benjamin Peterson7e790a02021-04-15 01:37:10 -0700586 .addTransitive(action.getInputs())
plfa445cda2020-10-29 10:39:30 -0700587 .addAll(expandedCoverageDir)
588 .add(action.getCollectCoverageScript())
589 .add(action.getCoverageDirectoryTreeArtifact())
590 .add(action.getCoverageManifest())
591 .addTransitive(action.getLcovMergerFilesToRun().build())
592 .build(),
janakr82b38962022-02-01 07:12:10 -0800593 /*tools=*/ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
594 /*outputs=*/ ImmutableSet.of(
ajurkowski0e71e902021-06-11 10:58:16 -0700595 ActionInputHelper.fromPath(action.getCoverageData().getExecPath())),
janakr82b38962022-02-01 07:12:10 -0800596 /*mandatoryOutputs=*/ null,
plfa445cda2020-10-29 10:39:30 -0700597 SpawnAction.DEFAULT_RESOURCE_SET);
598 }
599
600 private static Map<String, String> createEnvironment(
601 ActionExecutionContext actionExecutionContext,
602 TestRunnerAction action,
603 Path tmpDirRoot,
604 boolean splitXmlGeneration) {
605 Path execRoot = actionExecutionContext.getExecRoot();
606 ArtifactPathResolver pathResolver = actionExecutionContext.getPathResolver();
607 Path runfilesDir = pathResolver.convertPath(action.getExecutionSettings().getRunfilesDir());
608 Path tmpDir = pathResolver.convertPath(tmpDirRoot.getChild(TestStrategy.getTmpDirName(action)));
609 Map<String, String> testEnvironment =
610 setupEnvironment(
611 action, actionExecutionContext.getClientEnv(), execRoot, runfilesDir, tmpDir);
612 if (splitXmlGeneration) {
613 testEnvironment.put("EXPERIMENTAL_SPLIT_XML_GENERATION", "1");
614 }
615 return testEnvironment;
616 }
617
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100618 @Override
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100619 public TestResult newCachedTestResult(
620 Path execRoot, TestRunnerAction action, TestResultData data) {
mschallerd322b722020-12-28 15:16:32 -0800621 return new TestResult(action, data, /*cached*/ true, execRoot, /*systemFailure=*/ null);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100622 }
ulfjackb2a92ad2019-02-26 06:48:27 -0800623
George Gensurec53bdaf2020-06-04 01:32:39 -0700624 @VisibleForTesting
625 static final class StandaloneFailedAttemptResult implements FailedAttemptResult {
ulfjackb70f0c12019-02-27 07:23:38 -0800626 private final TestResultData testResultData;
627
628 StandaloneFailedAttemptResult(TestResultData testResultData) {
629 this.testResultData = testResultData;
630 }
George Gensurec53bdaf2020-06-04 01:32:39 -0700631
632 TestResultData testResultData() {
633 return testResultData;
634 }
ulfjackb70f0c12019-02-27 07:23:38 -0800635 }
636
arostovtseva6bfab02022-02-14 22:15:58 -0800637 private final class StandaloneTestRunnerSpawn implements TestRunnerSpawn {
ulfjackb2a92ad2019-02-26 06:48:27 -0800638 private final TestRunnerAction testAction;
639 private final ActionExecutionContext actionExecutionContext;
640 private final Spawn spawn;
641 private final Path tmpDir;
ulfjackb2a92ad2019-02-26 06:48:27 -0800642 private final Path workingDirectory;
643 private final Path execRoot;
644
645 StandaloneTestRunnerSpawn(
646 TestRunnerAction testAction,
647 ActionExecutionContext actionExecutionContext,
648 Spawn spawn,
649 Path tmpDir,
ulfjackb2a92ad2019-02-26 06:48:27 -0800650 Path workingDirectory,
651 Path execRoot) {
652 this.testAction = testAction;
653 this.actionExecutionContext = actionExecutionContext;
654 this.spawn = spawn;
655 this.tmpDir = tmpDir;
ulfjackb2a92ad2019-02-26 06:48:27 -0800656 this.workingDirectory = workingDirectory;
657 this.execRoot = execRoot;
658 }
659
ulfjack11883442019-03-15 12:10:47 -0700660 @Override
661 public ActionExecutionContext getActionExecutionContext() {
662 return actionExecutionContext;
663 }
664
ulfjack4a5e1b72019-03-18 06:56:57 -0700665 @Override
ulfjack415165a2019-09-12 07:56:40 -0700666 public TestAttemptContinuation beginExecution() throws InterruptedException, IOException {
ulfjack71f9cea2019-10-17 00:32:35 -0700667 prepareFileSystem(testAction, execRoot, tmpDir, workingDirectory);
ulfjack4a5e1b72019-03-18 06:56:57 -0700668 return beginTestAttempt(testAction, spawn, actionExecutionContext, execRoot);
669 }
670
671 @Override
ulfjackb70f0c12019-02-27 07:23:38 -0800672 public int getMaxAttempts(TestAttemptResult firstTestAttemptResult) {
673 return getTestAttempts(testAction);
674 }
675
676 @Override
677 public FailedAttemptResult finalizeFailedTestAttempt(
678 TestAttemptResult testAttemptResult, int attempt) throws IOException {
679 return processFailedTestAttempt(
680 attempt, actionExecutionContext, testAction, (StandaloneTestResult) testAttemptResult);
681 }
682
683 @Override
684 public void finalizeTest(
685 TestAttemptResult finalResult, List<FailedAttemptResult> failedAttempts)
ulfjackd23e5772019-03-15 07:39:42 -0700686 throws IOException {
ulfjackb70f0c12019-02-27 07:23:38 -0800687 StandaloneTestStrategy.this.finalizeTest(
688 testAction, actionExecutionContext, (StandaloneTestResult) finalResult, failedAttempts);
689 }
ulfjack797325f2019-10-17 06:33:40 -0700690
691 @Override
692 public void finalizeCancelledTest(List<FailedAttemptResult> failedAttempts) throws IOException {
693 TestResultData.Builder builder =
mschallerd322b722020-12-28 15:16:32 -0800694 TestResultData.newBuilder()
695 .setCachable(false)
696 .setTestPassed(false)
697 .setStatus(BlazeTestStatus.INCOMPLETE);
ulfjack797325f2019-10-17 06:33:40 -0700698 StandaloneTestResult standaloneTestResult =
699 StandaloneTestResult.builder()
700 .setSpawnResults(ImmutableList.of())
701 .setTestResultDataBuilder(builder)
702 .setExecutionInfo(ExecutionInfo.getDefaultInstance())
703 .build();
704 finalizeTest(standaloneTestResult, failedAttempts);
705 }
ulfjackb2a92ad2019-02-26 06:48:27 -0800706 }
ulfjack4a5e1b72019-03-18 06:56:57 -0700707
708 private final class BazelTestAttemptContinuation extends TestAttemptContinuation {
709 private final TestRunnerAction testAction;
Chi Wang97fb2cf2021-06-21 23:23:19 -0700710 @Nullable private final TestMetadataHandler testMetadataHandler;
ulfjack4a5e1b72019-03-18 06:56:57 -0700711 private final ActionExecutionContext actionExecutionContext;
712 private final Spawn spawn;
713 private final ResolvedPaths resolvedPaths;
714 private final FileOutErr fileOutErr;
715 private final Closeable streamed;
716 private final long startTimeMillis;
717 private final SpawnContinuation spawnContinuation;
plfa445cda2020-10-29 10:39:30 -0700718 private TestResultData.Builder testResultDataBuilder;
719 private ImmutableList<SpawnResult> spawnResults;
ulfjack4a5e1b72019-03-18 06:56:57 -0700720
721 BazelTestAttemptContinuation(
722 TestRunnerAction testAction,
Chi Wang97fb2cf2021-06-21 23:23:19 -0700723 @Nullable TestMetadataHandler testMetadataHandler,
ulfjack4a5e1b72019-03-18 06:56:57 -0700724 ActionExecutionContext actionExecutionContext,
725 Spawn spawn,
726 ResolvedPaths resolvedPaths,
727 FileOutErr fileOutErr,
728 Closeable streamed,
729 long startTimeMillis,
plfa445cda2020-10-29 10:39:30 -0700730 SpawnContinuation spawnContinuation,
731 TestResultData.Builder testResultDataBuilder,
732 ImmutableList<SpawnResult> spawnResults) {
ulfjack4a5e1b72019-03-18 06:56:57 -0700733 this.testAction = testAction;
Chi Wang97fb2cf2021-06-21 23:23:19 -0700734 this.testMetadataHandler = testMetadataHandler;
ulfjack4a5e1b72019-03-18 06:56:57 -0700735 this.actionExecutionContext = actionExecutionContext;
736 this.spawn = spawn;
737 this.resolvedPaths = resolvedPaths;
738 this.fileOutErr = fileOutErr;
739 this.streamed = streamed;
740 this.startTimeMillis = startTimeMillis;
741 this.spawnContinuation = spawnContinuation;
plfa445cda2020-10-29 10:39:30 -0700742 this.testResultDataBuilder = testResultDataBuilder;
743 this.spawnResults = spawnResults;
ulfjack4a5e1b72019-03-18 06:56:57 -0700744 }
745
746 @Nullable
747 @Override
748 public ListenableFuture<?> getFuture() {
749 return spawnContinuation.getFuture();
750 }
751
752 @Override
plfa445cda2020-10-29 10:39:30 -0700753 public TestAttemptContinuation execute()
754 throws InterruptedException, ExecException, IOException {
755
756 if (testResultDataBuilder == null) {
757 // We have two protos to represent test attempts:
758 // 1. com.google.devtools.build.lib.view.test.TestStatus.TestResultData represents both
759 // failed attempts and finished tests. Bazel stores this to disk to persist cached test
760 // result information across server restarts.
761 // 2. com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestResult
762 // represents only individual attempts (failed or not). Bazel reports this as an event to
763 // the Build Event Protocol, but never saves it to disk.
764 //
765 // The TestResult proto is always constructed from a TestResultData instance, either one
mschallerd322b722020-12-28 15:16:32 -0800766 // that is created right here, or one that is read back from disk.
plfa445cda2020-10-29 10:39:30 -0700767 TestResultData.Builder builder = null;
768 ImmutableList<SpawnResult> spawnResults;
769 try {
770 SpawnContinuation nextContinuation = spawnContinuation.execute();
771 if (!nextContinuation.isDone()) {
772 return new BazelTestAttemptContinuation(
773 testAction,
Chi Wang97fb2cf2021-06-21 23:23:19 -0700774 testMetadataHandler,
plfa445cda2020-10-29 10:39:30 -0700775 actionExecutionContext,
776 spawn,
777 resolvedPaths,
778 fileOutErr,
779 streamed,
780 startTimeMillis,
781 nextContinuation,
782 builder,
783 /* spawnResults= */ null);
784 }
785 spawnResults = nextContinuation.get();
786 builder = TestResultData.newBuilder();
mschallerd322b722020-12-28 15:16:32 -0800787 builder.setCachable(true).setTestPassed(true).setStatus(BlazeTestStatus.PASSED);
plfa445cda2020-10-29 10:39:30 -0700788 } catch (SpawnExecException e) {
789 if (e.isCatastrophic()) {
790 closeSuppressed(e, streamed);
791 closeSuppressed(e, fileOutErr);
792 throw e;
793 }
794 if (!e.getSpawnResult().setupSuccess()) {
795 closeSuppressed(e, streamed);
796 closeSuppressed(e, fileOutErr);
797 // Rethrow as the test could not be run and thus there's no point in retrying.
798 throw e;
799 }
800 spawnResults = ImmutableList.of(e.getSpawnResult());
801 builder = TestResultData.newBuilder();
802 builder
mschallerd322b722020-12-28 15:16:32 -0800803 .setCachable(e.getSpawnResult().status().isConsideredUserError())
plfa445cda2020-10-29 10:39:30 -0700804 .setTestPassed(false)
805 .setStatus(e.hasTimedOut() ? BlazeTestStatus.TIMEOUT : BlazeTestStatus.FAILED);
806 } catch (InterruptedException e) {
807 closeSuppressed(e, streamed);
808 closeSuppressed(e, fileOutErr);
809 throw e;
810 }
811 long endTimeMillis = actionExecutionContext.getClock().currentTimeMillis();
812
813 // SpawnActionContext guarantees the first entry to correspond to the spawn passed in (there
814 // may be additional entries due to tree artifact handling).
815 SpawnResult primaryResult = spawnResults.get(0);
816
817 // The SpawnResult of a remotely cached or remotely executed action may not have walltime
818 // set. We fall back to the time measured here for backwards compatibility.
819 long durationMillis = endTimeMillis - startTimeMillis;
820 durationMillis =
821 primaryResult.getWallTime().orElse(Duration.ofMillis(durationMillis)).toMillis();
822
823 builder
824 .setStartTimeMillisEpoch(startTimeMillis)
825 .addTestTimes(durationMillis)
826 .addTestProcessTimes(durationMillis)
827 .setRunDurationMillis(durationMillis)
828 .setHasCoverage(testAction.isCoverageMode());
829
830 if (testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing()) {
831 actionExecutionContext
832 .getMetadataHandler()
833 .getMetadata(testAction.getCoverageDirectoryTreeArtifact());
834 ImmutableSet<? extends ActionInput> expandedCoverageDir =
835 actionExecutionContext
836 .getMetadataHandler()
837 .getTreeArtifactChildren(
838 (SpecialArtifact) testAction.getCoverageDirectoryTreeArtifact());
839 Spawn coveragePostProcessingSpawn =
840 createCoveragePostProcessingSpawn(
841 actionExecutionContext,
842 testAction,
843 ImmutableList.copyOf(expandedCoverageDir),
844 tmpDirRoot,
845 executionOptions.splitXmlGeneration);
846 SpawnStrategyResolver spawnStrategyResolver =
847 actionExecutionContext.getContext(SpawnStrategyResolver.class);
848
849 Path testRoot =
850 actionExecutionContext.getInputPath(testAction.getTestLog()).getParentDirectory();
851
852 Path out = testRoot.getChild("coverage.log");
853 Path err = testRoot.getChild("coverage.err");
854 FileOutErr coverageOutErr = new FileOutErr(out, err);
855 ActionExecutionContext actionExecutionContextWithCoverageFileOutErr =
856 actionExecutionContext.withFileOutErr(coverageOutErr);
857
858 SpawnContinuation coveragePostProcessingContinuation =
859 spawnStrategyResolver.beginExecution(
860 coveragePostProcessingSpawn, actionExecutionContextWithCoverageFileOutErr);
861 writeOutFile(coverageOutErr.getErrorPath(), coverageOutErr.getOutputPath());
862 appendCoverageLog(coverageOutErr, fileOutErr);
863 return new BazelCoveragePostProcessingContinuation(
ulfjack4a5e1b72019-03-18 06:56:57 -0700864 testAction,
Chi Wang97fb2cf2021-06-21 23:23:19 -0700865 testMetadataHandler,
ulfjack4a5e1b72019-03-18 06:56:57 -0700866 actionExecutionContext,
867 spawn,
868 resolvedPaths,
869 fileOutErr,
870 streamed,
plfa445cda2020-10-29 10:39:30 -0700871 builder,
872 spawnResults,
873 coveragePostProcessingContinuation);
874 } else {
875 this.spawnResults = spawnResults;
876 this.testResultDataBuilder = builder;
ulfjack4a5e1b72019-03-18 06:56:57 -0700877 }
ulfjack4a5e1b72019-03-18 06:56:57 -0700878 }
plfa445cda2020-10-29 10:39:30 -0700879
880 Verify.verify(
881 !(testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing())
882 || testAction.getCoverageData().getPath().exists());
883 Verify.verifyNotNull(spawnResults);
884 Verify.verifyNotNull(testResultDataBuilder);
ulfjack4a5e1b72019-03-18 06:56:57 -0700885
ulfjack415165a2019-09-12 07:56:40 -0700886 try {
887 if (!fileOutErr.hasRecordedOutput()) {
888 // Make sure that the test.log exists.
889 FileSystemUtils.touchFile(fileOutErr.getOutputPath());
890 }
891 // Append any error output to the test.log. This is very rare.
plfa445cda2020-10-29 10:39:30 -0700892 writeOutFile(fileOutErr.getErrorPath(), fileOutErr.getOutputPath());
ulfjack415165a2019-09-12 07:56:40 -0700893 fileOutErr.close();
894 if (streamed != null) {
895 streamed.close();
896 }
897 } catch (IOException e) {
mschaller45576672020-06-10 19:15:07 -0700898 throw new EnvironmentalExecException(e, Code.TEST_OUT_ERR_IO_EXCEPTION);
ulfjack4a5e1b72019-03-18 06:56:57 -0700899 }
900
Chi Wang97fb2cf2021-06-21 23:23:19 -0700901 Path xmlOutputPath = resolvedPaths.getXmlOutputPath();
902 boolean testXmlGenerated = xmlOutputPath.exists();
903 if (!testXmlGenerated && testMetadataHandler != null) {
904 testXmlGenerated =
905 testMetadataHandler.fileInjected(
906 createArtifactOutput(testAction, testAction.getXmlOutputPath()));
907 }
ulfjack4a5e1b72019-03-18 06:56:57 -0700908
909 // If the test did not create a test.xml, and --experimental_split_xml_generation is enabled,
910 // then we run a separate action to create a test.xml from test.log. We do this as a spawn
911 // rather than doing it locally in-process, as the test.log file may only exist remotely (when
912 // remote execution is enabled), and we do not want to have to download it.
ulfjack4a5e1b72019-03-18 06:56:57 -0700913 if (executionOptions.splitXmlGeneration
914 && fileOutErr.getOutputPath().exists()
Chi Wang97fb2cf2021-06-21 23:23:19 -0700915 && !testXmlGenerated) {
Yun Peng35518982020-12-15 06:04:28 -0800916 Spawn xmlGeneratingSpawn =
917 createXmlGeneratingSpawn(testAction, spawn.getEnvironment(), spawnResults.get(0));
jcater56b9b182020-04-17 13:55:34 -0700918 SpawnStrategyResolver spawnStrategyResolver =
919 actionExecutionContext.getContext(SpawnStrategyResolver.class);
ulfjack4a5e1b72019-03-18 06:56:57 -0700920 // We treat all failures to generate the test.xml here as catastrophic, and won't rerun
921 // the test if this fails. We redirect the output to a temporary file.
922 FileOutErr xmlSpawnOutErr = actionExecutionContext.getFileOutErr().childOutErr();
ulfjack4a5e1b72019-03-18 06:56:57 -0700923 try {
ulfjack415165a2019-09-12 07:56:40 -0700924 SpawnContinuation xmlContinuation =
jcater56b9b182020-04-17 13:55:34 -0700925 spawnStrategyResolver.beginExecution(
Chi Wang97fb2cf2021-06-21 23:23:19 -0700926 xmlGeneratingSpawn,
927 actionExecutionContext
928 .withFileOutErr(xmlSpawnOutErr)
929 .withMetadataHandler(testMetadataHandler));
ulfjack4a5e1b72019-03-18 06:56:57 -0700930 return new BazelXmlCreationContinuation(
plfa445cda2020-10-29 10:39:30 -0700931 resolvedPaths, xmlSpawnOutErr, testResultDataBuilder, spawnResults, xmlContinuation);
ulfjack415165a2019-09-12 07:56:40 -0700932 } catch (InterruptedException e) {
933 closeSuppressed(e, xmlSpawnOutErr);
934 throw e;
ulfjack4a5e1b72019-03-18 06:56:57 -0700935 }
936 }
937
938 TestCase details = parseTestResult(xmlOutputPath);
939 if (details != null) {
plfa445cda2020-10-29 10:39:30 -0700940 testResultDataBuilder.setTestCase(details);
ulfjack4a5e1b72019-03-18 06:56:57 -0700941 }
942
ulfjackeb8cfc12019-04-29 04:43:14 -0700943 BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo =
plfa445cda2020-10-29 10:39:30 -0700944 extractExecutionInfo(spawnResults.get(0), testResultDataBuilder);
ulfjack4a5e1b72019-03-18 06:56:57 -0700945 StandaloneTestResult standaloneTestResult =
946 StandaloneTestResult.builder()
947 .setSpawnResults(spawnResults)
948 // We return the TestResultData.Builder rather than the finished TestResultData
949 // instance, as we may have to rename the output files in case the test needs to be
950 // rerun (if it failed here _and_ is marked flaky _and_ the number of flaky attempts
951 // is larger than 1).
plfa445cda2020-10-29 10:39:30 -0700952 .setTestResultDataBuilder(testResultDataBuilder)
ulfjackeb8cfc12019-04-29 04:43:14 -0700953 .setExecutionInfo(executionInfo)
ulfjack4a5e1b72019-03-18 06:56:57 -0700954 .build();
955 return TestAttemptContinuation.of(standaloneTestResult);
956 }
957 }
958
959 private final class BazelXmlCreationContinuation extends TestAttemptContinuation {
960 private final ResolvedPaths resolvedPaths;
961 private final FileOutErr fileOutErr;
962 private final TestResultData.Builder builder;
963 private final List<SpawnResult> primarySpawnResults;
964 private final SpawnContinuation spawnContinuation;
965
966 BazelXmlCreationContinuation(
967 ResolvedPaths resolvedPaths,
968 FileOutErr fileOutErr,
969 TestResultData.Builder builder,
970 List<SpawnResult> primarySpawnResults,
971 SpawnContinuation spawnContinuation) {
972 this.resolvedPaths = resolvedPaths;
973 this.fileOutErr = fileOutErr;
974 this.builder = builder;
975 this.primarySpawnResults = primarySpawnResults;
976 this.spawnContinuation = spawnContinuation;
977 }
978
979 @Nullable
980 @Override
981 public ListenableFuture<?> getFuture() {
982 return spawnContinuation.getFuture();
983 }
984
985 @Override
ulfjack415165a2019-09-12 07:56:40 -0700986 public TestAttemptContinuation execute() throws InterruptedException, ExecException {
ulfjack4a5e1b72019-03-18 06:56:57 -0700987 SpawnContinuation nextContinuation;
988 try {
989 nextContinuation = spawnContinuation.execute();
990 if (!nextContinuation.isDone()) {
991 return new BazelXmlCreationContinuation(
992 resolvedPaths, fileOutErr, builder, primarySpawnResults, nextContinuation);
993 }
994 } catch (ExecException | InterruptedException e) {
995 closeSuppressed(e, fileOutErr);
996 throw e;
997 }
998
ajurkowskie3bf8b72020-01-23 11:58:11 -0800999 ImmutableList.Builder<SpawnResult> spawnResults = ImmutableList.builder();
ulfjack4a5e1b72019-03-18 06:56:57 -07001000 spawnResults.addAll(primarySpawnResults);
1001 spawnResults.addAll(nextContinuation.get());
1002
1003 Path xmlOutputPath = resolvedPaths.getXmlOutputPath();
1004 TestCase details = parseTestResult(xmlOutputPath);
1005 if (details != null) {
1006 builder.setTestCase(details);
1007 }
1008
ulfjackeb8cfc12019-04-29 04:43:14 -07001009 BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo =
ulfjack4a5e1b72019-03-18 06:56:57 -07001010 extractExecutionInfo(primarySpawnResults.get(0), builder);
1011 StandaloneTestResult standaloneTestResult =
1012 StandaloneTestResult.builder()
ajurkowskie3bf8b72020-01-23 11:58:11 -08001013 .setSpawnResults(spawnResults.build())
ulfjack4a5e1b72019-03-18 06:56:57 -07001014 // We return the TestResultData.Builder rather than the finished TestResultData
1015 // instance, as we may have to rename the output files in case the test needs to be
1016 // rerun (if it failed here _and_ is marked flaky _and_ the number of flaky attempts
1017 // is larger than 1).
1018 .setTestResultDataBuilder(builder)
ulfjackeb8cfc12019-04-29 04:43:14 -07001019 .setExecutionInfo(executionInfo)
ulfjack4a5e1b72019-03-18 06:56:57 -07001020 .build();
1021 return TestAttemptContinuation.of(standaloneTestResult);
1022 }
1023 }
plfa445cda2020-10-29 10:39:30 -07001024
1025 private final class BazelCoveragePostProcessingContinuation extends TestAttemptContinuation {
1026 private final ResolvedPaths resolvedPaths;
Chi Wang97fb2cf2021-06-21 23:23:19 -07001027 @Nullable private final TestMetadataHandler testMetadataHandler;
plfa445cda2020-10-29 10:39:30 -07001028 private final FileOutErr fileOutErr;
1029 private final Closeable streamed;
1030 private final TestResultData.Builder testResultDataBuilder;
1031 private final ImmutableList<SpawnResult> primarySpawnResults;
1032 private final SpawnContinuation spawnContinuation;
1033 private final TestRunnerAction testAction;
1034 private final ActionExecutionContext actionExecutionContext;
1035 private final Spawn spawn;
1036
1037 BazelCoveragePostProcessingContinuation(
1038 TestRunnerAction testAction,
Chi Wang97fb2cf2021-06-21 23:23:19 -07001039 @Nullable TestMetadataHandler testMetadataHandler,
plfa445cda2020-10-29 10:39:30 -07001040 ActionExecutionContext actionExecutionContext,
1041 Spawn spawn,
1042 ResolvedPaths resolvedPaths,
1043 FileOutErr fileOutErr,
1044 Closeable streamed,
1045 TestResultData.Builder testResultDataBuilder,
1046 ImmutableList<SpawnResult> primarySpawnResults,
1047 SpawnContinuation spawnContinuation) {
1048 this.testAction = testAction;
Chi Wang97fb2cf2021-06-21 23:23:19 -07001049 this.testMetadataHandler = testMetadataHandler;
plfa445cda2020-10-29 10:39:30 -07001050 this.actionExecutionContext = actionExecutionContext;
1051 this.spawn = spawn;
1052 this.resolvedPaths = resolvedPaths;
1053 this.fileOutErr = fileOutErr;
1054 this.streamed = streamed;
1055 this.testResultDataBuilder = testResultDataBuilder;
1056 this.primarySpawnResults = primarySpawnResults;
1057 this.spawnContinuation = spawnContinuation;
1058 }
1059
1060 @Nullable
1061 @Override
1062 public ListenableFuture<?> getFuture() {
1063 return spawnContinuation.getFuture();
1064 }
1065
1066 @Override
1067 public TestAttemptContinuation execute() throws InterruptedException, ExecException {
1068 SpawnContinuation nextContinuation = null;
1069 try {
1070 nextContinuation = spawnContinuation.execute();
1071 if (!nextContinuation.isDone()) {
1072 return new BazelCoveragePostProcessingContinuation(
1073 testAction,
Chi Wang97fb2cf2021-06-21 23:23:19 -07001074 testMetadataHandler,
plfa445cda2020-10-29 10:39:30 -07001075 actionExecutionContext,
1076 spawn,
1077 resolvedPaths,
1078 fileOutErr,
1079 streamed,
1080 testResultDataBuilder,
1081 ImmutableList.<SpawnResult>builder()
1082 .addAll(primarySpawnResults)
1083 .addAll(nextContinuation.get())
1084 .build(),
1085 nextContinuation);
1086 }
1087 } catch (SpawnExecException e) {
1088 if (e.isCatastrophic()) {
1089 closeSuppressed(e, streamed);
1090 closeSuppressed(e, fileOutErr);
1091 throw e;
1092 }
1093 if (!e.getSpawnResult().setupSuccess()) {
1094 closeSuppressed(e, streamed);
1095 closeSuppressed(e, fileOutErr);
1096 // Rethrow as the test could not be run and thus there's no point in retrying.
1097 throw e;
1098 }
1099 testResultDataBuilder
mschallerd322b722020-12-28 15:16:32 -08001100 .setCachable(e.getSpawnResult().status().isConsideredUserError())
plfa445cda2020-10-29 10:39:30 -07001101 .setTestPassed(false)
1102 .setStatus(e.hasTimedOut() ? BlazeTestStatus.TIMEOUT : BlazeTestStatus.FAILED);
1103 } catch (ExecException | InterruptedException e) {
1104 closeSuppressed(e, fileOutErr);
1105 closeSuppressed(e, streamed);
1106 throw e;
1107 }
1108
1109 return new BazelTestAttemptContinuation(
1110 testAction,
Chi Wang97fb2cf2021-06-21 23:23:19 -07001111 testMetadataHandler,
plfa445cda2020-10-29 10:39:30 -07001112 actionExecutionContext,
1113 spawn,
1114 resolvedPaths,
1115 fileOutErr,
1116 streamed,
1117 /* startTimeMillis= */ 0,
1118 nextContinuation,
1119 testResultDataBuilder,
1120 primarySpawnResults);
1121 }
1122 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001123}