/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso;
import android.graphics.Bitmap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import static android.graphics.Bitmap.Config.ALPHA_8;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
import static org.fest.assertions.api.Assertions.assertThat;
@RunWith(RobolectricGradleTestRunner.class)
public class LruCacheTest {
// The use of ALPHA_8 simplifies the size math in tests since only one byte is used per-pixel.
private final Bitmap A = Bitmap.createBitmap(1, 1, ALPHA_8);
private final Bitmap B = Bitmap.createBitmap(1, 1, ALPHA_8);
private final Bitmap C = Bitmap.createBitmap(1, 1, ALPHA_8);
private final Bitmap D = Bitmap.createBitmap(1, 1, ALPHA_8);
private final Bitmap E = Bitmap.createBitmap(1, 1, ALPHA_8);
private int expectedPutCount;
private int expectedHitCount;
private int expectedMissCount;
private int expectedEvictionCount;
@Test public void testStatistics() {
LruCache cache = new LruCache(3);
assertStatistics(cache);
cache.set("a", A);
expectedPutCount++;
assertStatistics(cache);
assertHit(cache, "a", A);
cache.set("b", B);
expectedPutCount++;
assertStatistics(cache);
assertHit(cache, "a", A);
assertHit(cache, "b", B);
assertSnapshot(cache, "a", A, "b", B);
cache.set("c", C);
expectedPutCount++;
assertStatistics(cache);
assertHit(cache, "a", A);
assertHit(cache, "b", B);
assertHit(cache, "c", C);
assertSnapshot(cache, "a", A, "b", B, "c", C);
cache.set("d", D);
expectedPutCount++;
expectedEvictionCount++; // a should have been evicted
assertStatistics(cache);
assertMiss(cache, "a");
assertHit(cache, "b", B);
assertHit(cache, "c", C);
assertHit(cache, "d", D);
assertHit(cache, "b", B);
assertHit(cache, "c", C);
assertSnapshot(cache, "d", D, "b", B, "c", C);
cache.set("e", E);
expectedPutCount++;
expectedEvictionCount++; // d should have been evicted
assertStatistics(cache);
assertMiss(cache, "d");
assertMiss(cache, "a");
assertHit(cache, "e", E);
assertHit(cache, "b", B);
assertHit(cache, "c", C);
assertSnapshot(cache, "e", E, "b", B, "c", C);
}
@Test public void constructorDoesNotAllowZeroCacheSize() {
try {
new LruCache(0);
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test public void cannotPutNullKey() {
LruCache cache = new LruCache(3);
try {
cache.set(null, A);
fail();
} catch (NullPointerException expected) {
}
}
@Test public void cannotPutNullValue() {
LruCache cache = new LruCache(3);
try {
cache.set("a", null);
fail();
} catch (NullPointerException expected) {
}
}
@Test public void evictionWithSingletonCache() {
LruCache cache = new LruCache(1);
cache.set("a", A);
cache.set("b", B);
assertSnapshot(cache, "b", B);
}
@Test public void throwsWithNullKey() {
LruCache cache = new LruCache(1);
try {
cache.get(null);
fail("Expected NullPointerException");
} catch (NullPointerException expected) {
}
}
/**
* Replacing the value for a key doesn't cause an eviction but it does bring the replaced entry to
* the front of the queue.
*/
@Test public void putCauseEviction() {
LruCache cache = new LruCache(3);
cache.set("a", A);
cache.set("b", B);
cache.set("c", C);
cache.set("b", D);
assertSnapshot(cache, "a", A, "c", C, "b", D);
}
@Test public void evictAll() {
LruCache cache = new LruCache(4);
cache.set("a", A);
cache.set("b", B);
cache.set("c", C);
cache.evictAll();
assertThat(cache.map).isEmpty();
}
@Test public void clearPrefixedKey() {
LruCache cache = new LruCache(3);
cache.set("Hello\nAlice!", A);
cache.set("Hello\nBob!", B);
cache.set("Hello\nEve!", C);
cache.set("Hellos\nWorld!", D);
cache.clearKeyUri("Hello");
assertThat(cache.map).hasSize(1).containsKey("Hellos\nWorld!");
}
@Test public void invalidate() {
LruCache cache = new LruCache(3);
cache.set("Hello\nAlice!", A);
assertThat(cache.size()).isEqualTo(1);
cache.clearKeyUri("Hello");
assertThat(cache.size()).isZero();
}
@Test public void overMaxSizeDoesNotClear() {
LruCache cache = new LruCache(16);
Bitmap size4 = Bitmap.createBitmap(2, 2, ALPHA_8);
Bitmap size16 = Bitmap.createBitmap(4, 4, ALPHA_8);
Bitmap size25 = Bitmap.createBitmap(5, 5, ALPHA_8);
cache.set("4", size4);
expectedPutCount++;
assertHit(cache, "4", size4);
cache.set("16", size16);
expectedPutCount++;
expectedEvictionCount++; // size4 was evicted.
assertMiss(cache, "4");
assertHit(cache, "16", size16);
cache.set("25", size25);
assertHit(cache, "16", size16);
assertMiss(cache, "25");
assertEquals(cache.size(), 16);
}
private void assertHit(LruCache cache, String key, Bitmap value) {
assertThat(cache.get(key)).isEqualTo(value);
expectedHitCount++;
assertStatistics(cache);
}
private void assertMiss(LruCache cache, String key) {
assertThat(cache.get(key)).isNull();
expectedMissCount++;
assertStatistics(cache);
}
private void assertStatistics(LruCache cache) {
assertThat(cache.putCount()).isEqualTo(expectedPutCount);
assertThat(cache.hitCount()).isEqualTo(expectedHitCount);
assertThat(cache.missCount()).isEqualTo(expectedMissCount);
assertThat(cache.evictionCount()).isEqualTo(expectedEvictionCount);
}
private void assertSnapshot(LruCache cache, Object... keysAndValues) {
List<Object> actualKeysAndValues = new ArrayList<>();
for (Map.Entry<String, Bitmap> entry : cache.map.entrySet()) {
actualKeysAndValues.add(entry.getKey());
actualKeysAndValues.add(entry.getValue());
}
// assert using lists because order is important for LRUs
assertThat(actualKeysAndValues).isEqualTo(Arrays.asList(keysAndValues));
}
}