blob: c41e6da1582747261bb465bb3543883e72694d33 [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
Googler52deefe2023-03-01 05:08:50 -080016import static com.google.common.base.Preconditions.checkState;
chiwang8cea7652022-05-23 02:24:47 -070017import static com.google.common.base.Throwables.throwIfInstanceOf;
18import static com.google.common.truth.Truth.assertThat;
Googler52deefe2023-03-01 05:08:50 -080019import static com.google.common.truth.Truth.assertWithMessage;
Googlerffc560d2022-06-23 04:30:57 -070020import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.createTreeArtifactWithGeneratingAction;
Googler895f3072023-03-15 07:13:15 -070021import static com.google.devtools.build.lib.remote.util.Utils.getFromFuture;
chiwang8cea7652022-05-23 02:24:47 -070022import static java.nio.charset.StandardCharsets.UTF_8;
23import static org.junit.Assert.assertThrows;
24import static org.mockito.ArgumentMatchers.any;
Googlerebd6e582022-10-24 05:30:19 -070025import static org.mockito.ArgumentMatchers.eq;
chiwang8cea7652022-05-23 02:24:47 -070026import static org.mockito.Mockito.doAnswer;
Googlerebd6e582022-10-24 05:30:19 -070027import static org.mockito.Mockito.never;
chiwang8cea7652022-05-23 02:24:47 -070028import static org.mockito.Mockito.spy;
Tiago Quelhase0cdace2023-03-08 06:50:56 -080029import static org.mockito.Mockito.times;
Googlerebd6e582022-10-24 05:30:19 -070030import static org.mockito.Mockito.verify;
chiwang8cea7652022-05-23 02:24:47 -070031
32import com.google.common.collect.ImmutableList;
33import com.google.common.collect.ImmutableMap;
34import com.google.common.hash.HashCode;
35import com.google.common.util.concurrent.Futures;
36import com.google.common.util.concurrent.ListenableFuture;
37import com.google.common.util.concurrent.SettableFuture;
38import com.google.devtools.build.lib.actions.ActionInput;
Googler77c4ce32023-03-10 15:46:29 -080039import com.google.devtools.build.lib.actions.ActionInputPrefetcher.Priority;
chiwang8cea7652022-05-23 02:24:47 -070040import com.google.devtools.build.lib.actions.Artifact;
Googlerffc560d2022-06-23 04:30:57 -070041import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
42import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
chiwang8cea7652022-05-23 02:24:47 -070043import com.google.devtools.build.lib.actions.ArtifactRoot;
44import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
Chi Wang99cff332023-02-14 05:01:31 -080045import com.google.devtools.build.lib.actions.ExecException;
chiwang8cea7652022-05-23 02:24:47 -070046import com.google.devtools.build.lib.actions.FileArtifactValue;
47import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
chiwang8cea7652022-05-23 02:24:47 -070048import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
chiwang8cea7652022-05-23 02:24:47 -070049import com.google.devtools.build.lib.remote.util.TempPathGenerator;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070050import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
Tiago Quelhase0cdace2023-03-08 06:50:56 -080051import com.google.devtools.build.lib.testing.vfs.SpiedFileSystem;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070052import com.google.devtools.build.lib.util.Pair;
chiwang8cea7652022-05-23 02:24:47 -070053import com.google.devtools.build.lib.vfs.DigestHashFunction;
Googler52deefe2023-03-01 05:08:50 -080054import com.google.devtools.build.lib.vfs.Dirent;
chiwang8cea7652022-05-23 02:24:47 -070055import com.google.devtools.build.lib.vfs.FileSystemUtils;
56import com.google.devtools.build.lib.vfs.Path;
Googlerffc560d2022-06-23 04:30:57 -070057import com.google.devtools.build.lib.vfs.PathFragment;
Googler52deefe2023-03-01 05:08:50 -080058import com.google.devtools.build.lib.vfs.Symlinks;
chiwang8cea7652022-05-23 02:24:47 -070059import java.io.IOException;
60import java.util.HashMap;
61import java.util.Map;
62import java.util.concurrent.ExecutionException;
63import java.util.concurrent.Semaphore;
64import java.util.concurrent.atomic.AtomicBoolean;
65import java.util.function.Supplier;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070066import javax.annotation.Nullable;
chiwang8cea7652022-05-23 02:24:47 -070067import org.junit.Before;
68import org.junit.Test;
69
70/** Base test class for {@link AbstractActionInputPrefetcher} implementations. */
71public abstract class ActionInputPrefetcherTestBase {
72 protected static final DigestHashFunction HASH_FUNCTION = DigestHashFunction.SHA256;
73
Tiago Quelhase0cdace2023-03-08 06:50:56 -080074 protected SpiedFileSystem fs;
chiwang8cea7652022-05-23 02:24:47 -070075 protected Path execRoot;
76 protected ArtifactRoot artifactRoot;
77 protected TempPathGenerator tempPathGenerator;
78
79 @Before
80 public void setUp() throws IOException {
Tiago Quelhase0cdace2023-03-08 06:50:56 -080081 fs = SpiedFileSystem.createInMemorySpy();
chiwang8cea7652022-05-23 02:24:47 -070082 execRoot = fs.getPath("/exec");
83 execRoot.createDirectoryAndParents();
84 artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, RootType.Output, "root");
85 artifactRoot.getRoot().asPath().createDirectoryAndParents();
86 Path tempDir = fs.getPath("/tmp");
87 tempDir.createDirectoryAndParents();
88 tempPathGenerator = new TempPathGenerator(tempDir);
89 }
90
91 protected Artifact createRemoteArtifact(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070092 String pathFragment,
chiwang8cea7652022-05-23 02:24:47 -070093 String contents,
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070094 @Nullable PathFragment materializationExecPath,
chiwang8cea7652022-05-23 02:24:47 -070095 Map<ActionInput, FileArtifactValue> metadata,
Chi Wang963640a2023-02-20 03:20:39 -080096 @Nullable Map<HashCode, byte[]> cas) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070097 Path p = artifactRoot.getRoot().getRelative(pathFragment);
98 Artifact a = ActionsTestUtil.createArtifact(artifactRoot, p);
chiwang8cea7652022-05-23 02:24:47 -070099 byte[] contentsBytes = contents.getBytes(UTF_8);
100 HashCode hashCode = HASH_FUNCTION.getHashFunction().hashBytes(contentsBytes);
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700101 RemoteFileArtifactValue f =
Googlerde02e3f2022-09-29 02:47:24 -0700102 RemoteFileArtifactValue.create(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700103 hashCode.asBytes(),
104 contentsBytes.length,
105 /* locationIndex= */ 1,
Chi Wang1ebb04b2023-03-03 07:13:57 -0800106 /* expireAtEpochMilli= */ -1,
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700107 materializationExecPath);
chiwang8cea7652022-05-23 02:24:47 -0700108 metadata.put(a, f);
Chi Wang963640a2023-02-20 03:20:39 -0800109 if (cas != null) {
110 cas.put(hashCode, contentsBytes);
111 }
chiwang8cea7652022-05-23 02:24:47 -0700112 return a;
113 }
114
Googlerffc560d2022-06-23 04:30:57 -0700115 protected Artifact createRemoteArtifact(
116 String pathFragment,
117 String contents,
118 Map<ActionInput, FileArtifactValue> metadata,
Chi Wang963640a2023-02-20 03:20:39 -0800119 @Nullable Map<HashCode, byte[]> cas) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700120 return createRemoteArtifact(
121 pathFragment, contents, /* materializationExecPath= */ null, metadata, cas);
122 }
123
124 protected Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> createRemoteTreeArtifact(
125 String pathFragment,
Googler52deefe2023-03-01 05:08:50 -0800126 Map<String, String> localContentMap,
127 Map<String, String> remoteContentMap,
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700128 @Nullable PathFragment materializationExecPath,
129 Map<ActionInput, FileArtifactValue> metadata,
130 Map<HashCode, byte[]> cas)
131 throws IOException {
132 SpecialArtifact parent = createTreeArtifactWithGeneratingAction(artifactRoot, pathFragment);
Googler52deefe2023-03-01 05:08:50 -0800133
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700134 parent.getPath().createDirectoryAndParents();
135 parent.getPath().chmod(0555);
Googler52deefe2023-03-01 05:08:50 -0800136
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700137 TreeArtifactValue.Builder treeBuilder = TreeArtifactValue.newBuilder(parent);
Googler52deefe2023-03-01 05:08:50 -0800138 for (Map.Entry<String, String> entry : localContentMap.entrySet()) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700139 TreeFileArtifact child =
140 TreeFileArtifact.createTreeOutput(parent, PathFragment.create(entry.getKey()));
Googler52deefe2023-03-01 05:08:50 -0800141 byte[] contents = entry.getValue().getBytes(UTF_8);
142 HashCode hashCode = HASH_FUNCTION.getHashFunction().hashBytes(contents);
143 FileArtifactValue childValue =
144 FileArtifactValue.createForNormalFile(
145 hashCode.asBytes(), /* proxy= */ null, contents.length);
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700146 treeBuilder.putChild(child, childValue);
147 metadata.put(child, childValue);
Googler52deefe2023-03-01 05:08:50 -0800148 cas.put(hashCode, contents);
149 }
150 for (Map.Entry<String, String> entry : remoteContentMap.entrySet()) {
151 TreeFileArtifact child =
152 TreeFileArtifact.createTreeOutput(parent, PathFragment.create(entry.getKey()));
153 byte[] contents = entry.getValue().getBytes(UTF_8);
154 HashCode hashCode = HASH_FUNCTION.getHashFunction().hashBytes(contents);
155 RemoteFileArtifactValue childValue =
156 RemoteFileArtifactValue.create(
Chi Wang1ebb04b2023-03-03 07:13:57 -0800157 hashCode.asBytes(),
158 contents.length,
159 /* locationIndex= */ 1,
160 /* expireAtEpochMilli= */ -1);
Googler52deefe2023-03-01 05:08:50 -0800161 treeBuilder.putChild(child, childValue);
162 metadata.put(child, childValue);
163 cas.put(hashCode, contents);
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700164 }
165 if (materializationExecPath != null) {
166 treeBuilder.setMaterializationExecPath(materializationExecPath);
167 }
168 TreeArtifactValue treeValue = treeBuilder.build();
Googler52deefe2023-03-01 05:08:50 -0800169
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700170 metadata.put(parent, treeValue.getMetadata());
Googler52deefe2023-03-01 05:08:50 -0800171
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700172 return Pair.of(parent, treeValue.getChildren().asList());
173 }
174
175 protected Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> createRemoteTreeArtifact(
176 String pathFragment,
Googler52deefe2023-03-01 05:08:50 -0800177 Map<String, String> localContentMap,
178 Map<String, String> remoteContentMap,
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700179 Map<ActionInput, FileArtifactValue> metadata,
180 Map<HashCode, byte[]> cas)
181 throws IOException {
182 return createRemoteTreeArtifact(
Googler52deefe2023-03-01 05:08:50 -0800183 pathFragment,
184 localContentMap,
185 remoteContentMap,
186 /* materializationExecPath= */ null,
187 metadata,
188 cas);
Googlerffc560d2022-06-23 04:30:57 -0700189 }
190
chiwang8cea7652022-05-23 02:24:47 -0700191 protected abstract AbstractActionInputPrefetcher createPrefetcher(Map<HashCode, byte[]> cas);
192
193 @Test
Chi Wang99cff332023-02-14 05:01:31 -0800194 public void prefetchFiles_fileExists_doNotDownload()
195 throws IOException, ExecException, InterruptedException {
Googlerebd6e582022-10-24 05:30:19 -0700196 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
197 Map<HashCode, byte[]> cas = new HashMap<>();
198 Artifact a = createRemoteArtifact("file", "hello world", metadata, cas);
199 FileSystemUtils.writeContent(a.getPath(), "hello world".getBytes(UTF_8));
Googlerebd6e582022-10-24 05:30:19 -0700200 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
201
Googlerd8e13c82023-04-11 00:07:32 -0700202 wait(prefetcher.prefetchFiles(metadata.keySet(), metadata::get, Priority.MEDIUM));
Googlerebd6e582022-10-24 05:30:19 -0700203
Chi Wang0f524c62023-02-28 08:03:19 -0800204 verify(prefetcher, never()).doDownloadFile(any(), any(), any(), any(), any());
Googlerebd6e582022-10-24 05:30:19 -0700205 assertThat(prefetcher.downloadedFiles()).containsExactly(a.getPath());
206 assertThat(prefetcher.downloadsInProgress()).isEmpty();
207 }
208
209 @Test
210 public void prefetchFiles_fileExistsButContentMismatches_download()
Chi Wang99cff332023-02-14 05:01:31 -0800211 throws IOException, ExecException, InterruptedException {
Googlerebd6e582022-10-24 05:30:19 -0700212 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
213 Map<HashCode, byte[]> cas = new HashMap<>();
214 Artifact a = createRemoteArtifact("file", "hello world remote", metadata, cas);
215 FileSystemUtils.writeContent(a.getPath(), "hello world local".getBytes(UTF_8));
Googlerebd6e582022-10-24 05:30:19 -0700216 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
217
Googlerd8e13c82023-04-11 00:07:32 -0700218 wait(prefetcher.prefetchFiles(metadata.keySet(), metadata::get, Priority.MEDIUM));
Googlerebd6e582022-10-24 05:30:19 -0700219
Chi Wang0f524c62023-02-28 08:03:19 -0800220 verify(prefetcher).doDownloadFile(any(), any(), eq(a.getExecPath()), any(), any());
Googlerebd6e582022-10-24 05:30:19 -0700221 assertThat(prefetcher.downloadedFiles()).containsExactly(a.getPath());
222 assertThat(prefetcher.downloadsInProgress()).isEmpty();
223 assertThat(FileSystemUtils.readContent(a.getPath(), UTF_8)).isEqualTo("hello world remote");
224 }
225
226 @Test
chiwang8cea7652022-05-23 02:24:47 -0700227 public void prefetchFiles_downloadRemoteFiles() throws Exception {
228 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
229 Map<HashCode, byte[]> cas = new HashMap<>();
230 Artifact a1 = createRemoteArtifact("file1", "hello world", metadata, cas);
231 Artifact a2 = createRemoteArtifact("file2", "fizz buzz", metadata, cas);
chiwang8cea7652022-05-23 02:24:47 -0700232 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
233
Googlerd8e13c82023-04-11 00:07:32 -0700234 wait(prefetcher.prefetchFiles(metadata.keySet(), metadata::get, Priority.MEDIUM));
chiwang8cea7652022-05-23 02:24:47 -0700235
236 assertThat(FileSystemUtils.readContent(a1.getPath(), UTF_8)).isEqualTo("hello world");
Googlerffc560d2022-06-23 04:30:57 -0700237 assertReadableNonWritableAndExecutable(a1.getPath());
chiwang8cea7652022-05-23 02:24:47 -0700238 assertThat(FileSystemUtils.readContent(a2.getPath(), UTF_8)).isEqualTo("fizz buzz");
Googlerffc560d2022-06-23 04:30:57 -0700239 assertReadableNonWritableAndExecutable(a2.getPath());
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700240 assertThat(prefetcher.downloadedFiles()).containsExactly(a1.getPath(), a2.getPath());
chiwang8cea7652022-05-23 02:24:47 -0700241 assertThat(prefetcher.downloadsInProgress()).isEmpty();
242 }
243
244 @Test
Googler52deefe2023-03-01 05:08:50 -0800245 public void prefetchFiles_downloadRemoteFiles_withMaterializationExecPath() throws Exception {
Googlerffc560d2022-06-23 04:30:57 -0700246 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
247 Map<HashCode, byte[]> cas = new HashMap<>();
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700248 PathFragment targetExecPath = artifactRoot.getExecPath().getChild("target");
249 Artifact a = createRemoteArtifact("file", "hello world", targetExecPath, metadata, cas);
Googlerffc560d2022-06-23 04:30:57 -0700250 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
251
Googlerd8e13c82023-04-11 00:07:32 -0700252 wait(prefetcher.prefetchFiles(metadata.keySet(), metadata::get, Priority.MEDIUM));
Googlerffc560d2022-06-23 04:30:57 -0700253
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700254 assertThat(a.getPath().isSymbolicLink()).isTrue();
255 assertThat(a.getPath().readSymbolicLink())
256 .isEqualTo(execRoot.getRelative(targetExecPath).asFragment());
257 assertThat(FileSystemUtils.readContent(a.getPath(), UTF_8)).isEqualTo("hello world");
258 assertThat(prefetcher.downloadedFiles())
259 .containsExactly(a.getPath(), execRoot.getRelative(targetExecPath));
260 assertThat(prefetcher.downloadsInProgress()).isEmpty();
261 }
262
263 @Test
264 public void prefetchFiles_downloadRemoteTrees() throws Exception {
265 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
266 Map<HashCode, byte[]> cas = new HashMap<>();
Googler52deefe2023-03-01 05:08:50 -0800267 Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> treeAndChildren =
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700268 createRemoteTreeArtifact(
269 "dir",
Googler52deefe2023-03-01 05:08:50 -0800270 /* localContentMap= */ ImmutableMap.of(),
271 /* remoteContentMap= */ ImmutableMap.of(
272 "file1", "content1", "nested_dir/file2", "content2"),
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700273 metadata,
274 cas);
Googler52deefe2023-03-01 05:08:50 -0800275 SpecialArtifact tree = treeAndChildren.getFirst();
276 ImmutableList<TreeFileArtifact> children = treeAndChildren.getSecond();
277 Artifact firstChild = children.get(0);
278 Artifact secondChild = children.get(1);
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700279
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700280 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
281
Googlerd8e13c82023-04-11 00:07:32 -0700282 wait(prefetcher.prefetchFiles(children, metadata::get, Priority.MEDIUM));
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700283
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700284 assertThat(FileSystemUtils.readContent(firstChild.getPath(), UTF_8)).isEqualTo("content1");
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700285 assertThat(FileSystemUtils.readContent(secondChild.getPath(), UTF_8)).isEqualTo("content2");
Googler52deefe2023-03-01 05:08:50 -0800286
287 assertTreeReadableNonWritableAndExecutable(tree.getPath());
288
289 assertThat(prefetcher.downloadedFiles())
290 .containsExactly(firstChild.getPath(), secondChild.getPath());
Googlerffc560d2022-06-23 04:30:57 -0700291 assertThat(prefetcher.downloadsInProgress()).isEmpty();
Googlerffc560d2022-06-23 04:30:57 -0700292 }
293
294 @Test
Googler52deefe2023-03-01 05:08:50 -0800295 public void prefetchFiles_downloadRemoteTrees_partial() throws Exception {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700296 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
297 Map<HashCode, byte[]> cas = new HashMap<>();
Googler52deefe2023-03-01 05:08:50 -0800298 Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> treeAndChildren =
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700299 createRemoteTreeArtifact(
300 "dir",
Googler52deefe2023-03-01 05:08:50 -0800301 /* localContentMap= */ ImmutableMap.of("file1", "content1"),
302 /* remoteContentMap= */ ImmutableMap.of("file2", "content2"),
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700303 metadata,
304 cas);
Googler52deefe2023-03-01 05:08:50 -0800305 SpecialArtifact tree = treeAndChildren.getFirst();
306 ImmutableList<TreeFileArtifact> children = treeAndChildren.getSecond();
307 Artifact firstChild = children.get(0);
308 Artifact secondChild = children.get(1);
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700309
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700310 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
311
Googler77c4ce32023-03-10 15:46:29 -0800312 wait(
313 prefetcher.prefetchFiles(
Googlerd8e13c82023-04-11 00:07:32 -0700314 ImmutableList.of(firstChild, secondChild), metadata::get, Priority.MEDIUM));
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700315
Googler52deefe2023-03-01 05:08:50 -0800316 assertThat(firstChild.getPath().exists()).isFalse();
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700317 assertThat(FileSystemUtils.readContent(secondChild.getPath(), UTF_8)).isEqualTo("content2");
Googler52deefe2023-03-01 05:08:50 -0800318 assertTreeReadableNonWritableAndExecutable(tree.getPath());
319 assertThat(prefetcher.downloadedFiles()).containsExactly(secondChild.getPath());
320 }
321
322 @Test
323 public void prefetchFiles_downloadRemoteTrees_withMaterializationExecPath() throws Exception {
324 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
325 Map<HashCode, byte[]> cas = new HashMap<>();
326 PathFragment targetExecPath = artifactRoot.getExecPath().getChild("target");
327 Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> treeAndChildren =
328 createRemoteTreeArtifact(
329 "dir",
330 /* localContentMap= */ ImmutableMap.of(),
331 /* remoteContentMap= */ ImmutableMap.of(
332 "file1", "content1", "nested_dir/file2", "content2"),
333 targetExecPath,
334 metadata,
335 cas);
336 SpecialArtifact tree = treeAndChildren.getFirst();
337 ImmutableList<TreeFileArtifact> children = treeAndChildren.getSecond();
338 Artifact firstChild = children.get(0);
339 Artifact secondChild = children.get(1);
340
Googler52deefe2023-03-01 05:08:50 -0800341 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
342
Googlerd8e13c82023-04-11 00:07:32 -0700343 wait(prefetcher.prefetchFiles(children, metadata::get, Priority.MEDIUM));
Googler52deefe2023-03-01 05:08:50 -0800344
345 assertThat(tree.getPath().isSymbolicLink()).isTrue();
346 assertThat(tree.getPath().readSymbolicLink())
347 .isEqualTo(execRoot.getRelative(targetExecPath).asFragment());
348 assertThat(FileSystemUtils.readContent(firstChild.getPath(), UTF_8)).isEqualTo("content1");
349 assertThat(FileSystemUtils.readContent(secondChild.getPath(), UTF_8)).isEqualTo("content2");
350
351 assertTreeReadableNonWritableAndExecutable(execRoot.getRelative(targetExecPath));
352
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700353 assertThat(prefetcher.downloadedFiles())
Googler52deefe2023-03-01 05:08:50 -0800354 .containsExactly(
355 tree.getPath(),
356 execRoot.getRelative(targetExecPath.getRelative(firstChild.getParentRelativePath())),
357 execRoot.getRelative(targetExecPath.getRelative(secondChild.getParentRelativePath())));
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700358 assertThat(prefetcher.downloadsInProgress()).isEmpty();
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700359 }
360
361 @Test
chiwang8cea7652022-05-23 02:24:47 -0700362 public void prefetchFiles_missingFiles_fails() throws Exception {
363 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
364 Artifact a = createRemoteArtifact("file1", "hello world", metadata, /* cas= */ new HashMap<>());
chiwang8cea7652022-05-23 02:24:47 -0700365 AbstractActionInputPrefetcher prefetcher = createPrefetcher(new HashMap<>());
366
367 assertThrows(
Chi Wang99cff332023-02-14 05:01:31 -0800368 Exception.class,
Googlerd8e13c82023-04-11 00:07:32 -0700369 () -> wait(prefetcher.prefetchFiles(ImmutableList.of(a), metadata::get, Priority.MEDIUM)));
chiwang8cea7652022-05-23 02:24:47 -0700370
371 assertThat(prefetcher.downloadedFiles()).isEmpty();
372 assertThat(prefetcher.downloadsInProgress()).isEmpty();
373 }
374
375 @Test
376 public void prefetchFiles_ignoreNonRemoteFiles() throws Exception {
Googler52deefe2023-03-01 05:08:50 -0800377 // Test that non-remote files are not downloaded.
chiwang8cea7652022-05-23 02:24:47 -0700378
379 Path p = execRoot.getRelative(artifactRoot.getExecPath()).getRelative("file1");
380 FileSystemUtils.writeContent(p, UTF_8, "hello world");
381 Artifact a = ActionsTestUtil.createArtifact(artifactRoot, p);
382 FileArtifactValue f = FileArtifactValue.createForTesting(a);
Googlerd8e13c82023-04-11 00:07:32 -0700383 ImmutableMap<ActionInput, FileArtifactValue> metadata = ImmutableMap.of(a, f);
chiwang8cea7652022-05-23 02:24:47 -0700384 AbstractActionInputPrefetcher prefetcher = createPrefetcher(new HashMap<>());
385
Googlerd8e13c82023-04-11 00:07:32 -0700386 wait(prefetcher.prefetchFiles(ImmutableList.of(a), metadata::get, Priority.MEDIUM));
chiwang8cea7652022-05-23 02:24:47 -0700387
388 assertThat(prefetcher.downloadedFiles()).isEmpty();
389 assertThat(prefetcher.downloadsInProgress()).isEmpty();
390 }
391
392 @Test
Googler52deefe2023-03-01 05:08:50 -0800393 public void prefetchFiles_ignoreNonRemoteFiles_tree() throws Exception {
394 // Test that non-remote tree files are not downloaded, but other files in the tree are.
395
396 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
397 Map<HashCode, byte[]> cas = new HashMap<>();
398 Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> treeAndChildren =
399 createRemoteTreeArtifact(
400 "dir",
401 ImmutableMap.of("file1", "content1"),
402 ImmutableMap.of("file2", "content2"),
403 metadata,
404 cas);
405 SpecialArtifact tree = treeAndChildren.getFirst();
406 ImmutableList<TreeFileArtifact> children = treeAndChildren.getSecond();
407 Artifact firstChild = children.get(0);
408 Artifact secondChild = children.get(1);
409
Googler52deefe2023-03-01 05:08:50 -0800410 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
411
Googlerd8e13c82023-04-11 00:07:32 -0700412 wait(prefetcher.prefetchFiles(children, metadata::get, Priority.MEDIUM));
Googler52deefe2023-03-01 05:08:50 -0800413
414 assertThat(firstChild.getPath().exists()).isFalse();
415 assertThat(FileSystemUtils.readContent(secondChild.getPath(), UTF_8)).isEqualTo("content2");
416 assertTreeReadableNonWritableAndExecutable(tree.getPath());
417 assertThat(prefetcher.downloadedFiles()).containsExactly(secondChild.getPath());
418 }
419
420 @Test
Tiago Quelhase0cdace2023-03-08 06:50:56 -0800421 public void prefetchFiles_treeFiles_minimizeFilesystemOperations() throws Exception {
422 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
423 Map<HashCode, byte[]> cas = new HashMap<>();
424 Pair<SpecialArtifact, ImmutableList<TreeFileArtifact>> treeAndChildren =
425 createRemoteTreeArtifact(
426 "dir",
427 /* localContentMap= */ ImmutableMap.of("subdir/file1", "content1"),
428 /* remoteContentMap= */ ImmutableMap.of("subdir/file2", "content2"),
429 metadata,
430 cas);
431 SpecialArtifact tree = treeAndChildren.getFirst();
432 ImmutableList<TreeFileArtifact> children = treeAndChildren.getSecond();
433 Artifact firstChild = children.get(0);
434 Artifact secondChild = children.get(1);
435
Tiago Quelhase0cdace2023-03-08 06:50:56 -0800436 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
437
Googler77c4ce32023-03-10 15:46:29 -0800438 wait(
439 prefetcher.prefetchFiles(
Googlerd8e13c82023-04-11 00:07:32 -0700440 ImmutableList.of(firstChild, secondChild), metadata::get, Priority.MEDIUM));
Tiago Quelhase0cdace2023-03-08 06:50:56 -0800441
442 verify(fs, times(1)).createWritableDirectory(tree.getPath().asFragment());
443 verify(fs, times(1)).createWritableDirectory(tree.getPath().getChild("subdir").asFragment());
444 }
445
446 @Test
chiwang8cea7652022-05-23 02:24:47 -0700447 public void prefetchFiles_multipleThreads_downloadIsCancelled() throws Exception {
448 // Test shared downloads are cancelled if all threads/callers are interrupted
449
450 // arrange
451 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
452 Map<HashCode, byte[]> cas = new HashMap<>();
453 Artifact artifact = createRemoteArtifact("file1", "hello world", metadata, cas);
chiwang8cea7652022-05-23 02:24:47 -0700454
455 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
456 SettableFuture<Void> downloadThatNeverFinishes = SettableFuture.create();
457 mockDownload(prefetcher, cas, () -> downloadThatNeverFinishes);
458
459 Thread cancelledThread1 =
460 new Thread(
461 () -> {
462 try {
Googler77c4ce32023-03-10 15:46:29 -0800463 wait(
464 prefetcher.prefetchFiles(
Googlerd8e13c82023-04-11 00:07:32 -0700465 ImmutableList.of(artifact), metadata::get, Priority.MEDIUM));
Chi Wang99cff332023-02-14 05:01:31 -0800466 } catch (IOException | ExecException | InterruptedException ignored) {
chiwang8cea7652022-05-23 02:24:47 -0700467 // do nothing
468 }
469 });
470
471 Thread cancelledThread2 =
472 new Thread(
473 () -> {
474 try {
Googler77c4ce32023-03-10 15:46:29 -0800475 wait(
476 prefetcher.prefetchFiles(
Googlerd8e13c82023-04-11 00:07:32 -0700477 ImmutableList.of(artifact), metadata::get, Priority.MEDIUM));
Chi Wang99cff332023-02-14 05:01:31 -0800478 } catch (IOException | ExecException | InterruptedException ignored) {
chiwang8cea7652022-05-23 02:24:47 -0700479 // do nothing
480 }
481 });
482
483 // act
484 cancelledThread1.start();
485 cancelledThread2.start();
486 cancelledThread1.interrupt();
487 cancelledThread2.interrupt();
488 cancelledThread1.join();
489 cancelledThread2.join();
490
491 // assert
492 assertThat(downloadThatNeverFinishes.isCancelled()).isTrue();
493 assertThat(artifact.getPath().exists()).isFalse();
494 assertThat(tempPathGenerator.getTempDir().getDirectoryEntries()).isEmpty();
495 }
496
497 @Test
498 public void prefetchFiles_multipleThreads_downloadIsNotCancelledByOtherThreads()
499 throws Exception {
500 // Test multiple threads can share downloads, but do not cancel each other when interrupted
501
502 // arrange
503 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
504 Map<HashCode, byte[]> cas = new HashMap<>();
505 Artifact artifact = createRemoteArtifact("file1", "hello world", metadata, cas);
chiwang8cea7652022-05-23 02:24:47 -0700506 SettableFuture<Void> download = SettableFuture.create();
507 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
508 mockDownload(prefetcher, cas, () -> download);
509 Thread cancelledThread =
510 new Thread(
511 () -> {
512 try {
Googler77c4ce32023-03-10 15:46:29 -0800513 wait(
514 prefetcher.prefetchFiles(
Googlerd8e13c82023-04-11 00:07:32 -0700515 ImmutableList.of(artifact), metadata::get, Priority.MEDIUM));
Chi Wang99cff332023-02-14 05:01:31 -0800516 } catch (IOException | ExecException | InterruptedException ignored) {
chiwang8cea7652022-05-23 02:24:47 -0700517 // do nothing
518 }
519 });
520
521 AtomicBoolean successful = new AtomicBoolean(false);
522 Thread successfulThread =
523 new Thread(
524 () -> {
525 try {
Googler77c4ce32023-03-10 15:46:29 -0800526 wait(
527 prefetcher.prefetchFiles(
Googlerd8e13c82023-04-11 00:07:32 -0700528 ImmutableList.of(artifact), metadata::get, Priority.MEDIUM));
chiwang8cea7652022-05-23 02:24:47 -0700529 successful.set(true);
Chi Wang99cff332023-02-14 05:01:31 -0800530 } catch (IOException | ExecException | InterruptedException ignored) {
chiwang8cea7652022-05-23 02:24:47 -0700531 // do nothing
532 }
533 });
534 cancelledThread.start();
535 successfulThread.start();
536 while (true) {
537 if (prefetcher
538 .getDownloadCache()
539 .getSubscriberCount(execRoot.getRelative(artifact.getExecPath()))
540 == 2) {
541 break;
542 }
543 }
544
545 // act
546 cancelledThread.interrupt();
547 cancelledThread.join();
548 // simulate the download finishing
549 assertThat(download.isCancelled()).isFalse();
550 download.set(null);
551 successfulThread.join();
552
553 // assert
554 assertThat(successful.get()).isTrue();
555 assertThat(FileSystemUtils.readContent(artifact.getPath(), UTF_8)).isEqualTo("hello world");
556 }
557
558 @Test
Googler895f3072023-03-15 07:13:15 -0700559 public void prefetchFiles_onInterrupt_deletePartialDownloadedFile() throws Exception {
chiwang8cea7652022-05-23 02:24:47 -0700560 Semaphore startSemaphore = new Semaphore(0);
561 Semaphore endSemaphore = new Semaphore(0);
562 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
563 Map<HashCode, byte[]> cas = new HashMap<>();
564 Artifact a1 = createRemoteArtifact("file1", "hello world", metadata, cas);
565 AbstractActionInputPrefetcher prefetcher = spy(createPrefetcher(cas));
566 mockDownload(
567 prefetcher,
568 cas,
569 () -> {
570 startSemaphore.release();
571 return SettableFuture.create(); // A future that never complete so we can interrupt later
572 });
573
574 AtomicBoolean interrupted = new AtomicBoolean(false);
575 Thread t =
576 new Thread(
577 () -> {
578 try {
Googler895f3072023-03-15 07:13:15 -0700579 getFromFuture(
Googlerd8e13c82023-04-11 00:07:32 -0700580 prefetcher.prefetchFiles(ImmutableList.of(a1), metadata::get, Priority.MEDIUM));
chiwang8cea7652022-05-23 02:24:47 -0700581 } catch (IOException ignored) {
582 // Intentionally left empty
583 } catch (InterruptedException e) {
584 interrupted.set(true);
585 }
586 endSemaphore.release();
587 });
588 t.start();
589 startSemaphore.acquire();
590 t.interrupt();
591 endSemaphore.acquire();
592
593 assertThat(interrupted.get()).isTrue();
594 assertThat(a1.getPath().exists()).isFalse();
595 assertThat(tempPathGenerator.getTempDir().getDirectoryEntries()).isEmpty();
596 }
597
Googlerac695d72023-02-24 05:50:52 -0800598 @Test
599 public void missingInputs_addedToList() {
600 Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
601 Map<HashCode, byte[]> cas = new HashMap<>();
602 Artifact a = createRemoteArtifact("file", "hello world", metadata, /* cas= */ null);
Googlerac695d72023-02-24 05:50:52 -0800603 AbstractActionInputPrefetcher prefetcher = createPrefetcher(cas);
604
605 assertThrows(
Googler77c4ce32023-03-10 15:46:29 -0800606 Exception.class,
Googlerd8e13c82023-04-11 00:07:32 -0700607 () -> wait(prefetcher.prefetchFiles(metadata.keySet(), metadata::get, Priority.MEDIUM)));
Googlerac695d72023-02-24 05:50:52 -0800608
609 assertThat(prefetcher.getMissingActionInputs()).contains(a);
610 }
611
chiwang8cea7652022-05-23 02:24:47 -0700612 protected static void wait(ListenableFuture<Void> future)
Chi Wang99cff332023-02-14 05:01:31 -0800613 throws IOException, ExecException, InterruptedException {
chiwang8cea7652022-05-23 02:24:47 -0700614 try {
615 future.get();
616 } catch (ExecutionException e) {
617 Throwable cause = e.getCause();
618 if (cause != null) {
619 throwIfInstanceOf(cause, IOException.class);
Chi Wang99cff332023-02-14 05:01:31 -0800620 throwIfInstanceOf(cause, ExecException.class);
chiwang8cea7652022-05-23 02:24:47 -0700621 throwIfInstanceOf(cause, InterruptedException.class);
622 throwIfInstanceOf(cause, RuntimeException.class);
623 }
624 throw new IOException(e);
625 } catch (InterruptedException e) {
Googlerac695d72023-02-24 05:50:52 -0800626 future.cancel(/* mayInterruptIfRunning= */ true);
chiwang8cea7652022-05-23 02:24:47 -0700627 throw e;
628 }
629 }
630
631 protected static void mockDownload(
632 AbstractActionInputPrefetcher prefetcher,
633 Map<HashCode, byte[]> cas,
634 Supplier<ListenableFuture<Void>> resultSupplier)
635 throws IOException {
636 doAnswer(
637 invocation -> {
Chi Wang0f524c62023-02-28 08:03:19 -0800638 Path path = invocation.getArgument(1);
639 FileArtifactValue metadata = invocation.getArgument(3);
chiwang8cea7652022-05-23 02:24:47 -0700640 byte[] content = cas.get(HashCode.fromBytes(metadata.getDigest()));
641 if (content == null) {
642 return Futures.immediateFailedFuture(new IOException("Not found"));
643 }
644 FileSystemUtils.writeContent(path, content);
645 return resultSupplier.get();
646 })
647 .when(prefetcher)
Chi Wang0f524c62023-02-28 08:03:19 -0800648 .doDownloadFile(any(), any(), any(), any(), any());
chiwang8cea7652022-05-23 02:24:47 -0700649 }
Googlerffc560d2022-06-23 04:30:57 -0700650
651 private void assertReadableNonWritableAndExecutable(Path path) throws IOException {
Googler52deefe2023-03-01 05:08:50 -0800652 assertWithMessage(path + " should be readable").that(path.isReadable()).isTrue();
653 assertWithMessage(path + " should not be writable").that(path.isWritable()).isFalse();
654 assertWithMessage(path + " should be executable").that(path.isExecutable()).isTrue();
655 }
656
657 private void assertTreeReadableNonWritableAndExecutable(Path path) throws IOException {
658 checkState(path.isDirectory());
659 assertReadableNonWritableAndExecutable(path);
660 for (Dirent dirent : path.readdir(Symlinks.NOFOLLOW)) {
661 if (dirent.getType().equals(Dirent.Type.DIRECTORY)) {
662 assertTreeReadableNonWritableAndExecutable(path.getChild(dirent.getName()));
663 }
664 }
Googlerffc560d2022-06-23 04:30:57 -0700665 }
chiwang8cea7652022-05-23 02:24:47 -0700666}