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