blob: 74352d477dcb45760a3d72f7f8d0fecf519ee6e4 [file] [log] [blame]
Cody Northrop6cca6c22023-02-08 20:23:13 -07001/*
2 ** Copyright 2023, The Android Open Source Project
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 "MultifileBlobCache.h"
18
Cody Northrop027f2422023-11-12 22:51:01 -070019#include <android-base/properties.h>
Cody Northrop6cca6c22023-02-08 20:23:13 -070020#include <android-base/test_utils.h>
21#include <fcntl.h>
22#include <gtest/gtest.h>
23#include <stdio.h>
24
Cody Northrop027f2422023-11-12 22:51:01 -070025#include <fstream>
Cody Northrop6cca6c22023-02-08 20:23:13 -070026#include <memory>
27
Cody Northrop4ee63862024-11-19 22:41:23 -070028#include <com_android_graphics_egl_flags.h>
29
30using namespace com::android::graphics::egl;
31
Cody Northrop027f2422023-11-12 22:51:01 -070032using namespace std::literals;
33
Cody Northrop6cca6c22023-02-08 20:23:13 -070034namespace android {
35
36template <typename T>
37using sp = std::shared_ptr<T>;
38
Cody Northrop5dbcfa72023-03-24 15:34:09 -060039constexpr size_t kMaxKeySize = 2 * 1024;
40constexpr size_t kMaxValueSize = 6 * 1024;
Cody Northrop6cca6c22023-02-08 20:23:13 -070041constexpr size_t kMaxTotalSize = 32 * 1024;
Cody Northropb5267032023-10-24 10:11:21 -060042constexpr size_t kMaxTotalEntries = 64;
Cody Northrop6cca6c22023-02-08 20:23:13 -070043
44class MultifileBlobCacheTest : public ::testing::Test {
45protected:
46 virtual void SetUp() {
Cody Northrop027f2422023-11-12 22:51:01 -070047 clearProperties();
Cody Northrop6cca6c22023-02-08 20:23:13 -070048 mTempFile.reset(new TemporaryFile());
Cody Northrop5dbcfa72023-03-24 15:34:09 -060049 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
Cody Northropb5267032023-10-24 10:11:21 -060050 kMaxTotalEntries, &mTempFile->path[0]));
Cody Northrop6cca6c22023-02-08 20:23:13 -070051 }
52
Cody Northrop027f2422023-11-12 22:51:01 -070053 virtual void TearDown() {
54 clearProperties();
55 mMBC.reset();
56 }
Cody Northrop6cca6c22023-02-08 20:23:13 -070057
Cody Northrop5f8117a2023-09-26 20:48:59 -060058 int getFileDescriptorCount();
Cody Northrop027f2422023-11-12 22:51:01 -070059 std::vector<std::string> getCacheEntries();
60
61 void clearProperties();
Cody Northrop5f8117a2023-09-26 20:48:59 -060062
Cody Northrop6cca6c22023-02-08 20:23:13 -070063 std::unique_ptr<TemporaryFile> mTempFile;
64 std::unique_ptr<MultifileBlobCache> mMBC;
65};
66
Cody Northrop027f2422023-11-12 22:51:01 -070067void MultifileBlobCacheTest::clearProperties() {
68 // Clear any debug properties used in the tests
69 base::SetProperty("debug.egl.blobcache.cache_version", "");
70 base::WaitForProperty("debug.egl.blobcache.cache_version", "");
71
72 base::SetProperty("debug.egl.blobcache.build_id", "");
73 base::WaitForProperty("debug.egl.blobcache.build_id", "");
74}
75
Cody Northrop6cca6c22023-02-08 20:23:13 -070076TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
77 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
78 mMBC->set("abcd", 4, "efgh", 4);
79 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
80 ASSERT_EQ('e', buf[0]);
81 ASSERT_EQ('f', buf[1]);
82 ASSERT_EQ('g', buf[2]);
83 ASSERT_EQ('h', buf[3]);
84}
85
86TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) {
87 unsigned char buf[2] = {0xee, 0xee};
88 mMBC->set("ab", 2, "cd", 2);
89 mMBC->set("ef", 2, "gh", 2);
90 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
91 ASSERT_EQ('c', buf[0]);
92 ASSERT_EQ('d', buf[1]);
93 ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2));
94 ASSERT_EQ('g', buf[0]);
95 ASSERT_EQ('h', buf[1]);
96}
97
98TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) {
99 unsigned char buf[2] = {0xee, 0xee};
100 mMBC->set("ab", 2, "cd", 2);
101 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
102 ASSERT_EQ('c', buf[0]);
103 ASSERT_EQ('d', buf[1]);
104 // Use the same key, but different value
105 mMBC->set("ab", 2, "ef", 2);
106 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
107 ASSERT_EQ('e', buf[0]);
108 ASSERT_EQ('f', buf[1]);
109}
110
111TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) {
112 unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee};
113 mMBC->set("abcd", 4, "efgh", 4);
114 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4));
115 ASSERT_EQ(0xee, buf[0]);
116 ASSERT_EQ('e', buf[1]);
117 ASSERT_EQ('f', buf[2]);
118 ASSERT_EQ('g', buf[3]);
119 ASSERT_EQ('h', buf[4]);
120 ASSERT_EQ(0xee, buf[5]);
121}
122
123TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
124 unsigned char buf[3] = {0xee, 0xee, 0xee};
125 mMBC->set("abcd", 4, "efgh", 4);
126 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3));
127 ASSERT_EQ(0xee, buf[0]);
128 ASSERT_EQ(0xee, buf[1]);
129 ASSERT_EQ(0xee, buf[2]);
130}
131
132TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) {
133 mMBC->set("abcd", 4, "efgh", 4);
134 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0));
135}
136
137TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) {
138 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
139 mMBC->set("abcd", 4, "efgh", 4);
140 mMBC->set("abcd", 4, "ijkl", 4);
141 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
142 ASSERT_EQ('i', buf[0]);
143 ASSERT_EQ('j', buf[1]);
144 ASSERT_EQ('k', buf[2]);
145 ASSERT_EQ('l', buf[3]);
146}
147
148TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
149 unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee};
150 mMBC->set("abcd", 4, "efgh", 4);
151 mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
152 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
153 ASSERT_EQ('e', buf[0]);
154 ASSERT_EQ('f', buf[1]);
155 ASSERT_EQ('g', buf[2]);
156 ASSERT_EQ('h', buf[3]);
157}
158
159TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) {
160 char key[kMaxKeySize + 1];
161 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
162 for (int i = 0; i < kMaxKeySize + 1; i++) {
163 key[i] = 'a';
164 }
165 mMBC->set(key, kMaxKeySize + 1, "bbbb", 4);
166 ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4));
167 ASSERT_EQ(0xee, buf[0]);
168 ASSERT_EQ(0xee, buf[1]);
169 ASSERT_EQ(0xee, buf[2]);
170 ASSERT_EQ(0xee, buf[3]);
171}
172
173TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) {
174 char buf[kMaxValueSize + 1];
175 for (int i = 0; i < kMaxValueSize + 1; i++) {
176 buf[i] = 'b';
177 }
178 mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
179 for (int i = 0; i < kMaxValueSize + 1; i++) {
180 buf[i] = 0xee;
181 }
182 ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1));
183 for (int i = 0; i < kMaxValueSize + 1; i++) {
184 SCOPED_TRACE(i);
185 ASSERT_EQ(0xee, buf[i]);
186 }
187}
188
189TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) {
190 char key[kMaxKeySize];
191 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
192 for (int i = 0; i < kMaxKeySize; i++) {
193 key[i] = 'a';
194 }
195 mMBC->set(key, kMaxKeySize, "wxyz", 4);
196 ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4));
197 ASSERT_EQ('w', buf[0]);
198 ASSERT_EQ('x', buf[1]);
199 ASSERT_EQ('y', buf[2]);
200 ASSERT_EQ('z', buf[3]);
201}
202
203TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) {
204 char buf[kMaxValueSize];
205 for (int i = 0; i < kMaxValueSize; i++) {
206 buf[i] = 'b';
207 }
208 mMBC->set("abcd", 4, buf, kMaxValueSize);
209 for (int i = 0; i < kMaxValueSize; i++) {
210 buf[i] = 0xee;
211 }
212 mMBC->get("abcd", 4, buf, kMaxValueSize);
213 for (int i = 0; i < kMaxValueSize; i++) {
214 SCOPED_TRACE(i);
215 ASSERT_EQ('b', buf[i]);
216 }
217}
218
Cody Northropbe163732023-03-22 10:14:26 -0600219TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) {
220 char key[kMaxKeySize];
221 for (int i = 0; i < kMaxKeySize; i++) {
222 key[i] = 'a';
223 }
224 char buf[kMaxValueSize];
225 for (int i = 0; i < kMaxValueSize; i++) {
226 buf[i] = 'b';
227 }
228 mMBC->set(key, kMaxKeySize, buf, kMaxValueSize);
229 for (int i = 0; i < kMaxValueSize; i++) {
230 buf[i] = 0xee;
231 }
232 mMBC->get(key, kMaxKeySize, buf, kMaxValueSize);
233 for (int i = 0; i < kMaxValueSize; i++) {
234 SCOPED_TRACE(i);
235 ASSERT_EQ('b', buf[i]);
236 }
237}
238
Cody Northropb5267032023-10-24 10:11:21 -0600239TEST_F(MultifileBlobCacheTest, CacheMaxEntrySucceeds) {
240 // Fill the cache with max entries
241 int i = 0;
242 for (i = 0; i < kMaxTotalEntries; i++) {
243 mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
244 }
245
246 // Ensure it is full
247 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
248
249 // Add another entry
250 mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
251
252 // Ensure total entries is cut in half + 1
253 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries / 2 + 1);
254}
255
Cody Northrop6cca6c22023-02-08 20:23:13 -0700256TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
257 unsigned char buf[1] = {0xee};
258 mMBC->set("x", 1, "y", 1);
259 ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1));
260 ASSERT_EQ('y', buf[0]);
261}
262
Cody Northrop5f8117a2023-09-26 20:48:59 -0600263int MultifileBlobCacheTest::getFileDescriptorCount() {
264 DIR* directory = opendir("/proc/self/fd");
265
266 int fileCount = 0;
267 struct dirent* entry;
268 while ((entry = readdir(directory)) != NULL) {
269 fileCount++;
270 // printf("File: %s\n", entry->d_name);
271 }
272
273 closedir(directory);
274 return fileCount;
275}
276
277TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
278 // Populate the cache with a bunch of entries
Cody Northropb5267032023-10-24 10:11:21 -0600279 for (int i = 0; i < kMaxTotalEntries; i++) {
Cody Northrop5f8117a2023-09-26 20:48:59 -0600280 // printf("Caching: %i", i);
281
282 // Use the index as the key and value
283 mMBC->set(&i, sizeof(i), &i, sizeof(i));
284
285 int result = 0;
286 ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
287 ASSERT_EQ(i, result);
288 }
289
290 // Ensure we don't have a bunch of open fds
Cody Northropb5267032023-10-24 10:11:21 -0600291 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
Cody Northrop5f8117a2023-09-26 20:48:59 -0600292
293 // Close the cache so everything writes out
294 mMBC->finish();
295 mMBC.reset();
296
297 // Now open it again and ensure we still don't have a bunch of open fds
Cody Northropb5267032023-10-24 10:11:21 -0600298 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
299 &mTempFile->path[0]));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600300
301 // Check after initialization
Cody Northropb5267032023-10-24 10:11:21 -0600302 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
Cody Northrop5f8117a2023-09-26 20:48:59 -0600303
Cody Northropb5267032023-10-24 10:11:21 -0600304 for (int i = 0; i < kMaxTotalEntries; i++) {
Cody Northrop5f8117a2023-09-26 20:48:59 -0600305 int result = 0;
306 ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
307 ASSERT_EQ(i, result);
308 }
309
310 // And again after we've actually used it
Cody Northropb5267032023-10-24 10:11:21 -0600311 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
Cody Northrop5f8117a2023-09-26 20:48:59 -0600312}
313
Cody Northrop027f2422023-11-12 22:51:01 -0700314std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() {
315 std::string cachePath = &mTempFile->path[0];
316 std::string multifileDirName = cachePath + ".multifile";
317 std::vector<std::string> cacheEntries;
318
319 struct stat info;
320 if (stat(multifileDirName.c_str(), &info) == 0) {
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700321 // We have a multifile dir. Skip the status file and return the entries.
Cody Northrop027f2422023-11-12 22:51:01 -0700322 DIR* dir;
323 struct dirent* entry;
324 if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
325 while ((entry = readdir(dir)) != nullptr) {
326 if (entry->d_name == "."s || entry->d_name == ".."s) {
327 continue;
328 }
329 if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
330 continue;
331 }
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700332 // printf("Found entry: %s\n", entry->d_name);
Cody Northrop027f2422023-11-12 22:51:01 -0700333 cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
334 }
335 } else {
336 printf("Unable to open %s, error: %s\n", multifileDirName.c_str(),
337 std::strerror(errno));
338 }
339 } else {
340 printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno));
341 }
342
343 return cacheEntries;
344}
345
346TEST_F(MultifileBlobCacheTest, CacheContainsStatus) {
347 struct stat info;
348 std::stringstream statusFile;
349 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
350
351 // After INIT, cache should have a status
352 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
353
354 // Set one entry
355 mMBC->set("abcd", 4, "efgh", 4);
356
357 // Close the cache so everything writes out
358 mMBC->finish();
359 mMBC.reset();
360
361 // Ensure status lives after closing the cache
362 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
363
364 // Open the cache again
365 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
366 &mTempFile->path[0]));
367
368 // Ensure we still have a status
369 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
370}
371
372// Verify missing cache status file causes cache the be cleared
373TEST_F(MultifileBlobCacheTest, MissingCacheStatusClears) {
374 // Set one entry
375 mMBC->set("abcd", 4, "efgh", 4);
376
377 // Close the cache so everything writes out
378 mMBC->finish();
379 mMBC.reset();
380
381 // Ensure there is one cache entry
382 ASSERT_EQ(getCacheEntries().size(), 1);
383
384 // Delete the status file
385 std::stringstream statusFile;
386 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
387 remove(statusFile.str().c_str());
388
389 // Open the cache again and ensure no cache hits
390 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
391 &mTempFile->path[0]));
392
393 // Ensure we have no entries
394 ASSERT_EQ(getCacheEntries().size(), 0);
395}
396
397// Verify modified cache status file BEGIN causes cache to be cleared
398TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusBeginClears) {
399 // Set one entry
400 mMBC->set("abcd", 4, "efgh", 4);
401
402 // Close the cache so everything writes out
403 mMBC->finish();
404 mMBC.reset();
405
406 // Ensure there is one cache entry
407 ASSERT_EQ(getCacheEntries().size(), 1);
408
409 // Modify the status file
410 std::stringstream statusFile;
411 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
412
413 // Stomp on the beginning of the cache file
414 const char* stomp = "BADF00D";
415 std::fstream fs(statusFile.str());
416 fs.seekp(0, std::ios_base::beg);
417 fs.write(stomp, strlen(stomp));
418 fs.flush();
419 fs.close();
420
421 // Open the cache again and ensure no cache hits
422 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
423 &mTempFile->path[0]));
424
425 // Ensure we have no entries
426 ASSERT_EQ(getCacheEntries().size(), 0);
427}
428
429// Verify modified cache status file END causes cache to be cleared
430TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusEndClears) {
431 // Set one entry
432 mMBC->set("abcd", 4, "efgh", 4);
433
434 // Close the cache so everything writes out
435 mMBC->finish();
436 mMBC.reset();
437
438 // Ensure there is one cache entry
439 ASSERT_EQ(getCacheEntries().size(), 1);
440
441 // Modify the status file
442 std::stringstream statusFile;
443 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
444
445 // Stomp on the END of the cache status file, modifying its contents
446 const char* stomp = "BADF00D";
447 std::fstream fs(statusFile.str());
448 fs.seekp(-strlen(stomp), std::ios_base::end);
449 fs.write(stomp, strlen(stomp));
450 fs.flush();
451 fs.close();
452
453 // Open the cache again and ensure no cache hits
454 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
455 &mTempFile->path[0]));
456
457 // Ensure we have no entries
458 ASSERT_EQ(getCacheEntries().size(), 0);
459}
460
461// Verify mismatched cacheVersion causes cache to be cleared
462TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) {
463 // Set one entry
464 mMBC->set("abcd", 4, "efgh", 4);
465
Cody Northrop4ee63862024-11-19 22:41:23 -0700466 uint32_t initialCacheVersion = mMBC->getCurrentCacheVersion();
467
Cody Northrop027f2422023-11-12 22:51:01 -0700468 // Close the cache so everything writes out
469 mMBC->finish();
470 mMBC.reset();
471
472 // Ensure there is one cache entry
473 ASSERT_EQ(getCacheEntries().size(), 1);
474
475 // Set a debug cacheVersion
Cody Northrop4ee63862024-11-19 22:41:23 -0700476 std::string newCacheVersion = std::to_string(initialCacheVersion + 1);
Cody Northrop027f2422023-11-12 22:51:01 -0700477 ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
478 ASSERT_TRUE(
479 base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
480
481 // Open the cache again and ensure no cache hits
482 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
483 &mTempFile->path[0]));
484
485 // Ensure we have no entries
486 ASSERT_EQ(getCacheEntries().size(), 0);
487}
488
489// Verify mismatched buildId causes cache to be cleared
490TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) {
491 // Set one entry
492 mMBC->set("abcd", 4, "efgh", 4);
493
494 // Close the cache so everything writes out
495 mMBC->finish();
496 mMBC.reset();
497
498 // Ensure there is one cache entry
499 ASSERT_EQ(getCacheEntries().size(), 1);
500
501 // Set a debug buildId
502 base::SetProperty("debug.egl.blobcache.build_id", "foo");
503 base::WaitForProperty("debug.egl.blobcache.build_id", "foo");
504
505 // Open the cache again and ensure no cache hits
506 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
507 &mTempFile->path[0]));
508
509 // Ensure we have no entries
510 ASSERT_EQ(getCacheEntries().size(), 0);
511}
512
Cody Northrop6aebcf22024-11-08 15:55:30 -0700513// Ensure cache is correct when a key is reused
514TEST_F(MultifileBlobCacheTest, SameKeyDifferentValues) {
515 if (!flags::multifile_blobcache_advanced_usage()) {
516 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
517 }
518
519 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
520
521 size_t startingSize = mMBC->getTotalSize();
522
523 // New cache should be empty
524 ASSERT_EQ(startingSize, 0);
525
526 // Set an initial value
527 mMBC->set("ab", 2, "cdef", 4);
528
529 // Grab the new size
530 size_t firstSize = mMBC->getTotalSize();
531
532 // Ensure the size went up
533 // Note: Checking for an exact size is challenging, as the
534 // file size can differ between platforms.
535 ASSERT_GT(firstSize, startingSize);
536
537 // Verify the cache is correct
538 ASSERT_EQ(size_t(4), mMBC->get("ab", 2, buf, 4));
539 ASSERT_EQ('c', buf[0]);
540 ASSERT_EQ('d', buf[1]);
541 ASSERT_EQ('e', buf[2]);
542 ASSERT_EQ('f', buf[3]);
543
544 // Now reuse the key with a smaller value
545 mMBC->set("ab", 2, "gh", 2);
546
547 // Grab the new size
548 size_t secondSize = mMBC->getTotalSize();
549
550 // Ensure it decreased in size
551 ASSERT_LT(secondSize, firstSize);
552
553 // Verify the cache is correct
554 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
555 ASSERT_EQ('g', buf[0]);
556 ASSERT_EQ('h', buf[1]);
557
558 // Now put back the original value
559 mMBC->set("ab", 2, "cdef", 4);
560
561 // And we should get back a stable size
562 size_t finalSize = mMBC->getTotalSize();
563 ASSERT_EQ(firstSize, finalSize);
564}
565
566// Ensure cache is correct when a key is reused with large value size
567TEST_F(MultifileBlobCacheTest, SameKeyLargeValues) {
568 if (!flags::multifile_blobcache_advanced_usage()) {
569 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
570 }
571
572 // Create the cache with larger limits to stress test reuse
573 constexpr uint32_t kLocalMaxKeySize = 1 * 1024 * 1024;
574 constexpr uint32_t kLocalMaxValueSize = 4 * 1024 * 1024;
575 constexpr uint32_t kLocalMaxTotalSize = 32 * 1024 * 1024;
576 mMBC.reset(new MultifileBlobCache(kLocalMaxKeySize, kLocalMaxValueSize, kLocalMaxTotalSize,
577 kMaxTotalEntries, &mTempFile->path[0]));
578
579 constexpr uint32_t kLargeValueCount = 8;
580 constexpr uint32_t kLargeValueSize = 64 * 1024;
581
582 // Create a several really large values
583 unsigned char largeValue[kLargeValueCount][kLargeValueSize];
584 for (int i = 0; i < kLargeValueCount; i++) {
585 for (int j = 0; j < kLargeValueSize; j++) {
586 // Fill the value with the index for uniqueness
587 largeValue[i][j] = i;
588 }
589 }
590
591 size_t startingSize = mMBC->getTotalSize();
592
593 // New cache should be empty
594 ASSERT_EQ(startingSize, 0);
595
596 // Cycle through the values and set them all in sequence
597 for (int i = 0; i < kLargeValueCount; i++) {
598 mMBC->set("abcd", 4, largeValue[i], kLargeValueSize);
599 }
600
601 // Ensure we get the last one back
602 unsigned char outBuf[kLargeValueSize];
603 mMBC->get("abcd", 4, outBuf, kLargeValueSize);
604
605 for (int i = 0; i < kLargeValueSize; i++) {
606 // Buffer should contain highest index value
607 ASSERT_EQ(kLargeValueCount - 1, outBuf[i]);
608 }
609}
610
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700611// Ensure cache eviction is LRU
612TEST_F(MultifileBlobCacheTest, CacheEvictionIsLRU) {
613 if (!flags::multifile_blobcache_advanced_usage()) {
614 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
615 }
616
617 // Fill the cache with exactly how much it can hold
618 int entry = 0;
619 for (entry = 0; entry < kMaxTotalEntries; entry++) {
620 // Use the index as the key and value
621 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
622
623 int result = 0;
624 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
625 ASSERT_EQ(entry, result);
626 }
627
628 // Ensure the cache is full
629 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
630
631 // Add one more entry to trigger eviction
632 size_t overflowEntry = kMaxTotalEntries;
633 mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
634
635 // Verify it contains the right amount, which will be one more than reduced size
636 // because we evict the cache before adding a new entry
637 size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
638 ASSERT_EQ(mMBC->getTotalEntries(), evictionLimit + 1);
639
640 // Ensure cache is as expected, with old entries removed, newer entries remaining
641 for (entry = 0; entry < kMaxTotalEntries; entry++) {
642 int result = 0;
643 mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
644
645 if (entry < evictionLimit) {
646 // We should get no hits on evicted entries, i.e. the first added
647 ASSERT_EQ(result, 0);
648 } else {
649 // Above the limit should still be present
650 ASSERT_EQ(result, entry);
651 }
652 }
653}
654
655// Ensure calling GET on an entry updates its access time, even if already in hotcache
656TEST_F(MultifileBlobCacheTest, GetUpdatesAccessTime) {
657 if (!flags::multifile_blobcache_advanced_usage()) {
658 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
659 }
660
661 // Fill the cache with exactly how much it can hold
662 int entry = 0;
663 int result = 0;
664 for (entry = 0; entry < kMaxTotalEntries; entry++) {
665 // Use the index as the key and value
666 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
667 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
668 ASSERT_EQ(entry, result);
669 }
670
671 // Ensure the cache is full
672 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
673
674 // GET the first few entries to update their access time
675 std::vector<int> accessedEntries = {1, 2, 3};
676 for (int i = 0; i < accessedEntries.size(); i++) {
677 entry = accessedEntries[i];
678 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
679 }
680
681 // Add one more entry to trigger eviction
682 size_t overflowEntry = kMaxTotalEntries;
683 mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
684
685 size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
686
687 // Ensure cache is as expected, with old entries removed, newer entries remaining
688 for (entry = 0; entry < kMaxTotalEntries; entry++) {
689 int result = 0;
690 mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
691
692 if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
693 accessedEntries.end()) {
694 // If this is one of the handful we accessed after filling the cache,
695 // they should still be in the cache because LRU
696 ASSERT_EQ(result, entry);
697 } else if (entry >= (evictionLimit + accessedEntries.size())) {
698 // If they were above the eviction limit (plus three for our updated entries),
699 // they should still be present
700 ASSERT_EQ(result, entry);
701 } else {
702 // Otherwise, they shold be evicted and no longer present
703 ASSERT_EQ(result, 0);
704 }
705 }
706
707 // Close the cache so everything writes out
708 mMBC->finish();
709 mMBC.reset();
710
711 // Open the cache again
712 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
713 &mTempFile->path[0]));
714
715 // Check the cache again, ensuring the updated access time made it to disk
716 for (entry = 0; entry < kMaxTotalEntries; entry++) {
717 int result = 0;
718 mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
719 if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
720 accessedEntries.end()) {
721 ASSERT_EQ(result, entry);
722 } else if (entry >= (evictionLimit + accessedEntries.size())) {
723 ASSERT_EQ(result, entry);
724 } else {
725 ASSERT_EQ(result, 0);
726 }
727 }
728}
729
Cody Northrop6cca6c22023-02-08 20:23:13 -0700730} // namespace android