xref: /aosp_15_r20/external/federated-compute/fcp/client/cache/file_backed_resource_cache_test.cc (revision 14675a029014e728ec732f129a32e299b2da0601)
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "fcp/client/cache/file_backed_resource_cache.h"
18 
19 #include <cstddef>
20 #include <filesystem>
21 #include <fstream>
22 #include <functional>
23 #include <optional>
24 #include <string>
25 #include <utility>
26 
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "absl/status/statusor.h"
30 #include "absl/time/time.h"
31 #include "fcp/base/monitoring.h"
32 #include "fcp/base/simulated_clock.h"
33 #include "fcp/client/selector_context.pb.h"
34 #include "fcp/client/test_helpers.h"
35 #include "fcp/testing/testing.h"
36 
37 namespace fcp {
38 namespace client {
39 namespace cache {
40 namespace {
41 
42 constexpr char kKey1[] = "1";
Resource1()43 absl::Cord Resource1() { return absl::Cord("stream RENAISSANCE by Beyoncé"); }
44 constexpr char kKey2[] = "2";
Resource2()45 absl::Cord Resource2() { return absl::Cord("stream PURE/HONEY by Beyoncé"); }
46 constexpr char kKey3[] = "3";
Resource3()47 absl::Cord Resource3() {
48   return absl::Cord("A third resource?? In this economy");
49 }
SampleStoredMetadata()50 SelectorContext SampleStoredMetadata() {
51   SelectorContext sample_stored_metadata;
52   sample_stored_metadata.mutable_computation_properties()->set_session_name(
53       "test");
54   return sample_stored_metadata;
55 }
Metadata()56 google::protobuf::Any Metadata() {
57   google::protobuf::Any metadata;
58   metadata.PackFrom(SampleStoredMetadata());
59   return metadata;
60 }
61 absl::Duration kMaxAge = absl::Hours(1);
62 int64_t kMaxCacheSizeBytes = 10000000;
63 
NumFilesInDir(std::filesystem::path dir)64 int NumFilesInDir(std::filesystem::path dir) {
65   int num_files_in_dir = 0;
66   for ([[maybe_unused]] auto& de : std::filesystem::directory_iterator(dir)) {
67     num_files_in_dir++;
68   }
69   return num_files_in_dir;
70 }
71 
72 class FileBackedResourceCacheTest : public testing::Test {
73  protected:
SetUp()74   void SetUp() override {
75     root_cache_dir_ = testing::TempDir();
76     std::filesystem::path root_cache_dir(root_cache_dir_);
77     cache_dir_ = root_cache_dir / "fcp" / "cache";
78     root_files_dir_ = testing::TempDir();
79     std::filesystem::path root_files_dir(root_files_dir_);
80     manifest_path_ = root_files_dir / "fcp" / "cache_manifest.pb";
81   }
82 
TearDown()83   void TearDown() override {
84     std::filesystem::remove_all(root_cache_dir_);
85     std::filesystem::remove_all(root_files_dir_);
86   }
87 
88   testing::StrictMock<MockLogManager> log_manager_;
89   SimulatedClock clock_;
90   std::string root_cache_dir_;
91   std::string root_files_dir_;
92   std::filesystem::path cache_dir_;
93   std::filesystem::path manifest_path_;
94 };
95 
TEST_F(FileBackedResourceCacheTest,FailToCreateParentDirectoryInBaseDir)96 TEST_F(FileBackedResourceCacheTest, FailToCreateParentDirectoryInBaseDir) {
97   EXPECT_CALL(
98       log_manager_,
99       LogDiag(ProdDiagCode::RESOURCE_CACHE_FAILED_TO_CREATE_MANIFEST_DIR));
100   ASSERT_THAT(
101       FileBackedResourceCache::Create("/proc/0", root_cache_dir_, &log_manager_,
102                                       &clock_, kMaxCacheSizeBytes),
103       IsCode(INTERNAL));
104 }
105 
TEST_F(FileBackedResourceCacheTest,FailToCreateParentDirectoryInCacheDir)106 TEST_F(FileBackedResourceCacheTest, FailToCreateParentDirectoryInCacheDir) {
107   EXPECT_CALL(log_manager_,
108               LogDiag(ProdDiagCode::RESOURCE_CACHE_FAILED_TO_CREATE_CACHE_DIR));
109   ASSERT_THAT(
110       FileBackedResourceCache::Create(root_files_dir_, "/proc/0", &log_manager_,
111                                       &clock_, kMaxCacheSizeBytes),
112       IsCode(INTERNAL));
113 }
114 
TEST_F(FileBackedResourceCacheTest,InvalidBaseDirRelativePath)115 TEST_F(FileBackedResourceCacheTest, InvalidBaseDirRelativePath) {
116   EXPECT_CALL(log_manager_,
117               LogDiag(ProdDiagCode::RESOURCE_CACHE_INVALID_MANIFEST_PATH));
118   ASSERT_THAT(FileBackedResourceCache::Create("relative/base", root_cache_dir_,
119                                               &log_manager_, &clock_,
120                                               kMaxCacheSizeBytes),
121               IsCode(INVALID_ARGUMENT));
122 }
123 
TEST_F(FileBackedResourceCacheTest,InvalidCacheDirRelativePath)124 TEST_F(FileBackedResourceCacheTest, InvalidCacheDirRelativePath) {
125   EXPECT_CALL(
126       log_manager_,
127       LogDiag(ProdDiagCode::RESOURCE_CACHE_CACHE_ROOT_PATH_NOT_ABSOLUTE));
128   ASSERT_THAT(FileBackedResourceCache::Create(root_files_dir_, "relative/cache",
129                                               &log_manager_, &clock_,
130                                               kMaxCacheSizeBytes),
131               IsCode(INVALID_ARGUMENT));
132 }
133 
TEST_F(FileBackedResourceCacheTest,SuccessfulInitialization)134 TEST_F(FileBackedResourceCacheTest, SuccessfulInitialization) {
135   ASSERT_OK(FileBackedResourceCache::Create(root_files_dir_, root_cache_dir_,
136                                             &log_manager_, &clock_,
137                                             kMaxCacheSizeBytes));
138 }
139 
TEST_F(FileBackedResourceCacheTest,CacheFile)140 TEST_F(FileBackedResourceCacheTest, CacheFile) {
141   auto resource_cache = FileBackedResourceCache::Create(
142       root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
143       kMaxCacheSizeBytes);
144   ASSERT_OK(resource_cache);
145   ASSERT_OK(
146       (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
147 
148   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
149   absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata> cached_resource =
150       (*resource_cache)->Get(kKey1, std::nullopt);
151   ASSERT_OK(cached_resource);
152   ASSERT_EQ(Resource1(), (*cached_resource).resource);
153   ASSERT_EQ(Metadata().GetTypeName(),
154             (*cached_resource).metadata.GetTypeName());
155   SelectorContext stored_metadata;
156   (*cached_resource).metadata.UnpackTo(&stored_metadata);
157   ASSERT_THAT(SampleStoredMetadata(), EqualsProto(stored_metadata));
158 }
159 
TEST_F(FileBackedResourceCacheTest,CacheFileCloseReinitializeFileStillCached)160 TEST_F(FileBackedResourceCacheTest, CacheFileCloseReinitializeFileStillCached) {
161   {
162     auto resource_cache = FileBackedResourceCache::Create(
163         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
164         kMaxCacheSizeBytes);
165     ASSERT_OK(resource_cache);
166     ASSERT_OK(
167         (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
168   }
169 
170   // Advance the clock a little bit
171   clock_.AdvanceTime(absl::Minutes(1));
172 
173   {
174     auto resource_cache = FileBackedResourceCache::Create(
175         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
176         kMaxCacheSizeBytes);
177     ASSERT_OK(resource_cache);
178     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
179     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
180         cached_resource = (*resource_cache)->Get(kKey1, std::nullopt);
181     ASSERT_OK(cached_resource);
182     ASSERT_EQ(Resource1(), (*cached_resource).resource);
183   }
184 }
185 
TEST_F(FileBackedResourceCacheTest,CacheTooBigFileReturnsResourceExhausted)186 TEST_F(FileBackedResourceCacheTest, CacheTooBigFileReturnsResourceExhausted) {
187   auto resource_cache = FileBackedResourceCache::Create(
188       root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
189       (int64_t)(Resource1().size() / 2));
190   ASSERT_OK(resource_cache);
191   ASSERT_THAT(
192       (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)),
193       IsCode(RESOURCE_EXHAUSTED));
194 }
195 
TEST_F(FileBackedResourceCacheTest,UnreadableManifestReturnsInternalButIsThenReadable)196 TEST_F(FileBackedResourceCacheTest,
197        UnreadableManifestReturnsInternalButIsThenReadable) {
198   {
199     auto resource_cache = FileBackedResourceCache::Create(
200         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
201         kMaxCacheSizeBytes);
202     ASSERT_OK(resource_cache);
203     ASSERT_OK(
204         (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
205   }
206 
207   // There should be the one file we cached.
208   ASSERT_EQ(NumFilesInDir(cache_dir_), 1);
209 
210   // Write some garbage to the manifest.
211   {
212     std::ofstream ofs(manifest_path_, std::ofstream::trunc);
213     ofs << "garbage garbage garbage";
214   }
215 
216   {
217     EXPECT_CALL(log_manager_,
218                 LogDiag(ProdDiagCode::RESOURCE_CACHE_MANIFEST_READ_FAILED));
219     auto resource_cache = FileBackedResourceCache::Create(
220         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
221         kMaxCacheSizeBytes);
222     ASSERT_THAT(resource_cache, IsCode(INTERNAL));
223   }
224 
225   // Failing to read the manifest should have deleted it.
226   ASSERT_EQ(std::filesystem::exists(manifest_path_), false);
227   // But there will still be files in the cache dir. These files will be cleaned
228   // up the next time the cache is initialized.
229   ASSERT_EQ(NumFilesInDir(cache_dir_), 1);
230 
231   // We should be able to create a new FileBackedResourceCache successfully
232   // since the garbage manifest was deleted.
233   {
234     auto resource_cache = FileBackedResourceCache::Create(
235         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
236         kMaxCacheSizeBytes);
237     ASSERT_OK(resource_cache);
238     // Initializing the cache should have deleted the untracked files in the
239     // cache dir.
240     ASSERT_EQ(NumFilesInDir(cache_dir_), 0);
241     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
242     ASSERT_THAT((*resource_cache)->Get(kKey1, std::nullopt), IsCode(NOT_FOUND));
243   }
244 }
245 
TEST_F(FileBackedResourceCacheTest,UnreadableManifestReturnsInternalButIsThenWritable)246 TEST_F(FileBackedResourceCacheTest,
247        UnreadableManifestReturnsInternalButIsThenWritable) {
248   {
249     auto resource_cache = FileBackedResourceCache::Create(
250         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
251         kMaxCacheSizeBytes);
252     ASSERT_OK(resource_cache);
253     ASSERT_OK(
254         (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
255   }
256 
257   // There should be the one file we cached.
258   ASSERT_EQ(NumFilesInDir(cache_dir_), 1);
259 
260   // Write some garbage to the manifest.
261   {
262     std::ofstream ofs(manifest_path_, std::ofstream::trunc);
263     ofs << "garbage garbage garbage";
264   }
265 
266   {
267     EXPECT_CALL(log_manager_,
268                 LogDiag(ProdDiagCode::RESOURCE_CACHE_MANIFEST_READ_FAILED));
269     auto resource_cache = FileBackedResourceCache::Create(
270         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
271         kMaxCacheSizeBytes);
272     ASSERT_THAT(resource_cache, IsCode(INTERNAL));
273   }
274 
275   // Failing to read the manifest should have deleted it.
276   ASSERT_EQ(std::filesystem::exists(manifest_path_), false);
277   // But there will still be files in the cache dir. These files will be cleaned
278   // up the next time the cache is initialized.
279   ASSERT_EQ(NumFilesInDir(cache_dir_), 1);
280 
281   // We should be able to create a new FileBackedResourceCache successfully
282   // since it was reset.
283   {
284     auto resource_cache = FileBackedResourceCache::Create(
285         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
286         kMaxCacheSizeBytes);
287     // Initializing the cache should have deleted the untracked files in the
288     // cache dir.
289     ASSERT_EQ(NumFilesInDir(cache_dir_), 0);
290     ASSERT_OK(resource_cache);
291     ASSERT_OK(
292         (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
293     ASSERT_EQ(NumFilesInDir(cache_dir_), 1);
294   }
295 }
296 
TEST_F(FileBackedResourceCacheTest,PutTwoFilesThenGetThem)297 TEST_F(FileBackedResourceCacheTest, PutTwoFilesThenGetThem) {
298   auto resource_cache = FileBackedResourceCache::Create(
299       root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
300       kMaxCacheSizeBytes);
301   ASSERT_OK(resource_cache);
302   ASSERT_OK((*resource_cache)->Put(kKey1, Resource1(), Metadata(), kMaxAge));
303   ASSERT_OK((*resource_cache)->Put(kKey2, Resource2(), Metadata(), kMaxAge));
304 
305   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
306   absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
307       cached_resource1 = (*resource_cache)->Get(kKey1, std::nullopt);
308   ASSERT_OK(cached_resource1);
309   ASSERT_EQ(Resource1(), (*cached_resource1).resource);
310 
311   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
312   absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
313       cached_resource2 = (*resource_cache)->Get(kKey2, std::nullopt);
314   ASSERT_OK(cached_resource2);
315   ASSERT_EQ(Resource2(), (*cached_resource2).resource);
316 }
317 
TEST_F(FileBackedResourceCacheTest,CacheFileThenExpire)318 TEST_F(FileBackedResourceCacheTest, CacheFileThenExpire) {
319   {
320     auto resource_cache = FileBackedResourceCache::Create(
321         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
322         kMaxCacheSizeBytes);
323     ASSERT_OK(resource_cache);
324     ASSERT_OK((*resource_cache)->Put(kKey1, Resource1(), Metadata(), kMaxAge));
325   }
326 
327   // Advance the clock a little bit beyond max_age
328   clock_.AdvanceTime(kMaxAge + absl::Minutes(1));
329 
330   {
331     auto resource_cache = FileBackedResourceCache::Create(
332         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
333         kMaxCacheSizeBytes);
334     ASSERT_OK(resource_cache);
335 
336     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
337     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
338         cached_resource = (*resource_cache)->Get(kKey1, std::nullopt);
339     ASSERT_THAT(cached_resource, IsCode(NOT_FOUND));
340   }
341 }
342 
TEST_F(FileBackedResourceCacheTest,PutTwoFilesThenOneExpires)343 TEST_F(FileBackedResourceCacheTest, PutTwoFilesThenOneExpires) {
344   {
345     auto resource_cache = FileBackedResourceCache::Create(
346         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
347         kMaxCacheSizeBytes);
348     ASSERT_OK(resource_cache);
349     ASSERT_OK((*resource_cache)->Put(kKey1, Resource1(), Metadata(), kMaxAge));
350     ASSERT_OK(
351         (*resource_cache)->Put(kKey2, Resource2(), Metadata(), kMaxAge * 2));
352   }
353 
354   // Advance the clock a little bit beyond the first resource's expiry.
355 
356   clock_.AdvanceTime(kMaxAge + absl::Minutes(1));
357   {
358     auto resource_cache = FileBackedResourceCache::Create(
359         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
360         kMaxCacheSizeBytes);
361     ASSERT_OK(resource_cache);
362     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
363     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
364         cached_resource1 = (*resource_cache)->Get(kKey1, std::nullopt);
365     ASSERT_THAT(cached_resource1, IsCode(NOT_FOUND));
366 
367     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
368     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
369         cached_resource2 = (*resource_cache)->Get(kKey2, std::nullopt);
370     ASSERT_OK(cached_resource2);
371     ASSERT_EQ(Resource2(), (*cached_resource2).resource);
372   }
373 }
374 
TEST_F(FileBackedResourceCacheTest,CacheFileThenUpdateExpiry)375 TEST_F(FileBackedResourceCacheTest, CacheFileThenUpdateExpiry) {
376   {
377     auto resource_cache = FileBackedResourceCache::Create(
378         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
379         kMaxCacheSizeBytes);
380     ASSERT_OK(resource_cache);
381     ASSERT_OK((*resource_cache)->Put(kKey1, Resource1(), Metadata(), kMaxAge));
382   }
383 
384   {
385     auto resource_cache = FileBackedResourceCache::Create(
386         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
387         kMaxCacheSizeBytes);
388     ASSERT_OK(resource_cache);
389 
390     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
391     // Pass a new max_age when we Get the resource, updating its expiry time.
392     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
393         cached_resource = (*resource_cache)->Get(kKey1, 6 * kMaxAge);
394     ASSERT_OK(cached_resource);
395     ASSERT_EQ(Resource1(), (*cached_resource).resource);
396   }
397 
398   // Advance the clock. Even though we've now passed the original expiry, the
399   // resource should still be cached because we updated the expiry with the
400   // Get().
401   clock_.AdvanceTime(kMaxAge + absl::Minutes(5));
402 
403   {
404     auto resource_cache = FileBackedResourceCache::Create(
405         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
406         kMaxCacheSizeBytes);
407     ASSERT_OK(resource_cache);
408 
409     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
410     // Pass a new max_age when we Get the resource, updating its expiry time.
411     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
412         cached_resource = (*resource_cache)->Get(kKey1, 6 * kMaxAge);
413     ASSERT_OK(cached_resource);
414     ASSERT_EQ(Resource1(), (*cached_resource).resource);
415   }
416 }
417 
TEST_F(FileBackedResourceCacheTest,CacheExceedsMaxCacheSize)418 TEST_F(FileBackedResourceCacheTest, CacheExceedsMaxCacheSize) {
419   // Room for resource2 and resource3 but not quite enough for resource1 as
420   // well.
421   int64_t local_max_cache_size_bytes =
422       Resource2().size() + Resource3().size() + (Resource1().size() / 2);
423 
424   auto resource_cache = FileBackedResourceCache::Create(
425       root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
426       local_max_cache_size_bytes);
427   ASSERT_OK(resource_cache);
428   ASSERT_OK(
429       (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
430   clock_.AdvanceTime(absl::Minutes(1));
431   ASSERT_OK(
432       (*resource_cache)->Put(kKey2, Resource2(), Metadata(), absl::Hours(1)));
433   clock_.AdvanceTime(absl::Minutes(1));
434   ASSERT_OK(
435       (*resource_cache)->Put(kKey3, Resource3(), Metadata(), absl::Hours(1)));
436 
437   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
438   ASSERT_OK((*resource_cache)->Get(kKey3, std::nullopt));
439   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
440   ASSERT_OK((*resource_cache)->Get(kKey2, std::nullopt));
441   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
442   ASSERT_THAT((*resource_cache)->Get(kKey1, std::nullopt), IsCode(NOT_FOUND));
443 }
444 
TEST_F(FileBackedResourceCacheTest,CacheExceedsMaxCacheSizeLeastRecentlyUsedDeleted)445 TEST_F(FileBackedResourceCacheTest,
446        CacheExceedsMaxCacheSizeLeastRecentlyUsedDeleted) {
447   int64_t local_max_cache_size_bytes =
448       Resource1().size() + (Resource2().size() / 2) + Resource3().size();
449 
450   auto resource_cache = FileBackedResourceCache::Create(
451       root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
452       local_max_cache_size_bytes);
453   ASSERT_OK(resource_cache);
454   ASSERT_OK(
455       (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
456   clock_.AdvanceTime(absl::Minutes(1));
457   ASSERT_OK(
458       (*resource_cache)->Put(kKey2, Resource2(), Metadata(), absl::Hours(1)));
459   clock_.AdvanceTime(absl::Minutes(1));
460   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
461   // Get resource1 so we update it's least recently used time before we put in
462   // resource3. This should cause resource2 to get deleted instead of resource1
463   // when we add resource3.
464   ASSERT_OK((*resource_cache)->Get(kKey1, std::nullopt));
465   clock_.AdvanceTime(absl::Minutes(1));
466   ASSERT_OK(
467       (*resource_cache)->Put(kKey3, Resource3(), Metadata(), absl::Hours(1)));
468 
469   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
470   ASSERT_OK((*resource_cache)->Get(kKey3, std::nullopt));
471   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
472   ASSERT_THAT((*resource_cache)->Get(kKey2, std::nullopt), IsCode(NOT_FOUND));
473   EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_HIT));
474   ASSERT_OK((*resource_cache)->Get(kKey1, std::nullopt));
475 }
476 
TEST_F(FileBackedResourceCacheTest,FileInCacheDirButNotInManifest)477 TEST_F(FileBackedResourceCacheTest, FileInCacheDirButNotInManifest) {
478   {
479     auto resource_cache = FileBackedResourceCache::Create(
480         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
481         kMaxCacheSizeBytes);
482     ASSERT_OK(resource_cache);
483     ASSERT_OK(
484         (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
485   }
486 
487   // Delete the manifest!
488   std::filesystem::remove(manifest_path_);
489 
490   // There should be the one file we cached.
491   ASSERT_EQ(NumFilesInDir(cache_dir_), 1);
492 
493   {
494     auto resource_cache = FileBackedResourceCache::Create(
495         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
496         kMaxCacheSizeBytes);
497     ASSERT_OK(resource_cache);
498 
499     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
500     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
501         cached_resource = (*resource_cache)->Get(kKey1, std::nullopt);
502     ASSERT_THAT(cached_resource, IsCode(NOT_FOUND));
503     // The cache dir should also be empty, because we reinitialized the cache
504     // and there was an untracked file in it.
505     ASSERT_EQ(NumFilesInDir(cache_dir_), 0);
506   }
507 }
508 
509 // Covers the case where a user manually deletes the app's cache dir.
TEST_F(FileBackedResourceCacheTest,FileInManifestButRootCacheDirDeleted)510 TEST_F(FileBackedResourceCacheTest, FileInManifestButRootCacheDirDeleted) {
511   {
512     auto resource_cache = FileBackedResourceCache::Create(
513         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
514         kMaxCacheSizeBytes);
515     ASSERT_OK(resource_cache);
516     ASSERT_OK(
517         (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
518   }
519 
520   // Delete the entire cache dir from the root.
521   std::filesystem::remove_all(root_cache_dir_);
522 
523   {
524     auto resource_cache = FileBackedResourceCache::Create(
525         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
526         kMaxCacheSizeBytes);
527     ASSERT_OK(resource_cache);
528 
529     // Now we should gracefully fail even though the file is in the manifest but
530     // not on disk.
531     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
532     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
533         cached_resource = (*resource_cache)->Get(kKey1, std::nullopt);
534     ASSERT_THAT(cached_resource, IsCode(NOT_FOUND));
535   }
536 }
537 
TEST_F(FileBackedResourceCacheTest,FileInManifestButNotInCacheDir)538 TEST_F(FileBackedResourceCacheTest, FileInManifestButNotInCacheDir) {
539   {
540     auto resource_cache = FileBackedResourceCache::Create(
541         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
542         kMaxCacheSizeBytes);
543     ASSERT_OK(resource_cache);
544     ASSERT_OK(
545         (*resource_cache)->Put(kKey1, Resource1(), Metadata(), absl::Hours(1)));
546   }
547 
548   // Delete the file we just cached.
549   std::filesystem::remove(cache_dir_ / kKey1);
550 
551   {
552     auto resource_cache = FileBackedResourceCache::Create(
553         root_files_dir_, root_cache_dir_, &log_manager_, &clock_,
554         kMaxCacheSizeBytes);
555     ASSERT_OK(resource_cache);
556 
557     // Now we should gracefully fail even though the file is in the manifest but
558     // not on disk.
559     EXPECT_CALL(log_manager_, LogDiag(DebugDiagCode::RESOURCE_CACHE_MISS));
560     absl::StatusOr<FileBackedResourceCache::ResourceAndMetadata>
561         cached_resource = (*resource_cache)->Get(kKey1, std::nullopt);
562     ASSERT_THAT(cached_resource, IsCode(NOT_FOUND));
563   }
564 }
565 
566 }  // namespace
567 }  // namespace cache
568 }  // namespace client
569 }  // namespace fcp
570