package com.bumptech.glide.load.engine.cache;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.bumptech.glide.util.LruCache;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class LruCacheTest {
// 1MB
private static final int SIZE = 2;
private LruCache<String, Object> cache;
private CacheListener listener;
private String currentKey;
@Before
public void setUp() throws Exception {
currentKey = "";
listener = mock(CacheListener.class);
cache = new TestLruCache(SIZE, listener);
when(listener.getSize(anyObject())).thenReturn(1);
}
@Test
public void testCanAddAndRetrieveItem() {
String key = getKey();
Object object = new Object();
cache.put(key, object);
assertEquals(object, cache.get(key));
}
@Test
public void testCanPutNullItemWithoutChangingSize() {
String key = getKey();
cache.put(key, null);
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
verify(listener, never()).onItemRemoved(anyObject());
}
@Test
public void testReplacingNonNullItemWithNullItemDecreasesSize() {
String key = getKey();
cache.put(key, new Object());
cache.put(key, null);
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
verify(listener, never()).onItemRemoved(anyObject());
}
@Test
public void testReplacingNullItemWIthNullItemIncreasesSize() {
String key = getKey();
cache.put(key, null);
cache.put(key, new Object());
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
verify(listener).onItemRemoved(anyObject());
}
@Test
public void testReplacingNonNullItemWithNonNullItemUpdatesSize() {
String key = getKey();
cache.put(key, new Object());
cache.put(key, new Object());
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
verify(listener).onItemRemoved(anyObject());
}
@Test
public void testCacheContainsAddedBitmap() {
final String key = getKey();
cache.put(key, new Object());
assertTrue(cache.contains(key));
}
@Test
public void testEmptyCacheDoesNotContainKey() {
assertFalse(cache.contains(getKey()));
}
@Test
public void testItIsSizeLimited() {
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
verify(listener, never()).onItemRemoved(anyObject());
cache.put(getKey(), new Object());
verify(listener).onItemRemoved(anyObject());
}
@Test
public void testLeastRecentlyAddKeyEvictedFirstIfGetsAreEqual() {
Object first = new Object();
cache.put(getKey(), first);
cache.put(getKey(), new Object());
cache.put(getKey(), new Object());
verify(listener).onItemRemoved(eq(first));
verify(listener, times(1)).onItemRemoved(any(Object.class));
}
@Test
public void testLeastRecentlyUsedKeyEvictedFirst() {
String mostRecentlyUsedKey = getKey();
Object mostRecentlyUsedObject = new Object();
String leastRecentlyUsedKey = getKey();
Object leastRecentlyUsedObject = new Object();
cache.put(mostRecentlyUsedKey, mostRecentlyUsedObject);
cache.put(leastRecentlyUsedKey, leastRecentlyUsedObject);
cache.get(mostRecentlyUsedKey);
cache.put(getKey(), new Object());
verify(listener).onItemRemoved(eq(leastRecentlyUsedObject));
verify(listener, times(1)).onItemRemoved(any(Object.class));
}
@Test
public void testItemLargerThanCacheIsImmediatelyEvicted() {
Object tooLarge = new Object();
when(listener.getSize(eq(tooLarge))).thenReturn(SIZE + 1);
cache.put(getKey(), tooLarge);
verify(listener).onItemRemoved(eq(tooLarge));
}
@Test
public void testItemLargerThanCacheDoesNotCauseAdditionalEvictions() {
cache.put(getKey(), new Object());
Object tooLarge = new Object();
when(listener.getSize(eq(tooLarge))).thenReturn(SIZE + 1);
cache.put(getKey(), tooLarge);
verify(listener, times(1)).onItemRemoved(anyObject());
}
@Test
public void testClearMemoryRemovesAllItems() {
String first = getKey();
String second = getKey();
cache.put(first, new Object());
cache.put(second, new Object());
cache.clearMemory();
assertFalse(cache.contains(first));
assertFalse(cache.contains(second));
}
@Test
public void testCanPutSameItemMultipleTimes() {
String key = getKey();
Object value = new Object();
for (int i = 0; i < SIZE * 2; i++) {
cache.put(key, value);
}
verify(listener, never()).onItemRemoved(anyObject());
}
@Test
public void testCanIncreaseSizeDynamically() {
int sizeMultiplier = 2;
cache.setSizeMultiplier(sizeMultiplier);
for (int i = 0; i < SIZE * sizeMultiplier; i++) {
cache.put(getKey(), new Object());
}
verify(listener, never()).onItemRemoved(anyObject());
}
@Test
public void testCanDecreaseSizeDynamically() {
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
verify(listener, never()).onItemRemoved(anyObject());
cache.setSizeMultiplier(0.5f);
verify(listener).onItemRemoved(anyObject());
}
@Test
public void testCanResetSizeDynamically() {
int sizeMultiplier = 2;
cache.setSizeMultiplier(sizeMultiplier);
for (int i = 0; i < SIZE * sizeMultiplier; i++) {
cache.put(getKey(), new Object());
}
cache.setSizeMultiplier(1);
verify(listener, times(sizeMultiplier)).onItemRemoved(anyObject());
}
@Test(expected = IllegalArgumentException.class)
public void testThrowsIfMultiplierLessThanZero() {
cache.setSizeMultiplier(-1);
}
@Test
public void testCanHandleZeroAsMultiplier() {
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
cache.setSizeMultiplier(0);
verify(listener, times(SIZE)).onItemRemoved(anyObject());
}
@Test
public void testCanRemoveKeys() {
String key = getKey();
Object value = new Object();
cache.put(key, value);
cache.remove(key);
assertNull(cache.get(key));
assertFalse(cache.contains(key));
}
@Test
public void testDecreasesSizeWhenRemovesKey() {
String key = getKey();
Object value = new Object();
cache.put(key, value);
for (int i = 0; i < SIZE - 1; i++) {
cache.put(key, value);
}
cache.remove(key);
cache.put(key, value);
verify(listener, never()).onItemRemoved(anyObject());
}
@Test
public void testDoesNotCallListenerWhenRemovesKey() {
String key = getKey();
cache.put(key, new Object());
cache.remove(key);
verify(listener, never()).onItemRemoved(anyObject());
}
@Test
public void testGetMaxSizeReturnsCurrentMaxSizeOfCache() {
assertEquals(SIZE, cache.getMaxSize());
}
@Test
public void testGetMaxSizeChangesIfMaxSizeChanges() {
int multiplier = 2;
cache.setSizeMultiplier(multiplier);
assertEquals(SIZE * multiplier, cache.getMaxSize());
}
@Test
public void getCurrentSizeReturnsZeroForEmptyCache() {
assertEquals(0, cache.getCurrentSize());
}
@Test
public void testGetCurrentSizeIncreasesAsSizeIncreases() {
cache.put(getKey(), new Object());
assertEquals(1, cache.getCurrentSize());
cache.put(getKey(), new Object());
assertEquals(2, cache.getCurrentSize());
}
@Test
public void testGetCurrentSizeDoesNotChangeWhenSizeMultiplierChangesIfNoItemsAreEvicted() {
cache.put(getKey(), new Object());
assertEquals(1, cache.getCurrentSize());
cache.setSizeMultiplier(2);
assertEquals(1, cache.getCurrentSize());
}
@Test
public void testGetCurrentSizeChangesIfItemsAreEvictedWhenSizeMultiplierChanges() {
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
assertEquals(SIZE, cache.getCurrentSize());
cache.setSizeMultiplier(0.5f);
assertEquals(SIZE / 2, cache.getCurrentSize());
}
private String getKey() {
currentKey += "1";
return currentKey;
}
private interface CacheListener {
public void onItemRemoved(Object item);
public int getSize(Object item);
}
private static class TestLruCache extends LruCache<String, Object> {
private final CacheListener listener;
public TestLruCache(int size, CacheListener listener) {
super(size);
this.listener = listener;
}
@Override
protected void onItemEvicted(String key, Object item) {
listener.onItemRemoved(item);
}
@Override
protected int getSize(Object item) {
return listener.getSize(item);
}
}
}