LruCacheTest.java revision 56b6ad3e28f9f86fb3186c96ddd8754e190afdf0
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 17package android.util; 18 19import java.util.ArrayList; 20import java.util.Arrays; 21import java.util.List; 22import java.util.Map; 23import junit.framework.TestCase; 24 25public final class LruCacheTest extends TestCase { 26 private int expectedCreateCount; 27 private int expectedPutCount; 28 private int expectedHitCount; 29 private int expectedMissCount; 30 private int expectedEvictionCount; 31 32 public void testStatistics() { 33 LruCache<String, String> cache = new LruCache<String, String>(3); 34 assertStatistics(cache); 35 36 assertEquals(null, cache.put("a", "A")); 37 expectedPutCount++; 38 assertStatistics(cache); 39 assertHit(cache, "a", "A"); 40 assertSnapshot(cache, "a", "A"); 41 42 assertEquals(null, cache.put("b", "B")); 43 expectedPutCount++; 44 assertStatistics(cache); 45 assertHit(cache, "a", "A"); 46 assertHit(cache, "b", "B"); 47 assertSnapshot(cache, "a", "A", "b", "B"); 48 49 assertEquals(null, cache.put("c", "C")); 50 expectedPutCount++; 51 assertStatistics(cache); 52 assertHit(cache, "a", "A"); 53 assertHit(cache, "b", "B"); 54 assertHit(cache, "c", "C"); 55 assertSnapshot(cache, "a", "A", "b", "B", "c", "C"); 56 57 assertEquals(null, cache.put("d", "D")); 58 expectedPutCount++; 59 expectedEvictionCount++; // a should have been evicted 60 assertStatistics(cache); 61 assertMiss(cache, "a"); 62 assertHit(cache, "b", "B"); 63 assertHit(cache, "c", "C"); 64 assertHit(cache, "d", "D"); 65 assertHit(cache, "b", "B"); 66 assertHit(cache, "c", "C"); 67 assertSnapshot(cache, "d", "D", "b", "B", "c", "C"); 68 69 assertEquals(null, cache.put("e", "E")); 70 expectedPutCount++; 71 expectedEvictionCount++; // d should have been evicted 72 assertStatistics(cache); 73 assertMiss(cache, "d"); 74 assertMiss(cache, "a"); 75 assertHit(cache, "e", "E"); 76 assertHit(cache, "b", "B"); 77 assertHit(cache, "c", "C"); 78 assertSnapshot(cache, "e", "E", "b", "B", "c", "C"); 79 } 80 81 public void testStatisticsWithCreate() { 82 LruCache<String, String> cache = newCreatingCache(); 83 assertStatistics(cache); 84 85 assertCreated(cache, "aa", "created-aa"); 86 assertHit(cache, "aa", "created-aa"); 87 assertSnapshot(cache, "aa", "created-aa"); 88 89 assertCreated(cache, "bb", "created-bb"); 90 assertMiss(cache, "c"); 91 assertSnapshot(cache, "aa", "created-aa", "bb", "created-bb"); 92 93 assertCreated(cache, "cc", "created-cc"); 94 assertSnapshot(cache, "aa", "created-aa", "bb", "created-bb", "cc", "created-cc"); 95 96 expectedEvictionCount++; // aa will be evicted 97 assertCreated(cache, "dd", "created-dd"); 98 assertSnapshot(cache, "bb", "created-bb", "cc", "created-cc", "dd", "created-dd"); 99 100 expectedEvictionCount++; // bb will be evicted 101 assertCreated(cache, "aa", "created-aa"); 102 assertSnapshot(cache, "cc", "created-cc", "dd", "created-dd", "aa", "created-aa"); 103 } 104 105 public void testCreateOnCacheMiss() { 106 LruCache<String, String> cache = newCreatingCache(); 107 String created = cache.get("aa"); 108 assertEquals("created-aa", created); 109 } 110 111 public void testNoCreateOnCacheHit() { 112 LruCache<String, String> cache = newCreatingCache(); 113 cache.put("aa", "put-aa"); 114 assertEquals("put-aa", cache.get("aa")); 115 } 116 117 public void testConstructorDoesNotAllowZeroCacheSize() { 118 try { 119 new LruCache<String, String>(0); 120 fail(); 121 } catch (IllegalArgumentException expected) { 122 } 123 } 124 125 public void testCannotPutNullKey() { 126 LruCache<String, String> cache = new LruCache<String, String>(3); 127 try { 128 cache.put(null, "A"); 129 fail(); 130 } catch (NullPointerException expected) { 131 } 132 } 133 134 public void testCannotPutNullValue() { 135 LruCache<String, String> cache = new LruCache<String, String>(3); 136 try { 137 cache.put("a", null); 138 fail(); 139 } catch (NullPointerException expected) { 140 } 141 } 142 143 public void testToString() { 144 LruCache<String, String> cache = new LruCache<String, String>(3); 145 assertEquals("LruCache[maxSize=3,hits=0,misses=0,hitRate=0%]", cache.toString()); 146 147 cache.put("a", "A"); 148 cache.put("b", "B"); 149 cache.put("c", "C"); 150 cache.put("d", "D"); 151 152 cache.get("a"); // miss 153 cache.get("b"); // hit 154 cache.get("c"); // hit 155 cache.get("d"); // hit 156 cache.get("e"); // miss 157 158 assertEquals("LruCache[maxSize=3,hits=3,misses=2,hitRate=60%]", cache.toString()); 159 } 160 161 public void testEvictionWithSingletonCache() { 162 LruCache<String, String> cache = new LruCache<String, String>(1); 163 cache.put("a", "A"); 164 cache.put("b", "B"); 165 assertSnapshot(cache, "b", "B"); 166 } 167 168 public void testEntryEvictedWhenFull() { 169 List<String> expectedEvictionLog = new ArrayList<String>(); 170 final List<String> evictionLog = new ArrayList<String>(); 171 LruCache<String, String> cache = new LruCache<String, String>(3) { 172 @Override protected void entryEvicted(String key, String value) { 173 evictionLog.add(key + "=" + value); 174 } 175 }; 176 177 cache.put("a", "A"); 178 cache.put("b", "B"); 179 cache.put("c", "C"); 180 assertEquals(expectedEvictionLog, evictionLog); 181 182 cache.put("d", "D"); 183 expectedEvictionLog.add("a=A"); 184 assertEquals(expectedEvictionLog, evictionLog); 185 } 186 187 /** 188 * Replacing the value for a key doesn't cause an eviction but it does bring 189 * the replaced entry to the front of the queue. 190 */ 191 public void testPutDoesNotCauseEviction() { 192 final List<String> evictionLog = new ArrayList<String>(); 193 List<String> expectedEvictionLog = new ArrayList<String>(); 194 LruCache<String, String> cache = new LruCache<String, String>(3) { 195 @Override protected void entryEvicted(String key, String value) { 196 evictionLog.add(key + "=" + value); 197 } 198 }; 199 200 cache.put("a", "A"); 201 cache.put("b", "B"); 202 cache.put("c", "C"); 203 cache.put("b", "B2"); 204 assertEquals(expectedEvictionLog, evictionLog); 205 assertSnapshot(cache, "a", "A", "c", "C", "b", "B2"); 206 } 207 208 public void testCustomSizesImpactsSize() { 209 LruCache<String, String> cache = new LruCache<String, String>(10) { 210 @Override protected int sizeOf(String key, String value) { 211 return key.length() + value.length(); 212 } 213 }; 214 215 assertEquals(0, cache.size()); 216 cache.put("a", "AA"); 217 assertEquals(3, cache.size()); 218 cache.put("b", "BBBB"); 219 assertEquals(8, cache.size()); 220 cache.put("a", ""); 221 assertEquals(6, cache.size()); 222 } 223 224 public void testEvictionWithCustomSizes() { 225 LruCache<String, String> cache = new LruCache<String, String>(4) { 226 @Override protected int sizeOf(String key, String value) { 227 return value.length(); 228 } 229 }; 230 231 cache.put("a", "AAAA"); 232 assertSnapshot(cache, "a", "AAAA"); 233 cache.put("b", "BBBB"); // should evict a 234 assertSnapshot(cache, "b", "BBBB"); 235 cache.put("c", "CC"); // should evict b 236 assertSnapshot(cache, "c", "CC"); 237 cache.put("d", "DD"); 238 assertSnapshot(cache, "c", "CC", "d", "DD"); 239 cache.put("e", "E"); // should evict c 240 assertSnapshot(cache, "d", "DD", "e", "E"); 241 cache.put("f", "F"); 242 assertSnapshot(cache, "d", "DD", "e", "E", "f", "F"); 243 cache.put("g", "G"); // should evict d 244 assertSnapshot(cache, "e", "E", "f", "F", "g", "G"); 245 cache.put("h", "H"); 246 assertSnapshot(cache, "e", "E", "f", "F", "g", "G", "h", "H"); 247 cache.put("i", "III"); // should evict e, f, and g 248 assertSnapshot(cache, "h", "H", "i", "III"); 249 cache.put("j", "JJJ"); // should evict h and i 250 assertSnapshot(cache, "j", "JJJ"); 251 } 252 253 public void testEvictionThrowsWhenSizesAreInconsistent() { 254 LruCache<String, int[]> cache = new LruCache<String, int[]>(4) { 255 @Override protected int sizeOf(String key, int[] value) { 256 return value[0]; 257 } 258 }; 259 260 int[] a = { 4 }; 261 cache.put("a", a); 262 263 // get the cache size out of sync 264 a[0] = 1; 265 assertEquals(4, cache.size()); 266 267 // evict something 268 try { 269 cache.put("b", new int[] { 2 }); 270 fail(); 271 } catch (IllegalStateException expected) { 272 } 273 } 274 275 public void testEvictionThrowsWhenSizesAreNegative() { 276 LruCache<String, String> cache = new LruCache<String, String>(4) { 277 @Override protected int sizeOf(String key, String value) { 278 return -1; 279 } 280 }; 281 282 try { 283 cache.put("a", "A"); 284 fail(); 285 } catch (IllegalStateException expected) { 286 } 287 } 288 289 /** 290 * Naive caches evict at most one element at a time. This is problematic 291 * because evicting a small element may be insufficient to make room for a 292 * large element. 293 */ 294 public void testDifferentElementSizes() { 295 LruCache<String, String> cache = new LruCache<String, String>(10) { 296 @Override protected int sizeOf(String key, String value) { 297 return value.length(); 298 } 299 }; 300 301 cache.put("a", "1"); 302 cache.put("b", "12345678"); 303 cache.put("c", "1"); 304 assertSnapshot(cache, "a", "1", "b", "12345678", "c", "1"); 305 cache.put("d", "12345678"); // should evict a and b 306 assertSnapshot(cache, "c", "1", "d", "12345678"); 307 cache.put("e", "12345678"); // should evict c and d 308 assertSnapshot(cache, "e", "12345678"); 309 } 310 311 public void testEvictAll() { 312 final List<String> evictionLog = new ArrayList<String>(); 313 LruCache<String, String> cache = new LruCache<String, String>(10) { 314 @Override protected void entryEvicted(String key, String value) { 315 evictionLog.add(key + "=" + value); 316 } 317 }; 318 319 cache.put("a", "A"); 320 cache.put("b", "B"); 321 cache.put("c", "C"); 322 cache.evictAll(); 323 assertEquals(0, cache.size()); 324 assertEquals(Arrays.asList("a=A", "b=B", "c=C"), evictionLog); 325 } 326 327 public void testEvictAllEvictsSizeZeroElements() { 328 LruCache<String, String> cache = new LruCache<String, String>(10) { 329 @Override protected int sizeOf(String key, String value) { 330 return 0; 331 } 332 }; 333 334 cache.put("a", "A"); 335 cache.put("b", "B"); 336 cache.evictAll(); 337 assertSnapshot(cache); 338 } 339 340 public void testRemoveDoesNotCallEntryEvicted() { 341 LruCache<String, String> cache = new LruCache<String, String>(10) { 342 @Override protected void entryEvicted(String key, String value) { 343 fail(); 344 } 345 }; 346 cache.put("a", "A"); 347 assertEquals("A", cache.remove("a")); 348 } 349 350 public void testRemoveWithCustomSizes() { 351 LruCache<String, String> cache = new LruCache<String, String>(10) { 352 @Override protected int sizeOf(String key, String value) { 353 return value.length(); 354 } 355 }; 356 cache.put("a", "123456"); 357 cache.put("b", "1234"); 358 cache.remove("a"); 359 assertEquals(4, cache.size()); 360 } 361 362 public void testRemoveAbsentElement() { 363 LruCache<String, String> cache = new LruCache<String, String>(10); 364 cache.put("a", "A"); 365 cache.put("b", "B"); 366 assertEquals(null, cache.remove("c")); 367 assertEquals(2, cache.size()); 368 } 369 370 public void testRemoveNullThrows() { 371 LruCache<String, String> cache = new LruCache<String, String>(10); 372 try { 373 cache.remove(null); 374 fail(); 375 } catch (NullPointerException expected) { 376 } 377 } 378 379 private LruCache<String, String> newCreatingCache() { 380 return new LruCache<String, String>(3) { 381 @Override protected String create(String key) { 382 return (key.length() > 1) ? ("created-" + key) : null; 383 } 384 }; 385 } 386 387 private void assertHit(LruCache<String, String> cache, String key, String value) { 388 assertEquals(value, cache.get(key)); 389 expectedHitCount++; 390 assertStatistics(cache); 391 } 392 393 private void assertMiss(LruCache<String, String> cache, String key) { 394 assertEquals(null, cache.get(key)); 395 expectedMissCount++; 396 assertStatistics(cache); 397 } 398 399 private void assertCreated(LruCache<String, String> cache, String key, String value) { 400 assertEquals(value, cache.get(key)); 401 expectedMissCount++; 402 expectedCreateCount++; 403 assertStatistics(cache); 404 } 405 406 private void assertStatistics(LruCache<?, ?> cache) { 407 assertEquals("create count", expectedCreateCount, cache.createCount()); 408 assertEquals("put count", expectedPutCount, cache.putCount()); 409 assertEquals("hit count", expectedHitCount, cache.hitCount()); 410 assertEquals("miss count", expectedMissCount, cache.missCount()); 411 assertEquals("eviction count", expectedEvictionCount, cache.evictionCount()); 412 } 413 414 private <T> void assertSnapshot(LruCache<T, T> cache, T... keysAndValues) { 415 List<T> actualKeysAndValues = new ArrayList<T>(); 416 for (Map.Entry<T, T> entry : cache.snapshot().entrySet()) { 417 actualKeysAndValues.add(entry.getKey()); 418 actualKeysAndValues.add(entry.getValue()); 419 } 420 421 // assert using lists because order is important for LRUs 422 assertEquals(Arrays.asList(keysAndValues), actualKeysAndValues); 423 } 424} 425