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