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