Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 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 Adams | e11063a | 2016-12-15 17:33:32 +0000 | [diff] [blame] | 15 | package com.google.devtools.build.lib.exec; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 16 | |
George Gensure | c53bdaf | 2020-06-04 01:32:39 -0700 | [diff] [blame] | 17 | import com.google.common.annotations.VisibleForTesting; |
Laszlo Csomor | 6b08ff1 | 2019-05-02 01:35:41 -0700 | [diff] [blame] | 18 | import com.google.common.base.Preconditions; |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 19 | import com.google.common.base.Verify; |
Klaus Aehlig | 89512a7 | 2017-02-10 14:36:43 +0000 | [diff] [blame] | 20 | import com.google.common.collect.ImmutableList; |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 21 | import com.google.common.collect.ImmutableMap; |
ulfjack | e4cca14 | 2020-01-08 04:44:40 -0800 | [diff] [blame] | 22 | import com.google.common.collect.ImmutableSet; |
Chi Wang | f5cf8b0 | 2021-11-07 20:05:55 -0800 | [diff] [blame] | 23 | import com.google.common.collect.Maps; |
felly | 7148054 | 2019-02-06 07:55:51 -0800 | [diff] [blame] | 24 | import com.google.common.io.ByteStreams; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 25 | import com.google.common.util.concurrent.ListenableFuture; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 26 | import com.google.devtools.build.lib.actions.ActionExecutionContext; |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 27 | import com.google.devtools.build.lib.actions.ActionInput; |
ulfjack | 0858ae1 | 2018-07-27 02:37:53 -0700 | [diff] [blame] | 28 | import com.google.devtools.build.lib.actions.ActionInputHelper; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 29 | import com.google.devtools.build.lib.actions.Artifact; |
| 30 | import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact; |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 31 | import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 32 | import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; |
buchgr | 580038e | 2019-09-02 02:16:19 -0700 | [diff] [blame] | 33 | import com.google.devtools.build.lib.actions.ArtifactPathResolver; |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 34 | import com.google.devtools.build.lib.actions.EnvironmentalExecException; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 35 | import com.google.devtools.build.lib.actions.ExecException; |
buchgr | 9f7edd7 | 2017-07-14 12:58:50 +0200 | [diff] [blame] | 36 | import com.google.devtools.build.lib.actions.ExecutionRequirements; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 37 | import com.google.devtools.build.lib.actions.FileArtifactValue; |
Ulf Adams | d345cf9 | 2017-03-07 10:04:53 +0000 | [diff] [blame] | 38 | import com.google.devtools.build.lib.actions.SimpleSpawn; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 39 | import com.google.devtools.build.lib.actions.Spawn; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 40 | import com.google.devtools.build.lib.actions.SpawnContinuation; |
ruperts | 4050a89 | 2017-10-07 00:46:20 +0200 | [diff] [blame] | 41 | import com.google.devtools.build.lib.actions.SpawnResult; |
buchgr | 580038e | 2019-09-02 02:16:19 -0700 | [diff] [blame] | 42 | import com.google.devtools.build.lib.actions.TestExecException; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 43 | import com.google.devtools.build.lib.actions.cache.MetadataHandler; |
ulfjack | 0858ae1 | 2018-07-27 02:37:53 -0700 | [diff] [blame] | 44 | import com.google.devtools.build.lib.analysis.actions.SpawnAction; |
jcater | f057da4 | 2020-04-14 05:08:40 -0700 | [diff] [blame] | 45 | import com.google.devtools.build.lib.analysis.test.TestAttempt; |
ulfjack | ab21d18 | 2017-08-10 15:36:14 +0200 | [diff] [blame] | 46 | import com.google.devtools.build.lib.analysis.test.TestResult; |
| 47 | import com.google.devtools.build.lib.analysis.test.TestRunnerAction; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 48 | import com.google.devtools.build.lib.analysis.test.TestRunnerAction.ResolvedPaths; |
jcater | f057da4 | 2020-04-14 05:08:40 -0700 | [diff] [blame] | 49 | import com.google.devtools.build.lib.analysis.test.TestStrategy; |
Benjamin Peterson | 1bbeadc | 2018-04-26 05:27:10 -0700 | [diff] [blame] | 50 | import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; |
ulfjack | 797325f | 2019-10-17 06:33:40 -0700 | [diff] [blame] | 51 | import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestResult.ExecutionInfo; |
Googler | 2f08a18 | 2017-09-21 18:51:20 +0200 | [diff] [blame] | 52 | import com.google.devtools.build.lib.buildeventstream.TestFileNameConstants; |
ulfjack | e4cca14 | 2020-01-08 04:44:40 -0800 | [diff] [blame] | 53 | import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| 54 | import com.google.devtools.build.lib.collect.nestedset.Order; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 55 | import com.google.devtools.build.lib.events.Reporter; |
mschaller | 4557667 | 2020-06-10 19:15:07 -0700 | [diff] [blame] | 56 | import com.google.devtools.build.lib.server.FailureDetails.Execution.Code; |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 57 | import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 58 | import com.google.devtools.build.lib.server.FailureDetails.TestAction; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 59 | import com.google.devtools.build.lib.skyframe.TreeArtifactValue; |
Klaus Aehlig | 89512a7 | 2017-02-10 14:36:43 +0000 | [diff] [blame] | 60 | import com.google.devtools.build.lib.util.Pair; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 61 | import com.google.devtools.build.lib.util.io.FileOutErr; |
felly | 7148054 | 2019-02-06 07:55:51 -0800 | [diff] [blame] | 62 | import com.google.devtools.build.lib.vfs.FileStatus; |
Damien Martin-Guillerez | cc49b66 | 2016-12-29 15:53:08 +0000 | [diff] [blame] | 63 | import com.google.devtools.build.lib.vfs.FileSystemUtils; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 64 | import com.google.devtools.build.lib.vfs.Path; |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 65 | import com.google.devtools.build.lib.vfs.PathFragment; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 66 | import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus; |
| 67 | import com.google.devtools.build.lib.view.test.TestStatus.TestCase; |
| 68 | import com.google.devtools.build.lib.view.test.TestStatus.TestResultData; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 69 | import java.io.Closeable; |
| 70 | import java.io.IOException; |
felly | 7148054 | 2019-02-06 07:55:51 -0800 | [diff] [blame] | 71 | import java.io.InputStream; |
| 72 | import java.io.OutputStream; |
ulfjack | cccd718 | 2018-03-28 16:49:14 -0700 | [diff] [blame] | 73 | import java.time.Duration; |
ruperts | 7967f33 | 2017-11-21 16:37:13 -0800 | [diff] [blame] | 74 | import java.util.List; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 75 | import java.util.Map; |
ulfjack | 404a670 | 2018-02-21 01:37:55 -0800 | [diff] [blame] | 76 | import java.util.TreeMap; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 77 | import java.util.concurrent.ConcurrentHashMap; |
| 78 | import java.util.concurrent.ConcurrentMap; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 79 | import javax.annotation.Nullable; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 80 | |
Ulf Adams | e11063a | 2016-12-15 17:33:32 +0000 | [diff] [blame] | 81 | /** Runs TestRunnerAction actions. */ |
lberki | 037c9dc | 2018-02-09 06:06:49 -0800 | [diff] [blame] | 82 | // TODO(bazel-team): add tests for this strategy. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 83 | public class StandaloneTestStrategy extends TestStrategy { |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 84 | private static final ImmutableMap<String, String> ENV_VARS = |
| 85 | ImmutableMap.<String, String>builder() |
| 86 | .put("TZ", "UTC") |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 87 | .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 Peng | 62c3105 | 2017-06-07 15:06:51 -0400 | [diff] [blame] | 93 | .put("RUN_UNDER_RUNFILES", "1") |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 94 | .build(); |
| 95 | |
| 96 | public static final TestPolicy DEFAULT_LOCAL_POLICY = new TestPolicy(ENV_VARS); |
| 97 | |
Ulf Adams | b21cfa1 | 2016-12-16 14:31:42 +0000 | [diff] [blame] | 98 | protected final Path tmpDirRoot; |
Kristina Chodorow | 379f7c0 | 2015-04-21 18:50:50 +0000 | [diff] [blame] | 99 | |
Han-Wen Nienhuys | a51b3f7 | 2015-09-28 10:15:15 +0000 | [diff] [blame] | 100 | public StandaloneTestStrategy( |
ulfjack | acd291a | 2017-06-16 15:25:42 +0200 | [diff] [blame] | 101 | ExecutionOptions executionOptions, BinTools binTools, Path tmpDirRoot) { |
| 102 | super(executionOptions, binTools); |
Ulf Adams | b21cfa1 | 2016-12-16 14:31:42 +0000 | [diff] [blame] | 103 | this.tmpDirRoot = tmpDirRoot; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 104 | } |
| 105 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 106 | @Override |
ulfjack | 645fe0f | 2019-02-28 05:58:36 -0800 | [diff] [blame] | 107 | public TestRunnerSpawn createTestRunnerSpawn( |
michajlo | b091d30 | 2020-11-17 14:29:42 -0800 | [diff] [blame] | 108 | TestRunnerAction action, ActionExecutionContext actionExecutionContext) |
| 109 | throws ExecException, InterruptedException { |
buchgr | 580038e | 2019-09-02 02:16:19 -0700 | [diff] [blame] | 110 | if (action.getExecutionSettings().getInputManifest() == null) { |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 111 | String errorMessage = "cannot run local tests with --nobuild_runfile_manifests"; |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 112 | throw new TestExecException( |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 113 | errorMessage, |
| 114 | FailureDetail.newBuilder() |
| 115 | .setTestAction( |
| 116 | TestAction.newBuilder().setCode(TestAction.Code.LOCAL_TEST_PREREQ_UNMET)) |
| 117 | .setMessage(errorMessage) |
| 118 | .build()); |
buchgr | 580038e | 2019-09-02 02:16:19 -0700 | [diff] [blame] | 119 | } |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 120 | Map<String, String> testEnvironment = |
| 121 | createEnvironment( |
| 122 | actionExecutionContext, action, tmpDirRoot, executionOptions.splitXmlGeneration); |
Han-Wen Nienhuys | b6f557b | 2015-06-23 12:39:03 +0000 | [diff] [blame] | 123 | |
ulfjack | 404a670 | 2018-02-21 01:37:55 -0800 | [diff] [blame] | 124 | Map<String, String> executionInfo = |
| 125 | new TreeMap<>(action.getTestProperties().getExecutionInfo()); |
buchgr | 9f7edd7 | 2017-07-14 12:58:50 +0200 | [diff] [blame] | 126 | if (!action.shouldCacheResult()) { |
| 127 | executionInfo.put(ExecutionRequirements.NO_CACHE, ""); |
| 128 | } |
ulfjack | 404a670 | 2018-02-21 01:37:55 -0800 | [diff] [blame] | 129 | executionInfo.put(ExecutionRequirements.TIMEOUT, "" + getTimeout(action).getSeconds()); |
Ulf Adams | 19948b0 | 2017-01-10 16:27:59 +0000 | [diff] [blame] | 130 | |
janakr | 91d93f6 | 2022-01-31 15:31:57 -0800 | [diff] [blame] | 131 | SimpleSpawn.LocalResourcesSupplier localResourcesSupplier = |
| 132 | () -> |
| 133 | action |
| 134 | .getTestProperties() |
| 135 | .getLocalResourceUsage( |
| 136 | action.getOwner().getLabel(), executionOptions.usingLocalTestJobs()); |
philwo | da21ba7 | 2017-05-02 20:25:12 +0200 | [diff] [blame] | 137 | |
Ulf Adams | 19948b0 | 2017-01-10 16:27:59 +0000 | [diff] [blame] | 138 | Spawn spawn = |
Ulf Adams | d345cf9 | 2017-03-07 10:04:53 +0000 | [diff] [blame] | 139 | new SimpleSpawn( |
| 140 | action, |
lberki | 037c9dc | 2018-02-09 06:06:49 -0800 | [diff] [blame] | 141 | getArgs(action), |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 142 | ImmutableMap.copyOf(testEnvironment), |
ulfjack | 404a670 | 2018-02-21 01:37:55 -0800 | [diff] [blame] | 143 | ImmutableMap.copyOf(executionInfo), |
buchgr | a30df55 | 2019-08-29 04:54:41 -0700 | [diff] [blame] | 144 | action.getRunfilesSupplier(), |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 145 | ImmutableMap.of(), |
ulfjack | e4cca14 | 2020-01-08 04:44:40 -0800 | [diff] [blame] | 146 | /*inputs=*/ action.getInputs(), |
cushon | 1f33504 | 2022-03-21 09:18:42 -0700 | [diff] [blame^] | 147 | NestedSetBuilder.emptySet(Order.STABLE_ORDER), |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 148 | createSpawnOutputs(action), |
janakr | 82b3896 | 2022-02-01 07:12:10 -0800 | [diff] [blame] | 149 | /*mandatoryOutputs=*/ ImmutableSet.of(), |
janakr | 91d93f6 | 2022-01-31 15:31:57 -0800 | [diff] [blame] | 150 | localResourcesSupplier); |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 151 | 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()); |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 156 | return new StandaloneTestRunnerSpawn( |
ulfjack | fb04c5b | 2019-04-26 00:15:05 -0700 | [diff] [blame] | 157 | action, actionExecutionContext, spawn, tmpDir, workingDirectory, execRoot); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 158 | } |
| 159 | |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 160 | 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 | |
ajurkowski | bd74a87 | 2020-01-23 13:51:33 -0800 | [diff] [blame] | 175 | private static ImmutableList<Pair<String, Path>> renameOutputs( |
ulfjack | 77c9f5e | 2017-06-19 14:17:52 +0200 | [diff] [blame] | 176 | ActionExecutionContext actionExecutionContext, |
Damien Martin-Guillerez | cc49b66 | 2016-12-29 15:53:08 +0000 | [diff] [blame] | 177 | TestRunnerAction action, |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 178 | ImmutableList<Pair<String, Path>> testOutputs, |
| 179 | int attemptId) |
Damien Martin-Guillerez | cc49b66 | 2016-12-29 15:53:08 +0000 | [diff] [blame] | 180 | throws IOException { |
| 181 | // Rename outputs |
| 182 | String namePrefix = |
| 183 | FileSystemUtils.removeExtension(action.getTestLog().getExecPath().getBaseName()); |
shahan | 18726b7 | 2018-03-15 14:18:46 -0700 | [diff] [blame] | 184 | Path testRoot = actionExecutionContext.getInputPath(action.getTestLog()).getParentDirectory(); |
Googler | 2f08a18 | 2017-09-21 18:51:20 +0200 | [diff] [blame] | 185 | Path attemptsDir = testRoot.getChild(namePrefix + "_attempts"); |
Damien Martin-Guillerez | cc49b66 | 2016-12-29 15:53:08 +0000 | [diff] [blame] | 186 | attemptsDir.createDirectory(); |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 187 | String attemptPrefix = "attempt_" + attemptId; |
Damien Martin-Guillerez | cc49b66 | 2016-12-29 15:53:08 +0000 | [diff] [blame] | 188 | Path testLog = attemptsDir.getChild(attemptPrefix + ".log"); |
Googler | 2f08a18 | 2017-09-21 18:51:20 +0200 | [diff] [blame] | 189 | |
| 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. |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 192 | ImmutableList.Builder<Pair<String, Path>> testOutputsBuilder = new ImmutableList.Builder<>(); |
Googler | 2f08a18 | 2017-09-21 18:51:20 +0200 | [diff] [blame] | 193 | 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(); |
ulfjack | 0c61edb | 2018-03-27 11:32:07 -0700 | [diff] [blame] | 196 | 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); |
Googler | 2f08a18 | 2017-09-21 18:51:20 +0200 | [diff] [blame] | 203 | |
ulfjack | 0c61edb | 2018-03-27 11:32:07 -0700 | [diff] [blame] | 204 | // e.g. attempt_1.dir/file |
| 205 | String destinationPathFragmentStr = |
| 206 | relativeToTestDirectory.getSafePathString().replaceFirst("test", attemptPrefix); |
| 207 | PathFragment destinationPathFragment = PathFragment.create(destinationPathFragmentStr); |
olaola | c35fe50 | 2017-10-17 04:09:07 +0200 | [diff] [blame] | 208 | |
ulfjack | 0c61edb | 2018-03-27 11:32:07 -0700 | [diff] [blame] | 209 | // e.g. /attemptsDir/attempt_1.dir/file |
| 210 | destinationPath = attemptsDir.getRelative(destinationPathFragment); |
| 211 | destinationPath.getParentDirectory().createDirectory(); |
| 212 | } |
olaola | c35fe50 | 2017-10-17 04:09:07 +0200 | [diff] [blame] | 213 | |
ulfjack | 0c61edb | 2018-03-27 11:32:07 -0700 | [diff] [blame] | 214 | // Move to the destination. |
Googler | 2f08a18 | 2017-09-21 18:51:20 +0200 | [diff] [blame] | 215 | testOutputPath.renameTo(destinationPath); |
| 216 | |
| 217 | testOutputsBuilder.add(Pair.of(testOutput.getFirst(), destinationPath)); |
Damien Martin-Guillerez | cc49b66 | 2016-12-29 15:53:08 +0000 | [diff] [blame] | 218 | } |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 219 | 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 | |
arostovtsev | a6bfab0 | 2022-02-14 22:15:58 -0800 | [diff] [blame] | 232 | private void finalizeTest( |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 233 | 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(); |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 257 | TestResult result = |
| 258 | new TestResult(action, data, false, standaloneTestResult.primarySystemFailure()); |
arostovtsev | a6bfab0 | 2022-02-14 22:15:58 -0800 | [diff] [blame] | 259 | postTestResult(actionExecutionContext, result); |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 260 | } |
| 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 | } |
Googler | 2f08a18 | 2017-09-21 18:51:20 +0200 | [diff] [blame] | 275 | |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 276 | // 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 Gensure | c53bdaf | 2020-06-04 01:32:39 -0700 | [diff] [blame] | 280 | Preconditions.checkState(renamedTestLog == null, "multiple test_log matches"); |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 281 | renamedTestLog = pair.getSecond(); |
| 282 | } |
| 283 | } |
ulfjack | 797325f | 2019-10-17 06:33:40 -0700 | [diff] [blame] | 284 | |
| 285 | TestResultData.Builder dataBuilder = result.testResultDataBuilder(); |
George Gensure | c53bdaf | 2020-06-04 01:32:39 -0700 | [diff] [blame] | 286 | // If the test log path does not exist, mark the test as incomplete |
| 287 | if (renamedTestLog == null) { |
| 288 | dataBuilder.setStatus(BlazeTestStatus.INCOMPLETE); |
| 289 | } |
| 290 | |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 291 | if (dataBuilder.getStatus() == BlazeTestStatus.PASSED) { |
| 292 | dataBuilder.setPassedLog(renamedTestLog.toString()); |
ulfjack | 797325f | 2019-10-17 06:33:40 -0700 | [diff] [blame] | 293 | } else if (dataBuilder.getStatus() == BlazeTestStatus.INCOMPLETE) { |
| 294 | // Incomplete (cancelled) test runs don't have a log. |
| 295 | Preconditions.checkState(renamedTestLog == null); |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 296 | } else { |
| 297 | dataBuilder.addFailedLogs(renamedTestLog.toString()); |
| 298 | } |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 299 | |
| 300 | // Add the test log to the output |
| 301 | TestResultData data = dataBuilder.build(); |
ulfjack | 77c9f5e | 2017-06-19 14:17:52 +0200 | [diff] [blame] | 302 | actionExecutionContext |
Benjamin Peterson | 1bbeadc | 2018-04-26 05:27:10 -0700 | [diff] [blame] | 303 | .getEventHandler() |
Klaus Aehlig | 0aaa6dd | 2017-03-08 17:33:08 +0000 | [diff] [blame] | 304 | .post( |
ulfjack | b22c7ee | 2018-05-14 05:32:03 -0700 | [diff] [blame] | 305 | TestAttempt.forExecutedTestResult( |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 306 | action, data, attemptId, testOutputs, result.executionInfo(), isLastAttempt)); |
| 307 | processTestOutput(actionExecutionContext, data, action.getTestName(), renamedTestLog); |
ulfjack | b70f0c1 | 2019-02-27 07:23:38 -0800 | [diff] [blame] | 308 | return new StandaloneFailedAttemptResult(data); |
Damien Martin-Guillerez | cc49b66 | 2016-12-29 15:53:08 +0000 | [diff] [blame] | 309 | } |
| 310 | |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 311 | private static Map<String, String> setupEnvironment( |
| 312 | TestRunnerAction action, |
| 313 | Map<String, String> clientEnv, |
| 314 | Path execRoot, |
| 315 | Path runfilesDir, |
ulfjack | d1c5329 | 2017-06-08 18:09:01 +0200 | [diff] [blame] | 316 | Path tmpDir) { |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 317 | PathFragment relativeTmpDir; |
Kristina Chodorow | 88de40e | 2016-08-18 14:24:58 +0000 | [diff] [blame] | 318 | if (tmpDir.startsWith(execRoot)) { |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 319 | relativeTmpDir = tmpDir.relativeTo(execRoot); |
Kristina Chodorow | 88de40e | 2016-08-18 14:24:58 +0000 | [diff] [blame] | 320 | } else { |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 321 | relativeTmpDir = tmpDir.asFragment(); |
Kristina Chodorow | 88de40e | 2016-08-18 14:24:58 +0000 | [diff] [blame] | 322 | } |
Ulf Adams | b3c833f | 2017-02-01 14:47:02 +0000 | [diff] [blame] | 323 | return DEFAULT_LOCAL_POLICY.computeTestEnvironment( |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 324 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 402 | } |
Kristina Chodorow | b579b94 | 2015-03-02 15:51:58 +0000 | [diff] [blame] | 403 | |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 404 | private TestAttemptContinuation beginTestAttempt( |
| 405 | TestRunnerAction testAction, |
| 406 | Spawn spawn, |
| 407 | ActionExecutionContext actionExecutionContext, |
| 408 | Path execRoot) |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 409 | throws IOException, InterruptedException { |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 410 | 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; |
jcater | d990df5 | 2020-04-07 04:38:15 -0700 | [diff] [blame] | 415 | if (executionOptions.testOutput.equals(ExecutionOptions.TestOutputFormat.STREAMED)) { |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 416 | streamed = |
michajlo | 9885c2f | 2020-03-09 12:33:06 -0700 | [diff] [blame] | 417 | createStreamedTestOutput( |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 418 | Reporter.outErrForReporter(actionExecutionContext.getEventHandler()), out); |
| 419 | } |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 420 | |
| 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 | |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 430 | long startTimeMillis = actionExecutionContext.getClock().currentTimeMillis(); |
jcater | 56b9b18 | 2020-04-17 13:55:34 -0700 | [diff] [blame] | 431 | SpawnStrategyResolver resolver = actionExecutionContext.getContext(SpawnStrategyResolver.class); |
jmmv | 397f172 | 2020-09-03 08:45:43 -0700 | [diff] [blame] | 432 | SpawnContinuation spawnContinuation; |
| 433 | try { |
| 434 | spawnContinuation = |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 435 | resolver.beginExecution( |
| 436 | spawn, |
| 437 | actionExecutionContext |
| 438 | .withFileOutErr(testOutErr) |
| 439 | .withMetadataHandler(testMetadataHandler)); |
jmmv | 397f172 | 2020-09-03 08:45:43 -0700 | [diff] [blame] | 440 | } catch (InterruptedException e) { |
| 441 | if (streamed != null) { |
| 442 | streamed.close(); |
| 443 | } |
| 444 | testOutErr.close(); |
| 445 | throw e; |
| 446 | } |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 447 | return new BazelTestAttemptContinuation( |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 448 | testAction, |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 449 | testMetadataHandler, |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 450 | actionExecutionContext, |
| 451 | spawn, |
| 452 | resolvedPaths, |
| 453 | testOutErr, |
| 454 | streamed, |
| 455 | startTimeMillis, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 456 | spawnContinuation, |
| 457 | /* testResultDataBuilder= */ null, |
| 458 | /* spawnResults= */ null); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 459 | } |
| 460 | |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 461 | 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(); |
felly | 7148054 | 2019-02-06 07:55:51 -0800 | [diff] [blame] | 469 | if (stat != null) { |
| 470 | try { |
| 471 | if (stat.getSize() > 0) { |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 472 | if (outFilePath.exists()) { |
| 473 | outFilePath.setWritable(true); |
felly | 7148054 | 2019-02-06 07:55:51 -0800 | [diff] [blame] | 474 | } |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 475 | try (OutputStream out = outFilePath.getOutputStream(true); |
| 476 | InputStream in = inFilePath.getInputStream()) { |
felly | 7148054 | 2019-02-06 07:55:51 -0800 | [diff] [blame] | 477 | ByteStreams.copy(in, out); |
| 478 | } |
| 479 | } |
| 480 | } finally { |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 481 | inFilePath.delete(); |
felly | 7148054 | 2019-02-06 07:55:51 -0800 | [diff] [blame] | 482 | } |
| 483 | } |
| 484 | } |
| 485 | |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 486 | private static BuildEventStreamProtos.TestResult.ExecutionInfo extractExecutionInfo( |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 487 | SpawnResult spawnResult, TestResultData.Builder result) { |
| 488 | BuildEventStreamProtos.TestResult.ExecutionInfo.Builder executionInfo = |
| 489 | BuildEventStreamProtos.TestResult.ExecutionInfo.newBuilder(); |
Benjamin Peterson | 1bbeadc | 2018-04-26 05:27:10 -0700 | [diff] [blame] | 490 | 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 | } |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 502 | return executionInfo.build(); |
Benjamin Peterson | 1bbeadc | 2018-04-26 05:27:10 -0700 | [diff] [blame] | 503 | } |
| 504 | |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 505 | private static Artifact.DerivedArtifact createArtifactOutput( |
| 506 | TestRunnerAction action, PathFragment outputPath) { |
| 507 | Artifact.DerivedArtifact testLog = (Artifact.DerivedArtifact) action.getTestLog(); |
jhorvitz | cab340f | 2021-09-27 09:16:23 -0700 | [diff] [blame] | 508 | return DerivedArtifact.create(testLog.getRoot(), outputPath, testLog.getArtifactOwner()); |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 509 | } |
| 510 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 511 | /** |
ulfjack | 0858ae1 | 2018-07-27 02:37:53 -0700 | [diff] [blame] | 512 | * 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 Peng | 3551898 | 2020-12-15 06:04:28 -0800 | [diff] [blame] | 515 | private static Spawn createXmlGeneratingSpawn( |
| 516 | TestRunnerAction action, ImmutableMap<String, String> testEnv, SpawnResult result) { |
laszlocsomor | ba87ba9 | 2020-01-15 05:33:02 -0800 | [diff] [blame] | 517 | 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 Peng | 3551898 | 2020-12-15 06:04:28 -0800 | [diff] [blame] | 524 | 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 Wang | f5cf8b0 | 2021-11-07 20:05:55 -0800 | [diff] [blame] | 535 | 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 | } |
ulfjack | 0858ae1 | 2018-07-27 02:37:53 -0700 | [diff] [blame] | 544 | return new SimpleSpawn( |
| 545 | action, |
laszlocsomor | ba87ba9 | 2020-01-15 05:33:02 -0800 | [diff] [blame] | 546 | args, |
Googler | 2fc3d3f | 2022-02-01 06:29:56 -0800 | [diff] [blame] | 547 | envBuilder.buildOrThrow(), |
Jakob Buchgraber | 803801d | 2019-03-21 09:22:58 -0700 | [diff] [blame] | 548 | // 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 Wang | f5cf8b0 | 2021-11-07 20:05:55 -0800 | [diff] [blame] | 550 | ImmutableMap.copyOf(executionInfo), |
ulfjack | 0858ae1 | 2018-07-27 02:37:53 -0700 | [diff] [blame] | 551 | null, |
| 552 | ImmutableMap.of(), |
ulfjack | e4cca14 | 2020-01-08 04:44:40 -0800 | [diff] [blame] | 553 | /*inputs=*/ NestedSetBuilder.create( |
| 554 | Order.STABLE_ORDER, action.getTestXmlGeneratorScript(), action.getTestLog()), |
| 555 | /*tools=*/ NestedSetBuilder.emptySet(Order.STABLE_ORDER), |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 556 | /*outputs=*/ ImmutableSet.of(createArtifactOutput(action, action.getXmlOutputPath())), |
janakr | 82b3896 | 2022-02-01 07:12:10 -0800 | [diff] [blame] | 557 | /*mandatoryOutputs=*/ null, |
ulfjack | 0858ae1 | 2018-07-27 02:37:53 -0700 | [diff] [blame] | 558 | SpawnAction.DEFAULT_RESOURCE_SET); |
| 559 | } |
| 560 | |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 561 | 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), |
jcater | 285b17f | 2021-12-16 07:16:28 -0800 | [diff] [blame] | 582 | action.getExecutionInfo(), |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 583 | action.getLcovMergerRunfilesSupplier(), |
janakr | 82b3896 | 2022-02-01 07:12:10 -0800 | [diff] [blame] | 584 | /*filesetMappings=*/ ImmutableMap.of(), |
| 585 | /*inputs=*/ NestedSetBuilder.<ActionInput>compileOrder() |
Benjamin Peterson | 7e790a0 | 2021-04-15 01:37:10 -0700 | [diff] [blame] | 586 | .addTransitive(action.getInputs()) |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 587 | .addAll(expandedCoverageDir) |
| 588 | .add(action.getCollectCoverageScript()) |
| 589 | .add(action.getCoverageDirectoryTreeArtifact()) |
| 590 | .add(action.getCoverageManifest()) |
| 591 | .addTransitive(action.getLcovMergerFilesToRun().build()) |
| 592 | .build(), |
janakr | 82b3896 | 2022-02-01 07:12:10 -0800 | [diff] [blame] | 593 | /*tools=*/ NestedSetBuilder.emptySet(Order.STABLE_ORDER), |
| 594 | /*outputs=*/ ImmutableSet.of( |
ajurkowski | 0e71e90 | 2021-06-11 10:58:16 -0700 | [diff] [blame] | 595 | ActionInputHelper.fromPath(action.getCoverageData().getExecPath())), |
janakr | 82b3896 | 2022-02-01 07:12:10 -0800 | [diff] [blame] | 596 | /*mandatoryOutputs=*/ null, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 597 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 618 | @Override |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 619 | public TestResult newCachedTestResult( |
| 620 | Path execRoot, TestRunnerAction action, TestResultData data) { |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 621 | return new TestResult(action, data, /*cached*/ true, execRoot, /*systemFailure=*/ null); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 622 | } |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 623 | |
George Gensure | c53bdaf | 2020-06-04 01:32:39 -0700 | [diff] [blame] | 624 | @VisibleForTesting |
| 625 | static final class StandaloneFailedAttemptResult implements FailedAttemptResult { |
ulfjack | b70f0c1 | 2019-02-27 07:23:38 -0800 | [diff] [blame] | 626 | private final TestResultData testResultData; |
| 627 | |
| 628 | StandaloneFailedAttemptResult(TestResultData testResultData) { |
| 629 | this.testResultData = testResultData; |
| 630 | } |
George Gensure | c53bdaf | 2020-06-04 01:32:39 -0700 | [diff] [blame] | 631 | |
| 632 | TestResultData testResultData() { |
| 633 | return testResultData; |
| 634 | } |
ulfjack | b70f0c1 | 2019-02-27 07:23:38 -0800 | [diff] [blame] | 635 | } |
| 636 | |
arostovtsev | a6bfab0 | 2022-02-14 22:15:58 -0800 | [diff] [blame] | 637 | private final class StandaloneTestRunnerSpawn implements TestRunnerSpawn { |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 638 | private final TestRunnerAction testAction; |
| 639 | private final ActionExecutionContext actionExecutionContext; |
| 640 | private final Spawn spawn; |
| 641 | private final Path tmpDir; |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 642 | private final Path workingDirectory; |
| 643 | private final Path execRoot; |
| 644 | |
| 645 | StandaloneTestRunnerSpawn( |
| 646 | TestRunnerAction testAction, |
| 647 | ActionExecutionContext actionExecutionContext, |
| 648 | Spawn spawn, |
| 649 | Path tmpDir, |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 650 | Path workingDirectory, |
| 651 | Path execRoot) { |
| 652 | this.testAction = testAction; |
| 653 | this.actionExecutionContext = actionExecutionContext; |
| 654 | this.spawn = spawn; |
| 655 | this.tmpDir = tmpDir; |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 656 | this.workingDirectory = workingDirectory; |
| 657 | this.execRoot = execRoot; |
| 658 | } |
| 659 | |
ulfjack | 1188344 | 2019-03-15 12:10:47 -0700 | [diff] [blame] | 660 | @Override |
| 661 | public ActionExecutionContext getActionExecutionContext() { |
| 662 | return actionExecutionContext; |
| 663 | } |
| 664 | |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 665 | @Override |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 666 | public TestAttemptContinuation beginExecution() throws InterruptedException, IOException { |
ulfjack | 71f9cea | 2019-10-17 00:32:35 -0700 | [diff] [blame] | 667 | prepareFileSystem(testAction, execRoot, tmpDir, workingDirectory); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 668 | return beginTestAttempt(testAction, spawn, actionExecutionContext, execRoot); |
| 669 | } |
| 670 | |
| 671 | @Override |
ulfjack | b70f0c1 | 2019-02-27 07:23:38 -0800 | [diff] [blame] | 672 | 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) |
ulfjack | d23e577 | 2019-03-15 07:39:42 -0700 | [diff] [blame] | 686 | throws IOException { |
ulfjack | b70f0c1 | 2019-02-27 07:23:38 -0800 | [diff] [blame] | 687 | StandaloneTestStrategy.this.finalizeTest( |
| 688 | testAction, actionExecutionContext, (StandaloneTestResult) finalResult, failedAttempts); |
| 689 | } |
ulfjack | 797325f | 2019-10-17 06:33:40 -0700 | [diff] [blame] | 690 | |
| 691 | @Override |
| 692 | public void finalizeCancelledTest(List<FailedAttemptResult> failedAttempts) throws IOException { |
| 693 | TestResultData.Builder builder = |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 694 | TestResultData.newBuilder() |
| 695 | .setCachable(false) |
| 696 | .setTestPassed(false) |
| 697 | .setStatus(BlazeTestStatus.INCOMPLETE); |
ulfjack | 797325f | 2019-10-17 06:33:40 -0700 | [diff] [blame] | 698 | StandaloneTestResult standaloneTestResult = |
| 699 | StandaloneTestResult.builder() |
| 700 | .setSpawnResults(ImmutableList.of()) |
| 701 | .setTestResultDataBuilder(builder) |
| 702 | .setExecutionInfo(ExecutionInfo.getDefaultInstance()) |
| 703 | .build(); |
| 704 | finalizeTest(standaloneTestResult, failedAttempts); |
| 705 | } |
ulfjack | b2a92ad | 2019-02-26 06:48:27 -0800 | [diff] [blame] | 706 | } |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 707 | |
| 708 | private final class BazelTestAttemptContinuation extends TestAttemptContinuation { |
| 709 | private final TestRunnerAction testAction; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 710 | @Nullable private final TestMetadataHandler testMetadataHandler; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 711 | 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; |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 718 | private TestResultData.Builder testResultDataBuilder; |
| 719 | private ImmutableList<SpawnResult> spawnResults; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 720 | |
| 721 | BazelTestAttemptContinuation( |
| 722 | TestRunnerAction testAction, |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 723 | @Nullable TestMetadataHandler testMetadataHandler, |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 724 | ActionExecutionContext actionExecutionContext, |
| 725 | Spawn spawn, |
| 726 | ResolvedPaths resolvedPaths, |
| 727 | FileOutErr fileOutErr, |
| 728 | Closeable streamed, |
| 729 | long startTimeMillis, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 730 | SpawnContinuation spawnContinuation, |
| 731 | TestResultData.Builder testResultDataBuilder, |
| 732 | ImmutableList<SpawnResult> spawnResults) { |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 733 | this.testAction = testAction; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 734 | this.testMetadataHandler = testMetadataHandler; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 735 | 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; |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 742 | this.testResultDataBuilder = testResultDataBuilder; |
| 743 | this.spawnResults = spawnResults; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 744 | } |
| 745 | |
| 746 | @Nullable |
| 747 | @Override |
| 748 | public ListenableFuture<?> getFuture() { |
| 749 | return spawnContinuation.getFuture(); |
| 750 | } |
| 751 | |
| 752 | @Override |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 753 | 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 |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 766 | // that is created right here, or one that is read back from disk. |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 767 | 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 Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 774 | testMetadataHandler, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 775 | 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(); |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 787 | builder.setCachable(true).setTestPassed(true).setStatus(BlazeTestStatus.PASSED); |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 788 | } 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 |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 803 | .setCachable(e.getSpawnResult().status().isConsideredUserError()) |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 804 | .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( |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 864 | testAction, |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 865 | testMetadataHandler, |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 866 | actionExecutionContext, |
| 867 | spawn, |
| 868 | resolvedPaths, |
| 869 | fileOutErr, |
| 870 | streamed, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 871 | builder, |
| 872 | spawnResults, |
| 873 | coveragePostProcessingContinuation); |
| 874 | } else { |
| 875 | this.spawnResults = spawnResults; |
| 876 | this.testResultDataBuilder = builder; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 877 | } |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 878 | } |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 879 | |
| 880 | Verify.verify( |
| 881 | !(testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing()) |
| 882 | || testAction.getCoverageData().getPath().exists()); |
| 883 | Verify.verifyNotNull(spawnResults); |
| 884 | Verify.verifyNotNull(testResultDataBuilder); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 885 | |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 886 | 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. |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 892 | writeOutFile(fileOutErr.getErrorPath(), fileOutErr.getOutputPath()); |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 893 | fileOutErr.close(); |
| 894 | if (streamed != null) { |
| 895 | streamed.close(); |
| 896 | } |
| 897 | } catch (IOException e) { |
mschaller | 4557667 | 2020-06-10 19:15:07 -0700 | [diff] [blame] | 898 | throw new EnvironmentalExecException(e, Code.TEST_OUT_ERR_IO_EXCEPTION); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 899 | } |
| 900 | |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 901 | 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 | } |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 908 | |
| 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. |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 913 | if (executionOptions.splitXmlGeneration |
| 914 | && fileOutErr.getOutputPath().exists() |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 915 | && !testXmlGenerated) { |
Yun Peng | 3551898 | 2020-12-15 06:04:28 -0800 | [diff] [blame] | 916 | Spawn xmlGeneratingSpawn = |
| 917 | createXmlGeneratingSpawn(testAction, spawn.getEnvironment(), spawnResults.get(0)); |
jcater | 56b9b18 | 2020-04-17 13:55:34 -0700 | [diff] [blame] | 918 | SpawnStrategyResolver spawnStrategyResolver = |
| 919 | actionExecutionContext.getContext(SpawnStrategyResolver.class); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 920 | // 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(); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 923 | try { |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 924 | SpawnContinuation xmlContinuation = |
jcater | 56b9b18 | 2020-04-17 13:55:34 -0700 | [diff] [blame] | 925 | spawnStrategyResolver.beginExecution( |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 926 | xmlGeneratingSpawn, |
| 927 | actionExecutionContext |
| 928 | .withFileOutErr(xmlSpawnOutErr) |
| 929 | .withMetadataHandler(testMetadataHandler)); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 930 | return new BazelXmlCreationContinuation( |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 931 | resolvedPaths, xmlSpawnOutErr, testResultDataBuilder, spawnResults, xmlContinuation); |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 932 | } catch (InterruptedException e) { |
| 933 | closeSuppressed(e, xmlSpawnOutErr); |
| 934 | throw e; |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 935 | } |
| 936 | } |
| 937 | |
| 938 | TestCase details = parseTestResult(xmlOutputPath); |
| 939 | if (details != null) { |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 940 | testResultDataBuilder.setTestCase(details); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 941 | } |
| 942 | |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 943 | BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo = |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 944 | extractExecutionInfo(spawnResults.get(0), testResultDataBuilder); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 945 | 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). |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 952 | .setTestResultDataBuilder(testResultDataBuilder) |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 953 | .setExecutionInfo(executionInfo) |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 954 | .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 |
ulfjack | 415165a | 2019-09-12 07:56:40 -0700 | [diff] [blame] | 986 | public TestAttemptContinuation execute() throws InterruptedException, ExecException { |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 987 | 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 | |
ajurkowski | e3bf8b7 | 2020-01-23 11:58:11 -0800 | [diff] [blame] | 999 | ImmutableList.Builder<SpawnResult> spawnResults = ImmutableList.builder(); |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 1000 | 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 | |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 1009 | BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo = |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 1010 | extractExecutionInfo(primarySpawnResults.get(0), builder); |
| 1011 | StandaloneTestResult standaloneTestResult = |
| 1012 | StandaloneTestResult.builder() |
ajurkowski | e3bf8b7 | 2020-01-23 11:58:11 -0800 | [diff] [blame] | 1013 | .setSpawnResults(spawnResults.build()) |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 1014 | // 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) |
ulfjack | eb8cfc1 | 2019-04-29 04:43:14 -0700 | [diff] [blame] | 1019 | .setExecutionInfo(executionInfo) |
ulfjack | 4a5e1b7 | 2019-03-18 06:56:57 -0700 | [diff] [blame] | 1020 | .build(); |
| 1021 | return TestAttemptContinuation.of(standaloneTestResult); |
| 1022 | } |
| 1023 | } |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 1024 | |
| 1025 | private final class BazelCoveragePostProcessingContinuation extends TestAttemptContinuation { |
| 1026 | private final ResolvedPaths resolvedPaths; |
Chi Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 1027 | @Nullable private final TestMetadataHandler testMetadataHandler; |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 1028 | 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 Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 1039 | @Nullable TestMetadataHandler testMetadataHandler, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 1040 | 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 Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 1049 | this.testMetadataHandler = testMetadataHandler; |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 1050 | 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 Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 1074 | testMetadataHandler, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 1075 | 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 |
mschaller | d322b72 | 2020-12-28 15:16:32 -0800 | [diff] [blame] | 1100 | .setCachable(e.getSpawnResult().status().isConsideredUserError()) |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 1101 | .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 Wang | 97fb2cf | 2021-06-21 23:23:19 -0700 | [diff] [blame] | 1111 | testMetadataHandler, |
plf | a445cda | 2020-10-29 10:39:30 -0700 | [diff] [blame] | 1112 | actionExecutionContext, |
| 1113 | spawn, |
| 1114 | resolvedPaths, |
| 1115 | fileOutErr, |
| 1116 | streamed, |
| 1117 | /* startTimeMillis= */ 0, |
| 1118 | nextContinuation, |
| 1119 | testResultDataBuilder, |
| 1120 | primarySpawnResults); |
| 1121 | } |
| 1122 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1123 | } |