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 "nnCache.h"
21
22 #include <android-base/file.h>
23 #include <gtest/gtest.h>
24 #include <log/log.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include <algorithm>
29 #include <memory>
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_SUITE_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