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:
SetUp()42     virtual void SetUp() {
43         mCache = NNCache::get();
44     }
45 
TearDown()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 
TEST_P(NNCacheTest,UninitializedCacheAlwaysMisses)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 
TEST_P(NNCacheTest,InitializedCacheAlwaysHits)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 
TEST_P(NNCacheTest,TerminatedCacheAlwaysMisses)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.
TEST_P(NNCacheTest,ExceedingTotalLimitFitsBigEntry)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 
SetUp()183     virtual void SetUp() {
184         NNCacheTest::SetUp();
185         mTempFile.reset(new TemporaryFile());
186     }
187 
TearDown()188     virtual void TearDown() {
189         mTempFile.reset(nullptr);
190         NNCacheTest::TearDown();
191     }
192 
193     std::unique_ptr<TemporaryFile> mTempFile;
194 
yesStringBlob(const char * key,const char * value)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 
noStringBlob(const char * key)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 
TEST_P(NNCacheSerializationTest,ReinitializedCacheContainsValues)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 
TEST_P(NNCacheSerializationTest,ReinitializedCacheContainsValuesSizeConstrained)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