Home | History | Annotate | Download | only in nnCache
      1 /*
      2  * Copyright (C) 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 #define LOG_TAG "nnCache_test"
     18 //#define LOG_NDEBUG 0
     19 
     20 #include <stdlib.h>
     21 #include <string.h>
     22 
     23 #include <memory>
     24 
     25 #include <android-base/file.h>
     26 #include <gtest/gtest.h>
     27 #include <log/log.h>
     28 
     29 #include "nnCache.h"
     30 
     31 // Cache size limits.
     32 static const size_t maxKeySize = 12 * 1024;
     33 static const size_t maxValueSize = 64 * 1024;
     34 static const size_t maxTotalSize = 2 * 1024 * 1024;
     35 
     36 namespace android {
     37 
     38 class NNCacheTest : public ::testing::TestWithParam<NNCache::Policy> {
     39 protected:
     40     virtual void SetUp() {
     41         mCache = NNCache::get();
     42     }
     43 
     44     virtual void TearDown() {
     45         mCache->setCacheFilename("");
     46         mCache->terminate();
     47     }
     48 
     49     NNCache* mCache;
     50 };
     51 
     52 INSTANTIATE_TEST_CASE_P(Policy, NNCacheTest,
     53     ::testing::Values(NNCache::Policy(NNCache::Select::RANDOM, NNCache::Capacity::HALVE),
     54                       NNCache::Policy(NNCache::Select::LRU, NNCache::Capacity::HALVE),
     55 
     56                       NNCache::Policy(NNCache::Select::RANDOM, NNCache::Capacity::FIT),
     57                       NNCache::Policy(NNCache::Select::LRU, NNCache::Capacity::FIT),
     58 
     59                       NNCache::Policy(NNCache::Select::RANDOM, NNCache::Capacity::FIT_HALVE),
     60                       NNCache::Policy(NNCache::Select::LRU, NNCache::Capacity::FIT_HALVE)));
     61 
     62 TEST_P(NNCacheTest, UninitializedCacheAlwaysMisses) {
     63     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     64     mCache->setBlob("abcd", 4, "efgh", 4);
     65     ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
     66     ASSERT_EQ(0xee, buf[0]);
     67     ASSERT_EQ(0xee, buf[1]);
     68     ASSERT_EQ(0xee, buf[2]);
     69     ASSERT_EQ(0xee, buf[3]);
     70 }
     71 
     72 TEST_P(NNCacheTest, InitializedCacheAlwaysHits) {
     73     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     74     mCache->initialize(maxKeySize, maxValueSize, maxTotalSize, GetParam());
     75     mCache->setBlob("abcd", 4, "efgh", 4);
     76     ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
     77     ASSERT_EQ('e', buf[0]);
     78     ASSERT_EQ('f', buf[1]);
     79     ASSERT_EQ('g', buf[2]);
     80     ASSERT_EQ('h', buf[3]);
     81 }
     82 
     83 TEST_P(NNCacheTest, TerminatedCacheAlwaysMisses) {
     84     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     85     mCache->initialize(maxKeySize, maxValueSize, maxTotalSize, GetParam());
     86     mCache->setBlob("abcd", 4, "efgh", 4);
     87 
     88     // cache entry lost after terminate
     89     mCache->terminate();
     90     ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
     91     ASSERT_EQ(0xee, buf[0]);
     92     ASSERT_EQ(0xee, buf[1]);
     93     ASSERT_EQ(0xee, buf[2]);
     94     ASSERT_EQ(0xee, buf[3]);
     95 
     96     // cache insertion ignored after terminate
     97     mCache->setBlob("abcd", 4, "efgh", 4);
     98     ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
     99     ASSERT_EQ(0xee, buf[0]);
    100     ASSERT_EQ(0xee, buf[1]);
    101     ASSERT_EQ(0xee, buf[2]);
    102     ASSERT_EQ(0xee, buf[3]);
    103 }
    104 
    105 // Also see corresponding test in BlobCache_test.cpp.
    106 // The purpose of this test here is to ensure that Policy
    107 // setting makes it through from NNCache to BlobCache.
    108 TEST_P(NNCacheTest, ExceedingTotalLimitFitsBigEntry) {
    109     enum {
    110         MAX_KEY_SIZE = 6,
    111         MAX_VALUE_SIZE = 8,
    112         MAX_TOTAL_SIZE = 13,
    113     };
    114 
    115     mCache->initialize(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE, GetParam());
    116 
    117     // Fill up the entire cache with 1 char key/value pairs.
    118     const int maxEntries = MAX_TOTAL_SIZE / 2;
    119     for (int i = 0; i < maxEntries; i++) {
    120         uint8_t k = i;
    121         mCache->setBlob(&k, 1, "x", 1);
    122     }
    123     // Insert one more entry, causing a cache overflow.
    124     const int bigValueSize = std::min((MAX_TOTAL_SIZE * 3) / 4 - 1, int(MAX_VALUE_SIZE));
    125     ASSERT_GT(bigValueSize+1, MAX_TOTAL_SIZE / 2);  // Check testing assumption
    126     {
    127         unsigned char buf[MAX_VALUE_SIZE];
    128         for (int i = 0; i < bigValueSize; i++)
    129             buf[i] = 0xee;
    130         uint8_t k = maxEntries;
    131         mCache->setBlob(&k, 1, buf, bigValueSize);
    132     }
    133     // Count the number and size of entries in the cache.
    134     int numCached = 0;
    135     size_t sizeCached = 0;
    136     for (int i = 0; i < maxEntries+1; i++) {
    137         uint8_t k = i;
    138         size_t size = mCache->getBlob(&k, 1, NULL, 0);
    139         if (size) {
    140             numCached++;
    141             sizeCached += (size + 1);
    142         }
    143     }
    144     switch (GetParam().second) {
    145         case NNCache::Capacity::HALVE:
    146             // New value is too big for this cleaning algorithm.  So
    147             // we cleaned the cache, but did not insert the new value.
    148             ASSERT_EQ(maxEntries/2, numCached);
    149             ASSERT_EQ(size_t((maxEntries/2)*2), sizeCached);
    150             break;
    151         case NNCache::Capacity::FIT:
    152         case NNCache::Capacity::FIT_HALVE: {
    153             // We had to clean more than half the cache to fit the new
    154             // value.
    155             const int initialNumEntries = maxEntries;
    156             const int initialSizeCached = initialNumEntries * 2;
    157             const int initialFreeSpace = MAX_TOTAL_SIZE - initialSizeCached;
    158 
    159             // (bigValueSize + 1) = value size + key size
    160             // trailing "+ 1" is in order to round up
    161             // "/ 2" is because initial entries are size 2 (1 byte key, 1 byte value)
    162             const int cleanNumEntries = ((bigValueSize + 1) - initialFreeSpace + 1) / 2;
    163 
    164             const int cleanSpace = cleanNumEntries * 2;
    165             const int postCleanNumEntries = initialNumEntries - cleanNumEntries;
    166             const int postCleanSizeCached = initialSizeCached - cleanSpace;
    167             ASSERT_EQ(postCleanNumEntries + 1, numCached);
    168             ASSERT_EQ(size_t(postCleanSizeCached + bigValueSize + 1), sizeCached);
    169 
    170             break;
    171         }
    172         default:
    173             FAIL() << "Unknown Capacity value";
    174     }
    175 }
    176 
    177 class NNCacheSerializationTest : public NNCacheTest {
    178 
    179 protected:
    180 
    181     virtual void SetUp() {
    182         NNCacheTest::SetUp();
    183         mTempFile.reset(new TemporaryFile());
    184     }
    185 
    186     virtual void TearDown() {
    187         mTempFile.reset(nullptr);
    188         NNCacheTest::TearDown();
    189     }
    190 
    191     std::unique_ptr<TemporaryFile> mTempFile;
    192 
    193     void yesStringBlob(const char *key, const char *value) {
    194         SCOPED_TRACE(key);
    195 
    196         uint8_t buf[10];
    197         memset(buf, 0xee, sizeof(buf));
    198         const size_t keySize = strlen(key);
    199         const size_t valueSize = strlen(value);
    200         ASSERT_LE(valueSize, sizeof(buf));  // Check testing assumption
    201 
    202         ASSERT_EQ(ssize_t(valueSize), mCache->getBlob(key, keySize, buf, sizeof(buf)));
    203         for (size_t i = 0; i < valueSize; i++) {
    204             SCOPED_TRACE(i);
    205             ASSERT_EQ(value[i], buf[i]);
    206         }
    207     }
    208 
    209     void noStringBlob(const char *key) {
    210         SCOPED_TRACE(key);
    211 
    212         uint8_t buf[10];
    213         memset(buf, 0xee, sizeof(buf));
    214         const size_t keySize = strlen(key);
    215 
    216         ASSERT_EQ(ssize_t(0), mCache->getBlob(key, keySize, buf, sizeof(buf)));
    217         for (size_t i = 0; i < sizeof(buf); i++) {
    218             SCOPED_TRACE(i);
    219             ASSERT_EQ(0xee, buf[i]);
    220         }
    221     }
    222 
    223 };
    224 
    225 TEST_P(NNCacheSerializationTest, ReinitializedCacheContainsValues) {
    226     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
    227     mCache->setCacheFilename(&mTempFile->path[0]);
    228     mCache->initialize(maxKeySize, maxValueSize, maxTotalSize, GetParam());
    229     mCache->setBlob("abcd", 4, "efgh", 4);
    230     mCache->terminate();
    231     mCache->initialize(maxKeySize, maxValueSize, maxTotalSize, GetParam());
    232 
    233     // For get-with-allocator, verify that:
    234     // - we get the expected value size
    235     // - we do not modify the buffer that value pointer originally points to
    236     // - the value pointer gets set to something other than nullptr
    237     // - the newly-allocated buffer is set properly
    238     uint8_t *bufPtr = &buf[0];
    239     ASSERT_EQ(4, mCache->getBlob("abcd", 4, &bufPtr, malloc));
    240     ASSERT_EQ(0xee, buf[0]);
    241     ASSERT_EQ(0xee, buf[1]);
    242     ASSERT_EQ(0xee, buf[2]);
    243     ASSERT_EQ(0xee, buf[3]);
    244     ASSERT_NE(nullptr, bufPtr);
    245     ASSERT_EQ('e', bufPtr[0]);
    246     ASSERT_EQ('f', bufPtr[1]);
    247     ASSERT_EQ('g', bufPtr[2]);
    248     ASSERT_EQ('h', bufPtr[3]);
    249 
    250     ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
    251     ASSERT_EQ('e', buf[0]);
    252     ASSERT_EQ('f', buf[1]);
    253     ASSERT_EQ('g', buf[2]);
    254     ASSERT_EQ('h', buf[3]);
    255 }
    256 
    257 TEST_P(NNCacheSerializationTest, ReinitializedCacheContainsValuesSizeConstrained) {
    258     mCache->setCacheFilename(&mTempFile->path[0]);
    259     mCache->initialize(6, 10, maxTotalSize, GetParam());
    260     mCache->setBlob("abcd", 4, "efgh", 4);
    261     mCache->setBlob("abcdef", 6, "ijkl", 4);
    262     mCache->setBlob("ab", 2, "abcdefghij", 10);
    263     {
    264         SCOPED_TRACE("before terminate()");
    265         yesStringBlob("abcd", "efgh");
    266         yesStringBlob("abcdef", "ijkl");
    267         yesStringBlob("ab", "abcdefghij");
    268     }
    269     mCache->terminate();
    270     // Re-initialize cache with lower key/value sizes.
    271     mCache->initialize(5, 7, maxTotalSize, GetParam());
    272     {
    273         SCOPED_TRACE("after second initialize()");
    274         yesStringBlob("abcd", "efgh");
    275         noStringBlob("abcdef");  // key too large
    276         noStringBlob("ab");  // value too large
    277     }
    278 }
    279 
    280 }
    281