blob: 3e8bb83f4bfc3e6f8ed84b8a3426894ddb6d9928 [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;
buchgr559a07d2017-11-30 11:09:35 -080047import com.google.devtools.build.lib.remote.DigestUtil.ActionKey;
ulfjack9274cba2017-08-11 23:19:48 +020048import com.google.devtools.build.lib.remote.TreeNodeRepository.TreeNode;
49import com.google.devtools.build.lib.util.io.FileOutErr;
50import com.google.devtools.build.lib.vfs.FileSystem;
buchgr559a07d2017-11-30 11:09:35 -080051import com.google.devtools.build.lib.vfs.FileSystem.HashFunction;
ulfjack9274cba2017-08-11 23:19:48 +020052import com.google.devtools.build.lib.vfs.FileSystemUtils;
53import com.google.devtools.build.lib.vfs.Path;
54import com.google.devtools.build.lib.vfs.PathFragment;
55import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
56import com.google.devtools.common.options.Options;
57import com.google.devtools.remoteexecution.v1test.ActionResult;
58import com.google.devtools.remoteexecution.v1test.Command;
olaolaba8b0b32017-10-20 09:48:56 +020059import com.google.devtools.remoteexecution.v1test.RequestMetadata;
ulfjack9274cba2017-08-11 23:19:48 +020060import java.io.IOException;
61import java.time.Duration;
62import java.util.Collection;
63import java.util.SortedMap;
64import org.junit.Before;
65import org.junit.Test;
66import org.junit.runner.RunWith;
67import org.junit.runners.JUnit4;
68import org.mockito.Mock;
olaolaba8b0b32017-10-20 09:48:56 +020069import org.mockito.Mockito;
ulfjack9274cba2017-08-11 23:19:48 +020070import org.mockito.MockitoAnnotations;
olaolaba8b0b32017-10-20 09:48:56 +020071import org.mockito.invocation.InvocationOnMock;
72import org.mockito.stubbing.Answer;
ulfjack9274cba2017-08-11 23:19:48 +020073
74/** Tests for {@link RemoteSpawnCache}. */
75@RunWith(JUnit4.class)
76public class RemoteSpawnCacheTest {
77 private static final ArtifactExpander SIMPLE_ARTIFACT_EXPANDER =
78 new ArtifactExpander() {
79 @Override
80 public void expand(Artifact artifact, Collection<? super Artifact> output) {
81 output.add(artifact);
82 }
83 };
84
85 private FileSystem fs;
buchgr559a07d2017-11-30 11:09:35 -080086 private DigestUtil digestUtil;
ulfjack9274cba2017-08-11 23:19:48 +020087 private Path execRoot;
88 private SimpleSpawn simpleSpawn;
89 private FakeActionInputFileCache fakeFileCache;
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -080090 @Mock private AbstractRemoteActionCache remoteCache;
ulfjack9274cba2017-08-11 23:19:48 +020091 private RemoteSpawnCache cache;
92 private FileOutErr outErr;
93
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020094 private StoredEventHandler eventHandler = new StoredEventHandler();
95
ulfjack9274cba2017-08-11 23:19:48 +020096 private final SpawnExecutionPolicy simplePolicy =
97 new SpawnExecutionPolicy() {
98 @Override
99 public int getId() {
100 return 0;
101 }
102
103 @Override
104 public void prefetchInputs() {
105 // CachedLocalSpawnRunner should never prefetch itself, though the nested SpawnRunner may.
106 throw new UnsupportedOperationException();
107 }
108
109 @Override
110 public void lockOutputFiles() throws InterruptedException {
111 throw new UnsupportedOperationException();
112 }
113
114 @Override
115 public boolean speculating() {
116 return false;
117 }
118
119 @Override
120 public ActionInputFileCache getActionInputFileCache() {
121 return fakeFileCache;
122 }
123
124 @Override
125 public ArtifactExpander getArtifactExpander() {
126 throw new UnsupportedOperationException();
127 }
128
129 @Override
130 public Duration getTimeout() {
131 return Duration.ZERO;
132 }
133
134 @Override
135 public FileOutErr getFileOutErr() {
136 return outErr;
137 }
138
139 @Override
140 public SortedMap<PathFragment, ActionInput> getInputMapping() throws IOException {
tomlu1a19b622018-01-11 15:17:28 -0800141 return new SpawnInputExpander(execRoot, /*strict*/ false)
ulfjack9274cba2017-08-11 23:19:48 +0200142 .getInputMapping(simpleSpawn, SIMPLE_ARTIFACT_EXPANDER, fakeFileCache, "workspace");
143 }
144
145 @Override
146 public void report(ProgressStatus state, String name) {
147 // TODO(ulfjack): Test that the right calls are made.
148 }
149 };
150
151 @Before
152 public final void setUp() throws Exception {
153 MockitoAnnotations.initMocks(this);
buchgr559a07d2017-11-30 11:09:35 -0800154 fs = new InMemoryFileSystem(new JavaClock(), HashFunction.SHA256);
155 digestUtil = new DigestUtil(HashFunction.SHA256);
ulfjack9274cba2017-08-11 23:19:48 +0200156 execRoot = fs.getPath("/exec/root");
157 FileSystemUtils.createDirectoryAndParents(execRoot);
158 fakeFileCache = new FakeActionInputFileCache(execRoot);
159 simpleSpawn =
160 new SimpleSpawn(
161 new FakeOwner("Mnemonic", "Progress Message"),
162 ImmutableList.of("/bin/echo", "Hi!"),
163 ImmutableMap.of("VARIABLE", "value"),
164 /*executionInfo=*/ ImmutableMap.<String, String>of(),
165 /*inputs=*/ ImmutableList.of(ActionInputHelper.fromPath("input")),
166 /*outputs=*/ ImmutableList.<ActionInput>of(),
167 ResourceSet.ZERO);
168
169 Path stdout = fs.getPath("/tmp/stdout");
170 Path stderr = fs.getPath("/tmp/stderr");
171 FileSystemUtils.createDirectoryAndParents(stdout.getParentDirectory());
172 FileSystemUtils.createDirectoryAndParents(stderr.getParentDirectory());
173 outErr = new FileOutErr(stdout, stderr);
174 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200175 Reporter reporter = new Reporter(new EventBus());
176 eventHandler = new StoredEventHandler();
177 reporter.addHandler(eventHandler);
olaola6f32d5a2017-09-20 17:12:19 +0200178 cache =
179 new RemoteSpawnCache(
buchgr559a07d2017-11-30 11:09:35 -0800180 execRoot,
181 options,
182 remoteCache,
183 "build-req-id",
184 "command-id",
185 false,
186 reporter,
187 digestUtil);
ulfjack9274cba2017-08-11 23:19:48 +0200188 fakeFileCache.createScratchInput(simpleSpawn.getInputFiles().get(0), "xyz");
189 }
190
191 @SuppressWarnings("unchecked")
192 @Test
193 public void cacheHit() throws Exception {
194 ActionResult actionResult = ActionResult.getDefaultInstance();
olaolaba8b0b32017-10-20 09:48:56 +0200195 when(remoteCache.getCachedActionResult(any(ActionKey.class)))
196 .thenAnswer(
197 new Answer<ActionResult>() {
198 @Override
199 public ActionResult answer(InvocationOnMock invocation) {
200 RequestMetadata meta = TracingMetadataUtils.fromCurrentContext();
201 assertThat(meta.getCorrelatedInvocationsId()).isEqualTo("build-req-id");
202 assertThat(meta.getToolInvocationId()).isEqualTo("command-id");
203 return actionResult;
204 }
205 });
206 Mockito.doAnswer(
207 new Answer<Void>() {
208 @Override
209 public Void answer(InvocationOnMock invocation) {
210 RequestMetadata meta = TracingMetadataUtils.fromCurrentContext();
211 assertThat(meta.getCorrelatedInvocationsId()).isEqualTo("build-req-id");
212 assertThat(meta.getToolInvocationId()).isEqualTo("command-id");
213 return null;
214 }
215 })
216 .when(remoteCache)
217 .download(actionResult, execRoot, outErr);
ulfjack9274cba2017-08-11 23:19:48 +0200218
219 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
220 assertThat(entry.hasResult()).isTrue();
221 SpawnResult result = entry.getResult();
222 // All other methods on RemoteActionCache have side effects, so we verify all of them.
223 verify(remoteCache).download(actionResult, execRoot, outErr);
224 verify(remoteCache, never())
225 .ensureInputsPresent(
226 any(TreeNodeRepository.class),
227 any(Path.class),
228 any(TreeNode.class),
229 any(Command.class));
230 verify(remoteCache, never())
231 .upload(
olaola7744b862017-09-18 23:04:33 +0200232 any(ActionKey.class),
233 any(Path.class),
234 any(Collection.class),
235 any(FileOutErr.class),
236 any(Boolean.class));
ulfjack9274cba2017-08-11 23:19:48 +0200237 assertThat(result.setupSuccess()).isTrue();
238 assertThat(result.exitCode()).isEqualTo(0);
239 // We expect the CachedLocalSpawnRunner to _not_ write to outErr at all.
240 assertThat(outErr.hasRecordedOutput()).isFalse();
241 assertThat(outErr.hasRecordedStderr()).isFalse();
242 }
243
244 @Test
245 public void cacheMiss() throws Exception {
246 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
247 assertThat(entry.hasResult()).isFalse();
248 SpawnResult result = new SpawnResult.Builder().setExitCode(0).setStatus(Status.SUCCESS).build();
249 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
olaolaba8b0b32017-10-20 09:48:56 +0200250 Mockito.doAnswer(
251 new Answer<Void>() {
252 @Override
253 public Void answer(InvocationOnMock invocation) {
254 RequestMetadata meta = TracingMetadataUtils.fromCurrentContext();
255 assertThat(meta.getCorrelatedInvocationsId()).isEqualTo("build-req-id");
256 assertThat(meta.getToolInvocationId()).isEqualTo("command-id");
257 return null;
258 }
259 })
260 .when(remoteCache)
261 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
ulfjack9274cba2017-08-11 23:19:48 +0200262 entry.store(result, outputFiles);
263 verify(remoteCache)
olaola7744b862017-09-18 23:04:33 +0200264 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
ulfjack9274cba2017-08-11 23:19:48 +0200265 }
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200266
267 @Test
olaolaa22d0e92017-12-11 07:53:15 -0800268 public void noCacheSpawns() throws Exception {
269 // Checks that spawns that have mayBeCached false are not looked up in the remote cache,
270 // and also that their result is not uploaded to the remote cache. The artifacts, however,
271 // are uploaded.
272 SimpleSpawn uncacheableSpawn = new SimpleSpawn(
273 new FakeOwner("foo", "bar"),
274 /*arguments=*/ ImmutableList.of(),
275 /*environment=*/ ImmutableMap.of(),
276 ImmutableMap.of(ExecutionRequirements.NO_CACHE, ""),
277 /*inputs=*/ ImmutableList.of(),
278 /*outputs=*/ ImmutableList.<ActionInput>of(),
279 ResourceSet.ZERO);
280 CacheHandle entry = cache.lookup(uncacheableSpawn, simplePolicy);
281 verify(remoteCache, never())
282 .getCachedActionResult(any(ActionKey.class));
283 assertThat(entry.hasResult()).isFalse();
284 SpawnResult result = new SpawnResult.Builder().setExitCode(0).setStatus(Status.SUCCESS).build();
285 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
286 entry.store(result, outputFiles);
287 verify(remoteCache)
288 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(false));
289 }
290
291 @Test
292 public void noCacheSpawnsNoResultStore() throws Exception {
293 // Only successful action results are uploaded to the remote cache. The artifacts, however,
294 // are uploaded regardless.
295 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
296 verify(remoteCache).getCachedActionResult(any(ActionKey.class));
297 assertThat(entry.hasResult()).isFalse();
298 SpawnResult result =
299 new SpawnResult.Builder().setExitCode(1).setStatus(Status.NON_ZERO_EXIT).build();
300 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
301 entry.store(result, outputFiles);
302 verify(remoteCache)
303 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(false));
304 }
305
306 @Test
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200307 public void printWarningIfUploadFails() throws Exception {
308 CacheHandle entry = cache.lookup(simpleSpawn, simplePolicy);
309 assertThat(entry.hasResult()).isFalse();
310 SpawnResult result = new SpawnResult.Builder().setExitCode(0).setStatus(Status.SUCCESS).build();
311 ImmutableList<Path> outputFiles = ImmutableList.of(fs.getPath("/random/file"));
312
olaola7744b862017-09-18 23:04:33 +0200313 doThrow(new IOException("cache down"))
314 .when(remoteCache)
315 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200316
317 entry.store(result, outputFiles);
318 verify(remoteCache)
olaola7744b862017-09-18 23:04:33 +0200319 .upload(any(ActionKey.class), any(Path.class), eq(outputFiles), eq(outErr), eq(true));
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200320
321 assertThat(eventHandler.getEvents()).hasSize(1);
322 Event evt = eventHandler.getEvents().get(0);
323 assertThat(evt.getKind()).isEqualTo(EventKind.WARNING);
324 assertThat(evt.getMessage()).contains("fail");
325 assertThat(evt.getMessage()).contains("upload");
326 }
ulfjack9274cba2017-08-11 23:19:48 +0200327}