package com.bumptech.glide.load.engine;
import static com.bumptech.glide.tests.Util.anyResource;
import static com.bumptech.glide.tests.Util.isADataSource;
import static com.bumptech.glide.tests.Util.mockResource;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doAnswer;
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.GlideContext;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.cache.DiskCache;
import com.bumptech.glide.load.engine.cache.MemoryCache;
import com.bumptech.glide.load.engine.executor.GlideExecutor;
import com.bumptech.glide.load.engine.executor.MockGlideExecutor;
import com.bumptech.glide.request.ResourceCallback;
import com.bumptech.glide.tests.BackgroundUtil;
import com.bumptech.glide.tests.GlideShadowLooper;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 18, shadows = { GlideShadowLooper.class })
@SuppressWarnings("unchecked")
public class EngineTest {
private EngineTestHarness harness;
@Before
public void setUp() {
harness = new EngineTestHarness();
}
@Test
public void testNewRunnerIsCreatedAndPostedWithNoExistingLoad() {
harness.doLoad();
verify(harness.job).start(any(DecodeJob.class));
}
@Test
public void testCallbackIsAddedToNewEngineJobWithNoExistingLoad() {
harness.doLoad();
verify(harness.job).addCallback(eq(harness.cb));
}
@Test
public void testLoadStatusIsReturnedForNewLoad() {
assertNotNull(harness.doLoad());
}
@Test
public void testEngineJobReceivesRemoveCallbackFromLoadStatus() {
Engine.LoadStatus loadStatus = harness.doLoad();
loadStatus.cancel();
verify(harness.job).removeCallback(eq(harness.cb));
}
@Test
public void testNewRunnerIsAddedToRunnersMap() {
harness.doLoad();
assertThat(harness.jobs).containsKey(harness.cacheKey);
}
@Test
public void testNewRunnerIsNotCreatedAndPostedWithExistingLoad() {
harness.doLoad();
harness.doLoad();
verify(harness.job, times(1)).start(any(DecodeJob.class));
}
@Test
public void testCallbackIsAddedToExistingRunnerWithExistingLoad() {
harness.doLoad();
ResourceCallback newCallback = mock(ResourceCallback.class);
harness.cb = newCallback;
harness.doLoad();
verify(harness.job).addCallback(eq(newCallback));
}
@Test
public void testLoadStatusIsReturnedForExistingJob() {
harness.doLoad();
Engine.LoadStatus loadStatus = harness.doLoad();
assertNotNull(loadStatus);
}
@Test
public void testResourceIsReturnedFromActiveResourcesIfPresent() {
harness.activeResources
.put(harness.cacheKey, new WeakReference<EngineResource<?>>(harness.resource));
harness.doLoad();
verify(harness.cb).onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE));
}
@Test
public void testResourceIsNotReturnedFromActiveResourcesIfRefIsCleared() {
harness.activeResources.put(harness.cacheKey, new WeakReference<EngineResource<?>>(null));
harness.doLoad();
verify(harness.cb, never()).onResourceReady(isNull(Resource.class), isADataSource());
}
@Test
public void testKeyIsRemovedFromActiveResourcesIfRefIsCleared() {
harness.activeResources.put(harness.cacheKey, new WeakReference<EngineResource<?>>(null));
harness.doLoad();
assertThat(harness.activeResources).doesNotContainKey(harness.cacheKey);
}
@Test
public void testResourceIsAcquiredIfReturnedFromActiveResources() {
harness.activeResources
.put(harness.cacheKey, new WeakReference<EngineResource<?>>(harness.resource));
harness.doLoad();
verify(harness.resource).acquire();
}
@Test
public void testNewLoadIsNotStartedIfResourceIsActive() {
harness.activeResources
.put(harness.cacheKey, new WeakReference<EngineResource<?>>(harness.resource));
harness.doLoad();
verify(harness.job, never()).start(any(DecodeJob.class));
}
@Test
public void testNullLoadStatusIsReturnedIfResourceIsActive() {
harness.activeResources
.put(harness.cacheKey, new WeakReference<EngineResource<?>>(harness.resource));
assertNull(harness.doLoad());
}
@Test
public void testActiveResourcesIsNotCheckedIfReturnedFromCache() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);
EngineResource<?> other = mock(EngineResource.class);
harness.activeResources.put(harness.cacheKey, new WeakReference<EngineResource<?>>(other));
harness.doLoad();
verify(harness.cb).onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE));
verify(harness.cb, never()).onResourceReady(eq(other), isADataSource());
}
@Test
public void testActiveResourcesIsNotCheckedIfNotMemoryCacheable() {
harness.activeResources
.put(harness.cacheKey, new WeakReference<EngineResource<?>>(harness.resource));
harness.isMemoryCacheable = false;
harness.doLoad();
verify(harness.resource, never()).acquire();
verify(harness.job).start(any(DecodeJob.class));
}
@Test
public void testCacheIsCheckedIfMemoryCacheable() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);
harness.doLoad();
verify(harness.cb).onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE));
}
@Test
public void testCacheIsNotCheckedIfNotMemoryCacheable() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);
harness.isMemoryCacheable = false;
harness.doLoad();
verify(harness.job).start(any(DecodeJob.class));
}
@Test
public void testResourceIsReturnedFromCacheIfPresent() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);
harness.doLoad();
verify(harness.cb).onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE));
}
@Test
public void testHandlesNonEngineResourcesFromCacheIfPresent() {
final Object expected = new Object();
@SuppressWarnings("rawtypes") Resource fromCache = mockResource();
when(fromCache.get()).thenReturn(expected);
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(fromCache);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
Resource<?> resource = (Resource<?>) invocationOnMock.getArguments()[0];
assertEquals(expected, resource.get());
return null;
}
}).when(harness.cb).onResourceReady(anyResource(), isADataSource());
harness.doLoad();
verify(harness.cb).onResourceReady(anyResource(), isADataSource());
}
@Test
public void testResourceIsAddedToActiveResourceIfReturnedFromCache() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);
harness.doLoad();
assertEquals(harness.resource, harness.activeResources.get(harness.cacheKey).get());
}
@Test
public void testResourceIsAcquiredIfReturnedFromCache() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);
harness.doLoad();
verify(harness.resource).acquire();
}
@Test
public void testNewLoadIsNotStartedIfResourceIsCached() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(mock(EngineResource.class));
harness.doLoad();
verify(harness.job, never()).start(any(DecodeJob.class));
}
@Test
public void testNullLoadStatusIsReturnedForCachedResource() {
when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(mock(EngineResource.class));
Engine.LoadStatus loadStatus = harness.doLoad();
assertNull(loadStatus);
}
@Test
public void testRunnerIsRemovedFromRunnersOnEngineNotifiedJobComplete() {
harness.doLoad();
harness.engine.onEngineJobComplete(harness.cacheKey, harness.resource);
assertThat(harness.jobs).doesNotContainKey(harness.cacheKey);
}
@Test
public void testEngineIsSetAsResourceListenerOnJobComplete() {
harness.doLoad();
harness.engine.onEngineJobComplete(harness.cacheKey, harness.resource);
verify(harness.resource).setResourceListener(eq(harness.cacheKey), eq(harness.engine));
}
@Test
public void testEngineIsNotSetAsResourceListenerIfResourceIsNullOnJobComplete() {
harness.doLoad();
harness.engine.onEngineJobComplete(harness.cacheKey, null);
}
@Test
public void testResourceIsAddedToActiveResourcesOnEngineComplete() {
when(harness.resource.isCacheable()).thenReturn(true);
harness.engine.onEngineJobComplete(harness.cacheKey, harness.resource);
WeakReference<EngineResource<?>> resourceRef = harness.activeResources.get(harness.cacheKey);
assertThat(harness.resource).isEqualTo(resourceRef.get());
}
@Test
public void testDoesNotPutNullResourceInActiveResourcesOnEngineComplete() {
harness.engine.onEngineJobComplete(harness.cacheKey, null);
assertThat(harness.activeResources).doesNotContainKey(harness.cacheKey);
}
@Test
public void testDoesNotPutResourceThatIsNotCacheableInActiveResourcesOnEngineComplete() {
when(harness.resource.isCacheable()).thenReturn(false);
harness.engine.onEngineJobComplete(harness.cacheKey, harness.resource);
assertThat(harness.activeResources).doesNotContainKey(harness.cacheKey);
}
@Test
public void testRunnerIsRemovedFromRunnersOnEngineNotifiedJobCancel() {
harness.doLoad();
harness.engine.onEngineJobCancelled(harness.job, harness.cacheKey);
assertThat(harness.jobs).doesNotContainKey(harness.cacheKey);
}
@Test
public void testJobIsNotRemovedFromJobsIfOldJobIsCancelled() {
harness.doLoad();
harness.engine.onEngineJobCancelled(mock(EngineJob.class), harness.cacheKey);
assertEquals(harness.job, harness.jobs.get(harness.cacheKey));
}
@Test
public void testResourceIsAddedToCacheOnReleased() {
final Object expected = new Object();
when(harness.resource.isCacheable()).thenReturn(true);
when(harness.resource.get()).thenReturn(expected);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
Resource<?> resource = (Resource<?>) invocationOnMock.getArguments()[1];
assertEquals(expected, resource.get());
return null;
}
}).when(harness.cache).put(eq(harness.cacheKey), anyResource());
harness.engine.onResourceReleased(harness.cacheKey, harness.resource);
verify(harness.cache).put(eq(harness.cacheKey), anyResource());
}
@Test
public void testResourceIsNotAddedToCacheOnReleasedIfNotCacheable() {
when(harness.resource.isCacheable()).thenReturn(false);
harness.engine.onResourceReleased(harness.cacheKey, harness.resource);
verify(harness.cache, never()).put(eq(harness.cacheKey), eq(harness.resource));
}
@Test
public void testResourceIsRecycledIfNotCacheableWhenReleased() {
when(harness.resource.isCacheable()).thenReturn(false);
harness.engine.onResourceReleased(harness.cacheKey, harness.resource);
verify(harness.resourceRecycler).recycle(eq(harness.resource));
}
@Test
public void testResourceIsRemovedFromActiveResourcesWhenReleased() {
harness.activeResources
.put(harness.cacheKey, new WeakReference<EngineResource<?>>(harness.resource));
harness.engine.onResourceReleased(harness.cacheKey, harness.resource);
assertThat(harness.activeResources).doesNotContainKey(harness.cacheKey);
}
@Test
public void testEngineAddedAsListenerToMemoryCache() {
verify(harness.cache).setResourceRemovedListener(eq(harness.engine));
}
@Test
public void testResourceIsRecycledWhenRemovedFromCache() {
harness.engine.onResourceRemoved(harness.resource);
verify(harness.resourceRecycler).recycle(eq(harness.resource));
}
@Test
public void testJobIsPutInJobWithCacheKeyWithRelevantIds() {
harness.doLoad();
assertThat(harness.jobs).containsEntry(harness.cacheKey, harness.job);
}
@Test
public void testKeyFactoryIsGivenNecessaryArguments() {
harness.doLoad();
verify(harness.keyFactory)
.buildKey(eq(harness.model), eq(harness.signature), eq(harness.width), eq(harness.height),
eq(harness.transformations), eq(Object.class), eq(Object.class), eq(harness.options));
}
@Test
public void testFactoryIsGivenNecessaryArguments() {
harness.doLoad();
verify(harness.engineJobFactory).build(
eq(harness.cacheKey),
eq(true) /*isMemoryCacheable*/,
eq(false) /*useUnlimitedSourceGeneratorPool*/);
}
@Test
public void testFactoryIsGivenNecessaryArgumentsWithUnlimitedPool() {
harness.useUnlimitedSourceGeneratorPool = true;
harness.doLoad();
verify(harness.engineJobFactory).build(
eq(harness.cacheKey),
eq(true) /*isMemoryCacheable*/,
eq(true) /*useUnlimitedSourceGeneratorPool*/);
}
@Test
public void testReleaseReleasesEngineResource() {
EngineResource<Object> engineResource = mock(EngineResource.class);
harness.engine.release(engineResource);
verify(engineResource).release();
}
@Test(expected = IllegalArgumentException.class)
public void testThrowsIfAskedToReleaseNonEngineResource() {
harness.engine.release(mockResource());
}
@Test(expected = RuntimeException.class)
public void testThrowsIfLoadCalledOnBackgroundThread() throws InterruptedException {
BackgroundUtil.testInBackground(new BackgroundUtil.BackgroundTester() {
@Override
public void runTest() throws Exception {
harness.doLoad();
}
});
}
private static class EngineTestHarness {
EngineKey cacheKey = mock(EngineKey.class);
EngineKeyFactory keyFactory = mock(EngineKeyFactory.class);
ResourceCallback cb = mock(ResourceCallback.class);
@SuppressWarnings("rawtypes")
EngineResource resource = mock(EngineResource.class);
Map<Key, EngineJob<?>> jobs = new HashMap<>();
Map<Key, WeakReference<EngineResource<?>>> activeResources = new HashMap<>();
int width = 100;
int height = 100;
Object model = new Object();
MemoryCache cache = mock(MemoryCache.class);
EngineJob<?> job;
Engine engine;
Engine.EngineJobFactory engineJobFactory = mock(Engine.EngineJobFactory.class);
Engine.DecodeJobFactory decodeJobFactory = mock(Engine.DecodeJobFactory.class);
ResourceRecycler resourceRecycler = mock(ResourceRecycler.class);
Key signature = mock(Key.class);
Map<Class<?>, Transformation<?>> transformations = new HashMap<>();
Options options = new Options();
GlideContext glideContext = mock(GlideContext.class);
boolean isMemoryCacheable = true;
boolean useUnlimitedSourceGeneratorPool = false;
boolean onlyRetrieveFromCache = false;
public EngineTestHarness() {
when(keyFactory.buildKey(eq(model), eq(signature), anyInt(), anyInt(), eq(transformations),
eq(Object.class), eq(Object.class), eq(options))).thenReturn(cacheKey);
job = mock(EngineJob.class);
engine = new Engine(cache, mock(DiskCache.Factory.class),
GlideExecutor.newDiskCacheExecutor(),
MockGlideExecutor.newMainThreadExecutor(),
MockGlideExecutor.newMainThreadUnlimitedExecutor(),
jobs, keyFactory, activeResources,
engineJobFactory, decodeJobFactory, resourceRecycler);
}
public Engine.LoadStatus doLoad() {
when(engineJobFactory.build(eq(cacheKey), anyBoolean(), anyBoolean()))
.thenReturn((EngineJob<Object>) job);
return engine.load(glideContext,
model,
signature,
width,
height,
Object.class /*resourceClass*/,
Object.class /*transcodeClass*/,
Priority.HIGH,
DiskCacheStrategy.ALL,
transformations,
false /*isTransformationRequired*/,
options,
isMemoryCacheable,
useUnlimitedSourceGeneratorPool,
onlyRetrieveFromCache,
cb);
}
}
}