blob: ea1ac727dfbc05107a0b0f9f28623e78169aa401 [file] [log] [blame]
buchgr9f7edd72017-07-14 12:58:50 +02001// Copyright 2017 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.remote;
15
16import static com.google.common.truth.Truth.assertThat;
buchgr2efea9d2017-08-11 16:19:00 +020017import static org.junit.Assert.fail;
buchgr9f7edd72017-07-14 12:58:50 +020018import static org.mockito.Matchers.any;
Jakob Buchgraber562fcf92017-07-27 12:51:13 +020019import static org.mockito.Matchers.eq;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020020import static org.mockito.Mockito.doNothing;
21import static org.mockito.Mockito.doThrow;
buchgr9f7edd72017-07-14 12:58:50 +020022import static org.mockito.Mockito.never;
Jakob Buchgraber562fcf92017-07-27 12:51:13 +020023import static org.mockito.Mockito.spy;
buchgr9f7edd72017-07-14 12:58:50 +020024import static org.mockito.Mockito.verify;
25import static org.mockito.Mockito.verifyZeroInteractions;
26import static org.mockito.Mockito.when;
27
28import com.google.common.collect.ImmutableList;
29import com.google.common.collect.ImmutableMap;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020030import com.google.common.eventbus.EventBus;
buchgr9f7edd72017-07-14 12:58:50 +020031import com.google.devtools.build.lib.actions.ActionInput;
32import com.google.devtools.build.lib.actions.ActionInputFileCache;
33import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
buchgr2efea9d2017-08-11 16:19:00 +020034import com.google.devtools.build.lib.actions.EnvironmentalExecException;
buchgr9f7edd72017-07-14 12:58:50 +020035import com.google.devtools.build.lib.actions.ExecutionRequirements;
36import com.google.devtools.build.lib.actions.ResourceSet;
37import com.google.devtools.build.lib.actions.SimpleSpawn;
38import com.google.devtools.build.lib.actions.Spawn;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020039import com.google.devtools.build.lib.events.Event;
40import com.google.devtools.build.lib.events.EventKind;
41import com.google.devtools.build.lib.events.Reporter;
42import com.google.devtools.build.lib.events.StoredEventHandler;
buchgr9f7edd72017-07-14 12:58:50 +020043import com.google.devtools.build.lib.exec.SpawnInputExpander;
Jakob Buchgraber562fcf92017-07-27 12:51:13 +020044import com.google.devtools.build.lib.exec.SpawnResult;
45import com.google.devtools.build.lib.exec.SpawnResult.Status;
buchgr9f7edd72017-07-14 12:58:50 +020046import com.google.devtools.build.lib.exec.SpawnRunner;
47import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
48import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
49import com.google.devtools.build.lib.exec.util.FakeOwner;
50import com.google.devtools.build.lib.remote.Digests.ActionKey;
51import com.google.devtools.build.lib.util.io.FileOutErr;
52import com.google.devtools.build.lib.vfs.FileSystem;
53import com.google.devtools.build.lib.vfs.FileSystemUtils;
54import com.google.devtools.build.lib.vfs.Path;
55import com.google.devtools.build.lib.vfs.PathFragment;
56import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
57import com.google.devtools.common.options.Options;
58import com.google.devtools.remoteexecution.v1test.ActionResult;
59import com.google.devtools.remoteexecution.v1test.ExecuteRequest;
60import com.google.devtools.remoteexecution.v1test.ExecuteResponse;
61import java.io.IOException;
ulfjackdeab0cf2017-08-08 13:08:24 +020062import java.time.Duration;
buchgr9f7edd72017-07-14 12:58:50 +020063import java.util.Collection;
64import java.util.SortedMap;
65import org.junit.Before;
66import org.junit.Test;
67import org.junit.runner.RunWith;
68import org.junit.runners.JUnit4;
69import org.mockito.ArgumentCaptor;
70import org.mockito.Mock;
Jakob Buchgraber562fcf92017-07-27 12:51:13 +020071import org.mockito.Mockito;
buchgr9f7edd72017-07-14 12:58:50 +020072import org.mockito.MockitoAnnotations;
73
74/** Tests for {@link com.google.devtools.build.lib.remote.RemoteSpawnRunner} */
75@RunWith(JUnit4.class)
76public class RemoteSpawnRunnerTest {
77
78 private static final ImmutableMap<String, String> NO_CACHE =
79 ImmutableMap.of(ExecutionRequirements.NO_CACHE, "");
80
81 private Path execRoot;
82 private FakeActionInputFileCache fakeFileCache;
83 private FileOutErr outErr;
84
85 @Mock
86 private RemoteActionCache cache;
87
88 @Mock
89 private GrpcRemoteExecutor executor;
90
91 @Mock
92 private SpawnRunner localRunner;
93
94 @Before
95 public final void setUp() throws Exception {
96 MockitoAnnotations.initMocks(this);
97
98 FileSystem fs = new InMemoryFileSystem();
99 execRoot = fs.getPath("/exec/root");
100 FileSystemUtils.createDirectoryAndParents(execRoot);
101 fakeFileCache = new FakeActionInputFileCache(execRoot);
102
103 Path stdout = fs.getPath("/tmp/stdout");
104 Path stderr = fs.getPath("/tmp/stderr");
105 FileSystemUtils.createDirectoryAndParents(stdout.getParentDirectory());
106 FileSystemUtils.createDirectoryAndParents(stderr.getParentDirectory());
107 outErr = new FileOutErr(stdout, stderr);
108 }
109
110 @Test
111 @SuppressWarnings("unchecked")
112 public void nonCachableSpawnsShouldNotBeCached_remote() throws Exception {
113 // Test that if a spawn is marked "NO_CACHE" that it's neither fetched from a remote cache
114 // nor uploaded to a remote cache. It should be executed remotely, however.
115
116 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
117 options.remoteAcceptCached = true;
118 options.remoteLocalFallback = false;
119 options.remoteUploadLocalResults = true;
120
121 RemoteSpawnRunner runner =
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200122 new RemoteSpawnRunner(execRoot, options, localRunner, true, /*cmdlineReporter=*/null,
123 cache, executor);
buchgr9f7edd72017-07-14 12:58:50 +0200124
125 ExecuteResponse succeeded = ExecuteResponse.newBuilder().setResult(
126 ActionResult.newBuilder().setExitCode(0).build()).build();
127 when(executor.executeRemotely(any(ExecuteRequest.class))).thenReturn(succeeded);
128
129 Spawn spawn = new SimpleSpawn(
130 new FakeOwner("foo", "bar"),
131 /*arguments=*/ ImmutableList.of(),
132 /*environment=*/ ImmutableMap.of(),
133 NO_CACHE,
134 /*inputs=*/ ImmutableList.of(),
135 /*outputs=*/ ImmutableList.<ActionInput>of(),
136 ResourceSet.ZERO);
137
138 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
139
140 runner.exec(spawn, policy);
141
142 ArgumentCaptor<ExecuteRequest> requestCaptor = ArgumentCaptor.forClass(ExecuteRequest.class);
143 verify(executor).executeRemotely(requestCaptor.capture());
144 assertThat(requestCaptor.getValue().getSkipCacheLookup()).isTrue();
145
146 verify(cache, never())
147 .getCachedActionResult(any(ActionKey.class));
148 verify(cache, never()).upload(any(ActionKey.class), any(Path.class), any(Collection.class),
149 any(FileOutErr.class));
150 verifyZeroInteractions(localRunner);
151 }
152
153 @Test
154 @SuppressWarnings("unchecked")
155 public void nonCachableSpawnsShouldNotBeCached_local() throws Exception {
156 // Test that if a spawn is executed locally, due to the local fallback, that its result is not
157 // uploaded to the remote cache.
158
159 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
160 options.remoteAcceptCached = true;
161 options.remoteLocalFallback = true;
162 options.remoteUploadLocalResults = true;
163
164 RemoteSpawnRunner runner =
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200165 new RemoteSpawnRunner(execRoot, options, localRunner, true, /*cmdlineReporter=*/null,
166 cache, null);
buchgr9f7edd72017-07-14 12:58:50 +0200167
168 // Throw an IOException to trigger the local fallback.
169 when(executor.executeRemotely(any(ExecuteRequest.class))).thenThrow(IOException.class);
170
171 Spawn spawn = new SimpleSpawn(
172 new FakeOwner("foo", "bar"),
173 /*arguments=*/ ImmutableList.of(),
174 /*environment=*/ ImmutableMap.of(),
175 NO_CACHE,
176 /*inputs=*/ ImmutableList.of(),
177 /*outputs=*/ ImmutableList.<ActionInput>of(),
178 ResourceSet.ZERO);
179
180 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
181
182 runner.exec(spawn, policy);
183
184 verify(localRunner).exec(spawn, policy);
185
186 verify(cache, never())
187 .getCachedActionResult(any(ActionKey.class));
188 verify(cache, never()).upload(any(ActionKey.class), any(Path.class), any(Collection.class),
189 any(FileOutErr.class));
190 }
191
Jakob Buchgraber562fcf92017-07-27 12:51:13 +0200192 @Test
193 @SuppressWarnings("unchecked")
194 public void failedActionShouldNotBeUploaded() throws Exception {
195 // Test that the outputs of a failed locally executed action are not uploaded to a remote
196 // cache.
197
198 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
199 options.remoteUploadLocalResults = true;
200
201 RemoteSpawnRunner runner =
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200202 spy(new RemoteSpawnRunner(execRoot, options, localRunner, true, /*cmdlineReporter=*/null,
203 cache, null));
Jakob Buchgraber562fcf92017-07-27 12:51:13 +0200204
205 Spawn spawn = new SimpleSpawn(
206 new FakeOwner("foo", "bar"),
207 /*arguments=*/ ImmutableList.of(),
208 /*environment=*/ ImmutableMap.of(),
209 /*executionInfo=*/ ImmutableMap.of(),
210 /*inputs=*/ ImmutableList.of(),
211 /*outputs=*/ ImmutableList.<ActionInput>of(),
212 ResourceSet.ZERO);
213 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
214
215 SpawnResult res = Mockito.mock(SpawnResult.class);
216 when(res.exitCode()).thenReturn(1);
217 when(res.status()).thenReturn(Status.EXECUTION_FAILED);
218 when(localRunner.exec(eq(spawn), eq(policy))).thenReturn(res);
219
220 assertThat(runner.exec(spawn, policy)).isSameAs(res);
221
222 verify(localRunner).exec(eq(spawn), eq(policy));
223 verify(runner).execLocallyAndUpload(eq(spawn), eq(policy), any(SortedMap.class), eq(cache),
224 any(ActionKey.class));
225 verify(cache, never()).upload(any(ActionKey.class), any(Path.class), any(Collection.class),
226 any(FileOutErr.class));
227 }
228
buchgr2efea9d2017-08-11 16:19:00 +0200229 @Test
230 public void dontAcceptFailedCachedAction() throws Exception {
231 // Test that bazel fails if the remote cache serves a failed action.
232
233 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
234
235 ActionResult failedAction = ActionResult.newBuilder().setExitCode(1).build();
236 when(cache.getCachedActionResult(any(ActionKey.class))).thenReturn(failedAction);
237
238 Spawn spawn = new SimpleSpawn(
239 new FakeOwner("foo", "bar"),
240 /*arguments=*/ ImmutableList.of(),
241 /*environment=*/ ImmutableMap.of(),
242 /*executionInfo=*/ ImmutableMap.of(),
243 /*inputs=*/ ImmutableList.of(),
244 /*outputs=*/ ImmutableList.<ActionInput>of(),
245 ResourceSet.ZERO);
246 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
247
248 RemoteSpawnRunner runner =
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200249 spy(new RemoteSpawnRunner(execRoot, options, localRunner, true, /*cmdlineReporter=*/null,
250 cache, null));
buchgr2efea9d2017-08-11 16:19:00 +0200251
252 try {
253 runner.exec(spawn, policy);
254 fail("Expected exception");
255 } catch (EnvironmentalExecException expected) {
256 // Intentionally left empty.
257 }
258 }
259
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200260 @Test
261 @SuppressWarnings("unchecked")
262 public void printWarningIfCacheIsDown() throws Exception {
263 // If we try to upload to a local cache, that is down a warning should be printed.
264
265 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
266 options.remoteUploadLocalResults = true;
267 options.remoteLocalFallback = true;
268
269 Reporter reporter = new Reporter(new EventBus());
270 StoredEventHandler eventHandler = new StoredEventHandler();
271 reporter.addHandler(eventHandler);
272
273 RemoteSpawnRunner runner =
274 new RemoteSpawnRunner(execRoot, options, localRunner, false, reporter, cache, null);
275
276 Spawn spawn =
277 new SimpleSpawn(
278 new FakeOwner("foo", "bar"),
279 /*arguments=*/ ImmutableList.of(),
280 /*environment=*/ ImmutableMap.of(),
281 /*executionInfo=*/ ImmutableMap.of(),
282 /*inputs=*/ ImmutableList.of(),
283 /*outputs=*/ ImmutableList.<ActionInput>of(),
284 ResourceSet.ZERO);
285 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
286
287 when(cache.getCachedActionResult(any(ActionKey.class)))
288 .thenThrow(new IOException("cache down"));
289
290 doThrow(new IOException("cache down")).when(cache)
291 .upload(any(ActionKey.class), any(Path.class), any(Collection.class),
292 any(FileOutErr.class));
293
294 SpawnResult res = new SpawnResult.Builder().setStatus(Status.SUCCESS).setExitCode(0).build();
295 when(localRunner.exec(eq(spawn), eq(policy))).thenReturn(res);
296
297 assertThat(runner.exec(spawn, policy)).isEqualTo(res);
298
299 verify(localRunner).exec(eq(spawn), eq(policy));
300
301 assertThat(eventHandler.getEvents()).hasSize(1);
302
303 Event evt = eventHandler.getEvents().get(0);
304 assertThat(evt.getKind()).isEqualTo(EventKind.WARNING);
305 assertThat(evt.getMessage()).contains("fail");
306 assertThat(evt.getMessage()).contains("upload");
307 }
308
309 @Test
310 public void fallbackFails() throws Exception {
311 // Errors from the fallback runner should be propogated out of the remote runner.
312
313 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
314 options.remoteUploadLocalResults = true;
315 options.remoteLocalFallback = true;
316
317 RemoteSpawnRunner runner =
318 new RemoteSpawnRunner(execRoot, options, localRunner, true, /*cmdlineReporter=*/null,
319 cache, null);
320
321 Spawn spawn =
322 new SimpleSpawn(
323 new FakeOwner("foo", "bar"),
324 /*arguments=*/ ImmutableList.of(),
325 /*environment=*/ ImmutableMap.of(),
326 /*executionInfo=*/ ImmutableMap.of(),
327 /*inputs=*/ ImmutableList.of(),
328 /*outputs=*/ ImmutableList.<ActionInput>of(),
329 ResourceSet.ZERO);
330 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
331
332 when(cache.getCachedActionResult(any(ActionKey.class))).thenReturn(null);
333
334 IOException err = new IOException("local execution error");
335 when(localRunner.exec(eq(spawn), eq(policy))).thenThrow(err);
336
337 try {
338 runner.exec(spawn, policy);
339 fail("expected IOException to be raised");
340 } catch (IOException e) {
341 assertThat(e).isSameAs(err);
342 }
343
344 verify(localRunner).exec(eq(spawn), eq(policy));
345 }
346
347 @Test
348 public void cacheDownloadFailureTriggersRemoteExecution() throws Exception {
349 // If downloading a cached action fails, remote execution should be tried.
350
351 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
352
353 RemoteSpawnRunner runner =
354 new RemoteSpawnRunner(execRoot, options, localRunner, true, /*cmdlineReporter=*/null,
355 cache, executor);
356
357 ActionResult cachedResult = ActionResult.newBuilder().setExitCode(0).build();
358 when(cache.getCachedActionResult(any(ActionKey.class))).thenReturn(cachedResult);
359 doThrow(CacheNotFoundException.class)
360 .when(cache)
361 .download(eq(cachedResult), any(Path.class), any(FileOutErr.class));
362 ActionResult execResult = ActionResult.newBuilder().setExitCode(31).build();
363 ExecuteResponse succeeded = ExecuteResponse.newBuilder().setResult(execResult).build();
364 when(executor.executeRemotely(any(ExecuteRequest.class))).thenReturn(succeeded);
365 doNothing().when(cache).download(eq(execResult), any(Path.class), any(FileOutErr.class));
366
367 Spawn spawn =
368 new SimpleSpawn(
369 new FakeOwner("foo", "bar"),
370 /*arguments=*/ ImmutableList.of(),
371 /*environment=*/ ImmutableMap.of(),
372 /*executionInfo=*/ ImmutableMap.of(),
373 /*inputs=*/ ImmutableList.of(),
374 /*outputs=*/ ImmutableList.<ActionInput>of(),
375 ResourceSet.ZERO);
376
377 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
378
379 SpawnResult res = runner.exec(spawn, policy);
380 assertThat(res.status()).isEqualTo(Status.SUCCESS);
381 assertThat(res.exitCode()).isEqualTo(31);
382
383 verify(executor).executeRemotely(any(ExecuteRequest.class));
384 }
385
buchgr4763abc2017-08-30 14:37:21 +0200386 @Test
387 public void testRemoteExecutionTimeout() throws Exception {
388 // If remote execution times out the SpawnResult status should be TIMEOUT.
389
390 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
391 options.remoteLocalFallback = false;
392
393 RemoteSpawnRunner runner =
394 new RemoteSpawnRunner(execRoot, options, localRunner, true, /*cmdlineReporter=*/null,
395 cache, executor);
396
397 ActionResult cachedResult = ActionResult.newBuilder().setExitCode(0).build();
398 when(cache.getCachedActionResult(any(ActionKey.class))).thenReturn(null);
399 when(executor.executeRemotely(any(ExecuteRequest.class))).thenThrow(new TimeoutException());
400
401 Spawn spawn =
402 new SimpleSpawn(
403 new FakeOwner("foo", "bar"),
404 /*arguments=*/ ImmutableList.of(),
405 /*environment=*/ ImmutableMap.of(),
406 /*executionInfo=*/ ImmutableMap.of(),
407 /*inputs=*/ ImmutableList.of(),
408 /*outputs=*/ ImmutableList.<ActionInput>of(),
409 ResourceSet.ZERO);
410
411 SpawnExecutionPolicy policy = new FakeSpawnExecutionPolicy(spawn);
412
413 SpawnResult res = runner.exec(spawn, policy);
414 assertThat(res.status()).isEqualTo(Status.TIMEOUT);
415
416 verify(executor).executeRemotely(any(ExecuteRequest.class));
417 verify(cache, never()).download(eq(cachedResult), eq(execRoot), any(FileOutErr.class));
418 }
419
buchgr9f7edd72017-07-14 12:58:50 +0200420 // TODO(buchgr): Extract a common class to be used for testing.
421 class FakeSpawnExecutionPolicy implements SpawnExecutionPolicy {
422
423 private final ArtifactExpander artifactExpander =
424 (artifact, output) -> output.add(artifact);
425
426 private final Spawn spawn;
427
428 FakeSpawnExecutionPolicy(Spawn spawn) {
429 this.spawn = spawn;
430 }
431
432 @Override
ulfjackeff223e2017-07-17 13:18:48 +0200433 public int getId() {
434 return 0;
435 }
436
437 @Override
ulfjacka7f513c2017-08-10 12:49:16 +0200438 public void prefetchInputs() throws IOException {
ulfjackeff223e2017-07-17 13:18:48 +0200439 throw new UnsupportedOperationException();
440 }
441
442 @Override
buchgr9f7edd72017-07-14 12:58:50 +0200443 public void lockOutputFiles() throws InterruptedException {
444 throw new UnsupportedOperationException();
445 }
446
447 @Override
Benjamin Peterson740cd902017-08-11 13:34:52 +0200448 public boolean speculating() {
449 return false;
450 }
451
452 @Override
buchgr9f7edd72017-07-14 12:58:50 +0200453 public ActionInputFileCache getActionInputFileCache() {
454 return fakeFileCache;
455 }
456
457 @Override
458 public ArtifactExpander getArtifactExpander() {
459 throw new UnsupportedOperationException();
460 }
461
462 @Override
ulfjackdeab0cf2017-08-08 13:08:24 +0200463 public Duration getTimeout() {
464 return Duration.ZERO;
buchgr9f7edd72017-07-14 12:58:50 +0200465 }
466
467 @Override
468 public FileOutErr getFileOutErr() {
469 return outErr;
470 }
471
472 @Override
473 public SortedMap<PathFragment, ActionInput> getInputMapping() throws IOException {
474 return new SpawnInputExpander(/*strict*/ false)
475 .getInputMapping(spawn, artifactExpander, fakeFileCache, "workspace");
476 }
477
478 @Override
ulfjackeff223e2017-07-17 13:18:48 +0200479 public void report(ProgressStatus state, String name) {
buchgr9f7edd72017-07-14 12:58:50 +0200480 assertThat(state).isEqualTo(ProgressStatus.EXECUTING);
481 }
482 }
483}