Raph Levien | 8185e47 | 2012-10-25 23:11:13 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (C) 2012 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 | #include <stdlib.h> |
| 18 | #include <utils/JenkinsHash.h> |
| 19 | #include <utils/LruCache.h> |
| 20 | #include <cutils/log.h> |
| 21 | #include <gtest/gtest.h> |
| 22 | |
| 23 | namespace android { |
| 24 | |
| 25 | typedef int SimpleKey; |
| 26 | typedef const char* StringValue; |
| 27 | |
| 28 | struct ComplexKey { |
| 29 | int k; |
| 30 | |
| 31 | explicit ComplexKey(int k) : k(k) { |
| 32 | instanceCount += 1; |
| 33 | } |
| 34 | |
| 35 | ComplexKey(const ComplexKey& other) : k(other.k) { |
| 36 | instanceCount += 1; |
| 37 | } |
| 38 | |
| 39 | ~ComplexKey() { |
| 40 | instanceCount -= 1; |
| 41 | } |
| 42 | |
| 43 | bool operator ==(const ComplexKey& other) const { |
| 44 | return k == other.k; |
| 45 | } |
| 46 | |
| 47 | bool operator !=(const ComplexKey& other) const { |
| 48 | return k != other.k; |
| 49 | } |
| 50 | |
| 51 | static ssize_t instanceCount; |
| 52 | }; |
| 53 | |
| 54 | ssize_t ComplexKey::instanceCount = 0; |
| 55 | |
| 56 | template<> inline hash_t hash_type(const ComplexKey& value) { |
| 57 | return hash_type(value.k); |
| 58 | } |
| 59 | |
| 60 | struct ComplexValue { |
| 61 | int v; |
| 62 | |
| 63 | explicit ComplexValue(int v) : v(v) { |
| 64 | instanceCount += 1; |
| 65 | } |
| 66 | |
| 67 | ComplexValue(const ComplexValue& other) : v(other.v) { |
| 68 | instanceCount += 1; |
| 69 | } |
| 70 | |
| 71 | ~ComplexValue() { |
| 72 | instanceCount -= 1; |
| 73 | } |
| 74 | |
| 75 | static ssize_t instanceCount; |
| 76 | }; |
| 77 | |
| 78 | ssize_t ComplexValue::instanceCount = 0; |
| 79 | |
| 80 | typedef LruCache<ComplexKey, ComplexValue> ComplexCache; |
| 81 | |
| 82 | class EntryRemovedCallback : public OnEntryRemoved<SimpleKey, StringValue> { |
| 83 | public: |
| 84 | EntryRemovedCallback() : callbackCount(0), lastKey(-1), lastValue(NULL) { } |
| 85 | ~EntryRemovedCallback() {} |
| 86 | void operator()(SimpleKey& k, StringValue& v) { |
| 87 | callbackCount += 1; |
| 88 | lastKey = k; |
| 89 | lastValue = v; |
| 90 | } |
| 91 | ssize_t callbackCount; |
| 92 | SimpleKey lastKey; |
| 93 | StringValue lastValue; |
| 94 | }; |
| 95 | |
| 96 | class LruCacheTest : public testing::Test { |
| 97 | protected: |
| 98 | virtual void SetUp() { |
| 99 | ComplexKey::instanceCount = 0; |
| 100 | ComplexValue::instanceCount = 0; |
| 101 | } |
| 102 | |
| 103 | virtual void TearDown() { |
| 104 | ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); |
| 105 | } |
| 106 | |
| 107 | void assertInstanceCount(ssize_t keys, ssize_t values) { |
| 108 | if (keys != ComplexKey::instanceCount || values != ComplexValue::instanceCount) { |
| 109 | FAIL() << "Expected " << keys << " keys and " << values << " values " |
| 110 | "but there were actually " << ComplexKey::instanceCount << " keys and " |
| 111 | << ComplexValue::instanceCount << " values"; |
| 112 | } |
| 113 | } |
| 114 | }; |
| 115 | |
| 116 | TEST_F(LruCacheTest, Empty) { |
| 117 | LruCache<SimpleKey, StringValue> cache(100); |
| 118 | |
| 119 | EXPECT_EQ(NULL, cache.get(0)); |
| 120 | EXPECT_EQ(0u, cache.size()); |
| 121 | } |
| 122 | |
| 123 | TEST_F(LruCacheTest, Simple) { |
| 124 | LruCache<SimpleKey, StringValue> cache(100); |
| 125 | |
| 126 | cache.put(1, "one"); |
| 127 | cache.put(2, "two"); |
| 128 | cache.put(3, "three"); |
| 129 | EXPECT_STREQ("one", cache.get(1)); |
| 130 | EXPECT_STREQ("two", cache.get(2)); |
| 131 | EXPECT_STREQ("three", cache.get(3)); |
| 132 | EXPECT_EQ(3u, cache.size()); |
| 133 | } |
| 134 | |
| 135 | TEST_F(LruCacheTest, MaxCapacity) { |
| 136 | LruCache<SimpleKey, StringValue> cache(2); |
| 137 | |
| 138 | cache.put(1, "one"); |
| 139 | cache.put(2, "two"); |
| 140 | cache.put(3, "three"); |
| 141 | EXPECT_EQ(NULL, cache.get(1)); |
| 142 | EXPECT_STREQ("two", cache.get(2)); |
| 143 | EXPECT_STREQ("three", cache.get(3)); |
| 144 | EXPECT_EQ(2u, cache.size()); |
| 145 | } |
| 146 | |
| 147 | TEST_F(LruCacheTest, RemoveLru) { |
| 148 | LruCache<SimpleKey, StringValue> cache(100); |
| 149 | |
| 150 | cache.put(1, "one"); |
| 151 | cache.put(2, "two"); |
| 152 | cache.put(3, "three"); |
| 153 | cache.removeOldest(); |
| 154 | EXPECT_EQ(NULL, cache.get(1)); |
| 155 | EXPECT_STREQ("two", cache.get(2)); |
| 156 | EXPECT_STREQ("three", cache.get(3)); |
| 157 | EXPECT_EQ(2u, cache.size()); |
| 158 | } |
| 159 | |
| 160 | TEST_F(LruCacheTest, GetUpdatesLru) { |
| 161 | LruCache<SimpleKey, StringValue> cache(100); |
| 162 | |
| 163 | cache.put(1, "one"); |
| 164 | cache.put(2, "two"); |
| 165 | cache.put(3, "three"); |
| 166 | EXPECT_STREQ("one", cache.get(1)); |
| 167 | cache.removeOldest(); |
| 168 | EXPECT_STREQ("one", cache.get(1)); |
| 169 | EXPECT_EQ(NULL, cache.get(2)); |
| 170 | EXPECT_STREQ("three", cache.get(3)); |
| 171 | EXPECT_EQ(2u, cache.size()); |
| 172 | } |
| 173 | |
| 174 | uint32_t hash_int(int x) { |
| 175 | return JenkinsHashWhiten(JenkinsHashMix(0, x)); |
| 176 | } |
| 177 | |
| 178 | TEST_F(LruCacheTest, StressTest) { |
| 179 | const size_t kCacheSize = 512; |
| 180 | LruCache<SimpleKey, StringValue> cache(512); |
| 181 | const size_t kNumKeys = 16 * 1024; |
| 182 | const size_t kNumIters = 100000; |
| 183 | char* strings[kNumKeys]; |
| 184 | |
| 185 | for (size_t i = 0; i < kNumKeys; i++) { |
| 186 | strings[i] = (char *)malloc(16); |
| 187 | sprintf(strings[i], "%d", i); |
| 188 | } |
| 189 | |
| 190 | srandom(12345); |
| 191 | int hitCount = 0; |
| 192 | for (size_t i = 0; i < kNumIters; i++) { |
| 193 | int index = random() % kNumKeys; |
| 194 | uint32_t key = hash_int(index); |
| 195 | const char *val = cache.get(key); |
| 196 | if (val != NULL) { |
| 197 | EXPECT_EQ(strings[index], val); |
| 198 | hitCount++; |
| 199 | } else { |
| 200 | cache.put(key, strings[index]); |
| 201 | } |
| 202 | } |
| 203 | size_t expectedHitCount = kNumIters * kCacheSize / kNumKeys; |
| 204 | EXPECT_LT(int(expectedHitCount * 0.9), hitCount); |
| 205 | EXPECT_GT(int(expectedHitCount * 1.1), hitCount); |
| 206 | EXPECT_EQ(kCacheSize, cache.size()); |
| 207 | |
| 208 | for (size_t i = 0; i < kNumKeys; i++) { |
| 209 | free((void *)strings[i]); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | TEST_F(LruCacheTest, NoLeak) { |
| 214 | ComplexCache cache(100); |
| 215 | |
| 216 | cache.put(ComplexKey(0), ComplexValue(0)); |
| 217 | cache.put(ComplexKey(1), ComplexValue(1)); |
| 218 | EXPECT_EQ(2, cache.size()); |
| 219 | assertInstanceCount(2, 3); // the null value counts as an instance |
| 220 | } |
| 221 | |
| 222 | TEST_F(LruCacheTest, Clear) { |
| 223 | ComplexCache cache(100); |
| 224 | |
| 225 | cache.put(ComplexKey(0), ComplexValue(0)); |
| 226 | cache.put(ComplexKey(1), ComplexValue(1)); |
| 227 | EXPECT_EQ(2, cache.size()); |
| 228 | assertInstanceCount(2, 3); |
| 229 | cache.clear(); |
| 230 | assertInstanceCount(0, 1); |
| 231 | } |
| 232 | |
| 233 | TEST_F(LruCacheTest, ClearNoDoubleFree) { |
| 234 | { |
| 235 | ComplexCache cache(100); |
| 236 | |
| 237 | cache.put(ComplexKey(0), ComplexValue(0)); |
| 238 | cache.put(ComplexKey(1), ComplexValue(1)); |
| 239 | EXPECT_EQ(2, cache.size()); |
| 240 | assertInstanceCount(2, 3); |
| 241 | cache.removeOldest(); |
| 242 | cache.clear(); |
| 243 | assertInstanceCount(0, 1); |
| 244 | } |
| 245 | assertInstanceCount(0, 0); |
| 246 | } |
| 247 | |
| 248 | TEST_F(LruCacheTest, ClearReuseOk) { |
| 249 | ComplexCache cache(100); |
| 250 | |
| 251 | cache.put(ComplexKey(0), ComplexValue(0)); |
| 252 | cache.put(ComplexKey(1), ComplexValue(1)); |
| 253 | EXPECT_EQ(2, cache.size()); |
| 254 | assertInstanceCount(2, 3); |
| 255 | cache.clear(); |
| 256 | assertInstanceCount(0, 1); |
| 257 | cache.put(ComplexKey(0), ComplexValue(0)); |
| 258 | cache.put(ComplexKey(1), ComplexValue(1)); |
| 259 | EXPECT_EQ(2, cache.size()); |
| 260 | assertInstanceCount(2, 3); |
| 261 | } |
| 262 | |
| 263 | TEST_F(LruCacheTest, Callback) { |
| 264 | LruCache<SimpleKey, StringValue> cache(100); |
| 265 | EntryRemovedCallback callback; |
| 266 | cache.setOnEntryRemovedListener(&callback); |
| 267 | |
| 268 | cache.put(1, "one"); |
| 269 | cache.put(2, "two"); |
| 270 | cache.put(3, "three"); |
| 271 | EXPECT_EQ(3, cache.size()); |
| 272 | cache.removeOldest(); |
| 273 | EXPECT_EQ(1, callback.callbackCount); |
| 274 | EXPECT_EQ(1, callback.lastKey); |
| 275 | EXPECT_STREQ("one", callback.lastValue); |
| 276 | } |
| 277 | |
| 278 | TEST_F(LruCacheTest, CallbackOnClear) { |
| 279 | LruCache<SimpleKey, StringValue> cache(100); |
| 280 | EntryRemovedCallback callback; |
| 281 | cache.setOnEntryRemovedListener(&callback); |
| 282 | |
| 283 | cache.put(1, "one"); |
| 284 | cache.put(2, "two"); |
| 285 | cache.put(3, "three"); |
| 286 | EXPECT_EQ(3, cache.size()); |
| 287 | cache.clear(); |
| 288 | EXPECT_EQ(3, callback.callbackCount); |
| 289 | } |
| 290 | |
| 291 | } |