blob: 9faf8e4d5cf5424e4c17dd8e884023348640c274 [file] [log] [blame]
ulfjack9274cba2017-08-11 23:19:48 +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;
17import static org.mockito.Matchers.any;
18import static org.mockito.Matchers.eq;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020019import static org.mockito.Mockito.doThrow;
ulfjack9274cba2017-08-11 23:19:48 +020020import static org.mockito.Mockito.never;
21import static org.mockito.Mockito.verify;
22import static org.mockito.Mockito.when;
23
24import com.google.common.collect.ImmutableList;
25import com.google.common.collect.ImmutableMap;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020026import com.google.common.eventbus.EventBus;
ulfjack9274cba2017-08-11 23:19:48 +020027import com.google.devtools.build.lib.actions.ActionInput;
28import com.google.devtools.build.lib.actions.ActionInputFileCache;
29import com.google.devtools.build.lib.actions.ActionInputHelper;
30import com.google.devtools.build.lib.actions.Artifact;
31import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
olaolaa22d0e92017-12-11 07:53:15 -080032import com.google.devtools.build.lib.actions.ExecutionRequirements;
ulfjack9274cba2017-08-11 23:19:48 +020033import com.google.devtools.build.lib.actions.ResourceSet;
34import com.google.devtools.build.lib.actions.SimpleSpawn;
rupertsda40fbf2017-09-22 05:59:42 +020035import com.google.devtools.build.lib.actions.SpawnResult;
36import com.google.devtools.build.lib.actions.SpawnResult.Status;
buchgr559a07d2017-11-30 11:09:35 -080037import com.google.devtools.build.lib.clock.JavaClock;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020038import com.google.devtools.build.lib.events.Event;
39import com.google.devtools.build.lib.events.EventKind;
40import com.google.devtools.build.lib.events.Reporter;
41import com.google.devtools.build.lib.events.StoredEventHandler;
ulfjack9274cba2017-08-11 23:19:48 +020042import com.google.devtools.build.lib.exec.SpawnCache.CacheHandle;
43import com.google.devtools.build.lib.exec.SpawnInputExpander;
ulfjack9274cba2017-08-11 23:19:48 +020044import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
45import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
46import com.google.devtools.build.lib.exec.util.FakeOwner;
ulfjack9274cba2017-08-11 23:19:48 +020047import com.google.devtools.build.lib.remote.TreeNodeRepository.TreeNode;
Googler922d1e62018-03-05 14:49:00 -080048import com.google.devtools.build.lib.remote.util.DigestUtil;
49import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey;
50import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
ulfjack9274cba2017-08-11 23:19:48 +020051import com.google.devtools.build.lib.util.io.FileOutErr;
52import com.google.devtools.build.lib.vfs.FileSystem;
buchgr559a07d2017-11-30 11:09:35 -080053import com.google.devtools.build.lib.vfs.FileSystem.HashFunction;
ulfjack9274cba2017-08-11 23:19:48 +020054import com.google.devtools.build.lib.vfs.FileSystemUtils;
55import com.google.devtools.build.lib.vfs.Path;
56import com.google.devtools.build.lib.vfs.PathFragment;
57import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
58import com.google.devtools.common.options.Options;
59import com.google.devtools.remoteexecution.v1test.ActionResult;
60import com.google.devtools.remoteexecution.v1test.Command;
olaolaba8b0b32017-10-20 09:48:56 +020061import com.google.devtools.remoteexecution.v1test.RequestMetadata;
ulfjack9274cba2017-08-11 23:19:48 +020062import java.io.IOException;
63import java.time.Duration;
64import java.util.Collection;
65import java.util.SortedMap;
66import org.junit.Before;
67import org.junit.Test;
68import org.junit.runner.RunWith;
69import org.junit.runners.JUnit4;
70import org.mockito.Mock;
olaolaba8b0b32017-10-20 09:48:56 +020071import org.mockito.Mockito;
ulfjack9274cba2017-08-11 23:19:48 +020072import org.mockito.MockitoAnnotations;
olaolaba8b0b32017-10-20 09:48:56 +020073import org.mockito.invocation.InvocationOnMock;
74import org.mockito.stubbing.Answer;
ulfjack9274cba2017-08-11 23:19:48 +020075
76/** Tests for {@link RemoteSpawnCache}. */
77@RunWith(JUnit4.class)
78public class RemoteSpawnCacheTest {
79 private static final ArtifactExpander SIMPLE_ARTIFACT_EXPANDER =
80 new ArtifactExpander() {
81 @Override
82 public void expand(Artifact artifact, Collection<? super Artifact> output) {
83 output.add(artifact);
84 }
85 };
86
87 private FileSystem fs;
buchgr559a07d2017-11-30 11:09:35 -080088 private DigestUtil digestUtil;
ulfjack9274cba2017-08-11 23:19:48 +020089 private Path execRoot;
90 private SimpleSpawn simpleSpawn;
91 private FakeActionInputFileCache fakeFileCache;
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -080092 @Mock private AbstractRemoteActionCache remoteCache;
ulfjack9274cba2017-08-11 23:19:48 +020093 private RemoteSpawnCache cache;
94 private FileOutErr outErr;
95
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020096 private StoredEventHandler eventHandler = new StoredEventHandler();
97
ulfjack9274cba2017-08-11 23:19:48 +020098 private final SpawnExecutionPolicy simplePolicy =
99 new SpawnExecutionPolicy() {
100 @Override
101 public int getId() {
102 return 0;
103 }
104
105 @Override
106 public void prefetchInputs() {
107 // CachedLocalSpawnRunner should never prefetch itself, though the nested SpawnRunner may.
108 throw new UnsupportedOperationException();
109 }
110
111 @Override
112 public void lockOutputFiles() throws InterruptedException {
113 throw new UnsupportedOperationException();
114 }
115
116 @Override
117 public boolean speculating() {
118 return false;
119 }
120
121 @Override
122 public ActionInputFileCache getActionInputFileCache() {
123 return fakeFileCache;
124 }
125
126 @Override
127 public ArtifactExpander getArtifactExpander() {
128 throw new UnsupportedOperationException();
129 }
130
131 @Override
132 public Duration getTimeout() {
133 return Duration.ZERO;
134 }
135
136 @Override
137 public FileOutErr getFileOutErr() {
138 return outErr;
139 }
140
141 @Override
142 public SortedMap<PathFragment, ActionInput> getInputMapping() throws IOException {
tomlu1a19b622018-01-11 15:17:28 -0800143 return new SpawnInputExpander(execRoot, /*strict*/ false)
ulfjack9274cba2017-08-11 23:19:48 +0200144 .getInputMapping(simpleSpawn, SIMPLE_ARTIFACT_EXPANDER, fakeFileCache, "workspace");
145 }
146
147 @Override
148 public void report(ProgressStatus state, String name) {
149 // TODO(ulfjack): Test that the right calls are made.
150 }
151 };
152
153 @Before
154 public final void setUp() throws Exception {
155 MockitoAnnotations.initMocks(this);
buchgr559a07d2017-11-30 11:09:35 -0800156 fs = new InMemoryFileSystem(new JavaClock(), HashFunction.SHA256);
157 digestUtil = new DigestUtil(HashFunction.SHA256);
ulfjack9274cba2017-08-11 23:19:48 +0200158 execRoot = fs.getPath("/exec/root");
159 FileSystemUtils.createDirectoryAndParents(execRoot);
160 fakeFileCache = new FakeActionInputFileCache(execRoot);
161 simpleSpawn =
162 new SimpleSpawn(
163 new FakeOwner("Mnemonic", "Progress Message"),
164 ImmutableList.of("/bin/echo", "Hi!"),
165 ImmutableMap.of("VARIABLE", "value"),
166 /*executionInfo=*/ ImmutableMap.<String, String>of(),
167 /*inputs=*/ ImmutableList.of(ActionInputHelper.fromPath("input")),
Benjamin Peterson4465dae2018-03-27 07:01:30 -0700168 /*outputs=*/ ImmutableList.of(ActionInputHelper.fromPath("/random/file")),
ulfjack9274cba2017-08-11 23:19:48 +0200169 ResourceSet.ZERO);
170
171 Path stdout = fs.getPath("/tmp/stdout");
172 Path stderr = fs.getPath("/tmp/stderr");
173 FileSystemUtils.createDirectoryAndParents(stdout.getParentDirectory());
174 FileSystemUtils.createDirectoryAndParents(stderr.getParentDirectory());
175 outErr = new FileOutErr(stdout, stderr);
176 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200177 Reporter reporter = new Reporter(new EventBus());
178 eventHandler = new StoredEventHandler();
179 reporter.addHandler(eventHandler);
olaola6f32d5a2017-09-20 17:12:19 +0200180 cache =
181 new RemoteSpawnCache(
buchgr559a07d2017-11-30 11:09:35 -0800182 execRoot,
183 options,
184 remoteCache,
185 "build-req-id",
186 "command-id",
187 false,
188 reporter,
189 digestUtil);
ulfjack9274cba2017-08-11 23:19:48 +0200190 fakeFileCache.createScratchInput(simpleSpawn.getInputFiles().get(0), "xyz");
191 }
192
193 @SuppressWarnings("unchecked")
194 @Test
195 public void cacheHit() throws Exception {
196 ActionResult actionResult = ActionResult.getDefaultInstance();
olaolaba8b0b32017-10-20 09:48:56 +0200197 when(remoteCache.getCachedActionResult(any(ActionKey.class)))
198 .thenAnswer(
199 new Answer<ActionResult>() {
200 @Override
201 public ActionResult answer(InvocationOnMock invocation) {
202 RequestMetadata meta = TracingMetadataUtils.fromCurrentContext();
203 assertThat(meta.getCorrelatedInvocationsId()).isEqualTo("build-req-id");
204 assertThat(meta.getToolInvocationId()).isEqualTo("command-id");
205 return actionResult;
206 }
207 });
208 Mockito.doAnswer(
209 new Answer<Void>() {
210 @Override
211 public Void answer(InvocationOnMock invocation) {
212 RequestMetadata meta = TracingMetadataUtils.fromCurrentContext();
213 assertThat(meta.getCorrelatedInvocationsId()).isEqualTo("build-req-id");
214 assertThat(meta.getToolInvocationId()).isEqualTo("command-id");
215 return null;
216 }
217 })
218 .when(remoteCache)
219 .download(actionResult, execRoot, outErr);
ulfjack9274cba2017-08-11 23:19:48 +0200220
221 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
222 assertThat(entry.hasResult()).isTrue();
223 SpawnResult result = entry.getResult();
224 // All other methods on RemoteActionCache have side effects, so we verify all of them.
225 verify(remoteCache).download(actionResult, execRoot, outErr);
226 verify(remoteCache, never())
227 .ensureInputsPresent(
228 any(TreeNodeRepository.class),
229 any(Path.class),
230 any(TreeNode.class),
231 any(Command.class));
232 verify(remoteCache, never())
233 .upload(
olaola7744b862017-09-18 23:04:33 +0200234 any(ActionKey.class),
235 any(Path.class),
236 any(Collection.class),
237 any(FileOutErr.class),
238 any(Boolean.class));
ulfjack9274cba2017-08-11 23:19:48 +0200239 assertThat(result.setupSuccess()).isTrue();
240 assertThat(result.exitCode()).isEqualTo(0);
olaolae5c9bdf2018-02-20 05:29:19 -0800241 assertThat(result.isCacheHit()).isTrue();
ulfjack9274cba2017-08-11 23:19:48 +0200242 // We expect the CachedLocalSpawnRunner to _not_ write to outErr at all.
243 assertThat(outErr.hasRecordedOutput()).isFalse();
244 assertThat(outErr.hasRecordedStderr()).isFalse();
245 }
246
247 @Test
248 public void cacheMiss() throws Exception {
249 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
250 assertThat(entry.hasResult()).isFalse();
251 SpawnResult result = new SpawnResult.Builder().setExitCode(0).setStatus(Status.SUCCESS).build();
252 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
olaolaba8b0b32017-10-20 09:48:56 +0200253 Mockito.doAnswer(
254 new Answer<Void>() {
255 @Override
256 public Void answer(InvocationOnMock invocation) {
257 RequestMetadata meta = TracingMetadataUtils.fromCurrentContext();
258 assertThat(meta.getCorrelatedInvocationsId()).isEqualTo("build-req-id");
259 assertThat(meta.getToolInvocationId()).isEqualTo("command-id");
260 return null;
261 }
262 })
263 .when(remoteCache)
264 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
Benjamin Peterson4465dae2018-03-27 07:01:30 -0700265 entry.store(result);
ulfjack9274cba2017-08-11 23:19:48 +0200266 verify(remoteCache)
olaola7744b862017-09-18 23:04:33 +0200267 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
ulfjack9274cba2017-08-11 23:19:48 +0200268 }
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200269
270 @Test
olaolaa22d0e92017-12-11 07:53:15 -0800271 public void noCacheSpawns() throws Exception {
272 // Checks that spawns that have mayBeCached false are not looked up in the remote cache,
273 // and also that their result is not uploaded to the remote cache. The artifacts, however,
274 // are uploaded.
Benjamin Peterson4465dae2018-03-27 07:01:30 -0700275 SimpleSpawn uncacheableSpawn =
276 new SimpleSpawn(
277 new FakeOwner("foo", "bar"),
278 /*arguments=*/ ImmutableList.of(),
279 /*environment=*/ ImmutableMap.of(),
280 ImmutableMap.of(ExecutionRequirements.NO_CACHE, ""),
281 /*inputs=*/ ImmutableList.of(),
282 /*outputs=*/ ImmutableList.of(ActionInputHelper.fromPath("/random/file")),
283 ResourceSet.ZERO);
olaolaa22d0e92017-12-11 07:53:15 -0800284 CacheHandle entry = cache.lookup(uncacheableSpawn, simplePolicy);
285 verify(remoteCache, never())
286 .getCachedActionResult(any(ActionKey.class));
287 assertThat(entry.hasResult()).isFalse();
288 SpawnResult result = new SpawnResult.Builder().setExitCode(0).setStatus(Status.SUCCESS).build();
Benjamin Peterson4465dae2018-03-27 07:01:30 -0700289 entry.store(result);
olaolaa22d0e92017-12-11 07:53:15 -0800290 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
olaolaa22d0e92017-12-11 07:53:15 -0800291 verify(remoteCache)
292 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(false));
293 }
294
295 @Test
296 public void noCacheSpawnsNoResultStore() throws Exception {
297 // Only successful action results are uploaded to the remote cache. The artifacts, however,
298 // are uploaded regardless.
299 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
300 verify(remoteCache).getCachedActionResult(any(ActionKey.class));
301 assertThat(entry.hasResult()).isFalse();
302 SpawnResult result =
303 new SpawnResult.Builder().setExitCode(1).setStatus(Status.NON_ZERO_EXIT).build();
304 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
Benjamin Peterson4465dae2018-03-27 07:01:30 -0700305 entry.store(result);
olaolaa22d0e92017-12-11 07:53:15 -0800306 verify(remoteCache)
307 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(false));
308 }
309
310 @Test
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200311 public void printWarningIfUploadFails() throws Exception {
312 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
313 assertThat(entry.hasResult()).isFalse();
314 SpawnResult result = new SpawnResult.Builder().setExitCode(0).setStatus(Status.SUCCESS).build();
315 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
316
olaola7744b862017-09-18 23:04:33 +0200317 doThrow(new IOException("cache down"))
318 .when(remoteCache)
319 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200320
Benjamin Peterson4465dae2018-03-27 07:01:30 -0700321 entry.store(result);
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200322 verify(remoteCache)
olaola7744b862017-09-18 23:04:33 +0200323 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200324
325 assertThat(eventHandler.getEvents()).hasSize(1);
326 Event evt = eventHandler.getEvents().get(0);
327 assertThat(evt.getKind()).isEqualTo(EventKind.WARNING);
328 assertThat(evt.getMessage()).contains("fail");
329 assertThat(evt.getMessage()).contains("upload");
330 }
ulfjack9274cba2017-08-11 23:19:48 +0200331}