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