blob: 9fc8213b9821e76db04e18434442d643b3fdb40f [file] [log] [blame]
package com.bumptech.glide.load.engine;
import android.os.Handler;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.request.ResourceCallback;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowLooper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
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;
@RunWith(RobolectricTestRunner.class)
public class EngineJobTest {
private EngineJobHarness harness;
@Before
public void setUp() {
harness = new EngineJobHarness();
}
@Test
public void testOnResourceReadyPassedToCallbacks() throws Exception {
harness.getJob().onResourceReady(harness.resource);
Robolectric.runUiThreadTasks();
verify(harness.cb).onResourceReady(eq(harness.engineResource));
}
@Test
public void testListenerNotifiedJobCompleteOnOnResourceReady() {
harness.getJob().onResourceReady(harness.resource);
Robolectric.runUiThreadTasks();
verify(harness.listener).onEngineJobComplete(eq(harness.key), eq(harness.engineResource));
}
@Test
public void testNotifiesAllCallbacksOnReady() {
MultiCbHarness harness = new MultiCbHarness();
harness.job.onResourceReady(harness.resource);
for (ResourceCallback cb : harness.cbs) {
verify(cb).onResourceReady(eq(harness.engineResource));
}
}
@Test
public void testNotifiesAllCallbacksOnException() {
for (Exception exception : list(new RuntimeException("test"), null)) {
MultiCbHarness harness = new MultiCbHarness();
harness.job.onException(exception);
for (ResourceCallback cb : harness.cbs) {
verify(cb).onException(eq(exception));
}
}
}
@Test
public void testAcquiresResourceOncePerCallback() {
MultiCbHarness harness = new MultiCbHarness();
harness.job.onResourceReady(harness.resource);
// Acquired once and then released while notifying.
InOrder order = inOrder(harness.engineResource);
order.verify(harness.engineResource, times(harness.numCbs + 1)).acquire();
order.verify(harness.engineResource, times(1)).release();
}
@Test
public void testListenerNotifiedJobCompleteOnException() {
for (Exception exception : list(new Exception("test"), null)) {
harness = new EngineJobHarness();
harness.getJob().onException(exception);
Robolectric.runUiThreadTasks();
verify(harness.listener).onEngineJobComplete(eq(harness.key), (EngineResource) isNull());
}
}
@Test
public void testResourceIsCacheableWhenIsCacheableOnReady() {
harness.isCacheable = true;
harness.getJob().onResourceReady(harness.resource);
Robolectric.runUiThreadTasks();
verify(harness.factory).build(any(Resource.class), eq(harness.isCacheable));
}
@Test
public void testResourceIsCacheableWhenNotIsCacheableOnReady() {
harness.isCacheable = false;
harness.getJob().onResourceReady(harness.resource);
Robolectric.runUiThreadTasks();
verify(harness.factory).build(any(Resource.class), eq(harness.isCacheable));
}
@Test
public void testListenerNotifiedOfCancelOnCancel() {
EngineJob job = harness.getJob();
job.cancel();
verify(harness.listener).onEngineJobCancelled(eq(job), eq(harness.key));
}
@Test
public void testOnResourceReadyNotDeliveredAfterCancel() {
EngineJob job = harness.getJob();
job.cancel();
job.onResourceReady(harness.resource);
Robolectric.runUiThreadTasks();
verify(harness.cb, never()).onResourceReady(eq(harness.resource));
}
@Test
public void testOnExceptionNotDeliveredAfterCancel() {
for (Exception exception : list(new RuntimeException("test"), null)) {
harness = new EngineJobHarness();
EngineJob job = harness.getJob();
job.cancel();
job.onException(exception);
Robolectric.runUiThreadTasks();
verify(harness.cb, never()).onException(any(Exception.class));
}
}
@Test
public void testRemovingAllCallbacksCancelsRunner() {
EngineJob job = harness.getJob();
job.removeCallback(harness.cb);
assertTrue(job.isCancelled());
}
@SuppressWarnings("unchecked")
@Test
public void removingSomeCallbacksDoesNotCancelRunner() {
EngineJob job = harness.getJob();
job.addCallback(mock(ResourceCallback.class));
job.removeCallback(harness.cb);
assertFalse(job.isCancelled());
}
@Test
public void testResourceIsAcquiredOncePerConsumerAndOnceForCache() {
harness.getJob().onResourceReady(harness.resource);
// Once while notifying and once for single callback.
verify(harness.engineResource, times(2)).acquire();
}
@Test
public void testDoesNotNotifyCancelledIfCompletesBeforeCancel() {
EngineJob job = harness.getJob();
job.onResourceReady(harness.resource);
job.cancel();
verify(harness.listener, never()).onEngineJobCancelled(eq(job), eq(harness.key));
}
@Test
public void testDoesNotNotifyCancelledIfAlreadyCancelled() {
EngineJob job = harness.getJob();
job.cancel();
job.cancel();
verify(harness.listener, times(1)).onEngineJobCancelled(eq(job), eq(harness.key));
}
@Test
public void testReleasesResourceIfCancelledOnReady() {
ShadowLooper shadowLooper = Robolectric.shadowOf(harness.mainHandler.getLooper());
shadowLooper.pause();
EngineJob job = harness.getJob();
job.onResourceReady(harness.resource);
job.cancel();
shadowLooper.runOneTask();
verify(harness.resource).recycle();
}
@Test
public void testDoesNotAcquireOnceForMemoryCacheIfNotCacheable() {
harness.isCacheable = false;
harness.getJob().onResourceReady(harness.resource);
verify(harness.engineResource, times(2)).acquire();
}
@Test
public void testNotifiesNewCallbackOfResourceIfCallbackIsAddedDuringOnResourceReady() {
final EngineJob job = harness.getJob();
final ResourceCallback existingCallback = mock(ResourceCallback.class);
final ResourceCallback newCallback = mock(ResourceCallback.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
job.addCallback(newCallback);
return null;
}
}).when(existingCallback).onResourceReady(any(Resource.class));
job.addCallback(existingCallback);
job.onResourceReady(harness.resource);
verify(newCallback).onResourceReady(eq(harness.engineResource));
}
@Test
public void testNotifiesNewCallbackOfExceptionIfCallbackIsAddedDuringOnException() {
for (Exception exception : list(new RuntimeException("test"), null)) {
harness = new EngineJobHarness();
final EngineJob job = harness.getJob();
final ResourceCallback existingCallback = mock(ResourceCallback.class);
final ResourceCallback newCallback = mock(ResourceCallback.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
job.addCallback(newCallback);
return null;
}
}).when(existingCallback).onException(any(Exception.class));
job.addCallback(existingCallback);
job.onException(exception);
verify(newCallback).onException(eq(exception));
}
}
@Test
public void testRemovingCallbackDuringOnResourceReadyIsIgnoredIfCallbackHasAlreadyBeenCalled() {
final EngineJob job = harness.getJob();
final ResourceCallback cb = mock(ResourceCallback.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
job.removeCallback(cb);
return null;
}
}).when(cb).onResourceReady(any(Resource.class));
job.addCallback(cb);
job.onResourceReady(harness.resource);
verify(cb, times(1)).onResourceReady(any(Resource.class));
}
@Test
public void testRemovingCallbackDuringOnExceptionIsIgnoredIfCallbackHasAlreadyBeenCalled() {
for (Exception exception : list(new RuntimeException("test"), null)) {
harness = new EngineJobHarness();
final EngineJob job = harness.getJob();
final ResourceCallback cb = mock(ResourceCallback.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
job.removeCallback(cb);
return null;
}
}).when(cb).onException(any(Exception.class));
job.addCallback(cb);
job.onException(exception);
verify(cb, times(1)).onException(any(Exception.class));
}
}
@Test
public void testRemovingCallbackDuringOnResourceReadyPreventsCallbackFromBeingCalledIfNotYetCalled() {
final EngineJob job = harness.getJob();
final ResourceCallback called = mock(ResourceCallback.class);
final ResourceCallback notYetCalled = mock(ResourceCallback.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
job.removeCallback(notYetCalled);
return null;
}
}).when(called).onResourceReady(any(Resource.class));
job.addCallback(called);
job.addCallback(notYetCalled);
job.onResourceReady(harness.resource);
verify(notYetCalled, never()).onResourceReady(any(Resource.class));
}
@Test
public void testRemovingCallbackDuringOnExceptionPreventsCallbackFromBeingCalledIfNotYetCalled() {
for (Exception exception : list(new RuntimeException("test"), null)) {
harness = new EngineJobHarness();
final EngineJob job = harness.getJob();
final ResourceCallback called = mock(ResourceCallback.class);
final ResourceCallback notYetCalled = mock(ResourceCallback.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
job.removeCallback(notYetCalled);
return null;
}
}).when(called).onException(any(Exception.class));
job.addCallback(called);
job.addCallback(notYetCalled);
job.onException(exception);
verify(notYetCalled, never()).onResourceReady(any(Resource.class));
}
}
@Test
public void testRemovingCallbackAfterLoadFailsWithNullExceptionDoesNotCancelJob() {
EngineJob job = harness.getJob();
job.onException(null);
job.removeCallback(harness.cb);
verify(harness.listener, never()).onEngineJobCancelled(any(EngineJob.class), any(Key.class));
}
private static <T> List<T> list(T... items) {
return Arrays.asList(items);
}
private static class MultiCbHarness {
Key key = mock(Key.class);
Handler mainHandler = new Handler();
Resource<Object> resource = mock(Resource.class);
EngineResource<Object> engineResource = mock(EngineResource.class);
EngineJobListener listener = mock(EngineJobListener.class);
boolean isCacheable = true;
int numCbs = 10;
List<ResourceCallback> cbs = new ArrayList<ResourceCallback>();
EngineJob.EngineResourceFactory factory = mock(EngineJob.EngineResourceFactory.class);
EngineJob job;
public MultiCbHarness() {
when(factory.build(eq(resource), eq(isCacheable))).thenReturn(engineResource);
job = new EngineJob(key, mainHandler, isCacheable, listener, factory);
for (int i = 0; i < numCbs; i++) {
cbs.add(mock(ResourceCallback.class));
}
for (ResourceCallback cb : cbs) {
job.addCallback(cb);
}
}
}
@SuppressWarnings("unchecked")
private static class EngineJobHarness {
EngineJob.EngineResourceFactory factory = mock(EngineJob.EngineResourceFactory.class);
Key key = mock(Key.class);
Handler mainHandler = new Handler();
ResourceCallback cb = mock(ResourceCallback.class);
Resource<Object> resource = mock(Resource.class);
EngineResource<Object> engineResource = mock(EngineResource.class);
EngineJobListener listener = mock(EngineJobListener.class);
boolean isCacheable = true;
public EngineJob getJob() {
when(factory.build(eq(resource), eq(isCacheable))).thenReturn(engineResource);
EngineJob result = new EngineJob(key, mainHandler, isCacheable, listener, factory);
result.addCallback(cb);
return result;
}
}
}