blob: 4ed7c32bac3b712d7da2081efbba9c1adbc8f29d [file] [log] [blame]
jmmv9573a0d2017-09-26 11:59:22 -04001// 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.
14
15package com.google.devtools.build.lib.actions;
16
17import static com.google.common.truth.Truth.assertThat;
Chi Wang4e290422021-08-03 17:56:19 -070018import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ARTIFACT_OWNER;
19import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.createArtifact;
20import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.createTreeArtifactWithGeneratingAction;
Googler5d63b522023-10-31 09:28:56 -070021import static com.google.devtools.build.lib.vfs.FileSystemUtils.readContent;
Chi Wang4e290422021-08-03 17:56:19 -070022import static com.google.devtools.build.lib.vfs.FileSystemUtils.writeIsoLatin1;
23import static java.nio.charset.StandardCharsets.UTF_8;
Googlercba991d2023-04-26 04:51:46 -070024import static org.mockito.ArgumentMatchers.any;
25import static org.mockito.ArgumentMatchers.argThat;
26import static org.mockito.Mockito.doReturn;
Chi Wang4e290422021-08-03 17:56:19 -070027import static org.mockito.Mockito.mock;
Googlercba991d2023-04-26 04:51:46 -070028import static org.mockito.Mockito.never;
29import static org.mockito.Mockito.verify;
Chi Wang4e290422021-08-03 17:56:19 -070030import static org.mockito.Mockito.when;
jmmv9573a0d2017-09-26 11:59:22 -040031
jmmv9573a0d2017-09-26 11:59:22 -040032import com.google.common.collect.ImmutableList;
ishikhman38af41d2019-09-27 01:12:23 -070033import com.google.common.collect.ImmutableMap;
jmmv9573a0d2017-09-26 11:59:22 -040034import com.google.devtools.build.lib.actions.ActionCacheChecker.Token;
jhorvitz53f18992021-10-29 10:00:35 -070035import com.google.devtools.build.lib.actions.Artifact.ArchivedTreeArtifact;
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -080036import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
37import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType;
Googlerf0b0c392021-01-27 17:56:52 -080038import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
Chi Wang4e290422021-08-03 17:56:19 -070039import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
jmmv9573a0d2017-09-26 11:59:22 -040040import com.google.devtools.build.lib.actions.cache.ActionCache;
Chi Wang4e290422021-08-03 17:56:19 -070041import com.google.devtools.build.lib.actions.cache.ActionCache.Entry.SerializableTreeArtifactValue;
jmmv9573a0d2017-09-26 11:59:22 -040042import com.google.devtools.build.lib.actions.cache.CompactPersistentActionCache;
Googler8ec4c502023-04-12 10:44:40 -070043import com.google.devtools.build.lib.actions.cache.OutputMetadataStore;
jmmv9573a0d2017-09-26 11:59:22 -040044import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics;
45import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics.MissDetail;
46import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics.MissReason;
jmmv9573a0d2017-09-26 11:59:22 -040047import com.google.devtools.build.lib.actions.util.ActionsTestUtil.FakeArtifactResolverBase;
Googler8ec4c502023-04-12 10:44:40 -070048import com.google.devtools.build.lib.actions.util.ActionsTestUtil.FakeInputMetadataHandlerBase;
jmmv9573a0d2017-09-26 11:59:22 -040049import com.google.devtools.build.lib.actions.util.ActionsTestUtil.MissDetailsBuilder;
50import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction;
51import com.google.devtools.build.lib.clock.Clock;
janakra6126342021-06-18 13:52:39 -070052import com.google.devtools.build.lib.events.NullEventHandler;
Chi Wang4e290422021-08-03 17:56:19 -070053import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
jmmv9573a0d2017-09-26 11:59:22 -040054import com.google.devtools.build.lib.testutil.ManualClock;
55import com.google.devtools.build.lib.testutil.Scratch;
jcater614fe0d2018-02-28 12:44:39 -080056import com.google.devtools.build.lib.util.Fingerprint;
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -080057import com.google.devtools.build.lib.vfs.DigestHashFunction;
Chi Wang4e290422021-08-03 17:56:19 -070058import com.google.devtools.build.lib.vfs.Dirent;
jmmv9573a0d2017-09-26 11:59:22 -040059import com.google.devtools.build.lib.vfs.FileSystem;
60import com.google.devtools.build.lib.vfs.FileSystemUtils;
Googler88c426e2023-01-09 12:56:29 -080061import com.google.devtools.build.lib.vfs.OutputPermissions;
jmmv9573a0d2017-09-26 11:59:22 -040062import com.google.devtools.build.lib.vfs.Path;
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -080063import com.google.devtools.build.lib.vfs.PathFragment;
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -080064import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070065import com.google.testing.junit.testparameterinjector.TestParameter;
66import com.google.testing.junit.testparameterinjector.TestParameterInjector;
jmmv9573a0d2017-09-26 11:59:22 -040067import java.io.IOException;
janakra6126342021-06-18 13:52:39 -070068import java.io.PrintStream;
Googlercba991d2023-04-26 04:51:46 -070069import java.time.Instant;
Chi Wang4e290422021-08-03 17:56:19 -070070import java.util.ArrayDeque;
71import java.util.Deque;
jmmv9573a0d2017-09-26 11:59:22 -040072import java.util.HashMap;
73import java.util.HashSet;
74import java.util.Map;
Chi Wang4e290422021-08-03 17:56:19 -070075import java.util.Optional;
jmmv9573a0d2017-09-26 11:59:22 -040076import java.util.Set;
Chi Wang49a95022023-03-14 07:32:41 -070077import java.util.function.Predicate;
ajurkowski4784d732020-07-07 11:33:14 -070078import javax.annotation.Nullable;
jmmv9573a0d2017-09-26 11:59:22 -040079import org.junit.After;
80import org.junit.Before;
81import org.junit.Test;
82import org.junit.runner.RunWith;
jmmv9573a0d2017-09-26 11:59:22 -040083
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070084@RunWith(TestParameterInjector.class)
jmmv9573a0d2017-09-26 11:59:22 -040085public class ActionCacheCheckerTest {
Googlercba991d2023-04-26 04:51:46 -070086 private static final RemoteArtifactChecker CHECK_TTL =
87 (file, metadata) -> metadata.isAlive(Instant.now());
88
janakra6126342021-06-18 13:52:39 -070089 private CorruptibleActionCache cache;
jmmv9573a0d2017-09-26 11:59:22 -040090 private ActionCacheChecker cacheChecker;
91 private Set<Path> filesToDelete;
Chi Wang4e290422021-08-03 17:56:19 -070092 private DigestHashFunction digestHashFunction;
93 private FileSystem fileSystem;
94 private ArtifactRoot artifactRoot;
jmmv9573a0d2017-09-26 11:59:22 -040095
96 @Before
97 public void setupCache() throws Exception {
98 Scratch scratch = new Scratch();
99 Clock clock = new ManualClock();
jmmv9573a0d2017-09-26 11:59:22 -0400100
janakra6126342021-06-18 13:52:39 -0700101 cache = new CorruptibleActionCache(scratch.resolve("/cache/test.dat"), clock);
Chi Wang4e290422021-08-03 17:56:19 -0700102 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ false);
103 digestHashFunction = DigestHashFunction.SHA256;
104 fileSystem = new InMemoryFileSystem(digestHashFunction);
105 Path execRoot = fileSystem.getPath("/output");
106 artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, RootType.Output, "bin");
107 }
108
109 private byte[] digest(byte[] content) {
110 return digestHashFunction.getHashFunction().hashBytes(content).asBytes();
111 }
112
113 private ActionCacheChecker createActionCacheChecker(boolean storeOutputMetadata) {
114 return new ActionCacheChecker(
115 cache,
116 new FakeArtifactResolverBase(),
117 new ActionKeyContext(),
118 action -> true,
119 ActionCacheChecker.CacheConfig.builder()
120 .setEnabled(true)
121 .setVerboseExplanations(false)
122 .setStoreOutputMetadata(storeOutputMetadata)
jhorvitz53f18992021-10-29 10:00:35 -0700123 .build());
jmmv9573a0d2017-09-26 11:59:22 -0400124 }
125
126 @Before
127 public void clearFilesToDeleteAfterTest() throws Exception {
128 filesToDelete = new HashSet<>();
129 }
130
131 @After
132 public void deleteFilesCreatedDuringTest() throws Exception {
133 for (Path path : filesToDelete) {
Chi Wang4e290422021-08-03 17:56:19 -0700134 if (path.isDirectory()) {
135 path.deleteTree();
136 } else {
137 path.delete();
138 }
jmmv9573a0d2017-09-26 11:59:22 -0400139 }
140 }
141
142 /** "Executes" the given action from the point of view of the cache's lifecycle. */
143 private void runAction(Action action) throws Exception {
144 runAction(action, new HashMap<>());
145 }
146
Googler088d8e92023-04-11 08:25:49 -0700147 private void runAction(
Googler8ec4c502023-04-12 10:44:40 -0700148 Action action,
149 InputMetadataProvider inputMetadataProvider,
150 OutputMetadataStore outputMetadataStore)
Googler088d8e92023-04-11 08:25:49 -0700151 throws Exception {
Googler8ec4c502023-04-12 10:44:40 -0700152 runAction(
153 action, new HashMap<>(), ImmutableMap.of(), inputMetadataProvider, outputMetadataStore);
Chi Wang4e290422021-08-03 17:56:19 -0700154 }
155
jmmv9573a0d2017-09-26 11:59:22 -0400156 /**
157 * "Executes" the given action from the point of view of the cache's lifecycle with a custom
158 * client environment.
159 */
160 private void runAction(Action action, Map<String, String> clientEnv) throws Exception {
ishikhman38af41d2019-09-27 01:12:23 -0700161 runAction(action, clientEnv, ImmutableMap.of());
162 }
163
164 private void runAction(Action action, Map<String, String> clientEnv, Map<String, String> platform)
165 throws Exception {
Googler8ec4c502023-04-12 10:44:40 -0700166 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Googler088d8e92023-04-11 08:25:49 -0700167 runAction(action, clientEnv, platform, metadataHandler, metadataHandler);
Chi Wang4e290422021-08-03 17:56:19 -0700168 }
jmmv9573a0d2017-09-26 11:59:22 -0400169
Chi Wang4e290422021-08-03 17:56:19 -0700170 private void runAction(
171 Action action,
172 Map<String, String> clientEnv,
173 Map<String, String> platform,
Googler8ec4c502023-04-12 10:44:40 -0700174 InputMetadataProvider inputMetadataProvider,
175 OutputMetadataStore outputMetadataStore)
Chi Wang4e290422021-08-03 17:56:19 -0700176 throws Exception {
Googler7bcd5cc2023-08-11 05:57:55 -0700177 runAction(
178 action,
179 clientEnv,
180 platform,
181 inputMetadataProvider,
182 outputMetadataStore,
183 RemoteArtifactChecker.TRUST_ALL);
184 }
185
186 private void runAction(
187 Action action,
188 Map<String, String> clientEnv,
189 Map<String, String> platform,
190 InputMetadataProvider inputMetadataProvider,
191 OutputMetadataStore outputMetadataStore,
192 RemoteArtifactChecker remoteArtifactChecker)
193 throws Exception {
ishikhman38af41d2019-09-27 01:12:23 -0700194 Token token =
195 cacheChecker.getTokenIfNeedToExecute(
ajurkowskie6cce762020-07-24 16:06:19 -0700196 action,
Googler88c426e2023-01-09 12:56:29 -0800197 /* resolvedCacheArtifacts= */ null,
ajurkowskie6cce762020-07-24 16:06:19 -0700198 clientEnv,
Googler88c426e2023-01-09 12:56:29 -0800199 OutputPermissions.READONLY,
200 /* handler= */ null,
Googler8ec4c502023-04-12 10:44:40 -0700201 inputMetadataProvider,
202 outputMetadataStore,
Googler88c426e2023-01-09 12:56:29 -0800203 /* artifactExpander= */ null,
chiwangc9b7e222021-11-17 00:05:02 -0800204 platform,
Googler7bcd5cc2023-08-11 05:57:55 -0700205 remoteArtifactChecker);
Googlercba991d2023-04-26 04:51:46 -0700206 runAction(action, clientEnv, platform, inputMetadataProvider, outputMetadataStore, token);
207 }
208
209 private void runAction(
210 Action action,
211 Map<String, String> clientEnv,
212 Map<String, String> platform,
213 InputMetadataProvider inputMetadataProvider,
214 OutputMetadataStore outputMetadataStore,
215 @Nullable Token token)
216 throws Exception {
jmmv9573a0d2017-09-26 11:59:22 -0400217 if (token != null) {
Googlercba991d2023-04-26 04:51:46 -0700218 for (Artifact artifact : action.getOutputs()) {
219 Path path = artifact.getPath();
220
221 // Record all action outputs as files to be deleted across tests to prevent cross-test
222 // pollution. We need to do this on a path basis because we don't know upfront which file
223 // system they live in so we cannot just recreate the file system. (E.g. all NullActions
224 // share an in-memory file system to hold dummy outputs.)
225 filesToDelete.add(path);
226
227 Path parent = path.getParentDirectory();
228 if (parent != null) {
229 parent.createDirectoryAndParents();
230 }
231 }
232
jmmv9573a0d2017-09-26 11:59:22 -0400233 // Real action execution would happen here.
Chi Wang4e290422021-08-03 17:56:19 -0700234 ActionExecutionContext context = mock(ActionExecutionContext.class);
Googler8ec4c502023-04-12 10:44:40 -0700235 when(context.getOutputMetadataStore()).thenReturn(outputMetadataStore);
Chi Wang4e290422021-08-03 17:56:19 -0700236 action.execute(context);
237
ajurkowskie6cce762020-07-24 16:06:19 -0700238 cacheChecker.updateActionCache(
Googler88c426e2023-01-09 12:56:29 -0800239 action,
240 token,
Googler8ec4c502023-04-12 10:44:40 -0700241 inputMetadataProvider,
242 outputMetadataStore,
Googler88c426e2023-01-09 12:56:29 -0800243 /* artifactExpander= */ null,
244 clientEnv,
245 OutputPermissions.READONLY,
246 platform);
jmmv9573a0d2017-09-26 11:59:22 -0400247 }
248 }
249
250 /** Ensures that the cache statistics match exactly the given values. */
251 private void assertStatistics(int hits, Iterable<MissDetail> misses) {
252 ActionCacheStatistics.Builder builder = ActionCacheStatistics.newBuilder();
253 cache.mergeIntoActionCacheStatistics(builder);
254 ActionCacheStatistics stats = builder.build();
255
256 assertThat(stats.getHits()).isEqualTo(hits);
257 assertThat(stats.getMissDetailsList()).containsExactlyElementsIn(misses);
258 }
259
260 private void doTestNotCached(Action action, MissReason missReason) throws Exception {
261 runAction(action);
262
263 assertStatistics(0, new MissDetailsBuilder().set(missReason, 1).build());
264 }
265
266 private void doTestCached(Action action, MissReason missReason) throws Exception {
267 int runs = 5;
268 for (int i = 0; i < runs; i++) {
269 runAction(action);
270 }
271
272 assertStatistics(runs - 1, new MissDetailsBuilder().set(missReason, 1).build());
273 }
274
275 private void doTestCorruptedCacheEntry(Action action) throws Exception {
276 cache.corruptAllEntries();
277 runAction(action);
278
279 assertStatistics(
280 0,
281 new MissDetailsBuilder().set(MissReason.CORRUPTED_CACHE_ENTRY, 1).build());
282 }
283
284 @Test
285 public void testNoActivity() throws Exception {
286 assertStatistics(0, new MissDetailsBuilder().build());
287 }
288
289 @Test
290 public void testNotCached() throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -0700291 doTestNotCached(new WriteEmptyOutputAction(), MissReason.NOT_CACHED);
jmmv9573a0d2017-09-26 11:59:22 -0400292 }
293
294 @Test
295 public void testCached() throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -0700296 doTestCached(new WriteEmptyOutputAction(), MissReason.NOT_CACHED);
jmmv9573a0d2017-09-26 11:59:22 -0400297 }
298
299 @Test
300 public void testCorruptedCacheEntry() throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -0700301 doTestCorruptedCacheEntry(new WriteEmptyOutputAction());
jmmv9573a0d2017-09-26 11:59:22 -0400302 }
303
304 @Test
305 public void testDifferentActionKey() throws Exception {
tomlu3d1a1942017-11-29 14:01:21 -0800306 Action action =
Chi Wang4e290422021-08-03 17:56:19 -0700307 new WriteEmptyOutputAction() {
tomlu3d1a1942017-11-29 14:01:21 -0800308 @Override
ajurkowski4784d732020-07-07 11:33:14 -0700309 protected void computeKey(
310 ActionKeyContext actionKeyContext,
311 @Nullable ArtifactExpander artifactExpander,
312 Fingerprint fp) {
jcater614fe0d2018-02-28 12:44:39 -0800313 fp.addString("key1");
tomlu3d1a1942017-11-29 14:01:21 -0800314 }
315 };
jmmv9573a0d2017-09-26 11:59:22 -0400316 runAction(action);
tomlu3d1a1942017-11-29 14:01:21 -0800317 action =
318 new NullAction() {
319 @Override
ajurkowski4784d732020-07-07 11:33:14 -0700320 protected void computeKey(
321 ActionKeyContext actionKeyContext,
322 @Nullable ArtifactExpander artifactExpander,
323 Fingerprint fp) {
jcater614fe0d2018-02-28 12:44:39 -0800324 fp.addString("key2");
tomlu3d1a1942017-11-29 14:01:21 -0800325 }
326 };
jmmv9573a0d2017-09-26 11:59:22 -0400327 runAction(action);
328
329 assertStatistics(
330 0,
331 new MissDetailsBuilder()
332 .set(MissReason.DIFFERENT_ACTION_KEY, 1)
333 .set(MissReason.NOT_CACHED, 1)
334 .build());
335 }
336
337 @Test
338 public void testDifferentEnvironment() throws Exception {
janakr2c059122021-06-29 09:19:56 -0700339 Action action =
Chi Wang4e290422021-08-03 17:56:19 -0700340 new WriteEmptyOutputAction() {
janakr2c059122021-06-29 09:19:56 -0700341 @Override
342 public ImmutableList<String> getClientEnvironmentVariables() {
343 return ImmutableList.of("used-var");
344 }
345 };
jmmv9573a0d2017-09-26 11:59:22 -0400346 Map<String, String> clientEnv = new HashMap<>();
347 clientEnv.put("unused-var", "1");
348 runAction(action, clientEnv); // Not cached.
349 clientEnv.remove("unused-var");
350 runAction(action, clientEnv); // Cache hit because we only modified uninteresting variables.
351 clientEnv.put("used-var", "2");
352 runAction(action, clientEnv); // Cache miss because of different environment.
353 runAction(action, clientEnv); // Cache hit because we did not change anything.
354
355 assertStatistics(
356 2,
357 new MissDetailsBuilder()
358 .set(MissReason.DIFFERENT_ENVIRONMENT, 1)
359 .set(MissReason.NOT_CACHED, 1)
360 .build());
361 }
362
363 @Test
ishikhman38af41d2019-09-27 01:12:23 -0700364 public void testDifferentRemoteDefaultPlatform() throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -0700365 Action action = new WriteEmptyOutputAction();
ishikhman38af41d2019-09-27 01:12:23 -0700366 Map<String, String> env = new HashMap<>();
367 env.put("unused-var", "1");
368
369 Map<String, String> platform = new HashMap<>();
370 platform.put("used-var", "1");
371 // Not cached.
372 runAction(action, env, platform);
373 // Cache hit because nothing changed.
374 runAction(action, env, platform);
375 // Cache miss because platform changed to an empty from a previous value.
376 runAction(action, env, ImmutableMap.of());
377 // Cache hit with an empty platform.
378 runAction(action, env, ImmutableMap.of());
379 // Cache miss because platform changed to a value from an empty one.
380 runAction(action, env, ImmutableMap.copyOf(platform));
381 platform.put("another-var", "1234");
382 // Cache miss because platform value changed.
383 runAction(action, env, ImmutableMap.copyOf(platform));
384
385 assertStatistics(
386 2,
387 new MissDetailsBuilder()
388 .set(MissReason.DIFFERENT_ENVIRONMENT, 3)
389 .set(MissReason.NOT_CACHED, 1)
390 .build());
391 }
392
393 @Test
jmmv9573a0d2017-09-26 11:59:22 -0400394 public void testDifferentFiles() throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -0700395 Action action = new WriteEmptyOutputAction();
Googler5d63b522023-10-31 09:28:56 -0700396 runAction(action); // Not cached.
397 assertThat(readContent(action.getPrimaryOutput().getPath(), UTF_8)).isEmpty();
Chi Wang4e290422021-08-03 17:56:19 -0700398 writeContentAsLatin1(action.getPrimaryOutput().getPath(), "modified");
Googler5d63b522023-10-31 09:28:56 -0700399 runAction(action); // Cache miss because output files were modified.
jmmv9573a0d2017-09-26 11:59:22 -0400400
401 assertStatistics(
402 0,
403 new MissDetailsBuilder()
404 .set(MissReason.DIFFERENT_FILES, 1)
405 .set(MissReason.NOT_CACHED, 1)
406 .build());
407 }
408
409 @Test
410 public void testUnconditionalExecution() throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -0700411 Action action =
412 new WriteEmptyOutputAction() {
413 @Override
414 public boolean executeUnconditionally() {
415 return true;
416 }
jmmv9573a0d2017-09-26 11:59:22 -0400417
Chi Wang4e290422021-08-03 17:56:19 -0700418 @Override
419 public boolean isVolatile() {
420 return true;
421 }
422 };
jmmv9573a0d2017-09-26 11:59:22 -0400423
424 int runs = 5;
425 for (int i = 0; i < runs; i++) {
426 runAction(action);
427 }
428
429 assertStatistics(
430 0, new MissDetailsBuilder().set(MissReason.UNCONDITIONAL_EXECUTION, runs).build());
431 }
432
433 @Test
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -0800434 public void testDeletedConstantMetadataOutputCausesReexecution() throws Exception {
435 SpecialArtifact output =
jhorvitzcab340f2021-09-27 09:16:23 -0700436 SpecialArtifact.create(
Chi Wang4e290422021-08-03 17:56:19 -0700437 artifactRoot,
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -0800438 PathFragment.create("bin/dummy"),
Chi Wang4e290422021-08-03 17:56:19 -0700439 NULL_ARTIFACT_OWNER,
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -0800440 SpecialArtifactType.CONSTANT_METADATA);
441 output.getPath().getParentDirectory().createDirectoryAndParents();
Chi Wang4e290422021-08-03 17:56:19 -0700442 Action action = new WriteEmptyOutputAction(output);
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -0800443 runAction(action);
444 output.getPath().delete();
Googler8ec4c502023-04-12 10:44:40 -0700445 FakeInputMetadataHandler fakeMetadataHandler = new FakeInputMetadataHandler();
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -0800446 assertThat(
447 cacheChecker.getTokenIfNeedToExecute(
448 action,
Googler88c426e2023-01-09 12:56:29 -0800449 /* resolvedCacheArtifacts= */ null,
450 /* clientEnv= */ ImmutableMap.of(),
451 OutputPermissions.READONLY,
452 /* handler= */ null,
Googler088d8e92023-04-11 08:25:49 -0700453 fakeMetadataHandler,
454 fakeMetadataHandler,
Googler88c426e2023-01-09 12:56:29 -0800455 /* artifactExpander= */ null,
456 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -0700457 RemoteArtifactChecker.TRUST_ALL))
Benjamin Peterson2bbae6a2020-11-16 08:35:23 -0800458 .isNotNull();
459 }
460
Chi Wang4e290422021-08-03 17:56:19 -0700461 private RemoteFileArtifactValue createRemoteFileMetadata(String content) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700462 return createRemoteFileMetadata(content, /* materializationExecPath= */ null);
463 }
464
465 private RemoteFileArtifactValue createRemoteFileMetadata(
466 String content, @Nullable PathFragment materializationExecPath) {
Chi Wang4e290422021-08-03 17:56:19 -0700467 byte[] bytes = content.getBytes(UTF_8);
Chi Wang1ebb04b2023-03-03 07:13:57 -0800468 return RemoteFileArtifactValue.create(
469 digest(bytes), bytes.length, 1, /* expireAtEpochMilli= */ -1, materializationExecPath);
470 }
471
472 private RemoteFileArtifactValue createRemoteFileMetadata(
473 String content, long expireAtEpochMilli, @Nullable PathFragment materializationExecPath) {
474 byte[] bytes = content.getBytes(UTF_8);
475 return RemoteFileArtifactValue.create(
476 digest(bytes), bytes.length, 1, expireAtEpochMilli, materializationExecPath);
Chi Wang4e290422021-08-03 17:56:19 -0700477 }
478
jhorvitz53f18992021-10-29 10:00:35 -0700479 private static TreeArtifactValue createTreeMetadata(
Chi Wang4e290422021-08-03 17:56:19 -0700480 SpecialArtifact parent,
481 ImmutableMap<String, ? extends FileArtifactValue> children,
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700482 Optional<FileArtifactValue> archivedArtifactValue,
483 Optional<PathFragment> materializationExecPath) {
Chi Wang4e290422021-08-03 17:56:19 -0700484 TreeArtifactValue.Builder builder = TreeArtifactValue.newBuilder(parent);
485 for (Map.Entry<String, ? extends FileArtifactValue> entry : children.entrySet()) {
486 builder.putChild(
487 Artifact.TreeFileArtifact.createTreeOutput(parent, entry.getKey()), entry.getValue());
488 }
489 archivedArtifactValue.ifPresent(
490 metadata -> {
jhorvitz53f18992021-10-29 10:00:35 -0700491 ArchivedTreeArtifact artifact = ArchivedTreeArtifact.createForTree(parent);
Chi Wang4e290422021-08-03 17:56:19 -0700492 builder.setArchivedRepresentation(
493 TreeArtifactValue.ArchivedRepresentation.create(artifact, metadata));
494 });
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700495 materializationExecPath.ifPresent(builder::setMaterializationExecPath);
Chi Wang4e290422021-08-03 17:56:19 -0700496 return builder.build();
497 }
498
499 @Test
500 public void saveOutputMetadata_remoteFileMetadataSaved() throws Exception {
501 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
502 Artifact output = createArtifact(artifactRoot, "bin/dummy");
503 String content = "content";
504 Action action = new InjectOutputFileMetadataAction(output, createRemoteFileMetadata(content));
505
506 // Not cached.
507 runAction(action);
508
509 assertThat(output.getPath().exists()).isFalse();
510 ActionCache.Entry entry = cache.get(output.getExecPathString());
511 assertThat(entry).isNotNull();
512 assertThat(entry.getOutputFile(output)).isEqualTo(createRemoteFileMetadata(content));
513 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
514 }
515
516 @Test
517 public void saveOutputMetadata_localFileMetadataNotSaved() throws Exception {
518 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
519 Artifact output = createArtifact(artifactRoot, "bin/dummy");
520 Action action = new WriteEmptyOutputAction(output);
521 output.getPath().delete();
522
523 runAction(action);
524
525 assertThat(output.getPath().exists()).isTrue();
526 ActionCache.Entry entry = cache.get(output.getExecPathString());
527 assertThat(entry).isNotNull();
528 assertThat(entry.getOutputFile(output)).isNull();
529 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
530 }
531
532 @Test
533 public void saveOutputMetadata_remoteMetadataInjectedAndLocalFilesStored() throws Exception {
534 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
535 Artifact output = createArtifact(artifactRoot, "bin/dummy");
536 Action action =
537 new WriteEmptyOutputAction(output) {
538 @Override
539 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
540 actionExecutionContext
Googler8ec4c502023-04-12 10:44:40 -0700541 .getOutputMetadataStore()
Chi Wang4e290422021-08-03 17:56:19 -0700542 .injectFile(output, createRemoteFileMetadata(""));
543 return super.execute(actionExecutionContext);
544 }
545 };
546 output.getPath().delete();
547
548 runAction(action);
549
550 assertThat(output.getPath().exists()).isTrue();
551 ActionCache.Entry entry = cache.get(output.getExecPathString());
552 assertThat(entry).isNotNull();
553 assertThat(entry.getOutputFile(output)).isEqualTo(createRemoteFileMetadata(""));
554 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
555 }
556
557 @Test
558 public void saveOutputMetadata_notSavedIfDisabled() throws Exception {
559 Artifact output = createArtifact(artifactRoot, "bin/dummy");
560 String content = "content";
561 Action action = new InjectOutputFileMetadataAction(output, createRemoteFileMetadata(content));
562
563 runAction(action);
564
565 assertThat(output.getPath().exists()).isFalse();
566 ActionCache.Entry entry = cache.get(output.getExecPathString());
567 assertThat(entry).isNotNull();
568 assertThat(entry.getOutputFile(output)).isNull();
569 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
570 }
571
572 @Test
573 public void saveOutputMetadata_remoteFileMetadataLoaded() throws Exception {
574 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
575 Artifact output = createArtifact(artifactRoot, "bin/dummy");
576 String content = "content";
577 Action action = new InjectOutputFileMetadataAction(output, createRemoteFileMetadata(content));
Googler8ec4c502023-04-12 10:44:40 -0700578 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang4e290422021-08-03 17:56:19 -0700579
580 runAction(action);
581 Token token =
582 cacheChecker.getTokenIfNeedToExecute(
583 action,
Googler88c426e2023-01-09 12:56:29 -0800584 /* resolvedCacheArtifacts= */ null,
585 /* clientEnv= */ ImmutableMap.of(),
586 OutputPermissions.READONLY,
587 /* handler= */ null,
Chi Wang4e290422021-08-03 17:56:19 -0700588 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -0700589 metadataHandler,
Googler88c426e2023-01-09 12:56:29 -0800590 /* artifactExpander= */ null,
591 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -0700592 RemoteArtifactChecker.TRUST_ALL);
Chi Wang4e290422021-08-03 17:56:19 -0700593
594 assertThat(output.getPath().exists()).isFalse();
595 assertThat(token).isNull();
596 ActionCache.Entry entry = cache.get(output.getExecPathString());
597 assertThat(entry).isNotNull();
598 assertThat(entry.getOutputFile(output)).isEqualTo(createRemoteFileMetadata(content));
Googlerd8e13c82023-04-11 00:07:32 -0700599 assertThat(metadataHandler.getOutputMetadata(output))
600 .isEqualTo(createRemoteFileMetadata(content));
Chi Wang4e290422021-08-03 17:56:19 -0700601 }
602
603 @Test
Chi Wang1ebb04b2023-03-03 07:13:57 -0800604 public void saveOutputMetadata_remoteFileExpired_remoteFileMetadataNotLoaded() throws Exception {
605 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ true);
606 Artifact output = createArtifact(artifactRoot, "bin/dummy");
607 String content = "content";
608 Action action =
609 new InjectOutputFileMetadataAction(
610 output,
611 createRemoteFileMetadata(
612 content, /* expireAtEpochMilli= */ 0, /* materializationExecPath= */ null));
Googler8ec4c502023-04-12 10:44:40 -0700613 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang1ebb04b2023-03-03 07:13:57 -0800614
615 runAction(action);
616 Token token =
617 cacheChecker.getTokenIfNeedToExecute(
618 action,
619 /* resolvedCacheArtifacts= */ null,
620 /* clientEnv= */ ImmutableMap.of(),
621 OutputPermissions.READONLY,
622 /* handler= */ null,
623 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -0700624 metadataHandler,
Chi Wang1ebb04b2023-03-03 07:13:57 -0800625 /* artifactExpander= */ null,
626 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -0700627 CHECK_TTL);
Chi Wang1ebb04b2023-03-03 07:13:57 -0800628
629 assertThat(output.getPath().exists()).isFalse();
630 assertThat(token).isNotNull();
631 ActionCache.Entry entry = cache.get(output.getExecPathString());
632 assertThat(entry).isNull();
633 }
634
635 @Test
Googler7bcd5cc2023-08-11 05:57:55 -0700636 public void saveOutputMetadata_storeOutputMetadataDisabled_remoteFileMetadataNotLoaded()
chiwangc9b7e222021-11-17 00:05:02 -0800637 throws Exception {
Googler7bcd5cc2023-08-11 05:57:55 -0700638 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ false);
chiwangc9b7e222021-11-17 00:05:02 -0800639 Artifact output = createArtifact(artifactRoot, "bin/dummy");
640 String content = "content";
641 Action action = new InjectOutputFileMetadataAction(output, createRemoteFileMetadata(content));
Googler8ec4c502023-04-12 10:44:40 -0700642 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
chiwangc9b7e222021-11-17 00:05:02 -0800643
644 runAction(action);
645 Token token =
646 cacheChecker.getTokenIfNeedToExecute(
647 action,
Googler88c426e2023-01-09 12:56:29 -0800648 /* resolvedCacheArtifacts= */ null,
649 /* clientEnv= */ ImmutableMap.of(),
650 OutputPermissions.READONLY,
651 /* handler= */ null,
chiwangc9b7e222021-11-17 00:05:02 -0800652 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -0700653 metadataHandler,
Googler88c426e2023-01-09 12:56:29 -0800654 /* artifactExpander= */ null,
655 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -0700656 /* remoteArtifactChecker= */ null);
chiwangc9b7e222021-11-17 00:05:02 -0800657
658 assertThat(output.getPath().exists()).isFalse();
659 assertThat(token).isNotNull();
660 ActionCache.Entry entry = cache.get(output.getExecPathString());
661 assertThat(entry).isNull();
662 }
663
664 @Test
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700665 public void saveOutputMetadata_localMetadataIsSameAsRemoteMetadata_cached(
666 @TestParameter({"", "/target/path"}) String materializationExecPathParam) throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -0700667 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
668 Artifact output = createArtifact(artifactRoot, "bin/dummy");
669 String content = "content";
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700670 PathFragment materializationExecPath =
671 materializationExecPathParam.isEmpty() ? null : PathFragment.create("/target/path");
672 Action action =
673 new InjectOutputFileMetadataAction(
674 output, createRemoteFileMetadata(content, materializationExecPath));
Chi Wang4e290422021-08-03 17:56:19 -0700675 runAction(action);
676 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
677
678 writeContentAsLatin1(output.getPath(), content);
679 // Cached since local metadata is same as remote metadata
680 runAction(action);
681
682 assertStatistics(1, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
683 ActionCache.Entry entry = cache.get(output.getExecPathString());
684 assertThat(entry).isNotNull();
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700685 assertThat(entry.getOutputFile(output))
686 .isEqualTo(createRemoteFileMetadata(content, materializationExecPath));
Chi Wang4e290422021-08-03 17:56:19 -0700687 }
688
689 @Test
690 public void saveOutputMetadata_localMetadataIsDifferentFromRemoteMetadata_notCached()
691 throws Exception {
692 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
693 Artifact output = createArtifact(artifactRoot, "bin/dummy");
694 String content1 = "content1";
695 String content2 = "content2";
696 Action action =
697 new InjectOutputFileMetadataAction(
698 output, createRemoteFileMetadata(content1), createRemoteFileMetadata(content2));
699 runAction(action);
700 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
701
702 writeContentAsLatin1(output.getPath(), content2);
Googlercba991d2023-04-26 04:51:46 -0700703
704 // Assert that if local file exists, shouldTrustRemoteArtifact is not called for the remote
705 // metadata.
706 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
707 var mockedRemoteArtifactChecker = mock(RemoteArtifactChecker.class);
708 var token =
709 cacheChecker.getTokenIfNeedToExecute(
710 action,
711 /* resolvedCacheArtifacts= */ null,
712 /* clientEnv= */ ImmutableMap.of(),
713 OutputPermissions.READONLY,
714 /* handler= */ null,
715 metadataHandler,
716 metadataHandler,
717 /* artifactExpander= */ null,
718 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
719 mockedRemoteArtifactChecker);
720 verify(mockedRemoteArtifactChecker, never()).shouldTrustRemoteArtifact(any(), any());
Chi Wang4e290422021-08-03 17:56:19 -0700721 // Not cached since local file changed
Googlercba991d2023-04-26 04:51:46 -0700722 runAction(
723 action,
724 /* clientEnv= */ ImmutableMap.of(),
725 /* platform= */ ImmutableMap.of(),
726 metadataHandler,
727 metadataHandler,
728 token);
Chi Wang4e290422021-08-03 17:56:19 -0700729
730 assertStatistics(
731 0,
732 new MissDetailsBuilder()
733 .set(MissReason.NOT_CACHED, 1)
734 .set(MissReason.DIFFERENT_FILES, 1)
735 .build());
736 ActionCache.Entry entry = cache.get(output.getExecPathString());
737 assertThat(entry).isNotNull();
738 assertThat(entry.getOutputFile(output)).isEqualTo(createRemoteFileMetadata(content2));
739 }
740
741 @Test
Googler7bcd5cc2023-08-11 05:57:55 -0700742 public void saveOutputMetadata_trustedRemoteMetadataFromOutputStore_cached() throws Exception {
743 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ true);
744 Artifact output = createArtifact(artifactRoot, "bin/dummy");
745 String content = "content";
746 RemoteFileArtifactValue metadata = createRemoteFileMetadata(content);
747 Action action = new InjectOutputFileMetadataAction(output, metadata, metadata);
748 runAction(action);
749 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
750
751 FakeInputMetadataHandler fakeOutputMetadataStore = new FakeInputMetadataHandler();
752 fakeOutputMetadataStore.injectFile(output, metadata);
753
754 runAction(
755 action,
756 ImmutableMap.of(),
757 ImmutableMap.of(),
758 new FakeInputMetadataHandler(),
759 fakeOutputMetadataStore);
760
761 assertStatistics(1, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
762
763 ActionCache.Entry entry = cache.get(output.getExecPathString());
764 assertThat(entry).isNotNull();
765 assertThat(entry.getOutputFile(output)).isEqualTo(metadata);
766 }
767
768 @Test
769 public void saveOutputMetadata_untrustedRemoteMetadataFromOutputStore_notCached()
770 throws Exception {
771 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ true);
772 Artifact output = createArtifact(artifactRoot, "bin/dummy");
773 String content = "content";
774 RemoteFileArtifactValue metadata = createRemoteFileMetadata(content);
775 Action action = new InjectOutputFileMetadataAction(output, metadata, metadata);
776 runAction(action);
777 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
778
779 FakeInputMetadataHandler fakeOutputMetadataStore = new FakeInputMetadataHandler();
780 fakeOutputMetadataStore.injectFile(output, metadata);
781
782 RemoteArtifactChecker remoteArtifactChecker = mock(RemoteArtifactChecker.class);
783 when(remoteArtifactChecker.shouldTrustRemoteArtifact(any(), any())).thenReturn(false);
784
785 runAction(
786 action,
787 ImmutableMap.of(),
788 ImmutableMap.of(),
789 new FakeInputMetadataHandler(),
790 fakeOutputMetadataStore,
791 remoteArtifactChecker);
792
793 assertStatistics(
794 0,
795 new MissDetailsBuilder()
796 .set(MissReason.NOT_CACHED, 1)
797 .set(MissReason.DIFFERENT_FILES, 1)
798 .build());
799
800 ActionCache.Entry entry = cache.get(output.getExecPathString());
801 assertThat(entry).isNotNull();
802 assertThat(entry.getOutputFile(output)).isEqualTo(metadata);
803 }
804
805 @Test
Chi Wang4e290422021-08-03 17:56:19 -0700806 public void saveOutputMetadata_treeMetadata_remoteFileMetadataSaved() throws Exception {
807 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
808 SpecialArtifact output =
809 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
810 ImmutableMap<String, RemoteFileArtifactValue> children =
811 ImmutableMap.of(
812 "file1", createRemoteFileMetadata("content1"),
813 "file2", createRemoteFileMetadata("content2"));
814 Action action =
815 new InjectOutputTreeMetadataAction(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700816 output,
817 createTreeMetadata(
818 output,
819 children,
820 /* archivedArtifactValue= */ Optional.empty(),
821 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -0700822
823 runAction(action);
824
825 assertThat(output.getPath().exists()).isFalse();
826 ActionCache.Entry entry = cache.get(output.getExecPathString());
827 assertThat(entry).isNotNull();
828 assertThat(entry.getOutputTree(output))
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700829 .isEqualTo(
830 SerializableTreeArtifactValue.create(
831 children,
832 /* archivedFileValue= */ Optional.empty(),
833 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -0700834 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
835 }
836
837 @Test
838 public void saveOutputMetadata_treeMetadata_remoteArchivedArtifactSaved() throws Exception {
839 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
840 SpecialArtifact output =
841 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
842 Action action =
843 new InjectOutputTreeMetadataAction(
844 output,
845 createTreeMetadata(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700846 output,
847 ImmutableMap.of(),
848 Optional.of(createRemoteFileMetadata("content")),
849 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -0700850
851 runAction(action);
852
853 assertThat(output.getPath().exists()).isFalse();
854 ActionCache.Entry entry = cache.get(output.getExecPathString());
855 assertThat(entry).isNotNull();
856 assertThat(entry.getOutputTree(output))
857 .isEqualTo(
858 SerializableTreeArtifactValue.create(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700859 ImmutableMap.of(),
860 Optional.of(createRemoteFileMetadata("content")),
861 /* materializationExecPath= */ Optional.empty()));
862 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
863 }
864
865 @Test
866 public void saveOutputMetadata_treeMetadata_materializationExecPathSaved() throws Exception {
867 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
868 SpecialArtifact output =
869 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
870 Action action =
871 new InjectOutputTreeMetadataAction(
872 output,
873 createTreeMetadata(
874 output,
875 ImmutableMap.of(),
876 /* archivedArtifactValue= */ Optional.empty(),
877 Optional.of(PathFragment.create("/target/path"))));
878
879 runAction(action);
880
881 assertThat(output.getPath().exists()).isFalse();
882 ActionCache.Entry entry = cache.get(output.getExecPathString());
883 assertThat(entry).isNotNull();
884 assertThat(entry.getOutputTree(output))
885 .isEqualTo(
886 SerializableTreeArtifactValue.create(
887 ImmutableMap.of(),
888 /* archivedFileValue= */ Optional.empty(),
889 Optional.of(PathFragment.create("/target/path"))));
Chi Wang4e290422021-08-03 17:56:19 -0700890 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
891 }
892
893 @Test
894 public void saveOutputMetadata_emptyTreeMetadata_notSaved() throws Exception {
895 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
896 SpecialArtifact output =
897 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
898 Action action =
899 new InjectOutputTreeMetadataAction(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700900 output,
901 createTreeMetadata(
902 output,
903 ImmutableMap.of(),
904 /* archivedArtifactValue= */ Optional.empty(),
905 /* materializationExecPath= */ Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -0700906 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang4e290422021-08-03 17:56:19 -0700907
908 runAction(action);
909 Token token =
910 cacheChecker.getTokenIfNeedToExecute(
911 action,
Googler88c426e2023-01-09 12:56:29 -0800912 /* resolvedCacheArtifacts= */ null,
913 /* clientEnv= */ ImmutableMap.of(),
914 OutputPermissions.READONLY,
915 /* handler= */ null,
Chi Wang4e290422021-08-03 17:56:19 -0700916 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -0700917 metadataHandler,
Googler88c426e2023-01-09 12:56:29 -0800918 /* artifactExpander= */ null,
919 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -0700920 RemoteArtifactChecker.TRUST_ALL);
Chi Wang4e290422021-08-03 17:56:19 -0700921
922 assertThat(token).isNull();
923 assertThat(output.getPath().exists()).isFalse();
924 ActionCache.Entry entry = cache.get(output.getExecPathString());
925 assertThat(entry).isNotNull();
926 assertThat(entry.getOutputTree(output)).isNull();
927 assertStatistics(1, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
928 }
929
930 @Test
931 public void saveOutputMetadata_treeMetadata_localFileMetadataNotSaved() throws Exception {
932 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
933 SpecialArtifact output =
934 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
935 writeIsoLatin1(fileSystem.getPath("/file2"), "");
936 ImmutableMap<String, FileArtifactValue> children =
937 ImmutableMap.of(
938 "file1", createRemoteFileMetadata("content1"),
939 "file2", FileArtifactValue.createForTesting(fileSystem.getPath("/file2")));
940 fileSystem.getPath("/file2").delete();
941 Action action =
942 new InjectOutputTreeMetadataAction(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700943 output,
944 createTreeMetadata(
945 output,
946 children,
947 /* archivedArtifactValue= */ Optional.empty(),
948 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -0700949
950 runAction(action);
951
952 assertThat(output.getPath().exists()).isFalse();
953 ActionCache.Entry entry = cache.get(output.getExecPathString());
954 assertThat(entry).isNotNull();
955 assertThat(entry.getOutputTree(output))
956 .isEqualTo(
957 SerializableTreeArtifactValue.create(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700958 ImmutableMap.of("file1", createRemoteFileMetadata("content1")),
959 /* archivedFileValue= */ Optional.empty(),
960 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -0700961 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
962 }
963
964 @Test
965 public void saveOutputMetadata_treeMetadata_localArchivedArtifactNotSaved() throws Exception {
966 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
967 SpecialArtifact output =
968 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
969 writeIsoLatin1(fileSystem.getPath("/archive"), "");
970 Action action =
971 new InjectOutputTreeMetadataAction(
972 output,
973 createTreeMetadata(
974 output,
975 ImmutableMap.of(),
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700976 Optional.of(FileArtifactValue.createForTesting(fileSystem.getPath("/archive"))),
977 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -0700978 fileSystem.getPath("/archive").delete();
979
980 runAction(action);
981
982 assertThat(output.getPath().exists()).isFalse();
983 ActionCache.Entry entry = cache.get(output.getExecPathString());
984 assertThat(entry).isNotNull();
985 assertThat(entry.getOutputTree(output)).isNull();
986 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
987 }
988
989 @Test
990 public void saveOutputMetadata_treeMetadata_remoteFileMetadataLoaded() throws Exception {
991 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
992 SpecialArtifact output =
993 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
994 ImmutableMap<String, FileArtifactValue> children =
995 ImmutableMap.of(
996 "file1", createRemoteFileMetadata("content1"),
997 "file2", createRemoteFileMetadata("content2"));
998 Action action =
999 new InjectOutputTreeMetadataAction(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001000 output,
1001 createTreeMetadata(
1002 output,
1003 children,
1004 /* archivedArtifactValue= */ Optional.empty(),
1005 /* materializationExecPath= */ Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -07001006 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang4e290422021-08-03 17:56:19 -07001007
1008 runAction(action);
1009 Token token =
1010 cacheChecker.getTokenIfNeedToExecute(
1011 action,
Googler88c426e2023-01-09 12:56:29 -08001012 /* resolvedCacheArtifacts= */ null,
1013 /* clientEnv= */ ImmutableMap.of(),
1014 OutputPermissions.READONLY,
1015 /* handler= */ null,
Chi Wang4e290422021-08-03 17:56:19 -07001016 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -07001017 metadataHandler,
Googler88c426e2023-01-09 12:56:29 -08001018 /* artifactExpander= */ null,
1019 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -07001020 RemoteArtifactChecker.TRUST_ALL);
Chi Wang4e290422021-08-03 17:56:19 -07001021
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001022 TreeArtifactValue expectedMetadata =
1023 createTreeMetadata(
1024 output,
1025 children,
1026 /* archivedArtifactValue= */ Optional.empty(),
1027 /* materializationExecPath= */ Optional.empty());
Chi Wang4e290422021-08-03 17:56:19 -07001028 assertThat(token).isNull();
1029 assertThat(output.getPath().exists()).isFalse();
1030 ActionCache.Entry entry = cache.get(output.getExecPathString());
1031 assertThat(entry).isNotNull();
1032 assertThat(entry.getOutputTree(output))
1033 .isEqualTo(SerializableTreeArtifactValue.createSerializable(expectedMetadata).get());
1034 assertThat(metadataHandler.getTreeArtifactValue(output)).isEqualTo(expectedMetadata);
1035 }
1036
1037 @Test
1038 public void saveOutputMetadata_treeMetadata_localFileMetadataLoaded() throws Exception {
1039 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
1040 SpecialArtifact output =
1041 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1042 ImmutableMap<String, FileArtifactValue> children1 =
1043 ImmutableMap.of(
1044 "file1", createRemoteFileMetadata("content1"),
1045 "file2", createRemoteFileMetadata("content2"));
1046 ImmutableMap<String, FileArtifactValue> children2 =
1047 ImmutableMap.of(
1048 "file1", createRemoteFileMetadata("content1"),
1049 "file2", createRemoteFileMetadata("modified_remote"));
1050 Action action =
1051 new InjectOutputTreeMetadataAction(
1052 output,
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001053 createTreeMetadata(
1054 output,
1055 children1,
1056 /* archivedArtifactValue= */ Optional.empty(),
1057 /* materializationExecPath= */ Optional.empty()),
1058 createTreeMetadata(
1059 output,
1060 children2,
1061 /* archivedArtifactValue= */ Optional.empty(),
1062 /* materializationExecPath= */ Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -07001063 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang4e290422021-08-03 17:56:19 -07001064
1065 runAction(action);
1066 writeIsoLatin1(output.getPath().getRelative("file2"), "modified_local");
Googlercba991d2023-04-26 04:51:46 -07001067 var mockedRemoteArtifactChecker = mock(RemoteArtifactChecker.class);
1068 doReturn(true).when(mockedRemoteArtifactChecker).shouldTrustRemoteArtifact(any(), any());
1069 var token =
1070 cacheChecker.getTokenIfNeedToExecute(
1071 action,
1072 /* resolvedCacheArtifacts= */ null,
1073 /* clientEnv= */ ImmutableMap.of(),
1074 OutputPermissions.READONLY,
1075 /* handler= */ null,
1076 metadataHandler,
1077 metadataHandler,
1078 /* artifactExpander= */ null,
1079 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
1080 mockedRemoteArtifactChecker);
1081 verify(mockedRemoteArtifactChecker)
Googlera5dde122023-05-04 08:59:07 -07001082 .shouldTrustRemoteArtifact(
1083 argThat(arg -> arg.getExecPathString().endsWith("file1")), any());
Googlercba991d2023-04-26 04:51:46 -07001084 verify(mockedRemoteArtifactChecker, never())
Googlera5dde122023-05-04 08:59:07 -07001085 .shouldTrustRemoteArtifact(
1086 argThat(arg -> arg.getExecPathString().endsWith("file2")), any());
Chi Wang4e290422021-08-03 17:56:19 -07001087 // Not cached since local file changed
Googlercba991d2023-04-26 04:51:46 -07001088 runAction(
1089 action,
1090 /* clientEnv= */ ImmutableMap.of(),
1091 /* platform= */ ImmutableMap.of(),
1092 metadataHandler,
1093 metadataHandler,
1094 token);
Chi Wang4e290422021-08-03 17:56:19 -07001095
1096 assertStatistics(
1097 0,
1098 new MissDetailsBuilder()
1099 .set(MissReason.NOT_CACHED, 1)
1100 .set(MissReason.DIFFERENT_FILES, 1)
1101 .build());
1102 assertThat(output.getPath().exists()).isTrue();
1103 TreeArtifactValue expectedMetadata =
1104 createTreeMetadata(
1105 output,
1106 ImmutableMap.of(
1107 "file1", createRemoteFileMetadata("content1"),
1108 "file2", createRemoteFileMetadata("modified_remote")),
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001109 /* archivedArtifactValue= */ Optional.empty(),
1110 /* materializationExecPath= */ Optional.empty());
Chi Wang4e290422021-08-03 17:56:19 -07001111 ActionCache.Entry entry = cache.get(output.getExecPathString());
1112 assertThat(entry).isNotNull();
1113 assertThat(entry.getOutputTree(output))
1114 .isEqualTo(SerializableTreeArtifactValue.createSerializable(expectedMetadata).get());
1115 assertThat(metadataHandler.getTreeArtifactValue(output)).isEqualTo(expectedMetadata);
1116 }
1117
1118 @Test
1119 public void saveOutputMetadata_treeMetadata_localArchivedArtifactLoaded() throws Exception {
1120 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
1121 SpecialArtifact output =
1122 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1123 Action action =
1124 new InjectOutputTreeMetadataAction(
1125 output,
1126 createTreeMetadata(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001127 output,
1128 ImmutableMap.of(),
1129 Optional.of(createRemoteFileMetadata("content")),
1130 /* materializationExecPath= */ Optional.empty()),
Chi Wang4e290422021-08-03 17:56:19 -07001131 createTreeMetadata(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001132 output,
1133 ImmutableMap.of(),
1134 Optional.of(createRemoteFileMetadata("modified")),
1135 /* materializationExecPath= */ Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -07001136 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang4e290422021-08-03 17:56:19 -07001137
1138 runAction(action);
jhorvitz53f18992021-10-29 10:00:35 -07001139 writeIsoLatin1(ArchivedTreeArtifact.createForTree(output).getPath(), "modified");
Googlercba991d2023-04-26 04:51:46 -07001140 var mockedRemoteArtifactChecker = mock(RemoteArtifactChecker.class);
1141 var token =
1142 cacheChecker.getTokenIfNeedToExecute(
1143 action,
1144 /* resolvedCacheArtifacts= */ null,
1145 /* clientEnv= */ ImmutableMap.of(),
1146 OutputPermissions.READONLY,
1147 /* handler= */ null,
1148 metadataHandler,
1149 metadataHandler,
1150 /* artifactExpander= */ null,
1151 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
1152 mockedRemoteArtifactChecker);
1153 verify(mockedRemoteArtifactChecker, never()).shouldTrustRemoteArtifact(any(), any());
Chi Wang4e290422021-08-03 17:56:19 -07001154 // Not cached since local file changed
Googlercba991d2023-04-26 04:51:46 -07001155 runAction(
1156 action,
1157 /* clientEnv= */ ImmutableMap.of(),
1158 /* platform= */ ImmutableMap.of(),
1159 metadataHandler,
1160 metadataHandler,
1161 token);
Chi Wang4e290422021-08-03 17:56:19 -07001162
1163 assertStatistics(
1164 0,
1165 new MissDetailsBuilder()
1166 .set(MissReason.NOT_CACHED, 1)
1167 .set(MissReason.DIFFERENT_FILES, 1)
1168 .build());
1169 assertThat(output.getPath().exists()).isFalse();
1170 TreeArtifactValue expectedMetadata =
1171 createTreeMetadata(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001172 output,
1173 ImmutableMap.of(),
1174 Optional.of(createRemoteFileMetadata("modified")),
1175 /* materializationExecPath= */ Optional.empty());
Chi Wang4e290422021-08-03 17:56:19 -07001176 ActionCache.Entry entry = cache.get(output.getExecPathString());
1177 assertThat(entry).isNotNull();
1178 assertThat(entry.getOutputTree(output))
1179 .isEqualTo(SerializableTreeArtifactValue.createSerializable(expectedMetadata).get());
1180 assertThat(metadataHandler.getTreeArtifactValue(output)).isEqualTo(expectedMetadata);
1181 }
1182
Chi Wang1ebb04b2023-03-03 07:13:57 -08001183 @Test
1184 public void saveOutputMetadata_treeFileExpired_treeMetadataNotLoaded() throws Exception {
1185 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ true);
1186 SpecialArtifact output =
1187 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1188 ImmutableMap<String, RemoteFileArtifactValue> children =
1189 ImmutableMap.of(
1190 "file1", createRemoteFileMetadata("content1"),
1191 "file2",
1192 createRemoteFileMetadata(
1193 "content2", /* expireAtEpochMilli= */ 0, /* materializationExecPath= */ null));
1194 Action action =
1195 new InjectOutputTreeMetadataAction(
1196 output,
1197 createTreeMetadata(
1198 output,
1199 children,
1200 /* archivedArtifactValue= */ Optional.empty(),
1201 /* materializationExecPath= */ Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -07001202 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang1ebb04b2023-03-03 07:13:57 -08001203
1204 runAction(action);
1205 Token token =
1206 cacheChecker.getTokenIfNeedToExecute(
1207 action,
1208 /* resolvedCacheArtifacts= */ null,
1209 /* clientEnv= */ ImmutableMap.of(),
1210 OutputPermissions.READONLY,
1211 /* handler= */ null,
1212 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -07001213 metadataHandler,
Chi Wang1ebb04b2023-03-03 07:13:57 -08001214 /* artifactExpander= */ null,
1215 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -07001216 CHECK_TTL);
Chi Wang1ebb04b2023-03-03 07:13:57 -08001217
1218 assertThat(output.getPath().exists()).isFalse();
1219 assertThat(token).isNotNull();
1220 ActionCache.Entry entry = cache.get(output.getExecPathString());
1221 assertThat(entry).isNull();
1222 }
1223
1224 @Test
1225 public void saveOutputMetadata_archivedRepresentationExpired_treeMetadataNotLoaded()
1226 throws Exception {
1227 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ true);
1228 SpecialArtifact output =
1229 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1230 ImmutableMap<String, RemoteFileArtifactValue> children =
1231 ImmutableMap.of(
1232 "file1", createRemoteFileMetadata("content1"),
1233 "file2", createRemoteFileMetadata("content2"));
1234 Action action =
1235 new InjectOutputTreeMetadataAction(
1236 output,
1237 createTreeMetadata(
1238 output,
1239 children,
1240 /* archivedArtifactValue= */ Optional.of(
1241 createRemoteFileMetadata(
1242 "archived",
1243 /* expireAtEpochMilli= */ 0,
1244 /* materializationExecPath= */ null)),
1245 /* materializationExecPath= */ Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -07001246 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang1ebb04b2023-03-03 07:13:57 -08001247
1248 runAction(action);
1249 Token token =
1250 cacheChecker.getTokenIfNeedToExecute(
1251 action,
1252 /* resolvedCacheArtifacts= */ null,
1253 /* clientEnv= */ ImmutableMap.of(),
1254 OutputPermissions.READONLY,
1255 /* handler= */ null,
1256 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -07001257 metadataHandler,
Chi Wang1ebb04b2023-03-03 07:13:57 -08001258 /* artifactExpander= */ null,
1259 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -07001260 CHECK_TTL);
Chi Wang1ebb04b2023-03-03 07:13:57 -08001261
1262 assertThat(output.getPath().exists()).isFalse();
1263 assertThat(token).isNotNull();
1264 ActionCache.Entry entry = cache.get(output.getExecPathString());
1265 assertThat(entry).isNull();
1266 }
1267
jhorvitz53f18992021-10-29 10:00:35 -07001268 private static void writeContentAsLatin1(Path path, String content) throws IOException {
Chi Wang4e290422021-08-03 17:56:19 -07001269 Path parent = path.getParentDirectory();
1270 if (parent != null) {
1271 parent.createDirectoryAndParents();
1272 }
1273 FileSystemUtils.writeContentAsLatin1(path, content);
1274 }
1275
1276 @Test
Googler93e56a32023-08-24 02:36:56 -07001277 public void saveOutputMetadata_treeMetadataWithSameLocalFileMetadata_cached() throws Exception {
Chi Wang4e290422021-08-03 17:56:19 -07001278 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
1279 SpecialArtifact output =
1280 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1281 ImmutableMap<String, RemoteFileArtifactValue> children =
1282 ImmutableMap.of(
1283 "file1", createRemoteFileMetadata("content1"),
1284 "file2", createRemoteFileMetadata("content2"));
1285 Action action =
1286 new InjectOutputTreeMetadataAction(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001287 output,
1288 createTreeMetadata(
Googler93e56a32023-08-24 02:36:56 -07001289 output, children, /* archivedArtifactValue= */ Optional.empty(), Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -07001290 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang4e290422021-08-03 17:56:19 -07001291
1292 runAction(action);
1293 writeContentAsLatin1(output.getPath().getRelative("file1"), "content1");
1294 // Cache hit
1295 Token token =
1296 cacheChecker.getTokenIfNeedToExecute(
1297 action,
Googler88c426e2023-01-09 12:56:29 -08001298 /* resolvedCacheArtifacts= */ null,
1299 /* clientEnv= */ ImmutableMap.of(),
1300 OutputPermissions.READONLY,
1301 /* handler= */ null,
Chi Wang4e290422021-08-03 17:56:19 -07001302 metadataHandler,
Googler088d8e92023-04-11 08:25:49 -07001303 metadataHandler,
Googler88c426e2023-01-09 12:56:29 -08001304 /* artifactExpander= */ null,
1305 /* remoteDefaultPlatformProperties= */ ImmutableMap.of(),
Googlercba991d2023-04-26 04:51:46 -07001306 RemoteArtifactChecker.TRUST_ALL);
Chi Wang4e290422021-08-03 17:56:19 -07001307
1308 assertThat(token).isNull();
1309 assertStatistics(1, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
1310 assertThat(output.getPath().exists()).isTrue();
1311 ActionCache.Entry entry = cache.get(output.getExecPathString());
1312 assertThat(entry).isNotNull();
1313 assertThat(entry.getOutputTree(output))
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001314 .isEqualTo(
1315 SerializableTreeArtifactValue.create(
Googler93e56a32023-08-24 02:36:56 -07001316 children, /* archivedFileValue= */ Optional.empty(), Optional.empty()));
1317
Chi Wang4e290422021-08-03 17:56:19 -07001318 assertThat(metadataHandler.getTreeArtifactValue(output))
1319 .isEqualTo(
1320 createTreeMetadata(
1321 output,
1322 ImmutableMap.of(
1323 "file1",
chiwangc9b7e222021-11-17 00:05:02 -08001324 FileArtifactValue.createForTesting(output.getPath().getRelative("file1")),
1325 "file2",
1326 createRemoteFileMetadata("content2")),
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001327 /* archivedArtifactValue= */ Optional.empty(),
Googler93e56a32023-08-24 02:36:56 -07001328 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -07001329 }
1330
1331 @Test
1332 public void saveOutputMetadata_treeMetadataWithSameLocalArchivedArtifact_cached()
1333 throws Exception {
1334 cacheChecker = createActionCacheChecker(/*storeOutputMetadata=*/ true);
1335 SpecialArtifact output =
1336 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1337 Action action =
1338 new InjectOutputTreeMetadataAction(
1339 output,
1340 createTreeMetadata(
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -07001341 output,
1342 ImmutableMap.of(),
1343 Optional.of(createRemoteFileMetadata("content")),
1344 /* materializationExecPath= */ Optional.empty()));
Googler8ec4c502023-04-12 10:44:40 -07001345 FakeInputMetadataHandler metadataHandler = new FakeInputMetadataHandler();
Chi Wang4e290422021-08-03 17:56:19 -07001346
1347 runAction(action);
jhorvitz53f18992021-10-29 10:00:35 -07001348 writeContentAsLatin1(ArchivedTreeArtifact.createForTree(output).getPath(), "content");
Chi Wang4e290422021-08-03 17:56:19 -07001349 // Cache hit
Googler088d8e92023-04-11 08:25:49 -07001350 runAction(action, metadataHandler, metadataHandler);
Chi Wang4e290422021-08-03 17:56:19 -07001351
1352 assertStatistics(1, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
1353 assertThat(output.getPath().exists()).isFalse();
Chi Wang4e290422021-08-03 17:56:19 -07001354 ActionCache.Entry entry = cache.get(output.getExecPathString());
1355 assertThat(entry).isNotNull();
1356 assertThat(entry.getOutputTree(output))
Googler93e56a32023-08-24 02:36:56 -07001357 .isEqualTo(
1358 SerializableTreeArtifactValue.create(
1359 ImmutableMap.of(),
1360 /* archivedFileValue= */ Optional.of(createRemoteFileMetadata("content")),
1361 /* materializationExecPath= */ Optional.empty()));
1362 assertThat(metadataHandler.getTreeArtifactValue(output))
1363 .isEqualTo(
1364 createTreeMetadata(
1365 output,
1366 ImmutableMap.of(),
1367 Optional.of(
1368 FileArtifactValue.createForTesting(ArchivedTreeArtifact.createForTree(output))),
1369 /* materializationExecPath= */ Optional.empty()));
Chi Wang4e290422021-08-03 17:56:19 -07001370 }
1371
Googler7bcd5cc2023-08-11 05:57:55 -07001372 @Test
1373 public void saveOutputMetadata_trustedRemoteTreeMetadataFromOutputStore_cached()
1374 throws Exception {
1375 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ true);
1376 SpecialArtifact tree =
1377 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1378 ImmutableMap<String, RemoteFileArtifactValue> children =
1379 ImmutableMap.of("file", createRemoteFileMetadata("content"));
1380 TreeArtifactValue treeMetadata =
1381 createTreeMetadata(
1382 tree,
1383 children,
1384 /* archivedArtifactValue= */ Optional.empty(),
1385 /* materializationExecPath= */ Optional.empty());
1386 Action action = new InjectOutputTreeMetadataAction(tree, treeMetadata, treeMetadata);
1387 runAction(action);
1388 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
1389
1390 FakeInputMetadataHandler fakeOutputMetadataStore = new FakeInputMetadataHandler();
1391 fakeOutputMetadataStore.injectTree(tree, treeMetadata);
1392
1393 runAction(
1394 action,
1395 ImmutableMap.of(),
1396 ImmutableMap.of(),
1397 new FakeInputMetadataHandler(),
1398 fakeOutputMetadataStore);
1399
1400 assertStatistics(1, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
1401
1402 ActionCache.Entry entry = cache.get(tree.getExecPathString());
1403 assertThat(entry).isNotNull();
1404 assertThat(entry.getOutputTree(tree))
1405 .isEqualTo(
1406 SerializableTreeArtifactValue.create(
1407 children,
1408 /* archivedFileValue= */ Optional.empty(),
1409 /* materializationExecPath= */ Optional.empty()));
1410 }
1411
1412 @Test
1413 public void saveOutputMetadata_untrustedRemoteTreeMetadataFromOutputStore_notCached()
1414 throws Exception {
1415 cacheChecker = createActionCacheChecker(/* storeOutputMetadata= */ true);
1416 SpecialArtifact tree =
1417 createTreeArtifactWithGeneratingAction(artifactRoot, PathFragment.create("bin/dummy"));
1418 ImmutableMap<String, RemoteFileArtifactValue> children =
1419 ImmutableMap.of("file", createRemoteFileMetadata("content"));
1420 TreeArtifactValue treeMetadata =
1421 createTreeMetadata(
1422 tree,
1423 children,
1424 /* archivedArtifactValue= */ Optional.empty(),
1425 /* materializationExecPath= */ Optional.empty());
1426 Action action = new InjectOutputTreeMetadataAction(tree, treeMetadata, treeMetadata);
1427 runAction(action);
1428 assertStatistics(0, new MissDetailsBuilder().set(MissReason.NOT_CACHED, 1).build());
1429
1430 FakeInputMetadataHandler fakeOutputMetadataStore = new FakeInputMetadataHandler();
1431 fakeOutputMetadataStore.injectTree(tree, treeMetadata);
1432
1433 RemoteArtifactChecker remoteArtifactChecker = mock(RemoteArtifactChecker.class);
1434 when(remoteArtifactChecker.shouldTrustRemoteArtifact(any(), any())).thenReturn(false);
1435
1436 runAction(
1437 action,
1438 ImmutableMap.of(),
1439 ImmutableMap.of(),
1440 new FakeInputMetadataHandler(),
1441 fakeOutputMetadataStore,
1442 remoteArtifactChecker);
1443
1444 assertStatistics(
1445 0,
1446 new MissDetailsBuilder()
1447 .set(MissReason.NOT_CACHED, 1)
1448 .set(MissReason.DIFFERENT_FILES, 1)
1449 .build());
1450
1451 ActionCache.Entry entry = cache.get(tree.getExecPathString());
1452 assertThat(entry).isNotNull();
1453 assertThat(entry.getOutputTree(tree))
1454 .isEqualTo(
1455 SerializableTreeArtifactValue.create(
1456 children,
1457 /* archivedFileValue= */ Optional.empty(),
1458 /* materializationExecPath= */ Optional.empty()));
1459 }
1460
Googler93e56a32023-08-24 02:36:56 -07001461 // TODO(tjgq): Add tests for cached tree artifacts with a materialization path. They should take
1462 // into account every combination of entirely/partially remote metadata and symlink present/not
1463 // present in the filesystem.
1464
janakra6126342021-06-18 13:52:39 -07001465 /** An {@link ActionCache} that allows injecting corruption for testing. */
jhorvitz53f18992021-10-29 10:00:35 -07001466 private static final class CorruptibleActionCache implements ActionCache {
janakra6126342021-06-18 13:52:39 -07001467 private final CompactPersistentActionCache delegate;
jmmv9573a0d2017-09-26 11:59:22 -04001468 private boolean corrupted = false;
1469
janakra6126342021-06-18 13:52:39 -07001470 CorruptibleActionCache(Path cacheRoot, Clock clock) throws IOException {
1471 this.delegate =
1472 CompactPersistentActionCache.create(cacheRoot, clock, NullEventHandler.INSTANCE);
jmmv9573a0d2017-09-26 11:59:22 -04001473 }
1474
1475 void corruptAllEntries() {
1476 corrupted = true;
1477 }
1478
1479 @Override
1480 public Entry get(String key) {
janakra6126342021-06-18 13:52:39 -07001481 return corrupted ? ActionCache.Entry.CORRUPTED : delegate.get(key);
1482 }
1483
1484 @Override
1485 public void put(String key, Entry entry) {
1486 delegate.put(key, entry);
1487 }
1488
1489 @Override
1490 public void remove(String key) {
1491 delegate.remove(key);
1492 }
1493
1494 @Override
Chi Wang49a95022023-03-14 07:32:41 -07001495 public void removeIf(Predicate<Entry> predicate) {
1496 delegate.removeIf(predicate);
1497 }
1498
1499 @Override
janakra6126342021-06-18 13:52:39 -07001500 public long save() throws IOException {
1501 return delegate.save();
1502 }
1503
1504 @Override
1505 public void clear() {
1506 delegate.clear();
1507 }
1508
1509 @Override
1510 public void dump(PrintStream out) {
1511 delegate.dump(out);
1512 }
1513
1514 @Override
Googler7a081082024-03-14 23:01:36 -07001515 public int size() {
1516 return delegate.size();
1517 }
1518
1519 @Override
janakra6126342021-06-18 13:52:39 -07001520 public void accountHit() {
1521 delegate.accountHit();
1522 }
1523
1524 @Override
1525 public void accountMiss(MissReason reason) {
1526 delegate.accountMiss(reason);
1527 }
1528
1529 @Override
1530 public void mergeIntoActionCacheStatistics(ActionCacheStatistics.Builder builder) {
1531 delegate.mergeIntoActionCacheStatistics(builder);
1532 }
1533
1534 @Override
1535 public void resetStatistics() {
1536 delegate.resetStatistics();
jmmv9573a0d2017-09-26 11:59:22 -04001537 }
1538 }
1539
jmmv9573a0d2017-09-26 11:59:22 -04001540 /** A fake metadata handler that is able to obtain metadata from the file system. */
Googler8ec4c502023-04-12 10:44:40 -07001541 private static final class FakeInputMetadataHandler extends FakeInputMetadataHandlerBase {
Chi Wang4e290422021-08-03 17:56:19 -07001542 private final Map<Artifact, FileArtifactValue> fileMetadata = new HashMap<>();
1543 private final Map<SpecialArtifact, TreeArtifactValue> treeMetadata = new HashMap<>();
Chi Wang4e290422021-08-03 17:56:19 -07001544
1545 @Override
1546 public void injectFile(Artifact output, FileArtifactValue metadata) {
1547 fileMetadata.put(output, metadata);
1548 }
1549
1550 @Override
1551 public void injectTree(SpecialArtifact treeArtifact, TreeArtifactValue tree) {
1552 treeMetadata.put(treeArtifact, tree);
1553 }
1554
jmmv9573a0d2017-09-26 11:59:22 -04001555 @Override
Googlerd8e13c82023-04-11 00:07:32 -07001556 public FileArtifactValue getInputMetadata(ActionInput input) throws IOException {
1557 if (!(input instanceof Artifact)) {
1558 return null;
1559 }
1560
1561 return FileArtifactValue.createForTesting((Artifact) input);
1562 }
1563
1564 @Override
Googler7b942de2023-04-11 06:47:57 -07001565 public FileArtifactValue getOutputMetadata(ActionInput input)
1566 throws IOException, InterruptedException {
Googler6f48f1c2024-04-16 14:29:09 -07001567 if (!(input instanceof Artifact output)) {
ulfjackf2b260c2018-09-28 05:09:32 -07001568 return null;
1569 }
Chi Wang4e290422021-08-03 17:56:19 -07001570
1571 if (output.isTreeArtifact()) {
1572 TreeArtifactValue treeArtifactValue = getTreeArtifactValue((SpecialArtifact) output);
1573 if (treeArtifactValue != null) {
1574 return treeArtifactValue.getMetadata();
1575 } else {
1576 return null;
1577 }
1578 }
1579
1580 if (fileMetadata.containsKey(output)) {
1581 return fileMetadata.get(output);
1582 }
1583 return FileArtifactValue.createForTesting(output);
1584 }
1585
1586 @Override
Googler7b942de2023-04-11 06:47:57 -07001587 public TreeArtifactValue getTreeArtifactValue(SpecialArtifact output)
1588 throws IOException, InterruptedException {
Chi Wang4e290422021-08-03 17:56:19 -07001589 if (treeMetadata.containsKey(output)) {
1590 return treeMetadata.get(output);
1591 }
1592
1593 TreeArtifactValue.Builder tree = TreeArtifactValue.newBuilder(output);
1594
1595 Path treeDir = output.getPath();
1596 if (treeDir.exists()) {
1597 TreeArtifactValue.visitTree(
1598 treeDir,
Googler4247c202024-02-19 13:05:37 -08001599 (parentRelativePath, type, traversedSymlink) -> {
Chi Wang4e290422021-08-03 17:56:19 -07001600 if (type == Dirent.Type.DIRECTORY) {
1601 return;
1602 }
1603 Artifact.TreeFileArtifact child =
1604 Artifact.TreeFileArtifact.createTreeOutput(output, parentRelativePath);
1605 FileArtifactValue metadata =
1606 FileArtifactValue.createForTesting(treeDir.getRelative(parentRelativePath));
Googler368bf112023-04-18 02:19:09 -07001607 synchronized (tree) {
1608 tree.putChild(child, metadata);
1609 }
Chi Wang4e290422021-08-03 17:56:19 -07001610 });
1611 }
1612
jhorvitz53f18992021-10-29 10:00:35 -07001613 ArchivedTreeArtifact archivedTreeArtifact = ArchivedTreeArtifact.createForTree(output);
Chi Wang4e290422021-08-03 17:56:19 -07001614 if (archivedTreeArtifact.getPath().exists()) {
1615 tree.setArchivedRepresentation(
1616 archivedTreeArtifact,
1617 FileArtifactValue.createForTesting(archivedTreeArtifact.getPath()));
1618 }
1619
1620 return tree.build();
jmmv9573a0d2017-09-26 11:59:22 -04001621 }
1622
1623 @Override
Googlerf72f46a2019-10-16 11:27:33 -07001624 public void setDigestForVirtualArtifact(Artifact artifact, byte[] digest) {}
jmmv9573a0d2017-09-26 11:59:22 -04001625 }
Chi Wang4e290422021-08-03 17:56:19 -07001626
1627 private static class WriteEmptyOutputAction extends NullAction {
jhorvitz53f18992021-10-29 10:00:35 -07001628 WriteEmptyOutputAction() {}
Chi Wang4e290422021-08-03 17:56:19 -07001629
jhorvitz53f18992021-10-29 10:00:35 -07001630 WriteEmptyOutputAction(Artifact... outputs) {
Chi Wang4e290422021-08-03 17:56:19 -07001631 super(outputs);
1632 }
1633
1634 @Override
1635 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
1636 for (Artifact output : getOutputs()) {
1637 Path path = output.getPath();
1638 if (!path.exists()) {
1639 try {
Googler5d63b522023-10-31 09:28:56 -07001640 writeContentAsLatin1(path, "");
Chi Wang4e290422021-08-03 17:56:19 -07001641 } catch (IOException e) {
1642 throw new IllegalStateException("Failed to create output", e);
1643 }
1644 }
1645 }
1646
1647 return super.execute(actionExecutionContext);
1648 }
1649 }
1650
1651 private static class InjectOutputFileMetadataAction extends NullAction {
1652 private final Artifact output;
1653 private final Deque<FileArtifactValue> metadataDeque;
1654
jhorvitz53f18992021-10-29 10:00:35 -07001655 InjectOutputFileMetadataAction(Artifact output, FileArtifactValue... metadata) {
Chi Wang4e290422021-08-03 17:56:19 -07001656 super(output);
1657
1658 this.output = output;
1659 this.metadataDeque = new ArrayDeque<>(ImmutableList.copyOf(metadata));
1660 }
1661
1662 @Override
1663 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
Googler8ec4c502023-04-12 10:44:40 -07001664 actionExecutionContext.getOutputMetadataStore().injectFile(output, metadataDeque.pop());
Chi Wang4e290422021-08-03 17:56:19 -07001665 return super.execute(actionExecutionContext);
1666 }
1667 }
1668
jhorvitz53f18992021-10-29 10:00:35 -07001669 private static final class InjectOutputTreeMetadataAction extends NullAction {
Chi Wang4e290422021-08-03 17:56:19 -07001670 private final SpecialArtifact output;
1671 private final Deque<TreeArtifactValue> metadataDeque;
1672
jhorvitz53f18992021-10-29 10:00:35 -07001673 InjectOutputTreeMetadataAction(SpecialArtifact output, TreeArtifactValue... metadata) {
Chi Wang4e290422021-08-03 17:56:19 -07001674 super(output);
1675
1676 this.output = output;
1677 this.metadataDeque = new ArrayDeque<>(ImmutableList.copyOf(metadata));
1678 }
1679
1680 @Override
1681 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
Googler8ec4c502023-04-12 10:44:40 -07001682 actionExecutionContext.getOutputMetadataStore().injectTree(output, metadataDeque.pop());
Chi Wang4e290422021-08-03 17:56:19 -07001683 return super.execute(actionExecutionContext);
1684 }
1685 }
jmmv9573a0d2017-09-26 11:59:22 -04001686}