1 /*
2  ** Copyright 2011, 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 <fcntl.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 
21 #include <algorithm>
22 #include <memory>
23 #include <numeric>
24 #include <random>
25 
26 #include <gtest/gtest.h>
27 
28 #include "BlobCache.h"
29 
30 namespace android {
31 
32 template<typename T> using sp = std::shared_ptr<T>;
33 
34 class BlobCacheTest : public ::testing::TestWithParam<BlobCache::Policy> {
35 protected:
36 
37     enum {
38         OK = 0,
39         BAD_VALUE = -EINVAL
40     };
41 
42     enum {
43         MAX_KEY_SIZE = 6,
44         MAX_VALUE_SIZE = 8,
45         MAX_TOTAL_SIZE = 13,
46     };
47 
SetUp()48     virtual void SetUp() {
49         mBC.reset(new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE, GetParam()));
50     }
51 
TearDown()52     virtual void TearDown() {
53         mBC.reset();
54     }
55 
56     std::unique_ptr<BlobCache> mBC;
57 };
58 
59 INSTANTIATE_TEST_CASE_P(Policy, BlobCacheTest,
60     ::testing::Values(BlobCache::Policy(BlobCache::Select::RANDOM, BlobCache::Capacity::HALVE),
61                       BlobCache::Policy(BlobCache::Select::LRU, BlobCache::Capacity::HALVE),
62 
63                       BlobCache::Policy(BlobCache::Select::RANDOM, BlobCache::Capacity::FIT),
64                       BlobCache::Policy(BlobCache::Select::LRU, BlobCache::Capacity::FIT),
65 
66                       BlobCache::Policy(BlobCache::Select::RANDOM, BlobCache::Capacity::FIT_HALVE),
67                       BlobCache::Policy(BlobCache::Select::LRU, BlobCache::Capacity::FIT_HALVE)));
68 
TEST_P(BlobCacheTest,CacheSingleValueSucceeds)69 TEST_P(BlobCacheTest, CacheSingleValueSucceeds) {
70     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
71     mBC->set("abcd", 4, "efgh", 4);
72     ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4));
73     ASSERT_EQ('e', buf[0]);
74     ASSERT_EQ('f', buf[1]);
75     ASSERT_EQ('g', buf[2]);
76     ASSERT_EQ('h', buf[3]);
77 }
78 
TEST_P(BlobCacheTest,CacheTwoValuesSucceeds)79 TEST_P(BlobCacheTest, CacheTwoValuesSucceeds) {
80     unsigned char buf[2] = { 0xee, 0xee };
81     mBC->set("ab", 2, "cd", 2);
82     mBC->set("ef", 2, "gh", 2);
83     ASSERT_EQ(size_t(2), mBC->get("ab", 2, buf, 2));
84     ASSERT_EQ('c', buf[0]);
85     ASSERT_EQ('d', buf[1]);
86     ASSERT_EQ(size_t(2), mBC->get("ef", 2, buf, 2));
87     ASSERT_EQ('g', buf[0]);
88     ASSERT_EQ('h', buf[1]);
89 }
90 
TEST_P(BlobCacheTest,CacheTwoValuesMallocSucceeds)91 TEST_P(BlobCacheTest, CacheTwoValuesMallocSucceeds) {
92     unsigned char *bufPtr;
93     mBC->set("ab", 2, "cd", 2);
94     mBC->set("ef", 2, "gh", 2);
95 
96     bufPtr = nullptr;
97     ASSERT_EQ(size_t(2), mBC->get("ab", 2, &bufPtr, malloc));
98     ASSERT_NE(nullptr, bufPtr);
99     ASSERT_EQ('c', bufPtr[0]);
100     ASSERT_EQ('d', bufPtr[1]);
101     free(bufPtr);
102 
103     bufPtr = nullptr;
104     ASSERT_EQ(size_t(2), mBC->get("ef", 2, &bufPtr, malloc));
105     ASSERT_NE(nullptr, bufPtr);
106     ASSERT_EQ('g', bufPtr[0]);
107     ASSERT_EQ('h', bufPtr[1]);
108     free(bufPtr);
109 }
110 
TEST_P(BlobCacheTest,GetOnlyWritesInsideBounds)111 TEST_P(BlobCacheTest, GetOnlyWritesInsideBounds) {
112     unsigned char buf[6] = { 0xee, 0xee, 0xee, 0xee, 0xee, 0xee };
113     mBC->set("abcd", 4, "efgh", 4);
114     ASSERT_EQ(size_t(4), mBC->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 
TEST_P(BlobCacheTest,GetOnlyWritesIfBufferIsLargeEnough)123 TEST_P(BlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
124     unsigned char buf[3] = { 0xee, 0xee, 0xee };
125     mBC->set("abcd", 4, "efgh", 4);
126     ASSERT_EQ(size_t(4), mBC->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 
TEST_P(BlobCacheTest,GetWithFailedAllocator)132 TEST_P(BlobCacheTest, GetWithFailedAllocator) {
133     unsigned char buf[3] = { 0xee, 0xee, 0xee };
134     mBC->set("abcd", 4, "efgh", 4);
135 
136     // If allocator fails, verify that we set the value pointer to
137     // nullptr, and that we do not modify the buffer that the value
138     // pointer originally pointed to.
139     unsigned char *bufPtr = &buf[0];
140     ASSERT_EQ(size_t(4), mBC->get("abcd", 4, &bufPtr, [](size_t) -> void* { return nullptr; }));
141     ASSERT_EQ(nullptr, bufPtr);
142     ASSERT_EQ(0xee, buf[0]);
143     ASSERT_EQ(0xee, buf[1]);
144     ASSERT_EQ(0xee, buf[2]);
145 }
146 
TEST_P(BlobCacheTest,GetDoesntAccessNullBuffer)147 TEST_P(BlobCacheTest, GetDoesntAccessNullBuffer) {
148     mBC->set("abcd", 4, "efgh", 4);
149     ASSERT_EQ(size_t(4), mBC->get("abcd", 4, NULL, 0));
150 }
151 
TEST_P(BlobCacheTest,MultipleSetsCacheLatestValue)152 TEST_P(BlobCacheTest, MultipleSetsCacheLatestValue) {
153     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
154     mBC->set("abcd", 4, "efgh", 4);
155     mBC->set("abcd", 4, "ijkl", 4);
156     ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4));
157     ASSERT_EQ('i', buf[0]);
158     ASSERT_EQ('j', buf[1]);
159     ASSERT_EQ('k', buf[2]);
160     ASSERT_EQ('l', buf[3]);
161 }
162 
TEST_P(BlobCacheTest,SecondSetKeepsFirstValueIfTooLarge)163 TEST_P(BlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
164     unsigned char buf[MAX_VALUE_SIZE+1] = { 0xee, 0xee, 0xee, 0xee };
165     mBC->set("abcd", 4, "efgh", 4);
166     mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1);
167     ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4));
168     ASSERT_EQ('e', buf[0]);
169     ASSERT_EQ('f', buf[1]);
170     ASSERT_EQ('g', buf[2]);
171     ASSERT_EQ('h', buf[3]);
172 }
173 
TEST_P(BlobCacheTest,DoesntCacheIfKeyIsTooBig)174 TEST_P(BlobCacheTest, DoesntCacheIfKeyIsTooBig) {
175     char key[MAX_KEY_SIZE+1];
176     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
177     for (int i = 0; i < MAX_KEY_SIZE+1; i++) {
178         key[i] = 'a';
179     }
180     mBC->set(key, MAX_KEY_SIZE+1, "bbbb", 4);
181 
182     ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE+1, buf, 4));
183     ASSERT_EQ(0xee, buf[0]);
184     ASSERT_EQ(0xee, buf[1]);
185     ASSERT_EQ(0xee, buf[2]);
186     ASSERT_EQ(0xee, buf[3]);
187 
188     // If key is too large, verify that we do not call the allocator,
189     // that we set the value pointer to nullptr, and that we do not
190     // modify the buffer that the value pointer originally pointed to.
191     unsigned char *bufPtr = &buf[0];
192     bool calledAlloc = false;
193     ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE+1, &bufPtr,
194                                   [&calledAlloc](size_t) -> void* {
195                                       calledAlloc = true;
196                                       return nullptr; }));
197     ASSERT_EQ(false, calledAlloc);
198     ASSERT_EQ(nullptr, bufPtr);
199     ASSERT_EQ(0xee, buf[0]);
200     ASSERT_EQ(0xee, buf[1]);
201     ASSERT_EQ(0xee, buf[2]);
202     ASSERT_EQ(0xee, buf[3]);
203 }
204 
TEST_P(BlobCacheTest,DoesntCacheIfValueIsTooBig)205 TEST_P(BlobCacheTest, DoesntCacheIfValueIsTooBig) {
206     unsigned char buf[MAX_VALUE_SIZE+1];
207     for (int i = 0; i < MAX_VALUE_SIZE+1; i++) {
208         buf[i] = 'b';
209     }
210     mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1);
211     for (int i = 0; i < MAX_VALUE_SIZE+1; i++) {
212         buf[i] = 0xee;
213     }
214     ASSERT_EQ(size_t(0), mBC->get("abcd", 4, buf, MAX_VALUE_SIZE+1));
215     for (int i = 0; i < MAX_VALUE_SIZE+1; i++) {
216         SCOPED_TRACE(i);
217         ASSERT_EQ(0xee, buf[i]);
218     }
219 }
220 
TEST_P(BlobCacheTest,DoesntCacheIfKeyValuePairIsTooBig)221 TEST_P(BlobCacheTest, DoesntCacheIfKeyValuePairIsTooBig) {
222     // Check a testing assumptions
223     ASSERT_TRUE(MAX_TOTAL_SIZE < MAX_KEY_SIZE + MAX_VALUE_SIZE);
224     ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE);
225 
226     enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE + 1 };
227 
228     char key[MAX_KEY_SIZE];
229     char buf[bufSize];
230     for (int i = 0; i < MAX_KEY_SIZE; i++) {
231         key[i] = 'a';
232     }
233     for (int i = 0; i < bufSize; i++) {
234         buf[i] = 'b';
235     }
236 
237     mBC->set(key, MAX_KEY_SIZE, buf, MAX_VALUE_SIZE);
238     ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE, NULL, 0));
239 }
240 
TEST_P(BlobCacheTest,CacheMaxKeySizeSucceeds)241 TEST_P(BlobCacheTest, CacheMaxKeySizeSucceeds) {
242     char key[MAX_KEY_SIZE];
243     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
244     for (int i = 0; i < MAX_KEY_SIZE; i++) {
245         key[i] = 'a';
246     }
247     mBC->set(key, MAX_KEY_SIZE, "wxyz", 4);
248     ASSERT_EQ(size_t(4), mBC->get(key, MAX_KEY_SIZE, buf, 4));
249     ASSERT_EQ('w', buf[0]);
250     ASSERT_EQ('x', buf[1]);
251     ASSERT_EQ('y', buf[2]);
252     ASSERT_EQ('z', buf[3]);
253 }
254 
TEST_P(BlobCacheTest,CacheMaxValueSizeSucceeds)255 TEST_P(BlobCacheTest, CacheMaxValueSizeSucceeds) {
256     char buf[MAX_VALUE_SIZE];
257     for (int i = 0; i < MAX_VALUE_SIZE; i++) {
258         buf[i] = 'b';
259     }
260     mBC->set("abcd", 4, buf, MAX_VALUE_SIZE);
261     for (int i = 0; i < MAX_VALUE_SIZE; i++) {
262         buf[i] = 0xee;
263     }
264     ASSERT_EQ(size_t(MAX_VALUE_SIZE), mBC->get("abcd", 4, buf,
265             MAX_VALUE_SIZE));
266     for (int i = 0; i < MAX_VALUE_SIZE; i++) {
267         SCOPED_TRACE(i);
268         ASSERT_EQ('b', buf[i]);
269     }
270 }
271 
TEST_P(BlobCacheTest,CacheMaxKeyValuePairSizeSucceeds)272 TEST_P(BlobCacheTest, CacheMaxKeyValuePairSizeSucceeds) {
273     // Check a testing assumption
274     ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE);
275 
276     enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE };
277 
278     char key[MAX_KEY_SIZE];
279     char buf[bufSize];
280     for (int i = 0; i < MAX_KEY_SIZE; i++) {
281         key[i] = 'a';
282     }
283     for (int i = 0; i < bufSize; i++) {
284         buf[i] = 'b';
285     }
286 
287     mBC->set(key, MAX_KEY_SIZE, buf, bufSize);
288     ASSERT_EQ(size_t(bufSize), mBC->get(key, MAX_KEY_SIZE, NULL, 0));
289 }
290 
TEST_P(BlobCacheTest,CacheMinKeyAndValueSizeSucceeds)291 TEST_P(BlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
292     unsigned char buf[1] = { 0xee };
293     mBC->set("x", 1, "y", 1);
294     ASSERT_EQ(size_t(1), mBC->get("x", 1, buf, 1));
295     ASSERT_EQ('y', buf[0]);
296 }
297 
TEST_P(BlobCacheTest,CacheSizeDoesntExceedTotalLimit)298 TEST_P(BlobCacheTest, CacheSizeDoesntExceedTotalLimit) {
299     for (int i = 0; i < 256; i++) {
300         uint8_t k = i;
301         mBC->set(&k, 1, "x", 1);
302     }
303     int numCached = 0;
304     for (int i = 0; i < 256; i++) {
305         uint8_t k = i;
306         if (mBC->get(&k, 1, NULL, 0) == 1) {
307             numCached++;
308         }
309     }
310     ASSERT_GE(MAX_TOTAL_SIZE / 2, numCached);
311 }
312 
TEST_P(BlobCacheTest,ExceedingTotalLimitHalvesCacheSize)313 TEST_P(BlobCacheTest, ExceedingTotalLimitHalvesCacheSize) {
314     if (GetParam().second == BlobCache::Capacity::FIT)
315         return;  // test doesn't apply for this policy
316 
317     // Fill up the entire cache with 1 char key/value pairs.
318     const int maxEntries = MAX_TOTAL_SIZE / 2;
319     for (int i = 0; i < maxEntries; i++) {
320         uint8_t k = i;
321         mBC->set(&k, 1, "x", 1);
322     }
323     // Insert one more entry, causing a cache overflow.
324     {
325         uint8_t k = maxEntries;
326         mBC->set(&k, 1, "x", 1);
327     }
328     // Count the number of entries in the cache; and check which
329     // entries they are.
330     int numCached = 0;
331     for (int i = 0; i < maxEntries+1; i++) {
332         uint8_t k = i;
333         bool found = (mBC->get(&k, 1, NULL, 0) == 1);
334         if (found)
335             numCached++;
336         if (GetParam().first == BlobCache::Select::LRU) {
337             SCOPED_TRACE(i);
338             ASSERT_EQ(found, i >= maxEntries/2);
339         }
340     }
341     ASSERT_EQ(maxEntries/2 + 1, numCached);
342 }
343 
TEST_P(BlobCacheTest,ExceedingTotalLimitJustFitsSmallEntry)344 TEST_P(BlobCacheTest, ExceedingTotalLimitJustFitsSmallEntry) {
345     if (GetParam().second != BlobCache::Capacity::FIT)
346         return;  // test doesn't apply for this policy
347 
348     // Fill up the entire cache with 1 char key/value pairs.
349     const int maxEntries = MAX_TOTAL_SIZE / 2;
350     for (int i = 0; i < maxEntries; i++) {
351         uint8_t k = i;
352         mBC->set(&k, 1, "x", 1);
353     }
354     // Insert one more entry, causing a cache overflow.
355     {
356         uint8_t k = maxEntries;
357         mBC->set(&k, 1, "x", 1);
358     }
359     // Count the number of entries in the cache.
360     int numCached = 0;
361     for (int i = 0; i < maxEntries+1; i++) {
362         uint8_t k = i;
363         if (mBC->get(&k, 1, NULL, 0) == 1)
364             numCached++;
365     }
366     ASSERT_EQ(maxEntries, numCached);
367 }
368 
369 // Also see corresponding test in nnCache_test.cpp
TEST_P(BlobCacheTest,ExceedingTotalLimitFitsBigEntry)370 TEST_P(BlobCacheTest, ExceedingTotalLimitFitsBigEntry) {
371     // Fill up the entire cache with 1 char key/value pairs.
372     const int maxEntries = MAX_TOTAL_SIZE / 2;
373     for (int i = 0; i < maxEntries; i++) {
374         uint8_t k = i;
375         mBC->set(&k, 1, "x", 1);
376     }
377     // Insert one more entry, causing a cache overflow.
378     const int bigValueSize = std::min((MAX_TOTAL_SIZE * 3) / 4 - 1, int(MAX_VALUE_SIZE));
379     ASSERT_GT(bigValueSize+1, MAX_TOTAL_SIZE / 2);  // Check testing assumption
380     {
381         unsigned char buf[MAX_VALUE_SIZE];
382         for (int i = 0; i < bigValueSize; i++)
383             buf[i] = 0xee;
384         uint8_t k = maxEntries;
385         mBC->set(&k, 1, buf, bigValueSize);
386     }
387     // Count the number and size of entries in the cache.
388     int numCached = 0;
389     size_t sizeCached = 0;
390     for (int i = 0; i < maxEntries+1; i++) {
391         uint8_t k = i;
392         size_t size = mBC->get(&k, 1, NULL, 0);
393         if (size) {
394             numCached++;
395             sizeCached += (size + 1);
396         }
397     }
398     switch (GetParam().second) {
399         case BlobCache::Capacity::HALVE:
400             // New value is too big for this cleaning algorithm.  So
401             // we cleaned the cache, but did not insert the new value.
402             ASSERT_EQ(maxEntries/2, numCached);
403             ASSERT_EQ(size_t((maxEntries/2)*2), sizeCached);
404             break;
405         case BlobCache::Capacity::FIT:
406         case BlobCache::Capacity::FIT_HALVE: {
407             // We had to clean more than half the cache to fit the new
408             // value.
409             const int initialNumEntries = maxEntries;
410             const int initialSizeCached = initialNumEntries * 2;
411             const int initialFreeSpace = MAX_TOTAL_SIZE - initialSizeCached;
412 
413             // (bigValueSize + 1) = value size + key size
414             // trailing "+ 1" is in order to round up
415             // "/ 2" is because initial entries are size 2 (1 byte key, 1 byte value)
416             const int cleanNumEntries = ((bigValueSize + 1) - initialFreeSpace + 1) / 2;
417 
418             const int cleanSpace = cleanNumEntries * 2;
419             const int postCleanNumEntries = initialNumEntries - cleanNumEntries;
420             const int postCleanSizeCached = initialSizeCached - cleanSpace;
421             ASSERT_EQ(postCleanNumEntries + 1, numCached);
422             ASSERT_EQ(size_t(postCleanSizeCached + bigValueSize + 1), sizeCached);
423 
424             break;
425         }
426         default:
427             FAIL() << "Unknown Capacity value";
428     }
429 }
430 
TEST_P(BlobCacheTest,FailedGetWithAllocator)431 TEST_P(BlobCacheTest, FailedGetWithAllocator) {
432     // If get doesn't find anything, verify that we do not call the
433     // allocator, that we set the value pointer to nullptr, and that
434     // we do not modify the buffer that the value pointer originally
435     // pointed to.
436     unsigned char buf[1] = { 0xee };
437     unsigned char *bufPtr = &buf[0];
438     bool calledAlloc = false;
439     ASSERT_EQ(size_t(0), mBC->get("a", 1, &bufPtr,
440                                   [&calledAlloc](size_t) -> void* {
441                                       calledAlloc = true;
442                                       return nullptr; }));
443     ASSERT_EQ(false, calledAlloc);
444     ASSERT_EQ(nullptr, bufPtr);
445     ASSERT_EQ(0xee, buf[0]);
446 }
447 
TEST_P(BlobCacheTest,ExceedingTotalLimitRemovesLRUEntries)448 TEST_P(BlobCacheTest, ExceedingTotalLimitRemovesLRUEntries) {
449     if (GetParam().first != BlobCache::Select::LRU)
450         return;  // test doesn't apply for this policy
451 
452     // Fill up the entire cache with 1 char key/value pairs.
453     static const int maxEntries = MAX_TOTAL_SIZE / 2;
454     for (int i = 0; i < maxEntries; i++) {
455         uint8_t k = i;
456         mBC->set(&k, 1, "x", 1);
457     }
458 
459     // Access entries in some known pseudorandom order.
460     int accessSequence[maxEntries];
461     std::iota(&accessSequence[0], &accessSequence[maxEntries], 0);
462     std::mt19937 randomEngine(MAX_TOTAL_SIZE /* seed */);
463     std::shuffle(&accessSequence[0], &accessSequence[maxEntries], randomEngine);
464     for (int i = 0; i < maxEntries; i++) {
465         uint8_t k = accessSequence[i];
466         uint8_t buf[1];
467         // If we were to pass NULL to get() as the value pointer, this
468         // won't count as an access for LRU purposes.
469         mBC->get(&k, 1, buf, 1);
470     }
471 
472     // Insert one more entry, causing a cache overflow.
473     {
474         uint8_t k = maxEntries;
475         mBC->set(&k, 1, "x", 1);
476     }
477 
478     // Check which entries are in the cache.  We expect to see the
479     // "one more entry" we just added, and also the most-recently
480     // accessed (according to accessSequence).  That is, we should
481     // find exactly the entries with the following keys:
482     // . maxEntries
483     // . accessSequence[j..maxEntries-1] for some 0 <= j < maxEntries
484     uint8_t k = maxEntries;
485     ASSERT_EQ(size_t(1), mBC->get(&k, 1, NULL, 0));
486     bool foundAny = false;
487     for (int i = 0; i < maxEntries; i++) {
488         uint8_t k = accessSequence[i];
489         bool found = (mBC->get(&k, 1, NULL, 0) == 1);
490         if (foundAny == found)
491             continue;
492         if (!foundAny) {
493             // found == true, so we just discovered j == i
494             foundAny = true;
495         } else {
496             // foundAny == true, found == false -- oops
497             FAIL() << "found [" << i-1 << "]th entry but not [" << i << "]th entry";
498         }
499     }
500 }
501 
502 class BlobCacheFlattenTest : public BlobCacheTest {
503 protected:
SetUp()504     virtual void SetUp() {
505         BlobCacheTest::SetUp();
506         mBC2.reset(new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE, GetParam()));
507     }
508 
TearDown()509     virtual void TearDown() {
510         mBC2.reset();
511         BlobCacheTest::TearDown();
512     }
513 
roundTrip()514     void roundTrip() {
515         size_t size = mBC->getFlattenedSize();
516         uint8_t* flat = new uint8_t[size];
517         ASSERT_EQ(OK, mBC->flatten(flat, size));
518         ASSERT_EQ(OK, mBC2->unflatten(flat, size));
519         delete[] flat;
520     }
521 
522     sp<BlobCache> mBC2;
523 };
524 
525 INSTANTIATE_TEST_CASE_P(Policy, BlobCacheFlattenTest,
526     ::testing::Values(BlobCache::Policy(BlobCache::Select::RANDOM, BlobCache::Capacity::HALVE),
527                       BlobCache::Policy(BlobCache::Select::LRU, BlobCache::Capacity::HALVE),
528 
529                       BlobCache::Policy(BlobCache::Select::RANDOM, BlobCache::Capacity::FIT),
530                       BlobCache::Policy(BlobCache::Select::LRU, BlobCache::Capacity::FIT),
531 
532                       BlobCache::Policy(BlobCache::Select::RANDOM, BlobCache::Capacity::FIT_HALVE),
533                       BlobCache::Policy(BlobCache::Select::LRU, BlobCache::Capacity::FIT_HALVE)));
534 
TEST_P(BlobCacheFlattenTest,FlattenOneValue)535 TEST_P(BlobCacheFlattenTest, FlattenOneValue) {
536     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
537     mBC->set("abcd", 4, "efgh", 4);
538     roundTrip();
539     ASSERT_EQ(size_t(4), mBC2->get("abcd", 4, buf, 4));
540     ASSERT_EQ('e', buf[0]);
541     ASSERT_EQ('f', buf[1]);
542     ASSERT_EQ('g', buf[2]);
543     ASSERT_EQ('h', buf[3]);
544 }
545 
TEST_P(BlobCacheFlattenTest,FlattenFullCache)546 TEST_P(BlobCacheFlattenTest, FlattenFullCache) {
547     // Fill up the entire cache with 1 char key/value pairs.
548     const int maxEntries = MAX_TOTAL_SIZE / 2;
549     for (int i = 0; i < maxEntries; i++) {
550         uint8_t k = i;
551         mBC->set(&k, 1, &k, 1);
552     }
553 
554     roundTrip();
555 
556     // Verify the deserialized cache
557     for (int i = 0; i < maxEntries; i++) {
558         uint8_t k = i;
559         uint8_t v = 0xee;
560         ASSERT_EQ(size_t(1), mBC2->get(&k, 1, &v, 1));
561         ASSERT_EQ(k, v);
562     }
563 }
564 
TEST_P(BlobCacheFlattenTest,FlattenDoesntChangeCache)565 TEST_P(BlobCacheFlattenTest, FlattenDoesntChangeCache) {
566     // Fill up the entire cache with 1 char key/value pairs.
567     const int maxEntries = MAX_TOTAL_SIZE / 2;
568     for (int i = 0; i < maxEntries; i++) {
569         uint8_t k = i;
570         mBC->set(&k, 1, &k, 1);
571     }
572 
573     size_t size = mBC->getFlattenedSize();
574     uint8_t* flat = new uint8_t[size];
575     ASSERT_EQ(OK, mBC->flatten(flat, size));
576     delete[] flat;
577 
578     // Verify the cache that we just serialized
579     for (int i = 0; i < maxEntries; i++) {
580         uint8_t k = i;
581         uint8_t v = 0xee;
582         ASSERT_EQ(size_t(1), mBC->get(&k, 1, &v, 1));
583         ASSERT_EQ(k, v);
584     }
585 }
586 
TEST_P(BlobCacheFlattenTest,FlattenCatchesBufferTooSmall)587 TEST_P(BlobCacheFlattenTest, FlattenCatchesBufferTooSmall) {
588     // Fill up the entire cache with 1 char key/value pairs.
589     const int maxEntries = MAX_TOTAL_SIZE / 2;
590     for (int i = 0; i < maxEntries; i++) {
591         uint8_t k = i;
592         mBC->set(&k, 1, &k, 1);
593     }
594 
595     size_t size = mBC->getFlattenedSize() - 1;
596     uint8_t* flat = new uint8_t[size];
597     // ASSERT_EQ(BAD_VALUE, mBC->flatten(flat, size));
598     // TODO: The above fails. I expect this is so because getFlattenedSize()
599     // overstimates the size by using PROPERTY_VALUE_MAX.
600     delete[] flat;
601 }
602 
TEST_P(BlobCacheFlattenTest,UnflattenCatchesBadMagic)603 TEST_P(BlobCacheFlattenTest, UnflattenCatchesBadMagic) {
604     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
605     mBC->set("abcd", 4, "efgh", 4);
606 
607     size_t size = mBC->getFlattenedSize();
608     uint8_t* flat = new uint8_t[size];
609     ASSERT_EQ(OK, mBC->flatten(flat, size));
610     flat[1] = ~flat[1];
611 
612     // Bad magic should cause an error.
613     ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size));
614     delete[] flat;
615 
616     // The error should cause the unflatten to result in an empty cache
617     ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
618 }
619 
TEST_P(BlobCacheFlattenTest,UnflattenCatchesBadBlobCacheVersion)620 TEST_P(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheVersion) {
621     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
622     mBC->set("abcd", 4, "efgh", 4);
623 
624     size_t size = mBC->getFlattenedSize();
625     uint8_t* flat = new uint8_t[size];
626     ASSERT_EQ(OK, mBC->flatten(flat, size));
627     flat[5] = ~flat[5];
628 
629     // Version mismatches shouldn't cause errors, but should not use the
630     // serialized entries
631     ASSERT_EQ(OK, mBC2->unflatten(flat, size));
632     delete[] flat;
633 
634     // The version mismatch should cause the unflatten to result in an empty
635     // cache
636     ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
637 }
638 
TEST_P(BlobCacheFlattenTest,UnflattenCatchesBadBlobCacheDeviceVersion)639 TEST_P(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheDeviceVersion) {
640     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
641     mBC->set("abcd", 4, "efgh", 4);
642 
643     size_t size = mBC->getFlattenedSize();
644     uint8_t* flat = new uint8_t[size];
645     ASSERT_EQ(OK, mBC->flatten(flat, size));
646     flat[10] = ~flat[10];
647 
648     // Version mismatches shouldn't cause errors, but should not use the
649     // serialized entries
650     ASSERT_EQ(OK, mBC2->unflatten(flat, size));
651     delete[] flat;
652 
653     // The version mismatch should cause the unflatten to result in an empty
654     // cache
655     ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
656 }
657 
TEST_P(BlobCacheFlattenTest,UnflattenCatchesBufferTooSmall)658 TEST_P(BlobCacheFlattenTest, UnflattenCatchesBufferTooSmall) {
659     unsigned char buf[4] = { 0xee, 0xee, 0xee, 0xee };
660     mBC->set("abcd", 4, "efgh", 4);
661 
662     size_t size = mBC->getFlattenedSize();
663     uint8_t* flat = new uint8_t[size];
664     ASSERT_EQ(OK, mBC->flatten(flat, size));
665 
666     // A buffer truncation shouldt cause an error
667     // ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size-1));
668     // TODO: The above appears to fail because getFlattenedSize() is
669     // conservative.
670     delete[] flat;
671 
672     // The error should cause the unflatten to result in an empty cache
673     ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
674 }
675 
676 } // namespace android
677