/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.imagepipeline.producers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import android.graphics.Bitmap;
import com.facebook.common.internal.ImmutableMap;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.references.ResourceReleaser;
import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory;
import com.facebook.imagepipeline.common.Priority;
import com.facebook.imagepipeline.image.CloseableAnimatedImage;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.CloseableStaticBitmap;
import com.facebook.imagepipeline.producers.PostprocessorProducer.RepeatedPostprocessorConsumer;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.RepeatedPostprocessor;
import com.facebook.imagepipeline.request.RepeatedPostprocessorRunner;
import com.facebook.imagepipeline.testing.FakeClock;
import com.facebook.imagepipeline.testing.TestExecutorService;
import org.junit.*;
import org.junit.runner.*;
import org.mockito.*;
import org.mockito.invocation.*;
import org.mockito.stubbing.*;
import org.robolectric.*;
import org.robolectric.annotation.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
@RunWith(RobolectricTestRunner.class)
@Config(manifest= Config.NONE)
public class AnimatedRepeatedPostprocessorProducerTest {
private static final String POSTPROCESSOR_NAME = "postprocessor_name";
private static final Map<String, String> mExtraMap =
ImmutableMap.of(PostprocessorProducer.POSTPROCESSOR, POSTPROCESSOR_NAME);
@Mock public PlatformBitmapFactory mPlatformBitmapFactory;
@Mock public ProducerListener mProducerListener;
@Mock public Producer<CloseableReference<CloseableImage>> mInputProducer;
@Mock public Consumer<CloseableReference<CloseableImage>> mConsumer;
@Mock public RepeatedPostprocessor mPostprocessor;
@Mock public ResourceReleaser<Bitmap> mBitmapResourceReleaser;
@Mock public ImageRequest mImageRequest;
private SettableProducerContext mProducerContext;
private String mRequestId = "mRequestId";
private Bitmap mSourceBitmap;
private CloseableStaticBitmap mSourceCloseableStaticBitmap;
private CloseableReference<CloseableImage> mSourceCloseableImageRef;
private Bitmap mDestinationBitmap;
private CloseableReference<Bitmap> mDestinationCloseableBitmapRef;
private TestExecutorService mTestExecutorService;
private PostprocessorProducer mPostprocessorProducer;
private List<CloseableReference<CloseableImage>> mResults;
private InOrder mInOrder;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestExecutorService = new TestExecutorService(new FakeClock());
mPostprocessorProducer =
new PostprocessorProducer(
mInputProducer,
mPlatformBitmapFactory,
mTestExecutorService);
mProducerContext =
new SettableProducerContext(
mImageRequest,
mRequestId,
mProducerListener,
mock(Object.class),
ImageRequest.RequestLevel.FULL_FETCH,
false /* isPrefetch */,
false /* isIntermediateResultExpected */,
Priority.MEDIUM);
when(mImageRequest.getPostprocessor()).thenReturn(mPostprocessor);
mResults = new ArrayList<>();
when(mPostprocessor.getName()).thenReturn(POSTPROCESSOR_NAME);
when(mProducerListener.requiresExtraMap(mRequestId)).thenReturn(true);
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
mResults.add(
((CloseableReference<CloseableImage>) invocation.getArguments()[0]).clone());
return null;
}
}
).when(mConsumer).onNewResult(any(CloseableReference.class), anyInt());
mInOrder = inOrder(mPostprocessor, mProducerListener, mConsumer);
}
@Test
public void testNonStaticBitmapIsPassedOn() {
RepeatedPostprocessorConsumer postprocessorConsumer = produceResults();
RepeatedPostprocessorRunner repeatedPostprocessorRunner = getRunner();
CloseableAnimatedImage sourceCloseableAnimatedImage = mock(CloseableAnimatedImage.class);
CloseableReference<CloseableImage> sourceCloseableImageRef =
CloseableReference.<CloseableImage>of(sourceCloseableAnimatedImage);
postprocessorConsumer.onNewResult(sourceCloseableImageRef, Consumer.IS_LAST);
sourceCloseableImageRef.close();
mTestExecutorService.runUntilIdle();
mInOrder.verify(mConsumer).onNewResult(any(CloseableReference.class), eq(Consumer.NO_FLAGS));
mInOrder.verifyNoMoreInteractions();
assertEquals(1, mResults.size());
CloseableReference<CloseableImage> res0 = mResults.get(0);
assertTrue(CloseableReference.isValid(res0));
assertSame(sourceCloseableAnimatedImage, res0.get());
res0.close();
performCancelAndVerifyOnCancellation();
verify(sourceCloseableAnimatedImage).close();
}
private void setupNewSourceImage() {
mSourceBitmap = mock(Bitmap.class);
mSourceCloseableStaticBitmap = mock(CloseableStaticBitmap.class);
when(mSourceCloseableStaticBitmap.getUnderlyingBitmap()).thenReturn(mSourceBitmap);
mSourceCloseableImageRef =
CloseableReference.<CloseableImage>of(mSourceCloseableStaticBitmap);
}
private void setupNewDestinationImage() {
mDestinationBitmap = mock(Bitmap.class);
mDestinationCloseableBitmapRef =
CloseableReference.of(mDestinationBitmap, mBitmapResourceReleaser);
doReturn(mDestinationCloseableBitmapRef)
.when(mPostprocessor).process(mSourceBitmap, mPlatformBitmapFactory);
}
private RepeatedPostprocessorConsumer produceResults() {
mPostprocessorProducer.produceResults(mConsumer, mProducerContext);
ArgumentCaptor<Consumer> consumerCaptor = ArgumentCaptor.forClass(Consumer.class);
verify(mInputProducer).produceResults(consumerCaptor.capture(), eq(mProducerContext));
return (RepeatedPostprocessorConsumer) consumerCaptor.getValue();
}
private RepeatedPostprocessorRunner getRunner() {
ArgumentCaptor<RepeatedPostprocessorRunner> captor =
ArgumentCaptor.forClass(RepeatedPostprocessorRunner.class);
mInOrder.verify(mPostprocessor).setCallback(captor.capture());
return captor.getValue();
}
private void performNewResult(RepeatedPostprocessorConsumer postprocessorConsumer, boolean run) {
setupNewSourceImage();
setupNewDestinationImage();
postprocessorConsumer.onNewResult(mSourceCloseableImageRef, Consumer.IS_LAST);
mSourceCloseableImageRef.close();
if (run) {
mTestExecutorService.runUntilIdle();
}
}
private void performUpdate(RepeatedPostprocessorRunner repeatedPostprocessorRunner, boolean run) {
setupNewDestinationImage();
repeatedPostprocessorRunner.update();
if (run) {
mTestExecutorService.runUntilIdle();
}
}
private void performUpdateDuringTheNextPostprocessing(
final RepeatedPostprocessorRunner repeatedPostprocessorRunner) {
doAnswer(
new Answer<CloseableReference<Bitmap>>() {
@Override
public CloseableReference<Bitmap> answer(InvocationOnMock invocation) throws Throwable {
CloseableReference<Bitmap> destBitmapRef = mDestinationCloseableBitmapRef;
performUpdate(repeatedPostprocessorRunner, false);
// the following call should be ignored
performUpdate(repeatedPostprocessorRunner, false);
return destBitmapRef;
}
}).when(mPostprocessor).process(mSourceBitmap, mPlatformBitmapFactory);
}
private void performFailure(RepeatedPostprocessorRunner repeatedPostprocessorRunner) {
setupNewDestinationImage();
doThrow(new RuntimeException())
.when(mPostprocessor).process(mSourceBitmap, mPlatformBitmapFactory);
repeatedPostprocessorRunner.update();
mTestExecutorService.runUntilIdle();
}
private void performCancelAndVerifyOnCancellation() {
performCancel();
mInOrder.verify(mConsumer).onCancellation();
}
private void performCancelAfterFinished() {
performCancel();
mInOrder.verify(mConsumer, never()).onCancellation();
}
private void performCancel() {
mProducerContext.cancel();
mTestExecutorService.runUntilIdle();
}
private void verifyNewResultProcessed(int index) {
verifyNewResultProcessed(index, mDestinationBitmap);
}
private void verifyNewResultProcessed(int index, Bitmap destBitmap) {
mInOrder.verify(mProducerListener).onProducerStart(mRequestId, PostprocessorProducer.NAME);
mInOrder.verify(mPostprocessor).process(mSourceBitmap, mPlatformBitmapFactory);
mInOrder.verify(mProducerListener).requiresExtraMap(mRequestId);
mInOrder.verify(mProducerListener)
.onProducerFinishWithSuccess(mRequestId, PostprocessorProducer.NAME, mExtraMap);
mInOrder.verify(mConsumer).onNewResult(any(CloseableReference.class), eq(Consumer.NO_FLAGS));
mInOrder.verifyNoMoreInteractions();
assertEquals(index + 1, mResults.size());
CloseableReference<CloseableImage> res0 = mResults.get(index);
assertTrue(CloseableReference.isValid(res0));
assertSame(destBitmap, ((CloseableStaticBitmap) res0.get()).getUnderlyingBitmap());
res0.close();
verify(mBitmapResourceReleaser).release(destBitmap);
}
}