package com.bumptech.glide.request; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; 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 android.os.Handler; import com.bumptech.glide.request.target.SizeReadyCallback; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; 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) public class RequestFutureTargetTest { private int width; private int height; private RequestFutureTarget<Object> future; private Request request; private Handler handler; private RequestFutureTarget.Waiter waiter; @Before public void setUp() { width = 100; height = 100; handler = mock(Handler.class); waiter = mock(RequestFutureTarget.Waiter.class); future = new RequestFutureTarget<>(handler, width, height, false, waiter); request = mock(Request.class); future.setRequest(request); } @Test public void testCallsSizeReadyCallbackOnGetSize() { SizeReadyCallback cb = mock(SizeReadyCallback.class); future.getSize(cb); verify(cb).onSizeReady(eq(width), eq(height)); } @Test public void testReturnsFalseForDoneBeforeDone() { assertFalse(future.isDone()); } @Test public void testReturnsTrueFromIsDoneIfDone() { future.onResourceReady(new Object(), null); assertTrue(future.isDone()); } @Test public void testReturnsFalseForIsCancelledBeforeCancelled() { assertFalse(future.isCancelled()); } @Test public void testReturnsTrueFromCancelIfNotYetDone() { assertTrue(future.cancel(false)); } @Test public void cancel_withMayInterruptIfRunningTrueAndNotFinishedRequest_clearsFutureOnMainThread() { future.cancel(true); verify(handler).post(eq(future)); } @Test public void cancel_withInterruptFalseAndNotFinishedRequest_doesNotclearFutureOnMainThread() { future.cancel(false); verify(handler, never()).post(eq(future)); } @Test public void testDoesNotRepeatedlyClearRequestOnMainThreadIfCancelledRepeatedly() { future.cancel(true); future.cancel(true); verify(handler, times(1)).post(any(Runnable.class)); } @Test public void testClearsRequestOnRun() { future.run(); verify(request).clear(); } @Test public void testDoesNotClearRequestIfCancelledAfterDone() { future.onResourceReady(new Object(), null); future.cancel(true); verify(request, never()).clear(); } @Test public void testReturnsTrueFromDoneIfCancelled() { future.cancel(true); assertTrue(future.isDone()); } @Test public void testReturnsFalseFromIsCancelledIfCancelledAfterDone() { future.onResourceReady(new Object(), null); future.cancel(true); assertFalse(future.isCancelled()); } @Test public void testReturnsTrueFromCancelIfCancelled() { future.cancel(true); assertTrue(future.isCancelled()); } @Test public void testReturnsFalseFromCancelIfDone() { future.onResourceReady(new Object(), null); assertFalse(future.cancel(true)); } @Test public void testReturnsResourceOnGetIfAlreadyDone() throws ExecutionException, InterruptedException { Object expected = new Object(); future.onResourceReady(expected, null); assertEquals(expected, future.get()); } @Test public void testReturnsResourceOnGetWithTimeoutIfAlreadyDone() throws InterruptedException, ExecutionException, TimeoutException { Object expected = new Object(); future.onResourceReady(expected, null); assertEquals(expected, future.get(100, TimeUnit.MILLISECONDS)); } @Test(expected = CancellationException.class) public void testThrowsCancellationExceptionIfCancelledBeforeGet() throws ExecutionException, InterruptedException { future.cancel(true); future.get(); } @Test(expected = CancellationException.class) public void testThrowsCancellationExceptionIfCancelledBeforeGetWithTimeout() throws InterruptedException, ExecutionException, TimeoutException { future.cancel(true); future.get(100, TimeUnit.MILLISECONDS); } @Test(expected = ExecutionException.class) public void testThrowsExecutionExceptionOnGetIfExceptionBeforeGet() throws ExecutionException, InterruptedException { future.onLoadFailed(null); future.get(); } @Test(expected = ExecutionException.class) public void testThrowsExecutionExceptionOnGetIfExceptionWithNullValueBeforeGet() throws ExecutionException, InterruptedException, TimeoutException { future.onLoadFailed(null); future.get(100, TimeUnit.MILLISECONDS); } @Test(expected = ExecutionException.class) public void testThrowsExecutionExceptionOnGetIfExceptionBeforeGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException { future.onLoadFailed(null); future.get(100, TimeUnit.MILLISECONDS); } @Test(expected = TimeoutException.class) public void testThrowsTimeoutExceptionOnGetIfFailedToReceiveResourceInTime() throws InterruptedException, ExecutionException, TimeoutException { future.get(1, TimeUnit.MILLISECONDS); } @Test(expected = IllegalArgumentException.class) public void testThrowsExceptionIfGetCalledOnMainThread() throws ExecutionException, InterruptedException { future = new RequestFutureTarget<>(handler, width, height, true, waiter); future.get(); } @Test public void testGetSucceedsOnMainThreadIfDone() throws ExecutionException, InterruptedException { future = new RequestFutureTarget<>(handler, width, height, true, waiter); future.onResourceReady(new Object(), null); future.get(); } @Test(expected = InterruptedException.class) public void testThrowsInterruptedExceptionIfThreadInterruptedWhenDoneWaiting() throws InterruptedException, ExecutionException { doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { Thread.currentThread().interrupt(); return null; } }).when(waiter).waitForTimeout(eq(future), anyLong()); future.get(); } @Test(expected = ExecutionException.class) public void testThrowsExecutionExceptionIfLoadFailsWhileWaiting() throws ExecutionException, InterruptedException { doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { future.onLoadFailed(null); return null; } }).when(waiter).waitForTimeout(eq(future), anyLong()); future.get(); } @Test(expected = CancellationException.class) public void testThrowsCancellationExceptionIfCancelledWhileWaiting() throws ExecutionException, InterruptedException { doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { future.cancel(false); return null; } }).when(waiter).waitForTimeout(eq(future), anyLong()); future.get(); } @Test(expected = TimeoutException.class) public void testThrowsTimeoutExceptionIfFinishesWaitingWithTimeoutAndDoesNotReceiveResult() throws ExecutionException, InterruptedException, TimeoutException { future.get(1, TimeUnit.MILLISECONDS); } @Test(expected = AssertionError.class) public void testThrowsAssertionErrorIfFinishesWaitingWithoutTimeoutAndDoesNotReceiveResult() throws ExecutionException, InterruptedException { future.get(); } @Test public void testNotifiesAllWhenLoadFails() { future.onLoadFailed(null); verify(waiter).notifyAll(eq(future)); } @Test public void testNotifiesAllWhenResourceReady() { future.onResourceReady(null, null); verify(waiter).notifyAll(eq(future)); } @Test public void testNotifiesAllOnCancelIfNotCancelled() { future.cancel(false); verify(waiter).notifyAll(eq(future)); } @Test public void testDoesNotNotifyAllOnSecondCancel() { future.cancel(true); verify(waiter).notifyAll(eq(future)); future.cancel(true); verify(waiter, times(1)).notifyAll(eq(future)); } @Test public void testReturnsResourceIfReceivedWhileWaiting() throws ExecutionException, InterruptedException { final Object expected = new Object(); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { future.onResourceReady(expected, null); return null; } }).when(waiter).waitForTimeout(eq(future), anyLong()); assertEquals(expected, future.get()); } @Test public void testWaitsForeverIfNoTimeoutSet() throws InterruptedException { try { future.get(); } catch (ExecutionException e) { throw new RuntimeException(e); } catch (AssertionError e) { // Expected. } verify(waiter).waitForTimeout(eq(future), eq(0L)); } @Test public void testWaitsForGivenTimeoutMillisIfTimeoutSet() throws InterruptedException { long timeout = 1234; try { future.get(1234, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } catch (TimeoutException e) { // Expected. } verify(waiter).waitForTimeout(eq(future), eq(timeout)); } @Test public void testConvertsOtherTimeUnitsToMillisForWaiter() throws InterruptedException { long timeoutSeconds = 10; try { future.get(timeoutSeconds, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } catch (TimeoutException e) { // Expected. } verify(waiter).waitForTimeout(eq(future), eq(TimeUnit.SECONDS.toMillis(timeoutSeconds))); } @Test public void testDoesNotWaitIfGivenTimeOutEqualToZero() throws InterruptedException { try { future.get(0, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } catch (TimeoutException e) { // Expected. } verify(waiter, never()).waitForTimeout(eq(future), anyLong()); } }