blob: 2b0124d61af773f28b8b4cfd11bfa6514a7dacdf [file] [log] [blame]
chiwang8cea7652022-05-23 02:24:47 -07001// Copyright 2019 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.base.Throwables.throwIfInstanceOf;
17import static com.google.common.truth.Truth.assertThat;
Googlerffc560d2022-06-23 04:30:57 -070018import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.createTreeArtifactWithGeneratingAction;
chiwang8cea7652022-05-23 02:24:47 -070019import static java.nio.charset.StandardCharsets.UTF_8;
20import static org.junit.Assert.assertThrows;
21import static org.mockito.ArgumentMatchers.any;
Googlerebd6e582022-10-24 05:30:19 -070022import static org.mockito.ArgumentMatchers.eq;
chiwang8cea7652022-05-23 02:24:47 -070023import static org.mockito.Mockito.doAnswer;
Googlerebd6e582022-10-24 05:30:19 -070024import static org.mockito.Mockito.never;
chiwang8cea7652022-05-23 02:24:47 -070025import static org.mockito.Mockito.spy;
Googlerebd6e582022-10-24 05:30:19 -070026import static org.mockito.Mockito.verify;
chiwang8cea7652022-05-23 02:24:47 -070027
28import com.google.common.collect.ImmutableList;
29import com.google.common.collect.ImmutableMap;
30import com.google.common.hash.HashCode;
31import com.google.common.util.concurrent.Futures;
32import com.google.common.util.concurrent.ListenableFuture;
33import com.google.common.util.concurrent.SettableFuture;
34import com.google.devtools.build.lib.actions.ActionInput;
35import com.google.devtools.build.lib.actions.Artifact;
Googlerffc560d2022-06-23 04:30:57 -070036import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
37import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
chiwang8cea7652022-05-23 02:24:47 -070038import com.google.devtools.build.lib.actions.ArtifactRoot;
39import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
40import com.google.devtools.build.lib.actions.FileArtifactValue;
41import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
42import com.google.devtools.build.lib.actions.MetadataProvider;
43import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
44import com.google.devtools.build.lib.clock.JavaClock;
45import com.google.devtools.build.lib.remote.common.BulkTransferException;
46import com.google.devtools.build.lib.remote.util.StaticMetadataProvider;
47import com.google.devtools.build.lib.remote.util.TempPathGenerator;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070048import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
49import com.google.devtools.build.lib.util.Pair;
chiwang8cea7652022-05-23 02:24:47 -070050import com.google.devtools.build.lib.vfs.DigestHashFunction;
51import com.google.devtools.build.lib.vfs.FileSystem;
52import com.google.devtools.build.lib.vfs.FileSystemUtils;
53import com.google.devtools.build.lib.vfs.Path;
Googlerffc560d2022-06-23 04:30:57 -070054import com.google.devtools.build.lib.vfs.PathFragment;
chiwang8cea7652022-05-23 02:24:47 -070055import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
56import java.io.IOException;
57import java.util.HashMap;
58import java.util.Map;
59import java.util.concurrent.ExecutionException;
60import java.util.concurrent.Semaphore;
61import java.util.concurrent.atomic.AtomicBoolean;
62import java.util.function.Supplier;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070063import javax.annotation.Nullable;
chiwang8cea7652022-05-23 02:24:47 -070064import org.junit.Before;
65import org.junit.Test;
66
67/** Base test class for {@link AbstractActionInputPrefetcher} implementations. */
68public abstract class ActionInputPrefetcherTestBase {
69 protected static final DigestHashFunction HASH_FUNCTION = DigestHashFunction.SHA256;
70
71 protected FileSystem fs;
72 protected Path execRoot;
73 protected ArtifactRoot artifactRoot;
74 protected TempPathGenerator tempPathGenerator;
75
76 @Before
77 public void setUp() throws IOException {
78 fs = new InMemoryFileSystem(new JavaClock(), HASH_FUNCTION);
79 execRoot = fs.getPath("/exec");
80 execRoot.createDirectoryAndParents();
81 artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, RootType.Output, "root");
82 artifactRoot.getRoot().asPath().createDirectoryAndParents();
83 Path tempDir = fs.getPath("/tmp");
84 tempDir.createDirectoryAndParents();
85 tempPathGenerator = new TempPathGenerator(tempDir);
86 }
87
88 protected Artifact createRemoteArtifact(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070089 String pathFragment,
chiwang8cea7652022-05-23 02:24:47 -070090 String contents,
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070091 @Nullable PathFragment materializationExecPath,
chiwang8cea7652022-05-23 02:24:47 -070092 Map<ActionInput, FileArtifactValue> metadata,
93 Map<HashCode, byte[]> cas) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070094 Path p = artifactRoot.getRoot().getRelative(pathFragment);
95 Artifact a = ActionsTestUtil.createArtifact(artifactRoot, p);
chiwang8cea7652022-05-23 02:24:47 -070096 byte[] contentsBytes = contents.getBytes(UTF_8);
97 HashCode hashCode = HASH_FUNCTION.getHashFunction().hashBytes(contentsBytes);
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070098 RemoteFileArtifactValue f =
Googlerde02e3f2022-09-29 02:47:24 -070099 RemoteFileArtifactValue.create(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700100 hashCode.asBytes(),
101 contentsBytes.length,
102 /* locationIndex= */ 1,
103 "action-id",
104 materializationExecPath);
chiwang8cea7652022-05-23 02:24:47 -0700105 metadata.put(a, f);
106 cas.put(hashCode, contentsBytes);
107 return a;
108 }
109
Googlerffc560d2022-06-23 04:30:57 -0700110 protected Artifact createRemoteArtifact(
111 String pathFragment,
112 String contents,
113 Map<ActionInput, FileArtifactValue> metadata,
114 Map<HashCode, byte[]> cas) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700115 return createRemoteArtifact(
116 pathFragment, contents, /* materializationExecPath= */ null, metadata, cas);
117 }
118
119 protected Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> createRemoteTreeArtifact(
120 String pathFragment,
121 Map<String, String> contentMap,
122 @Nullable PathFragment materializationExecPath,
123 Map<ActionInput, FileArtifactValue> metadata,
124 Map<HashCode, byte[]> cas)
125 throws IOException {
126 SpecialArtifact parent = createTreeArtifactWithGeneratingAction(artifactRoot, pathFragment);
127 parent.getPath().createDirectoryAndParents();
128 parent.getPath().chmod(0555);
129 TreeArtifactValue.Builder treeBuilder = TreeArtifactValue.newBuilder(parent);
130 for (Map.Entry<String, String> entry : contentMap.entrySet()) {
131 byte[] contentsBytes = entry.getValue().getBytes(UTF_8);
132 HashCode hashCode = HASH_FUNCTION.getHashFunction().hashBytes(contentsBytes);
133 TreeFileArtifact child =
134 TreeFileArtifact.createTreeOutput(parent, PathFragment.create(entry.getKey()));
135 RemoteFileArtifactValue childValue =
136 RemoteFileArtifactValue.create(
137 hashCode.asBytes(), contentsBytes.length, /* locationIndex= */ 1, "action-id");
138 treeBuilder.putChild(child, childValue);
139 metadata.put(child, childValue);
140 cas.put(hashCode, contentsBytes);
141 }
142 if (materializationExecPath != null) {
143 treeBuilder.setMaterializationExecPath(materializationExecPath);
144 }
145 TreeArtifactValue treeValue = treeBuilder.build();
146 metadata.put(parent, treeValue.getMetadata());
147 return Pair.of(parent, treeValue.getChildren().asList());
148 }
149
150 protected Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> createRemoteTreeArtifact(
151 String pathFragment,
152 Map<String, String> contentMap,
153 Map<ActionInput, FileArtifactValue> metadata,
154 Map<HashCode, byte[]> cas)
155 throws IOException {
156 return createRemoteTreeArtifact(
157 pathFragment, contentMap, /* materializationExecPath= */ null, metadata, cas);
Googlerffc560d2022-06-23 04:30:57 -0700158 }
159
chiwang8cea7652022-05-23 02:24:47 -0700160 protected abstract AbstractActionInputPrefetcher createPrefetcher(Map<HashCode, byte[]> cas);
161
162 @Test
Googlerebd6e582022-10-24 05:30:19 -0700163 public void prefetchFiles_fileExists_doNotDownload() throws IOException, InterruptedException {
164 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
165 Map<HashCode, byte[]> cas = new HashMap<>();
166 Artifact a = createRemoteArtifact("file", "hello world", metadata, cas);
167 FileSystemUtils.writeContent(a.getPath(), "hello world".getBytes(UTF_8));
168 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
169 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
170
171 wait(prefetcher.prefetchFiles(metadata.keySet(), metadataProvider));
172
173 verify(prefetcher, never()).doDownloadFile(any(), any(), any(), any());
174 assertThat(prefetcher.downloadedFiles()).containsExactly(a.getPath());
175 assertThat(prefetcher.downloadsInProgress()).isEmpty();
176 }
177
178 @Test
179 public void prefetchFiles_fileExistsButContentMismatches_download()
180 throws IOException, InterruptedException {
181 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
182 Map<HashCode, byte[]> cas = new HashMap<>();
183 Artifact a = createRemoteArtifact("file", "hello world remote", metadata, cas);
184 FileSystemUtils.writeContent(a.getPath(), "hello world local".getBytes(UTF_8));
185 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
186 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
187
188 wait(prefetcher.prefetchFiles(metadata.keySet(), metadataProvider));
189
190 verify(prefetcher).doDownloadFile(any(), eq(a.getExecPath()), any(), any());
191 assertThat(prefetcher.downloadedFiles()).containsExactly(a.getPath());
192 assertThat(prefetcher.downloadsInProgress()).isEmpty();
193 assertThat(FileSystemUtils.readContent(a.getPath(), UTF_8)).isEqualTo("hello world remote");
194 }
195
196 @Test
chiwang8cea7652022-05-23 02:24:47 -0700197 public void prefetchFiles_downloadRemoteFiles() throws Exception {
198 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
199 Map<HashCode, byte[]> cas = new HashMap<>();
200 Artifact a1 = createRemoteArtifact("file1", "hello world", metadata, cas);
201 Artifact a2 = createRemoteArtifact("file2", "fizz buzz", metadata, cas);
202 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
203 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
204
205 wait(prefetcher.prefetchFiles(metadata.keySet(), metadataProvider));
206
207 assertThat(FileSystemUtils.readContent(a1.getPath(), UTF_8)).isEqualTo("hello world");
Googlerffc560d2022-06-23 04:30:57 -0700208 assertReadableNonWritableAndExecutable(a1.getPath());
chiwang8cea7652022-05-23 02:24:47 -0700209 assertThat(FileSystemUtils.readContent(a2.getPath(), UTF_8)).isEqualTo("fizz buzz");
Googlerffc560d2022-06-23 04:30:57 -0700210 assertReadableNonWritableAndExecutable(a2.getPath());
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700211 assertThat(prefetcher.downloadedFiles()).containsExactly(a1.getPath(), a2.getPath());
chiwang8cea7652022-05-23 02:24:47 -0700212 assertThat(prefetcher.downloadsInProgress()).isEmpty();
213 }
214
215 @Test
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700216 public void prefetchFiles_downloadRemoteFiles_withmaterializationExecPath() throws Exception {
Googlerffc560d2022-06-23 04:30:57 -0700217 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
218 Map<HashCode, byte[]> cas = new HashMap<>();
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700219 PathFragment targetExecPath = artifactRoot.getExecPath().getChild("target");
220 Artifact a = createRemoteArtifact("file", "hello world", targetExecPath, metadata, cas);
Googlerffc560d2022-06-23 04:30:57 -0700221 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
222 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
223
224 wait(prefetcher.prefetchFiles(metadata.keySet(), metadataProvider));
225
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700226 assertThat(a.getPath().isSymbolicLink()).isTrue();
227 assertThat(a.getPath().readSymbolicLink())
228 .isEqualTo(execRoot.getRelative(targetExecPath).asFragment());
229 assertThat(FileSystemUtils.readContent(a.getPath(), UTF_8)).isEqualTo("hello world");
230 assertThat(prefetcher.downloadedFiles())
231 .containsExactly(a.getPath(), execRoot.getRelative(targetExecPath));
232 assertThat(prefetcher.downloadsInProgress()).isEmpty();
233 }
234
235 @Test
236 public void prefetchFiles_downloadRemoteTrees() throws Exception {
237 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
238 Map<HashCode, byte[]> cas = new HashMap<>();
239 Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> tree =
240 createRemoteTreeArtifact(
241 "dir",
242 ImmutableMap.of("file1", "content1", "nested_dir/file2", "content2"),
243 metadata,
244 cas);
245 SpecialArtifact parent = tree.getFirst();
246 Artifact firstChild = tree.getSecond().get(0);
247 Artifact secondChild = tree.getSecond().get(1);
248
249 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
250 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
251
252 wait(prefetcher.prefetchFiles(tree.getSecond(), metadataProvider));
253
Googlerffc560d2022-06-23 04:30:57 -0700254 assertReadableNonWritableAndExecutable(parent.getPath());
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700255 assertThat(FileSystemUtils.readContent(firstChild.getPath(), UTF_8)).isEqualTo("content1");
256 assertReadableNonWritableAndExecutable(firstChild.getPath());
257 assertThat(FileSystemUtils.readContent(secondChild.getPath(), UTF_8)).isEqualTo("content2");
258 assertReadableNonWritableAndExecutable(secondChild.getPath());
Googlerffc560d2022-06-23 04:30:57 -0700259 assertThat(prefetcher.downloadedFiles()).containsExactly(parent.getPath());
260 assertThat(prefetcher.downloadsInProgress()).isEmpty();
261 assertReadableNonWritableAndExecutable(parent.getPath().getRelative("nested_dir"));
262 }
263
264 @Test
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700265 public void prefetchFiles_downloadRemoteTrees_withmaterializationExecPath() throws Exception {
266 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
267 Map<HashCode, byte[]> cas = new HashMap<>();
268 PathFragment targetExecPath = artifactRoot.getExecPath().getChild("target");
269 Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> tree =
270 createRemoteTreeArtifact(
271 "dir",
272 ImmutableMap.of("file1", "content1", "nested_dir/file2", "content2"),
273 targetExecPath,
274 metadata,
275 cas);
276 SpecialArtifact parent = tree.getFirst();
277 Artifact firstChild = tree.getSecond().get(0);
278 Artifact secondChild = tree.getSecond().get(1);
279
280 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
281 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
282
283 wait(prefetcher.prefetchFiles(tree.getSecond(), metadataProvider));
284
285 assertThat(parent.getPath().isSymbolicLink()).isTrue();
286 assertThat(parent.getPath().readSymbolicLink())
287 .isEqualTo(execRoot.getRelative(targetExecPath).asFragment());
288 assertReadableNonWritableAndExecutable(parent.getPath());
289 assertThat(FileSystemUtils.readContent(firstChild.getPath(), UTF_8)).isEqualTo("content1");
290 assertReadableNonWritableAndExecutable(firstChild.getPath());
291 assertThat(FileSystemUtils.readContent(secondChild.getPath(), UTF_8)).isEqualTo("content2");
292 assertReadableNonWritableAndExecutable(secondChild.getPath());
293 assertThat(prefetcher.downloadedFiles())
294 .containsExactly(parent.getPath(), execRoot.getRelative(targetExecPath));
295 assertThat(prefetcher.downloadsInProgress()).isEmpty();
296 assertReadableNonWritableAndExecutable(parent.getPath().getRelative("nested_dir"));
297 }
298
299 @Test
chiwang8cea7652022-05-23 02:24:47 -0700300 public void prefetchFiles_missingFiles_fails() throws Exception {
301 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
302 Artifact a = createRemoteArtifact("file1", "hello world", metadata, /* cas= */ new HashMap<>());
303 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
304 AbstractActionInputPrefetcher prefetcher = createPrefetcher(new HashMap<>());
305
306 assertThrows(
307 BulkTransferException.class,
308 () -> wait(prefetcher.prefetchFiles(ImmutableList.of(a), metadataProvider)));
309
310 assertThat(prefetcher.downloadedFiles()).isEmpty();
311 assertThat(prefetcher.downloadsInProgress()).isEmpty();
312 }
313
314 @Test
315 public void prefetchFiles_ignoreNonRemoteFiles() throws Exception {
316 // Test that files that are not remote are not downloaded
317
318 Path p = execRoot.getRelative(artifactRoot.getExecPath()).getRelative("file1");
319 FileSystemUtils.writeContent(p, UTF_8, "hello world");
320 Artifact a = ActionsTestUtil.createArtifact(artifactRoot, p);
321 FileArtifactValue f = FileArtifactValue.createForTesting(a);
322 MetadataProvider metadataProvider = new StaticMetadataProvider(ImmutableMap.of(a, f));
323 AbstractActionInputPrefetcher prefetcher = createPrefetcher(new HashMap<>());
324
325 wait(prefetcher.prefetchFiles(ImmutableList.of(a), metadataProvider));
326
327 assertThat(prefetcher.downloadedFiles()).isEmpty();
328 assertThat(prefetcher.downloadsInProgress()).isEmpty();
329 }
330
331 @Test
332 public void prefetchFiles_multipleThreads_downloadIsCancelled() throws Exception {
333 // Test shared downloads are cancelled if all threads/callers are interrupted
334
335 // arrange
336 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
337 Map<HashCode, byte[]> cas = new HashMap<>();
338 Artifact artifact = createRemoteArtifact("file1", "hello world", metadata, cas);
339 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
340
341 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
342 SettableFuture<Void> downloadThatNeverFinishes = SettableFuture.create();
343 mockDownload(prefetcher, cas, () -> downloadThatNeverFinishes);
344
345 Thread cancelledThread1 =
346 new Thread(
347 () -> {
348 try {
349 wait(prefetcher.prefetchFiles(ImmutableList.of(artifact), metadataProvider));
350 } catch (IOException | InterruptedException ignored) {
351 // do nothing
352 }
353 });
354
355 Thread cancelledThread2 =
356 new Thread(
357 () -> {
358 try {
359 wait(prefetcher.prefetchFiles(ImmutableList.of(artifact), metadataProvider));
360 } catch (IOException | InterruptedException ignored) {
361 // do nothing
362 }
363 });
364
365 // act
366 cancelledThread1.start();
367 cancelledThread2.start();
368 cancelledThread1.interrupt();
369 cancelledThread2.interrupt();
370 cancelledThread1.join();
371 cancelledThread2.join();
372
373 // assert
374 assertThat(downloadThatNeverFinishes.isCancelled()).isTrue();
375 assertThat(artifact.getPath().exists()).isFalse();
376 assertThat(tempPathGenerator.getTempDir().getDirectoryEntries()).isEmpty();
377 }
378
379 @Test
380 public void prefetchFiles_multipleThreads_downloadIsNotCancelledByOtherThreads()
381 throws Exception {
382 // Test multiple threads can share downloads, but do not cancel each other when interrupted
383
384 // arrange
385 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
386 Map<HashCode, byte[]> cas = new HashMap<>();
387 Artifact artifact = createRemoteArtifact("file1", "hello world", metadata, cas);
388 MetadataProvider metadataProvider = new StaticMetadataProvider(metadata);
389 SettableFuture<Void> download = SettableFuture.create();
390 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
391 mockDownload(prefetcher, cas, () -> download);
392 Thread cancelledThread =
393 new Thread(
394 () -> {
395 try {
396 wait(prefetcher.prefetchFiles(ImmutableList.of(artifact), metadataProvider));
397 } catch (IOException | InterruptedException ignored) {
398 // do nothing
399 }
400 });
401
402 AtomicBoolean successful = new AtomicBoolean(false);
403 Thread successfulThread =
404 new Thread(
405 () -> {
406 try {
407 wait(prefetcher.prefetchFiles(ImmutableList.of(artifact), metadataProvider));
408 successful.set(true);
409 } catch (IOException | InterruptedException ignored) {
410 // do nothing
411 }
412 });
413 cancelledThread.start();
414 successfulThread.start();
415 while (true) {
416 if (prefetcher
417 .getDownloadCache()
418 .getSubscriberCount(execRoot.getRelative(artifact.getExecPath()))
419 == 2) {
420 break;
421 }
422 }
423
424 // act
425 cancelledThread.interrupt();
426 cancelledThread.join();
427 // simulate the download finishing
428 assertThat(download.isCancelled()).isFalse();
429 download.set(null);
430 successfulThread.join();
431
432 // assert
433 assertThat(successful.get()).isTrue();
434 assertThat(FileSystemUtils.readContent(artifact.getPath(), UTF_8)).isEqualTo("hello world");
435 }
436
437 @Test
438 public void downloadFile_downloadRemoteFiles() throws Exception {
439 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
440 Map<HashCode, byte[]> cas = new HashMap<>();
441 Artifact a1 = createRemoteArtifact("file1", "hello world", metadata, cas);
442 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
443
444 prefetcher.downloadFile(a1.getPath(), metadata.get(a1));
445
446 assertThat(FileSystemUtils.readContent(a1.getPath(), UTF_8)).isEqualTo("hello world");
447 assertThat(a1.getPath().isExecutable()).isTrue();
448 assertThat(a1.getPath().isReadable()).isTrue();
449 assertThat(a1.getPath().isWritable()).isFalse();
450 }
451
452 @Test
453 public void downloadFile_onInterrupt_deletePartialDownloadedFile() throws Exception {
454 Semaphore startSemaphore = new Semaphore(0);
455 Semaphore endSemaphore = new Semaphore(0);
456 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
457 Map<HashCode, byte[]> cas = new HashMap<>();
458 Artifact a1 = createRemoteArtifact("file1", "hello world", metadata, cas);
459 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
460 mockDownload(
461 prefetcher,
462 cas,
463 () -> {
464 startSemaphore.release();
465 return SettableFuture.create(); // A future that never complete so we can interrupt later
466 });
467
468 AtomicBoolean interrupted = new AtomicBoolean(false);
469 Thread t =
470 new Thread(
471 () -> {
472 try {
473 prefetcher.downloadFile(a1.getPath(), metadata.get(a1));
474 } catch (IOException ignored) {
475 // Intentionally left empty
476 } catch (InterruptedException e) {
477 interrupted.set(true);
478 }
479 endSemaphore.release();
480 });
481 t.start();
482 startSemaphore.acquire();
483 t.interrupt();
484 endSemaphore.acquire();
485
486 assertThat(interrupted.get()).isTrue();
487 assertThat(a1.getPath().exists()).isFalse();
488 assertThat(tempPathGenerator.getTempDir().getDirectoryEntries()).isEmpty();
489 }
490
491 protected static void wait(ListenableFuture<Void> future)
492 throws IOException, InterruptedException {
493 try {
494 future.get();
495 } catch (ExecutionException e) {
496 Throwable cause = e.getCause();
497 if (cause != null) {
498 throwIfInstanceOf(cause, IOException.class);
499 throwIfInstanceOf(cause, InterruptedException.class);
500 throwIfInstanceOf(cause, RuntimeException.class);
501 }
502 throw new IOException(e);
503 } catch (InterruptedException e) {
504 future.cancel(/*mayInterruptIfRunning=*/ true);
505 throw e;
506 }
507 }
508
509 protected static void mockDownload(
510 AbstractActionInputPrefetcher prefetcher,
511 Map<HashCode, byte[]> cas,
512 Supplier<ListenableFuture<Void>> resultSupplier)
513 throws IOException {
514 doAnswer(
515 invocation -> {
516 Path path = invocation.getArgument(0);
Googler3e83fbe2022-10-18 04:12:56 -0700517 FileArtifactValue metadata = invocation.getArgument(2);
chiwang8cea7652022-05-23 02:24:47 -0700518 byte[] content = cas.get(HashCode.fromBytes(metadata.getDigest()));
519 if (content == null) {
520 return Futures.immediateFailedFuture(new IOException("Not found"));
521 }
522 FileSystemUtils.writeContent(path, content);
523 return resultSupplier.get();
524 })
525 .when(prefetcher)
Googler3e83fbe2022-10-18 04:12:56 -0700526 .doDownloadFile(any(), any(), any(), any());
chiwang8cea7652022-05-23 02:24:47 -0700527 }
Googlerffc560d2022-06-23 04:30:57 -0700528
529 private void assertReadableNonWritableAndExecutable(Path path) throws IOException {
530 assertThat(path.isReadable()).isTrue();
531 assertThat(path.isWritable()).isFalse();
532 assertThat(path.isExecutable()).isTrue();
533 }
chiwang8cea7652022-05-23 02:24:47 -0700534}