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