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