blob: fb765a73bc7db6972c97c06782c00c5257c5dc6a [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 Northropb7f342a2024-11-21 18:07:59 -070062 bool clearCache();
Cody Northrop5f8117a2023-09-26 20:48:59 -060063
Cody Northrop6cca6c22023-02-08 20:23:13 -070064 std::unique_ptr<TemporaryFile> mTempFile;
65 std::unique_ptr<MultifileBlobCache> mMBC;
66};
67
Cody Northrop027f2422023-11-12 22:51:01 -070068void MultifileBlobCacheTest::clearProperties() {
69 // Clear any debug properties used in the tests
70 base::SetProperty("debug.egl.blobcache.cache_version", "");
71 base::WaitForProperty("debug.egl.blobcache.cache_version", "");
72
73 base::SetProperty("debug.egl.blobcache.build_id", "");
74 base::WaitForProperty("debug.egl.blobcache.build_id", "");
75}
76
Cody Northrop6cca6c22023-02-08 20:23:13 -070077TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
78 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
79 mMBC->set("abcd", 4, "efgh", 4);
80 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
81 ASSERT_EQ('e', buf[0]);
82 ASSERT_EQ('f', buf[1]);
83 ASSERT_EQ('g', buf[2]);
84 ASSERT_EQ('h', buf[3]);
85}
86
87TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) {
88 unsigned char buf[2] = {0xee, 0xee};
89 mMBC->set("ab", 2, "cd", 2);
90 mMBC->set("ef", 2, "gh", 2);
91 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
92 ASSERT_EQ('c', buf[0]);
93 ASSERT_EQ('d', buf[1]);
94 ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2));
95 ASSERT_EQ('g', buf[0]);
96 ASSERT_EQ('h', buf[1]);
97}
98
99TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) {
100 unsigned char buf[2] = {0xee, 0xee};
101 mMBC->set("ab", 2, "cd", 2);
102 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
103 ASSERT_EQ('c', buf[0]);
104 ASSERT_EQ('d', buf[1]);
105 // Use the same key, but different value
106 mMBC->set("ab", 2, "ef", 2);
107 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
108 ASSERT_EQ('e', buf[0]);
109 ASSERT_EQ('f', buf[1]);
110}
111
112TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) {
113 unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee};
114 mMBC->set("abcd", 4, "efgh", 4);
115 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4));
116 ASSERT_EQ(0xee, buf[0]);
117 ASSERT_EQ('e', buf[1]);
118 ASSERT_EQ('f', buf[2]);
119 ASSERT_EQ('g', buf[3]);
120 ASSERT_EQ('h', buf[4]);
121 ASSERT_EQ(0xee, buf[5]);
122}
123
124TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
125 unsigned char buf[3] = {0xee, 0xee, 0xee};
126 mMBC->set("abcd", 4, "efgh", 4);
127 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3));
128 ASSERT_EQ(0xee, buf[0]);
129 ASSERT_EQ(0xee, buf[1]);
130 ASSERT_EQ(0xee, buf[2]);
131}
132
133TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) {
134 mMBC->set("abcd", 4, "efgh", 4);
135 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0));
136}
137
138TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) {
139 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
140 mMBC->set("abcd", 4, "efgh", 4);
141 mMBC->set("abcd", 4, "ijkl", 4);
142 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
143 ASSERT_EQ('i', buf[0]);
144 ASSERT_EQ('j', buf[1]);
145 ASSERT_EQ('k', buf[2]);
146 ASSERT_EQ('l', buf[3]);
147}
148
149TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
150 unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee};
151 mMBC->set("abcd", 4, "efgh", 4);
152 mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
153 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
154 ASSERT_EQ('e', buf[0]);
155 ASSERT_EQ('f', buf[1]);
156 ASSERT_EQ('g', buf[2]);
157 ASSERT_EQ('h', buf[3]);
158}
159
160TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) {
161 char key[kMaxKeySize + 1];
162 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
163 for (int i = 0; i < kMaxKeySize + 1; i++) {
164 key[i] = 'a';
165 }
166 mMBC->set(key, kMaxKeySize + 1, "bbbb", 4);
167 ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4));
168 ASSERT_EQ(0xee, buf[0]);
169 ASSERT_EQ(0xee, buf[1]);
170 ASSERT_EQ(0xee, buf[2]);
171 ASSERT_EQ(0xee, buf[3]);
172}
173
174TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) {
175 char buf[kMaxValueSize + 1];
176 for (int i = 0; i < kMaxValueSize + 1; i++) {
177 buf[i] = 'b';
178 }
179 mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
180 for (int i = 0; i < kMaxValueSize + 1; i++) {
181 buf[i] = 0xee;
182 }
183 ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1));
184 for (int i = 0; i < kMaxValueSize + 1; i++) {
185 SCOPED_TRACE(i);
186 ASSERT_EQ(0xee, buf[i]);
187 }
188}
189
190TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) {
191 char key[kMaxKeySize];
192 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
193 for (int i = 0; i < kMaxKeySize; i++) {
194 key[i] = 'a';
195 }
196 mMBC->set(key, kMaxKeySize, "wxyz", 4);
197 ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4));
198 ASSERT_EQ('w', buf[0]);
199 ASSERT_EQ('x', buf[1]);
200 ASSERT_EQ('y', buf[2]);
201 ASSERT_EQ('z', buf[3]);
202}
203
204TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) {
205 char buf[kMaxValueSize];
206 for (int i = 0; i < kMaxValueSize; i++) {
207 buf[i] = 'b';
208 }
209 mMBC->set("abcd", 4, buf, kMaxValueSize);
210 for (int i = 0; i < kMaxValueSize; i++) {
211 buf[i] = 0xee;
212 }
213 mMBC->get("abcd", 4, buf, kMaxValueSize);
214 for (int i = 0; i < kMaxValueSize; i++) {
215 SCOPED_TRACE(i);
216 ASSERT_EQ('b', buf[i]);
217 }
218}
219
Cody Northropbe163732023-03-22 10:14:26 -0600220TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) {
221 char key[kMaxKeySize];
222 for (int i = 0; i < kMaxKeySize; i++) {
223 key[i] = 'a';
224 }
225 char buf[kMaxValueSize];
226 for (int i = 0; i < kMaxValueSize; i++) {
227 buf[i] = 'b';
228 }
229 mMBC->set(key, kMaxKeySize, buf, kMaxValueSize);
230 for (int i = 0; i < kMaxValueSize; i++) {
231 buf[i] = 0xee;
232 }
233 mMBC->get(key, kMaxKeySize, buf, kMaxValueSize);
234 for (int i = 0; i < kMaxValueSize; i++) {
235 SCOPED_TRACE(i);
236 ASSERT_EQ('b', buf[i]);
237 }
238}
239
Cody Northropb5267032023-10-24 10:11:21 -0600240TEST_F(MultifileBlobCacheTest, CacheMaxEntrySucceeds) {
241 // Fill the cache with max entries
242 int i = 0;
243 for (i = 0; i < kMaxTotalEntries; i++) {
244 mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
245 }
246
247 // Ensure it is full
248 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
249
250 // Add another entry
251 mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
252
253 // Ensure total entries is cut in half + 1
254 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries / 2 + 1);
255}
256
Cody Northrop6cca6c22023-02-08 20:23:13 -0700257TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
258 unsigned char buf[1] = {0xee};
259 mMBC->set("x", 1, "y", 1);
260 ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1));
261 ASSERT_EQ('y', buf[0]);
262}
263
Cody Northrop5f8117a2023-09-26 20:48:59 -0600264int MultifileBlobCacheTest::getFileDescriptorCount() {
265 DIR* directory = opendir("/proc/self/fd");
266
267 int fileCount = 0;
268 struct dirent* entry;
269 while ((entry = readdir(directory)) != NULL) {
270 fileCount++;
271 // printf("File: %s\n", entry->d_name);
272 }
273
274 closedir(directory);
275 return fileCount;
276}
277
278TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
279 // Populate the cache with a bunch of entries
Cody Northropb5267032023-10-24 10:11:21 -0600280 for (int i = 0; i < kMaxTotalEntries; i++) {
Cody Northrop5f8117a2023-09-26 20:48:59 -0600281 // printf("Caching: %i", i);
282
283 // Use the index as the key and value
284 mMBC->set(&i, sizeof(i), &i, sizeof(i));
285
286 int result = 0;
287 ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
288 ASSERT_EQ(i, result);
289 }
290
291 // Ensure we don't have a bunch of open fds
Cody Northropb5267032023-10-24 10:11:21 -0600292 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
Cody Northrop5f8117a2023-09-26 20:48:59 -0600293
294 // Close the cache so everything writes out
295 mMBC->finish();
296 mMBC.reset();
297
298 // Now open it again and ensure we still don't have a bunch of open fds
Cody Northropb5267032023-10-24 10:11:21 -0600299 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
300 &mTempFile->path[0]));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600301
302 // Check after initialization
Cody Northropb5267032023-10-24 10:11:21 -0600303 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
Cody Northrop5f8117a2023-09-26 20:48:59 -0600304
Cody Northropb5267032023-10-24 10:11:21 -0600305 for (int i = 0; i < kMaxTotalEntries; i++) {
Cody Northrop5f8117a2023-09-26 20:48:59 -0600306 int result = 0;
307 ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
308 ASSERT_EQ(i, result);
309 }
310
311 // And again after we've actually used it
Cody Northropb5267032023-10-24 10:11:21 -0600312 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
Cody Northrop5f8117a2023-09-26 20:48:59 -0600313}
314
Cody Northrop027f2422023-11-12 22:51:01 -0700315std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() {
316 std::string cachePath = &mTempFile->path[0];
317 std::string multifileDirName = cachePath + ".multifile";
318 std::vector<std::string> cacheEntries;
319
320 struct stat info;
321 if (stat(multifileDirName.c_str(), &info) == 0) {
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700322 // We have a multifile dir. Skip the status file and return the entries.
Cody Northrop027f2422023-11-12 22:51:01 -0700323 DIR* dir;
324 struct dirent* entry;
325 if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
326 while ((entry = readdir(dir)) != nullptr) {
327 if (entry->d_name == "."s || entry->d_name == ".."s) {
328 continue;
329 }
330 if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
331 continue;
332 }
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700333 // printf("Found entry: %s\n", entry->d_name);
Cody Northrop027f2422023-11-12 22:51:01 -0700334 cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
335 }
336 } else {
337 printf("Unable to open %s, error: %s\n", multifileDirName.c_str(),
338 std::strerror(errno));
339 }
340 } else {
341 printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno));
342 }
343
344 return cacheEntries;
345}
346
347TEST_F(MultifileBlobCacheTest, CacheContainsStatus) {
348 struct stat info;
349 std::stringstream statusFile;
350 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
351
352 // After INIT, cache should have a status
353 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
354
355 // Set one entry
356 mMBC->set("abcd", 4, "efgh", 4);
357
358 // Close the cache so everything writes out
359 mMBC->finish();
360 mMBC.reset();
361
362 // Ensure status lives after closing the cache
363 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
364
365 // Open the cache again
366 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
367 &mTempFile->path[0]));
368
369 // Ensure we still have a status
370 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
371}
372
373// Verify missing cache status file causes cache the be cleared
374TEST_F(MultifileBlobCacheTest, MissingCacheStatusClears) {
375 // Set one entry
376 mMBC->set("abcd", 4, "efgh", 4);
377
378 // Close the cache so everything writes out
379 mMBC->finish();
380 mMBC.reset();
381
382 // Ensure there is one cache entry
383 ASSERT_EQ(getCacheEntries().size(), 1);
384
385 // Delete the status file
386 std::stringstream statusFile;
387 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
388 remove(statusFile.str().c_str());
389
390 // Open the cache again and ensure no cache hits
391 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
392 &mTempFile->path[0]));
393
394 // Ensure we have no entries
395 ASSERT_EQ(getCacheEntries().size(), 0);
396}
397
398// Verify modified cache status file BEGIN causes cache to be cleared
399TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusBeginClears) {
400 // Set one entry
401 mMBC->set("abcd", 4, "efgh", 4);
402
403 // Close the cache so everything writes out
404 mMBC->finish();
405 mMBC.reset();
406
407 // Ensure there is one cache entry
408 ASSERT_EQ(getCacheEntries().size(), 1);
409
410 // Modify the status file
411 std::stringstream statusFile;
412 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
413
414 // Stomp on the beginning of the cache file
415 const char* stomp = "BADF00D";
416 std::fstream fs(statusFile.str());
417 fs.seekp(0, std::ios_base::beg);
418 fs.write(stomp, strlen(stomp));
419 fs.flush();
420 fs.close();
421
422 // Open the cache again and ensure no cache hits
423 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
424 &mTempFile->path[0]));
425
426 // Ensure we have no entries
427 ASSERT_EQ(getCacheEntries().size(), 0);
428}
429
430// Verify modified cache status file END causes cache to be cleared
431TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusEndClears) {
432 // Set one entry
433 mMBC->set("abcd", 4, "efgh", 4);
434
435 // Close the cache so everything writes out
436 mMBC->finish();
437 mMBC.reset();
438
439 // Ensure there is one cache entry
440 ASSERT_EQ(getCacheEntries().size(), 1);
441
442 // Modify the status file
443 std::stringstream statusFile;
444 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
445
446 // Stomp on the END of the cache status file, modifying its contents
447 const char* stomp = "BADF00D";
448 std::fstream fs(statusFile.str());
449 fs.seekp(-strlen(stomp), std::ios_base::end);
450 fs.write(stomp, strlen(stomp));
451 fs.flush();
452 fs.close();
453
454 // Open the cache again and ensure no cache hits
455 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
456 &mTempFile->path[0]));
457
458 // Ensure we have no entries
459 ASSERT_EQ(getCacheEntries().size(), 0);
460}
461
462// Verify mismatched cacheVersion causes cache to be cleared
463TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) {
464 // Set one entry
465 mMBC->set("abcd", 4, "efgh", 4);
466
Cody Northrop4ee63862024-11-19 22:41:23 -0700467 uint32_t initialCacheVersion = mMBC->getCurrentCacheVersion();
468
Cody Northrop027f2422023-11-12 22:51:01 -0700469 // Close the cache so everything writes out
470 mMBC->finish();
471 mMBC.reset();
472
473 // Ensure there is one cache entry
474 ASSERT_EQ(getCacheEntries().size(), 1);
475
476 // Set a debug cacheVersion
Cody Northrop4ee63862024-11-19 22:41:23 -0700477 std::string newCacheVersion = std::to_string(initialCacheVersion + 1);
Cody Northrop027f2422023-11-12 22:51:01 -0700478 ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
479 ASSERT_TRUE(
480 base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
481
482 // Open the cache again and ensure no cache hits
483 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
484 &mTempFile->path[0]));
485
486 // Ensure we have no entries
487 ASSERT_EQ(getCacheEntries().size(), 0);
488}
489
490// Verify mismatched buildId causes cache to be cleared
491TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) {
492 // Set one entry
493 mMBC->set("abcd", 4, "efgh", 4);
494
495 // Close the cache so everything writes out
496 mMBC->finish();
497 mMBC.reset();
498
499 // Ensure there is one cache entry
500 ASSERT_EQ(getCacheEntries().size(), 1);
501
502 // Set a debug buildId
503 base::SetProperty("debug.egl.blobcache.build_id", "foo");
504 base::WaitForProperty("debug.egl.blobcache.build_id", "foo");
505
506 // Open the cache again and ensure no cache hits
507 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
508 &mTempFile->path[0]));
509
510 // Ensure we have no entries
511 ASSERT_EQ(getCacheEntries().size(), 0);
512}
513
Cody Northrop6aebcf22024-11-08 15:55:30 -0700514// Ensure cache is correct when a key is reused
515TEST_F(MultifileBlobCacheTest, SameKeyDifferentValues) {
516 if (!flags::multifile_blobcache_advanced_usage()) {
517 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
518 }
519
520 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
521
522 size_t startingSize = mMBC->getTotalSize();
523
524 // New cache should be empty
525 ASSERT_EQ(startingSize, 0);
526
527 // Set an initial value
528 mMBC->set("ab", 2, "cdef", 4);
529
530 // Grab the new size
531 size_t firstSize = mMBC->getTotalSize();
532
533 // Ensure the size went up
534 // Note: Checking for an exact size is challenging, as the
535 // file size can differ between platforms.
536 ASSERT_GT(firstSize, startingSize);
537
538 // Verify the cache is correct
539 ASSERT_EQ(size_t(4), mMBC->get("ab", 2, buf, 4));
540 ASSERT_EQ('c', buf[0]);
541 ASSERT_EQ('d', buf[1]);
542 ASSERT_EQ('e', buf[2]);
543 ASSERT_EQ('f', buf[3]);
544
545 // Now reuse the key with a smaller value
546 mMBC->set("ab", 2, "gh", 2);
547
548 // Grab the new size
549 size_t secondSize = mMBC->getTotalSize();
550
551 // Ensure it decreased in size
552 ASSERT_LT(secondSize, firstSize);
553
554 // Verify the cache is correct
555 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
556 ASSERT_EQ('g', buf[0]);
557 ASSERT_EQ('h', buf[1]);
558
559 // Now put back the original value
560 mMBC->set("ab", 2, "cdef", 4);
561
562 // And we should get back a stable size
563 size_t finalSize = mMBC->getTotalSize();
564 ASSERT_EQ(firstSize, finalSize);
565}
566
567// Ensure cache is correct when a key is reused with large value size
568TEST_F(MultifileBlobCacheTest, SameKeyLargeValues) {
569 if (!flags::multifile_blobcache_advanced_usage()) {
570 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
571 }
572
573 // Create the cache with larger limits to stress test reuse
574 constexpr uint32_t kLocalMaxKeySize = 1 * 1024 * 1024;
575 constexpr uint32_t kLocalMaxValueSize = 4 * 1024 * 1024;
576 constexpr uint32_t kLocalMaxTotalSize = 32 * 1024 * 1024;
577 mMBC.reset(new MultifileBlobCache(kLocalMaxKeySize, kLocalMaxValueSize, kLocalMaxTotalSize,
578 kMaxTotalEntries, &mTempFile->path[0]));
579
580 constexpr uint32_t kLargeValueCount = 8;
581 constexpr uint32_t kLargeValueSize = 64 * 1024;
582
583 // Create a several really large values
584 unsigned char largeValue[kLargeValueCount][kLargeValueSize];
585 for (int i = 0; i < kLargeValueCount; i++) {
586 for (int j = 0; j < kLargeValueSize; j++) {
587 // Fill the value with the index for uniqueness
588 largeValue[i][j] = i;
589 }
590 }
591
592 size_t startingSize = mMBC->getTotalSize();
593
594 // New cache should be empty
595 ASSERT_EQ(startingSize, 0);
596
597 // Cycle through the values and set them all in sequence
598 for (int i = 0; i < kLargeValueCount; i++) {
599 mMBC->set("abcd", 4, largeValue[i], kLargeValueSize);
600 }
601
602 // Ensure we get the last one back
603 unsigned char outBuf[kLargeValueSize];
604 mMBC->get("abcd", 4, outBuf, kLargeValueSize);
605
606 for (int i = 0; i < kLargeValueSize; i++) {
607 // Buffer should contain highest index value
608 ASSERT_EQ(kLargeValueCount - 1, outBuf[i]);
609 }
610}
611
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700612// Ensure cache eviction is LRU
613TEST_F(MultifileBlobCacheTest, CacheEvictionIsLRU) {
614 if (!flags::multifile_blobcache_advanced_usage()) {
615 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
616 }
617
618 // Fill the cache with exactly how much it can hold
619 int entry = 0;
620 for (entry = 0; entry < kMaxTotalEntries; entry++) {
621 // Use the index as the key and value
622 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
623
624 int result = 0;
625 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
626 ASSERT_EQ(entry, result);
627 }
628
629 // Ensure the cache is full
630 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
631
632 // Add one more entry to trigger eviction
633 size_t overflowEntry = kMaxTotalEntries;
634 mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
635
636 // Verify it contains the right amount, which will be one more than reduced size
637 // because we evict the cache before adding a new entry
638 size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
639 ASSERT_EQ(mMBC->getTotalEntries(), evictionLimit + 1);
640
641 // Ensure cache is as expected, with old entries removed, newer entries remaining
642 for (entry = 0; entry < kMaxTotalEntries; entry++) {
643 int result = 0;
644 mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
645
646 if (entry < evictionLimit) {
647 // We should get no hits on evicted entries, i.e. the first added
648 ASSERT_EQ(result, 0);
649 } else {
650 // Above the limit should still be present
651 ASSERT_EQ(result, entry);
652 }
653 }
654}
655
656// Ensure calling GET on an entry updates its access time, even if already in hotcache
657TEST_F(MultifileBlobCacheTest, GetUpdatesAccessTime) {
658 if (!flags::multifile_blobcache_advanced_usage()) {
659 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
660 }
661
662 // Fill the cache with exactly how much it can hold
663 int entry = 0;
664 int result = 0;
665 for (entry = 0; entry < kMaxTotalEntries; entry++) {
666 // Use the index as the key and value
667 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
668 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
669 ASSERT_EQ(entry, result);
670 }
671
672 // Ensure the cache is full
673 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
674
675 // GET the first few entries to update their access time
676 std::vector<int> accessedEntries = {1, 2, 3};
677 for (int i = 0; i < accessedEntries.size(); i++) {
678 entry = accessedEntries[i];
679 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
680 }
681
682 // Add one more entry to trigger eviction
683 size_t overflowEntry = kMaxTotalEntries;
684 mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
685
686 size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
687
688 // Ensure cache is as expected, with old entries removed, newer entries remaining
689 for (entry = 0; entry < kMaxTotalEntries; entry++) {
690 int result = 0;
691 mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
692
693 if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
694 accessedEntries.end()) {
695 // If this is one of the handful we accessed after filling the cache,
696 // they should still be in the cache because LRU
697 ASSERT_EQ(result, entry);
698 } else if (entry >= (evictionLimit + accessedEntries.size())) {
699 // If they were above the eviction limit (plus three for our updated entries),
700 // they should still be present
701 ASSERT_EQ(result, entry);
702 } else {
703 // Otherwise, they shold be evicted and no longer present
704 ASSERT_EQ(result, 0);
705 }
706 }
707
708 // Close the cache so everything writes out
709 mMBC->finish();
710 mMBC.reset();
711
712 // Open the cache again
713 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
714 &mTempFile->path[0]));
715
716 // Check the cache again, ensuring the updated access time made it to disk
717 for (entry = 0; entry < kMaxTotalEntries; entry++) {
718 int result = 0;
719 mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
720 if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
721 accessedEntries.end()) {
722 ASSERT_EQ(result, entry);
723 } else if (entry >= (evictionLimit + accessedEntries.size())) {
724 ASSERT_EQ(result, entry);
725 } else {
726 ASSERT_EQ(result, 0);
727 }
728 }
729}
730
Cody Northropb7f342a2024-11-21 18:07:59 -0700731bool MultifileBlobCacheTest::clearCache() {
732 std::string cachePath = &mTempFile->path[0];
733 std::string multifileDirName = cachePath + ".multifile";
734
735 DIR* dir = opendir(multifileDirName.c_str());
736 if (dir == nullptr) {
737 printf("Error opening directory: %s\n", multifileDirName.c_str());
738 return false;
739 }
740
741 struct dirent* entry;
742 while ((entry = readdir(dir)) != nullptr) {
743 // Skip "." and ".." entries
744 if (std::string(entry->d_name) == "." || std::string(entry->d_name) == "..") {
745 continue;
746 }
747
748 std::string entryPath = multifileDirName + "/" + entry->d_name;
749
750 // Delete the entry (we assert it's a file, nothing nested here)
751 if (unlink(entryPath.c_str()) != 0) {
752 printf("Error deleting file: %s\n", entryPath.c_str());
753 closedir(dir);
754 return false;
755 }
756 }
757
758 closedir(dir);
759
760 // Delete the empty directory itself
761 if (rmdir(multifileDirName.c_str()) != 0) {
762 printf("Error deleting directory %s, error %s\n", multifileDirName.c_str(),
763 std::strerror(errno));
764 return false;
765 }
766
767 return true;
768}
769
770// Recover from lost cache in the case of app clearing it
771TEST_F(MultifileBlobCacheTest, RecoverFromLostCache) {
772 if (!flags::multifile_blobcache_advanced_usage()) {
773 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
774 }
775
776 int entry = 0;
777 int result = 0;
778
779 uint32_t kEntryCount = 10;
780
781 // Add some entries
782 for (entry = 0; entry < kEntryCount; entry++) {
783 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
784 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
785 ASSERT_EQ(entry, result);
786 }
787
788 // For testing, wait until the entries have completed writing
789 mMBC->finish();
790
791 // Manually delete the cache!
792 ASSERT_TRUE(clearCache());
793
794 // Cache should not contain any entries
795 for (entry = 0; entry < kEntryCount; entry++) {
796 ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
797 }
798
799 // Ensure we can still add new ones
800 for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
801 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
802 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
803 ASSERT_EQ(entry, result);
804 }
805
806 // Close the cache so everything writes out
807 mMBC->finish();
808 mMBC.reset();
809
810 // Open the cache again
811 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
812 &mTempFile->path[0]));
813
814 // Before fixes, writing the second entries to disk should have failed due to missing
815 // cache dir. But now they should have survived our shutdown above.
816 for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
817 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
818 ASSERT_EQ(entry, result);
819 }
820}
821
822// Ensure cache eviction succeeds if the cache is deleted
823TEST_F(MultifileBlobCacheTest, EvictAfterLostCache) {
824 if (!flags::multifile_blobcache_advanced_usage()) {
825 GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
826 }
827
828 int entry = 0;
829 int result = 0;
830
831 uint32_t kEntryCount = 10;
832
833 // Add some entries
834 for (entry = 0; entry < kEntryCount; entry++) {
835 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
836 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
837 ASSERT_EQ(entry, result);
838 }
839
840 // For testing, wait until the entries have completed writing
841 mMBC->finish();
842
843 // Manually delete the cache!
844 ASSERT_TRUE(clearCache());
845
846 // Now start adding entries to trigger eviction, cache should survive
847 for (entry = kEntryCount; entry < 2 * kMaxTotalEntries; entry++) {
848 mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
849 ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
850 ASSERT_EQ(entry, result);
851 }
852
853 // We should have triggered multiple evictions above and remain at or below the
854 // max amount of entries
855 ASSERT_LE(getCacheEntries().size(), kMaxTotalEntries);
856}
857
Cody Northrop6cca6c22023-02-08 20:23:13 -0700858} // namespace android