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); } } }