1 /*
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
19 #include <android-base/properties.h>
20 #include <android-base/test_utils.h>
21 #include <fcntl.h>
22 #include <gtest/gtest.h>
23 #include <stdio.h>
24
25 #include <fstream>
26 #include <memory>
27
28 using namespace std::literals;
29
30 namespace android {
31
32 template <typename T>
33 using sp = std::shared_ptr<T>;
34
35 constexpr size_t kMaxKeySize = 2 * 1024;
36 constexpr size_t kMaxValueSize = 6 * 1024;
37 constexpr size_t kMaxTotalSize = 32 * 1024;
38 constexpr size_t kMaxTotalEntries = 64;
39
40 class MultifileBlobCacheTest : public ::testing::Test {
41 protected:
SetUp()42 virtual void SetUp() {
43 clearProperties();
44 mTempFile.reset(new TemporaryFile());
45 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
46 kMaxTotalEntries, &mTempFile->path[0]));
47 }
48
TearDown()49 virtual void TearDown() {
50 clearProperties();
51 mMBC.reset();
52 }
53
54 int getFileDescriptorCount();
55 std::vector<std::string> getCacheEntries();
56
57 void clearProperties();
58
59 std::unique_ptr<TemporaryFile> mTempFile;
60 std::unique_ptr<MultifileBlobCache> mMBC;
61 };
62
clearProperties()63 void MultifileBlobCacheTest::clearProperties() {
64 // Clear any debug properties used in the tests
65 base::SetProperty("debug.egl.blobcache.cache_version", "");
66 base::WaitForProperty("debug.egl.blobcache.cache_version", "");
67
68 base::SetProperty("debug.egl.blobcache.build_id", "");
69 base::WaitForProperty("debug.egl.blobcache.build_id", "");
70 }
71
TEST_F(MultifileBlobCacheTest,CacheSingleValueSucceeds)72 TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
73 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
74 mMBC->set("abcd", 4, "efgh", 4);
75 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
76 ASSERT_EQ('e', buf[0]);
77 ASSERT_EQ('f', buf[1]);
78 ASSERT_EQ('g', buf[2]);
79 ASSERT_EQ('h', buf[3]);
80 }
81
TEST_F(MultifileBlobCacheTest,CacheTwoValuesSucceeds)82 TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) {
83 unsigned char buf[2] = {0xee, 0xee};
84 mMBC->set("ab", 2, "cd", 2);
85 mMBC->set("ef", 2, "gh", 2);
86 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
87 ASSERT_EQ('c', buf[0]);
88 ASSERT_EQ('d', buf[1]);
89 ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2));
90 ASSERT_EQ('g', buf[0]);
91 ASSERT_EQ('h', buf[1]);
92 }
93
TEST_F(MultifileBlobCacheTest,GetSetTwiceSucceeds)94 TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) {
95 unsigned char buf[2] = {0xee, 0xee};
96 mMBC->set("ab", 2, "cd", 2);
97 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
98 ASSERT_EQ('c', buf[0]);
99 ASSERT_EQ('d', buf[1]);
100 // Use the same key, but different value
101 mMBC->set("ab", 2, "ef", 2);
102 ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
103 ASSERT_EQ('e', buf[0]);
104 ASSERT_EQ('f', buf[1]);
105 }
106
TEST_F(MultifileBlobCacheTest,GetOnlyWritesInsideBounds)107 TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) {
108 unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee};
109 mMBC->set("abcd", 4, "efgh", 4);
110 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4));
111 ASSERT_EQ(0xee, buf[0]);
112 ASSERT_EQ('e', buf[1]);
113 ASSERT_EQ('f', buf[2]);
114 ASSERT_EQ('g', buf[3]);
115 ASSERT_EQ('h', buf[4]);
116 ASSERT_EQ(0xee, buf[5]);
117 }
118
TEST_F(MultifileBlobCacheTest,GetOnlyWritesIfBufferIsLargeEnough)119 TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
120 unsigned char buf[3] = {0xee, 0xee, 0xee};
121 mMBC->set("abcd", 4, "efgh", 4);
122 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3));
123 ASSERT_EQ(0xee, buf[0]);
124 ASSERT_EQ(0xee, buf[1]);
125 ASSERT_EQ(0xee, buf[2]);
126 }
127
TEST_F(MultifileBlobCacheTest,GetDoesntAccessNullBuffer)128 TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) {
129 mMBC->set("abcd", 4, "efgh", 4);
130 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0));
131 }
132
TEST_F(MultifileBlobCacheTest,MultipleSetsCacheLatestValue)133 TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) {
134 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
135 mMBC->set("abcd", 4, "efgh", 4);
136 mMBC->set("abcd", 4, "ijkl", 4);
137 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
138 ASSERT_EQ('i', buf[0]);
139 ASSERT_EQ('j', buf[1]);
140 ASSERT_EQ('k', buf[2]);
141 ASSERT_EQ('l', buf[3]);
142 }
143
TEST_F(MultifileBlobCacheTest,SecondSetKeepsFirstValueIfTooLarge)144 TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
145 unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee};
146 mMBC->set("abcd", 4, "efgh", 4);
147 mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
148 ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
149 ASSERT_EQ('e', buf[0]);
150 ASSERT_EQ('f', buf[1]);
151 ASSERT_EQ('g', buf[2]);
152 ASSERT_EQ('h', buf[3]);
153 }
154
TEST_F(MultifileBlobCacheTest,DoesntCacheIfKeyIsTooBig)155 TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) {
156 char key[kMaxKeySize + 1];
157 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
158 for (int i = 0; i < kMaxKeySize + 1; i++) {
159 key[i] = 'a';
160 }
161 mMBC->set(key, kMaxKeySize + 1, "bbbb", 4);
162 ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4));
163 ASSERT_EQ(0xee, buf[0]);
164 ASSERT_EQ(0xee, buf[1]);
165 ASSERT_EQ(0xee, buf[2]);
166 ASSERT_EQ(0xee, buf[3]);
167 }
168
TEST_F(MultifileBlobCacheTest,DoesntCacheIfValueIsTooBig)169 TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) {
170 char buf[kMaxValueSize + 1];
171 for (int i = 0; i < kMaxValueSize + 1; i++) {
172 buf[i] = 'b';
173 }
174 mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
175 for (int i = 0; i < kMaxValueSize + 1; i++) {
176 buf[i] = 0xee;
177 }
178 ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1));
179 for (int i = 0; i < kMaxValueSize + 1; i++) {
180 SCOPED_TRACE(i);
181 ASSERT_EQ(0xee, buf[i]);
182 }
183 }
184
TEST_F(MultifileBlobCacheTest,CacheMaxKeySizeSucceeds)185 TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) {
186 char key[kMaxKeySize];
187 unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
188 for (int i = 0; i < kMaxKeySize; i++) {
189 key[i] = 'a';
190 }
191 mMBC->set(key, kMaxKeySize, "wxyz", 4);
192 ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4));
193 ASSERT_EQ('w', buf[0]);
194 ASSERT_EQ('x', buf[1]);
195 ASSERT_EQ('y', buf[2]);
196 ASSERT_EQ('z', buf[3]);
197 }
198
TEST_F(MultifileBlobCacheTest,CacheMaxValueSizeSucceeds)199 TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) {
200 char buf[kMaxValueSize];
201 for (int i = 0; i < kMaxValueSize; i++) {
202 buf[i] = 'b';
203 }
204 mMBC->set("abcd", 4, buf, kMaxValueSize);
205 for (int i = 0; i < kMaxValueSize; i++) {
206 buf[i] = 0xee;
207 }
208 mMBC->get("abcd", 4, buf, kMaxValueSize);
209 for (int i = 0; i < kMaxValueSize; i++) {
210 SCOPED_TRACE(i);
211 ASSERT_EQ('b', buf[i]);
212 }
213 }
214
TEST_F(MultifileBlobCacheTest,CacheMaxKeyAndValueSizeSucceeds)215 TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) {
216 char key[kMaxKeySize];
217 for (int i = 0; i < kMaxKeySize; i++) {
218 key[i] = 'a';
219 }
220 char buf[kMaxValueSize];
221 for (int i = 0; i < kMaxValueSize; i++) {
222 buf[i] = 'b';
223 }
224 mMBC->set(key, kMaxKeySize, buf, kMaxValueSize);
225 for (int i = 0; i < kMaxValueSize; i++) {
226 buf[i] = 0xee;
227 }
228 mMBC->get(key, kMaxKeySize, buf, kMaxValueSize);
229 for (int i = 0; i < kMaxValueSize; i++) {
230 SCOPED_TRACE(i);
231 ASSERT_EQ('b', buf[i]);
232 }
233 }
234
TEST_F(MultifileBlobCacheTest,CacheMaxEntrySucceeds)235 TEST_F(MultifileBlobCacheTest, CacheMaxEntrySucceeds) {
236 // Fill the cache with max entries
237 int i = 0;
238 for (i = 0; i < kMaxTotalEntries; i++) {
239 mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
240 }
241
242 // Ensure it is full
243 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
244
245 // Add another entry
246 mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
247
248 // Ensure total entries is cut in half + 1
249 ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries / 2 + 1);
250 }
251
TEST_F(MultifileBlobCacheTest,CacheMinKeyAndValueSizeSucceeds)252 TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
253 unsigned char buf[1] = {0xee};
254 mMBC->set("x", 1, "y", 1);
255 ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1));
256 ASSERT_EQ('y', buf[0]);
257 }
258
getFileDescriptorCount()259 int MultifileBlobCacheTest::getFileDescriptorCount() {
260 DIR* directory = opendir("/proc/self/fd");
261
262 int fileCount = 0;
263 struct dirent* entry;
264 while ((entry = readdir(directory)) != NULL) {
265 fileCount++;
266 // printf("File: %s\n", entry->d_name);
267 }
268
269 closedir(directory);
270 return fileCount;
271 }
272
TEST_F(MultifileBlobCacheTest,EnsureFileDescriptorsClosed)273 TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
274 // Populate the cache with a bunch of entries
275 for (int i = 0; i < kMaxTotalEntries; i++) {
276 // printf("Caching: %i", i);
277
278 // Use the index as the key and value
279 mMBC->set(&i, sizeof(i), &i, sizeof(i));
280
281 int result = 0;
282 ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
283 ASSERT_EQ(i, result);
284 }
285
286 // Ensure we don't have a bunch of open fds
287 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
288
289 // Close the cache so everything writes out
290 mMBC->finish();
291 mMBC.reset();
292
293 // Now open it again and ensure we still don't have a bunch of open fds
294 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
295 &mTempFile->path[0]));
296
297 // Check after initialization
298 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
299
300 for (int i = 0; i < kMaxTotalEntries; i++) {
301 int result = 0;
302 ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
303 ASSERT_EQ(i, result);
304 }
305
306 // And again after we've actually used it
307 ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
308 }
309
getCacheEntries()310 std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() {
311 std::string cachePath = &mTempFile->path[0];
312 std::string multifileDirName = cachePath + ".multifile";
313 std::vector<std::string> cacheEntries;
314
315 struct stat info;
316 if (stat(multifileDirName.c_str(), &info) == 0) {
317 // We have a multifile dir. Skip the status file and return the only entry.
318 DIR* dir;
319 struct dirent* entry;
320 if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
321 while ((entry = readdir(dir)) != nullptr) {
322 if (entry->d_name == "."s || entry->d_name == ".."s) {
323 continue;
324 }
325 if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
326 continue;
327 }
328 cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
329 }
330 } else {
331 printf("Unable to open %s, error: %s\n", multifileDirName.c_str(),
332 std::strerror(errno));
333 }
334 } else {
335 printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno));
336 }
337
338 return cacheEntries;
339 }
340
TEST_F(MultifileBlobCacheTest,CacheContainsStatus)341 TEST_F(MultifileBlobCacheTest, CacheContainsStatus) {
342 struct stat info;
343 std::stringstream statusFile;
344 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
345
346 // After INIT, cache should have a status
347 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
348
349 // Set one entry
350 mMBC->set("abcd", 4, "efgh", 4);
351
352 // Close the cache so everything writes out
353 mMBC->finish();
354 mMBC.reset();
355
356 // Ensure status lives after closing the cache
357 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
358
359 // Open the cache again
360 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
361 &mTempFile->path[0]));
362
363 // Ensure we still have a status
364 ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
365 }
366
367 // Verify missing cache status file causes cache the be cleared
TEST_F(MultifileBlobCacheTest,MissingCacheStatusClears)368 TEST_F(MultifileBlobCacheTest, MissingCacheStatusClears) {
369 // Set one entry
370 mMBC->set("abcd", 4, "efgh", 4);
371
372 // Close the cache so everything writes out
373 mMBC->finish();
374 mMBC.reset();
375
376 // Ensure there is one cache entry
377 ASSERT_EQ(getCacheEntries().size(), 1);
378
379 // Delete the status file
380 std::stringstream statusFile;
381 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
382 remove(statusFile.str().c_str());
383
384 // Open the cache again and ensure no cache hits
385 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
386 &mTempFile->path[0]));
387
388 // Ensure we have no entries
389 ASSERT_EQ(getCacheEntries().size(), 0);
390 }
391
392 // Verify modified cache status file BEGIN causes cache to be cleared
TEST_F(MultifileBlobCacheTest,ModifiedCacheStatusBeginClears)393 TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusBeginClears) {
394 // Set one entry
395 mMBC->set("abcd", 4, "efgh", 4);
396
397 // Close the cache so everything writes out
398 mMBC->finish();
399 mMBC.reset();
400
401 // Ensure there is one cache entry
402 ASSERT_EQ(getCacheEntries().size(), 1);
403
404 // Modify the status file
405 std::stringstream statusFile;
406 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
407
408 // Stomp on the beginning of the cache file
409 const char* stomp = "BADF00D";
410 std::fstream fs(statusFile.str());
411 fs.seekp(0, std::ios_base::beg);
412 fs.write(stomp, strlen(stomp));
413 fs.flush();
414 fs.close();
415
416 // Open the cache again and ensure no cache hits
417 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
418 &mTempFile->path[0]));
419
420 // Ensure we have no entries
421 ASSERT_EQ(getCacheEntries().size(), 0);
422 }
423
424 // Verify modified cache status file END causes cache to be cleared
TEST_F(MultifileBlobCacheTest,ModifiedCacheStatusEndClears)425 TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusEndClears) {
426 // Set one entry
427 mMBC->set("abcd", 4, "efgh", 4);
428
429 // Close the cache so everything writes out
430 mMBC->finish();
431 mMBC.reset();
432
433 // Ensure there is one cache entry
434 ASSERT_EQ(getCacheEntries().size(), 1);
435
436 // Modify the status file
437 std::stringstream statusFile;
438 statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
439
440 // Stomp on the END of the cache status file, modifying its contents
441 const char* stomp = "BADF00D";
442 std::fstream fs(statusFile.str());
443 fs.seekp(-strlen(stomp), std::ios_base::end);
444 fs.write(stomp, strlen(stomp));
445 fs.flush();
446 fs.close();
447
448 // Open the cache again and ensure no cache hits
449 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
450 &mTempFile->path[0]));
451
452 // Ensure we have no entries
453 ASSERT_EQ(getCacheEntries().size(), 0);
454 }
455
456 // Verify mismatched cacheVersion causes cache to be cleared
TEST_F(MultifileBlobCacheTest,MismatchedCacheVersionClears)457 TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) {
458 // Set one entry
459 mMBC->set("abcd", 4, "efgh", 4);
460
461 // Close the cache so everything writes out
462 mMBC->finish();
463 mMBC.reset();
464
465 // Ensure there is one cache entry
466 ASSERT_EQ(getCacheEntries().size(), 1);
467
468 // Set a debug cacheVersion
469 std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1);
470 ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
471 ASSERT_TRUE(
472 base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
473
474 // Open the cache again and ensure no cache hits
475 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
476 &mTempFile->path[0]));
477
478 // Ensure we have no entries
479 ASSERT_EQ(getCacheEntries().size(), 0);
480 }
481
482 // Verify mismatched buildId causes cache to be cleared
TEST_F(MultifileBlobCacheTest,MismatchedBuildIdClears)483 TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) {
484 // Set one entry
485 mMBC->set("abcd", 4, "efgh", 4);
486
487 // Close the cache so everything writes out
488 mMBC->finish();
489 mMBC.reset();
490
491 // Ensure there is one cache entry
492 ASSERT_EQ(getCacheEntries().size(), 1);
493
494 // Set a debug buildId
495 base::SetProperty("debug.egl.blobcache.build_id", "foo");
496 base::WaitForProperty("debug.egl.blobcache.build_id", "foo");
497
498 // Open the cache again and ensure no cache hits
499 mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
500 &mTempFile->path[0]));
501
502 // Ensure we have no entries
503 ASSERT_EQ(getCacheEntries().size(), 0);
504 }
505
506 } // namespace android
507