blob: 09a8d0325db546c7e6809535ac19d31d833d9afe [file] [log] [blame]
jingwenb6f2ff192018-11-21 12:38:23 -08001// Copyright 2018 The Bazel Authors. All rights reserved.
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.
14package com.google.devtools.build.lib.dynamic;
15
16import static com.google.common.truth.Truth.assertThat;
jcater7472b372019-04-30 07:40:50 -070017import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
jingwenb6f2ff192018-11-21 12:38:23 -080018
19import com.google.common.base.Preconditions;
20import com.google.common.base.Throwables;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.ImmutableMap;
steinmana14a7222019-08-12 11:01:40 -070023import com.google.common.collect.Lists;
24import com.google.common.collect.Maps;
jingwenb6f2ff192018-11-21 12:38:23 -080025import com.google.devtools.build.lib.actions.ActionExecutionContext;
26import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
27import com.google.devtools.build.lib.actions.ActionInput;
28import com.google.devtools.build.lib.actions.ActionKeyContext;
29import com.google.devtools.build.lib.actions.Artifact;
30import com.google.devtools.build.lib.actions.ArtifactRoot;
31import com.google.devtools.build.lib.actions.BaseSpawn;
buchgra30df552019-08-29 04:54:41 -070032import com.google.devtools.build.lib.actions.EmptyRunfilesSupplier;
jingwenb6f2ff192018-11-21 12:38:23 -080033import com.google.devtools.build.lib.actions.ExecException;
34import com.google.devtools.build.lib.actions.ExecutionStrategy;
35import com.google.devtools.build.lib.actions.ExecutorInitException;
jingwenb6f2ff192018-11-21 12:38:23 -080036import com.google.devtools.build.lib.actions.ResourceSet;
37import com.google.devtools.build.lib.actions.SandboxedSpawnActionContext;
38import com.google.devtools.build.lib.actions.Spawn;
39import com.google.devtools.build.lib.actions.SpawnActionContext;
40import com.google.devtools.build.lib.actions.SpawnResult;
41import com.google.devtools.build.lib.actions.UserExecException;
42import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
43import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction;
44import com.google.devtools.build.lib.exec.ExecutionPolicy;
45import com.google.devtools.build.lib.testutil.TestThread;
46import com.google.devtools.build.lib.testutil.TestUtils;
47import com.google.devtools.build.lib.util.io.FileOutErr;
jingwenb6f2ff192018-11-21 12:38:23 -080048import com.google.devtools.build.lib.vfs.FileSystemUtils;
49import com.google.devtools.build.lib.vfs.Path;
jingwenb6f2ff192018-11-21 12:38:23 -080050import com.google.devtools.build.lib.vfs.Root;
51import com.google.devtools.build.lib.vfs.util.FileSystems;
52import java.io.IOException;
53import java.util.List;
54import java.util.concurrent.Callable;
55import java.util.concurrent.CountDownLatch;
56import java.util.concurrent.ExecutorService;
57import java.util.concurrent.Executors;
58import java.util.concurrent.TimeUnit;
jingwenb6f2ff192018-11-21 12:38:23 -080059import java.util.function.Function;
jmmvce393132019-09-10 08:13:29 -070060import javax.annotation.Nullable;
jingwenb6f2ff192018-11-21 12:38:23 -080061import org.junit.After;
62import org.junit.Before;
63import org.junit.Test;
64import org.junit.runner.RunWith;
65import org.junit.runners.JUnit4;
66
67/** Tests for {@link DynamicSpawnStrategy}. */
68@RunWith(JUnit4.class)
69public class DynamicSpawnStrategyTest {
jingwenb6f2ff192018-11-21 12:38:23 -080070 protected Path testRoot;
71 private ExecutorService executorService;
72 private MockLocalSpawnStrategy localStrategy;
73 private MockRemoteSpawnStrategy remoteStrategy;
steinmana14a7222019-08-12 11:01:40 -070074 private MockSandboxedSpawnStrategy sandboxedStrategy;
jingwenb6f2ff192018-11-21 12:38:23 -080075 private SpawnActionContext dynamicSpawnStrategy;
jingwenb6f2ff192018-11-21 12:38:23 -080076 private FileOutErr outErr;
77 private ActionExecutionContext actionExecutionContext;
78 private DynamicExecutionOptions options;
79 private final ActionKeyContext actionKeyContext = new ActionKeyContext();
80
81 abstract static class MockSpawnStrategy implements SandboxedSpawnActionContext {
82 private final Path testRoot;
83 private final int delayMs;
84 private volatile Spawn executedSpawn;
85 private CountDownLatch succeeded = new CountDownLatch(1);
86 private boolean failsDuringExecution;
87 private CountDownLatch beforeExecutionWaitFor;
88 private Callable<List<SpawnResult>> execute;
89
90 public MockSpawnStrategy(Path testRoot, int delayMs) {
91 this.testRoot = testRoot;
92 this.delayMs = delayMs;
93 }
94
95 @Override
96 public List<SpawnResult> exec(Spawn spawn, ActionExecutionContext actionExecutionContext)
97 throws ExecException, InterruptedException {
98 return exec(spawn, actionExecutionContext, null);
99 }
100
101 @Override
philwo849113c2019-02-20 15:09:30 -0800102 public boolean canExec(Spawn spawn) {
103 return true;
104 }
105
106 @Override
jingwenb6f2ff192018-11-21 12:38:23 -0800107 public List<SpawnResult> exec(
108 Spawn spawn,
109 ActionExecutionContext actionExecutionContext,
jmmvce393132019-09-10 08:13:29 -0700110 @Nullable StopConcurrentSpawns stopConcurrentSpawns)
jingwenb6f2ff192018-11-21 12:38:23 -0800111 throws ExecException, InterruptedException {
112 executedSpawn = spawn;
113
114 if (beforeExecutionWaitFor != null) {
115 beforeExecutionWaitFor.countDown();
116 beforeExecutionWaitFor.await();
117 }
118
119 if (delayMs > 0) {
120 Thread.sleep(delayMs);
121 }
122
123 List<SpawnResult> spawnResults = ImmutableList.of();
124 if (execute != null) {
125 try {
126 spawnResults = execute.call();
127 } catch (ExecException | InterruptedException e) {
128 throw e;
129 } catch (Exception e) {
130 Throwables.throwIfUnchecked(e);
131 throw new IllegalStateException(e);
132 }
133 }
134 if (failsDuringExecution) {
135 try {
136 FileSystemUtils.appendIsoLatin1(
137 actionExecutionContext.getFileOutErr().getOutputPath(),
138 "action failed with " + getClass().getSimpleName());
139 } catch (IOException e) {
140 throw new IllegalStateException(e);
141 }
142 throw new UserExecException(getClass().getSimpleName() + " failed to execute the Spawn");
143 }
144
jmmvce393132019-09-10 08:13:29 -0700145 if (stopConcurrentSpawns != null) {
146 stopConcurrentSpawns.stop();
147 }
148
149 for (ActionInput output : spawn.getOutputFiles()) {
150 try {
151 FileSystemUtils.writeIsoLatin1(
152 testRoot.getRelative(output.getExecPath()), getClass().getSimpleName());
153 } catch (IOException e) {
154 throw new IllegalStateException(e);
jingwenb6f2ff192018-11-21 12:38:23 -0800155 }
156 }
157
158 try {
159 FileSystemUtils.appendIsoLatin1(
160 actionExecutionContext.getFileOutErr().getOutputPath(),
161 "output files written with " + getClass().getSimpleName());
162 } catch (IOException e) {
163 throw new IllegalStateException(e);
164 }
165
166 succeeded.countDown();
167
168 return spawnResults;
169 }
170
171 public Spawn getExecutedSpawn() {
172 return executedSpawn;
173 }
174
175 boolean succeeded() {
176 return succeeded.getCount() == 0;
177 }
178
179 CountDownLatch getSucceededLatch() {
180 return succeeded;
181 }
182
183 public void failsDuringExecution() {
184 failsDuringExecution = true;
185 }
186
187 public void beforeExecutionWaitFor(CountDownLatch countDownLatch) {
188 beforeExecutionWaitFor = countDownLatch;
189 }
190
191 void setExecute(Callable<List<SpawnResult>> execute) {
192 this.execute = execute;
193 }
194 }
195
196 @ExecutionStrategy(
197 name = {"mock-remote"},
198 contextType = SpawnActionContext.class
199 )
200 private static class MockRemoteSpawnStrategy extends MockSpawnStrategy {
201 public MockRemoteSpawnStrategy(Path testRoot, int delayMs) {
202 super(testRoot, delayMs);
203 }
204 }
205
206 @ExecutionStrategy(
207 name = {"mock-local"},
208 contextType = SpawnActionContext.class
209 )
210 private static class MockLocalSpawnStrategy extends MockSpawnStrategy {
211 public MockLocalSpawnStrategy(Path testRoot, int delayMs) {
212 super(testRoot, delayMs);
213 }
214 }
215
steinmana14a7222019-08-12 11:01:40 -0700216 @ExecutionStrategy(
217 name = {"mock-sandboxed"},
218 contextType = SpawnActionContext.class)
219 private static class MockSandboxedSpawnStrategy extends MockSpawnStrategy {
220 public MockSandboxedSpawnStrategy(Path testRoot, int delayMs) {
221 super(testRoot, delayMs);
222 }
223 }
224
jingwenb6f2ff192018-11-21 12:38:23 -0800225 private static class DynamicSpawnStrategyUnderTest extends DynamicSpawnStrategy {
226 public DynamicSpawnStrategyUnderTest(
227 ExecutorService executorService,
228 DynamicExecutionOptions options,
229 Function<Spawn, ExecutionPolicy> executionPolicy) {
230 super(executorService, options, executionPolicy);
231 }
232 }
233
234 @Before
235 public void setUp() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700236 testRoot = FileSystems.getNativeFileSystem().getPath(TestUtils.tmpDir());
jmmv5cc1f652019-03-20 09:34:08 -0700237 testRoot.deleteTreesBelow();
jingwenb6f2ff192018-11-21 12:38:23 -0800238 executorService = Executors.newCachedThreadPool();
jingwenb6f2ff192018-11-21 12:38:23 -0800239 outErr = new FileOutErr(testRoot.getRelative("stdout"), testRoot.getRelative("stderr"));
240 actionExecutionContext =
ulfjack256be112019-04-05 05:42:01 -0700241 ActionsTestUtil.createContext(
242 /*executor=*/ null,
243 /*eventHandler=*/ null,
244 actionKeyContext,
245 outErr,
246 testRoot,
247 /*metadataHandler=*/ null,
248 /*actionGraph=*/ null);
jingwenb6f2ff192018-11-21 12:38:23 -0800249 }
250
251 void createSpawnStrategy(int localDelay, int remoteDelay) throws ExecutorInitException {
252 localStrategy = new MockLocalSpawnStrategy(testRoot, localDelay);
253 remoteStrategy = new MockRemoteSpawnStrategy(testRoot, remoteDelay);
steinmana14a7222019-08-12 11:01:40 -0700254 sandboxedStrategy = new MockSandboxedSpawnStrategy(testRoot, localDelay);
jingwenb6f2ff192018-11-21 12:38:23 -0800255 options = new DynamicExecutionOptions();
steinmana14a7222019-08-12 11:01:40 -0700256 options.dynamicLocalStrategy =
257 Lists.newArrayList(
258 Maps.immutableEntry("", ImmutableList.of("mock-local")),
259 Maps.immutableEntry("testMnemonic", ImmutableList.of("mock-sandboxed")));
260 options.dynamicRemoteStrategy =
261 Lists.newArrayList(
262 Maps.immutableEntry("", ImmutableList.of("mock-remote")),
263 Maps.immutableEntry("testMnemonic", ImmutableList.of("mock-sandboxed")));
jingwenb6f2ff192018-11-21 12:38:23 -0800264 options.dynamicWorkerStrategy = "mock-local";
265 options.internalSpawnScheduler = true;
266 options.localExecutionDelay = 0;
steinmanf7a9a4ec12019-08-16 14:14:32 -0700267 DynamicExecutionModule.setDefaultStrategiesByMnemonic(options);
jingwenb6f2ff192018-11-21 12:38:23 -0800268 dynamicSpawnStrategy =
269 new DynamicSpawnStrategyUnderTest(executorService, options, this::getExecutionPolicy);
steinmana14a7222019-08-12 11:01:40 -0700270 dynamicSpawnStrategy.executorCreated(
271 ImmutableList.of(localStrategy, remoteStrategy, sandboxedStrategy));
jingwenb6f2ff192018-11-21 12:38:23 -0800272 }
273
274 ExecutionPolicy getExecutionPolicy(Spawn spawn) {
275 if (spawn.getExecutionInfo().containsKey("local")) {
276 return ExecutionPolicy.LOCAL_EXECUTION_ONLY;
277 } else if (spawn.getExecutionInfo().containsKey("remote")) {
278 return ExecutionPolicy.REMOTE_EXECUTION_ONLY;
279 } else {
280 return ExecutionPolicy.ANYWHERE;
281 }
282 }
283
steinmana14a7222019-08-12 11:01:40 -0700284 private static class NullActionWithMnemonic extends NullAction {
285 private final String mnemonic;
286
287 private NullActionWithMnemonic(String mnemonic, List<Artifact> inputs, Artifact... outputs) {
288 super(inputs, outputs);
289 this.mnemonic = mnemonic;
290 }
291
292 @Override
293 public String getMnemonic() {
294 return mnemonic;
295 }
296 }
297
jingwenb6f2ff192018-11-21 12:38:23 -0800298 @After
299 public void tearDown() throws Exception {
300 executorService.shutdownNow();
301 }
302
jmmv6077d652019-09-10 09:25:37 -0700303 /** Constructs a new spawn with a custom mnemonic and execution info. */
304 private Spawn newCustomSpawn(String mnemonic, ImmutableMap<String, String> executionInfo) {
305 Artifact inputArtifact =
306 ActionsTestUtil.createArtifact(
307 ArtifactRoot.asSourceRoot(Root.fromPath(testRoot)), "input.txt");
308 Artifact outputArtifact =
309 ActionsTestUtil.createArtifact(
310 ArtifactRoot.asSourceRoot(Root.fromPath(testRoot)), "output.txt");
311
jingwenb6f2ff192018-11-21 12:38:23 -0800312 ActionExecutionMetadata action =
steinmana14a7222019-08-12 11:01:40 -0700313 new NullActionWithMnemonic(mnemonic, ImmutableList.of(inputArtifact), outputArtifact);
jingwenb6f2ff192018-11-21 12:38:23 -0800314 return new BaseSpawn(
jmmv6077d652019-09-10 09:25:37 -0700315 ImmutableList.of(),
316 ImmutableMap.of(),
317 executionInfo,
buchgra30df552019-08-29 04:54:41 -0700318 EmptyRunfilesSupplier.INSTANCE,
jingwenb6f2ff192018-11-21 12:38:23 -0800319 action,
320 ResourceSet.create(1, 0, 0));
321 }
322
jmmv6077d652019-09-10 09:25:37 -0700323 /** Constructs a new spawn that can be run locally and remotely with arbitrary settings. */
324 private Spawn newDynamicSpawn() {
325 return newCustomSpawn("Null", ImmutableMap.of());
326 }
327
jingwenb6f2ff192018-11-21 12:38:23 -0800328 @Test
329 public void nonRemotableSpawnRunsLocally() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700330 Spawn spawn = newCustomSpawn("Null", ImmutableMap.of("local", "1"));
jingwenb6f2ff192018-11-21 12:38:23 -0800331 createSpawnStrategy(0, 0);
332
333 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
334
335 assertThat(localStrategy.getExecutedSpawn()).isEqualTo(spawn);
336 assertThat(localStrategy.succeeded()).isTrue();
337 assertThat(remoteStrategy.getExecutedSpawn()).isNull();
338 assertThat(remoteStrategy.succeeded()).isFalse();
339
340 assertThat(outErr.outAsLatin1()).contains("output files written with MockLocalSpawnStrategy");
341 assertThat(outErr.outAsLatin1()).doesNotContain("MockRemoteSpawnStrategy");
342 }
343
344 @Test
steinmanf7a9a4ec12019-08-16 14:14:32 -0700345 public void localSpawnUsesStrategyByMnemonicWithWorkerFlagDisabled() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700346 Spawn spawn = newCustomSpawn("testMnemonic", ImmutableMap.of("local", "1"));
steinmanf7a9a4ec12019-08-16 14:14:32 -0700347 createSpawnStrategy(0, 0);
348
349 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
350
351 assertThat(localStrategy.getExecutedSpawn()).isNull();
352 assertThat(localStrategy.succeeded()).isFalse();
353 assertThat(remoteStrategy.getExecutedSpawn()).isNull();
354 assertThat(remoteStrategy.succeeded()).isFalse();
355 assertThat(sandboxedStrategy.getExecutedSpawn()).isEqualTo(spawn);
356 assertThat(sandboxedStrategy.succeeded()).isTrue();
357
358 assertThat(outErr.outAsLatin1())
359 .contains("output files written with MockSandboxedSpawnStrategy");
360 assertThat(outErr.outAsLatin1()).doesNotContain("MockLocalSpawnStrategy");
361 assertThat(outErr.outAsLatin1()).doesNotContain("MockRemoteSpawnStrategy");
362 }
363
364 @Test
jingwenb6f2ff192018-11-21 12:38:23 -0800365 public void nonLocallyExecutableSpawnRunsRemotely() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700366 Spawn spawn = newCustomSpawn("Null", ImmutableMap.of("remote", "1"));
jingwenb6f2ff192018-11-21 12:38:23 -0800367 createSpawnStrategy(0, 0);
368
369 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
370
371 assertThat(localStrategy.getExecutedSpawn()).isNull();
372 assertThat(localStrategy.succeeded()).isFalse();
373 assertThat(remoteStrategy.getExecutedSpawn()).isEqualTo(spawn);
374 assertThat(remoteStrategy.succeeded()).isTrue();
375
376 assertThat(outErr.outAsLatin1()).contains("output files written with MockRemoteSpawnStrategy");
377 assertThat(outErr.outAsLatin1()).doesNotContain("MockLocalSpawnStrategy");
378 }
379
380 @Test
steinmana14a7222019-08-12 11:01:40 -0700381 public void remoteSpawnUsesStrategyByMnemonic() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700382 Spawn spawn = newCustomSpawn("testMnemonic", ImmutableMap.of("remote", "1"));
steinmana14a7222019-08-12 11:01:40 -0700383 createSpawnStrategy(0, 0);
384
385 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
386
387 assertThat(localStrategy.getExecutedSpawn()).isNull();
388 assertThat(localStrategy.succeeded()).isFalse();
389 assertThat(remoteStrategy.getExecutedSpawn()).isNull();
390 assertThat(remoteStrategy.succeeded()).isFalse();
391 assertThat(sandboxedStrategy.getExecutedSpawn()).isEqualTo(spawn);
392 assertThat(sandboxedStrategy.succeeded()).isTrue();
393
394 assertThat(outErr.outAsLatin1())
395 .contains("output files written with MockSandboxedSpawnStrategy");
396 assertThat(outErr.outAsLatin1()).doesNotContain("MockLocalSpawnStrategy");
397 assertThat(outErr.outAsLatin1()).doesNotContain("MockRemoteSpawnStrategy");
398 }
399
400 @Test
jingwenb6f2ff192018-11-21 12:38:23 -0800401 public void actionSucceedsIfLocalExecutionSucceedsEvenIfRemoteFailsLater() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700402 Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800403 createSpawnStrategy(0, 2000);
404 CountDownLatch countDownLatch = new CountDownLatch(2);
405 localStrategy.beforeExecutionWaitFor(countDownLatch);
406 remoteStrategy.beforeExecutionWaitFor(countDownLatch);
407 remoteStrategy.failsDuringExecution();
408
409 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
410
411 assertThat(localStrategy.getExecutedSpawn()).isEqualTo(spawn);
412 assertThat(localStrategy.succeeded()).isTrue();
413 assertThat(remoteStrategy.getExecutedSpawn()).isEqualTo(spawn);
414 assertThat(remoteStrategy.succeeded()).isFalse();
415
416 assertThat(outErr.outAsLatin1()).contains("output files written with MockLocalSpawnStrategy");
417 assertThat(outErr.outAsLatin1()).doesNotContain("MockRemoteSpawnStrategy");
418 }
419
420 @Test
421 public void actionSucceedsIfRemoteExecutionSucceedsEvenIfLocalFailsLater() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700422 Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800423 createSpawnStrategy(2000, 0);
424 CountDownLatch countDownLatch = new CountDownLatch(2);
425 localStrategy.beforeExecutionWaitFor(countDownLatch);
426 localStrategy.failsDuringExecution();
427 remoteStrategy.beforeExecutionWaitFor(countDownLatch);
428
429 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
430
431 assertThat(localStrategy.getExecutedSpawn()).isEqualTo(spawn);
432 assertThat(localStrategy.succeeded()).isFalse();
433 assertThat(remoteStrategy.getExecutedSpawn()).isEqualTo(spawn);
434 assertThat(remoteStrategy.succeeded()).isTrue();
435
436 assertThat(outErr.outAsLatin1()).contains("output files written with MockRemoteSpawnStrategy");
437 assertThat(outErr.outAsLatin1()).doesNotContain("MockLocalSpawnStrategy");
438 }
439
440 @Test
441 public void actionFailsIfLocalFailsImmediatelyEvenIfRemoteSucceedsLater() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700442 Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800443 createSpawnStrategy(0, 2000);
444 CountDownLatch countDownLatch = new CountDownLatch(2);
445 localStrategy.beforeExecutionWaitFor(countDownLatch);
446 localStrategy.failsDuringExecution();
447 remoteStrategy.beforeExecutionWaitFor(countDownLatch);
448
jcater7472b372019-04-30 07:40:50 -0700449 ExecException e =
450 assertThrows(
451 ExecException.class, () -> dynamicSpawnStrategy.exec(spawn, actionExecutionContext));
452 assertThat(e).hasMessageThat().matches("MockLocalSpawnStrategy failed to execute the Spawn");
jingwenb6f2ff192018-11-21 12:38:23 -0800453
454 assertThat(localStrategy.getExecutedSpawn()).isEqualTo(spawn);
455 assertThat(localStrategy.succeeded()).isFalse();
456 assertThat(remoteStrategy.getExecutedSpawn()).isEqualTo(spawn);
457 assertThat(remoteStrategy.succeeded()).isFalse();
458
459 assertThat(outErr.outAsLatin1()).contains("action failed with MockLocalSpawnStrategy");
460 assertThat(outErr.outAsLatin1()).doesNotContain("MockRemoteSpawnStrategy");
461 }
462
463 @Test
464 public void actionFailsIfRemoteFailsImmediatelyEvenIfLocalSucceedsLater() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700465 Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800466 createSpawnStrategy(2000, 0);
467 CountDownLatch countDownLatch = new CountDownLatch(2);
468 localStrategy.beforeExecutionWaitFor(countDownLatch);
469 remoteStrategy.beforeExecutionWaitFor(countDownLatch);
470 remoteStrategy.failsDuringExecution();
471
jcater7472b372019-04-30 07:40:50 -0700472 ExecException e =
473 assertThrows(
474 ExecException.class, () -> dynamicSpawnStrategy.exec(spawn, actionExecutionContext));
475 assertThat(e).hasMessageThat().matches("MockRemoteSpawnStrategy failed to execute the Spawn");
jingwenb6f2ff192018-11-21 12:38:23 -0800476
477 assertThat(localStrategy.getExecutedSpawn()).isEqualTo(spawn);
478 assertThat(localStrategy.succeeded()).isFalse();
479 assertThat(remoteStrategy.getExecutedSpawn()).isEqualTo(spawn);
480 assertThat(remoteStrategy.succeeded()).isFalse();
481
482 assertThat(outErr.outAsLatin1()).contains("action failed with MockRemoteSpawnStrategy");
483 assertThat(outErr.outAsLatin1()).doesNotContain("MockLocalSpawnStrategy");
484 }
485
486 @Test
487 public void actionFailsIfLocalAndRemoteFail() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700488 Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800489 createSpawnStrategy(0, 0);
490 CountDownLatch countDownLatch = new CountDownLatch(2);
491 localStrategy.beforeExecutionWaitFor(countDownLatch);
492 remoteStrategy.beforeExecutionWaitFor(countDownLatch);
493 localStrategy.failsDuringExecution();
494 remoteStrategy.failsDuringExecution();
495
jcater7472b372019-04-30 07:40:50 -0700496 ExecException e =
497 assertThrows(
498 ExecException.class, () -> dynamicSpawnStrategy.exec(spawn, actionExecutionContext));
499 assertThat(e)
500 .hasMessageThat()
501 .matches("Mock(Local|Remote)SpawnStrategy failed to execute the Spawn");
jingwenb6f2ff192018-11-21 12:38:23 -0800502
503 assertThat(localStrategy.getExecutedSpawn()).isEqualTo(spawn);
504 assertThat(localStrategy.succeeded()).isFalse();
505 assertThat(remoteStrategy.getExecutedSpawn()).isEqualTo(spawn);
506 assertThat(remoteStrategy.succeeded()).isFalse();
507 }
508
509 @Test
510 public void noDeadlockWithSingleThreadedExecutor() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700511 Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800512
513 // Replace the executorService with a single threaded one.
514 executorService = Executors.newSingleThreadExecutor();
515 createSpawnStrategy(/*localDelay=*/ 0, /*remoteDelay=*/ 0);
516
517 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
518
519 assertThat(localStrategy.getExecutedSpawn()).isEqualTo(spawn);
520 assertThat(localStrategy.succeeded()).isTrue();
521
522 /**
523 * The single-threaded executorService#invokeAny does not comply to the contract where
524 * the callables are *always* called sequentially. In this case, both spawns will start
525 * executing, but the local one will always succeed as it's the first to be called. The remote
526 * one will then be cancelled, or is null if the local one completes before the remote one
527 * starts.
528 *
529 * See the documentation of {@link BoundedExectorService#invokeAny(Collection)}, specifically:
530 * "The following is less efficient (it goes on submitting tasks even if there is some task
531 * already finished), but quite straight-forward.".
532 */
533 assertThat(remoteStrategy.getExecutedSpawn()).isAnyOf(spawn, null);
534 assertThat(remoteStrategy.succeeded()).isFalse();
535 }
536
537 @Test
538 public void interruptDuringExecutionDoesActuallyInterruptTheExecution() throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700539 final Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800540 createSpawnStrategy(60000, 60000);
541 CountDownLatch countDownLatch = new CountDownLatch(2);
542 localStrategy.beforeExecutionWaitFor(countDownLatch);
543 remoteStrategy.beforeExecutionWaitFor(countDownLatch);
544
545 TestThread testThread =
546 new TestThread() {
547 @Override
548 public void runTest() throws Exception {
549 try {
550 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
551 } catch (InterruptedException e) {
552 // This is expected.
553 }
554 }
555 };
556 testThread.start();
557 countDownLatch.await(5, TimeUnit.SECONDS);
558 testThread.interrupt();
559 testThread.joinAndAssertState(5000);
560
561 assertThat(outErr.getOutputPath().exists()).isFalse();
562 assertThat(outErr.getErrorPath().exists()).isFalse();
563 }
564
565 private void strategyWaitsForBothSpawnsToFinish(boolean interruptThread, boolean executionFails)
566 throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700567 final Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800568 createSpawnStrategy(0, 0);
569 CountDownLatch waitToFinish = new CountDownLatch(1);
570 CountDownLatch wasInterrupted = new CountDownLatch(1);
571 CountDownLatch executionCanProceed = new CountDownLatch(2);
572 localStrategy.setExecute(
573 () -> {
574 executionCanProceed.countDown();
575 try {
576 Thread.sleep(TestUtils.WAIT_TIMEOUT_MILLISECONDS);
577 throw new IllegalStateException("Should have been interrupted");
578 } catch (InterruptedException e) {
579 // Expected.
580 }
581 wasInterrupted.countDown();
582 try {
583 Preconditions.checkState(
584 waitToFinish.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
585 } catch (InterruptedException e) {
586 throw new IllegalStateException(e);
587 }
588 return ImmutableList.of();
589 });
590 if (executionFails) {
591 remoteStrategy.failsDuringExecution();
592 }
593 remoteStrategy.beforeExecutionWaitFor(executionCanProceed);
594
595 TestThread testThread =
596 new TestThread() {
597 @Override
598 public void runTest() {
599 try {
600 dynamicSpawnStrategy.exec(spawn, actionExecutionContext);
601 Preconditions.checkState(!interruptThread && !executionFails);
602 } catch (InterruptedException e) {
603 Preconditions.checkState(interruptThread && !executionFails);
604 Preconditions.checkState(!Thread.currentThread().isInterrupted());
605 } catch (ExecException e) {
606 Preconditions.checkState(executionFails);
607 Preconditions.checkState(Thread.currentThread().isInterrupted() == interruptThread);
608 }
609 }
610 };
611 testThread.start();
612 if (!executionFails) {
613 assertThat(
614 remoteStrategy
615 .getSucceededLatch()
616 .await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
617 .isTrue();
618 }
619 assertThat(wasInterrupted.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
620 assertThat(testThread.isAlive()).isTrue();
621 if (interruptThread) {
622 testThread.interrupt();
623 }
624 // Wait up to 5 seconds for this thread to finish. It should not have finished.
625 testThread.join(5000);
626 assertThat(testThread.isAlive()).isTrue();
627 waitToFinish.countDown();
628 testThread.joinAndAssertState(TestUtils.WAIT_TIMEOUT_MILLISECONDS);
629 }
630
631 @Test
632 public void strategyWaitsForBothSpawnsToFinish() throws Exception {
633 strategyWaitsForBothSpawnsToFinish(false, false);
634 }
635
636 @Test
637 public void strategyWaitsForBothSpawnsToFinishEvenIfInterrupted() throws Exception {
638 strategyWaitsForBothSpawnsToFinish(true, false);
639 }
640
641 @Test
642 public void strategyWaitsForBothSpawnsToFinishOnFailure() throws Exception {
643 strategyWaitsForBothSpawnsToFinish(false, true);
644 }
645
646 @Test
647 public void strategyWaitsForBothSpawnsToFinishOnFailureEvenIfInterrupted() throws Exception {
648 strategyWaitsForBothSpawnsToFinish(true, true);
649 }
650
651 @Test
652 public void strategyPropagatesFasterLocalException() throws Exception {
653 strategyPropagatesException(true);
654 }
655
656 @Test
657 public void strategyPropagatesFasterRemoteException() throws Exception {
658 strategyPropagatesException(false);
659 }
660
661 private void strategyPropagatesException(boolean preferLocal) throws Exception {
jmmv6077d652019-09-10 09:25:37 -0700662 final Spawn spawn = newDynamicSpawn();
jingwenb6f2ff192018-11-21 12:38:23 -0800663 createSpawnStrategy(!preferLocal ? 60000 : 0, preferLocal ? 60000 : 0);
664
665 String message = "Mock spawn execution exception";
666 Callable<List<SpawnResult>> execute = () -> {
667 throw new IllegalStateException(message);
668 };
669 localStrategy.setExecute(execute);
670 remoteStrategy.setExecute(execute);
671
jcater7472b372019-04-30 07:40:50 -0700672 ExecException e =
673 assertThrows(
674 ExecException.class, () -> dynamicSpawnStrategy.exec(spawn, actionExecutionContext));
675 assertThat(e).hasMessageThat().matches("java.lang.IllegalStateException: " + message);
jingwenb6f2ff192018-11-21 12:38:23 -0800676
677 Spawn executedSpawn = localStrategy.getExecutedSpawn();
678 executedSpawn = executedSpawn == null ? remoteStrategy.getExecutedSpawn() : executedSpawn;
679 assertThat(executedSpawn).isEqualTo(spawn);
680 assertThat(localStrategy.succeeded()).isFalse();
681 assertThat(remoteStrategy.succeeded()).isFalse();
682 }
683}