/*
* 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 com.facebook.common.executors.CallerThreadExecutor;
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.Mockito.*;
@RunWith(RobolectricTestRunner.class)
@Config(manifest= Config.NONE)
public class ThrottlingProducerTest {
private static final String PRODUCER_NAME = ThrottlingProducer.PRODUCER_NAME;
private static final int MAX_SIMULTANEOUS_REQUESTS = 2;
@Mock public Producer<Object> mInputProducer;
@Mock public Exception mException;
private final Consumer<Object>[] mConsumers = new Consumer[5];
private final ProducerContext[] mProducerContexts = new ProducerContext[5];
private final ProducerListener[] mProducerListeners = new ProducerListener[5];
private final String[] mRequestIds = new String[5];
private final Consumer<Object>[] mThrottlerConsumers = new Consumer[5];
private final Object[] mResults = new Object[5];
private ThrottlingProducer<Object> mThrottlingProducer;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mThrottlingProducer = new ThrottlingProducer<Object>(
MAX_SIMULTANEOUS_REQUESTS,
CallerThreadExecutor.getInstance(),
mInputProducer);
for (int i = 0; i < 5; i++) {
mConsumers[i] = mock(Consumer.class);
mProducerContexts[i] = mock(ProducerContext.class);
mProducerListeners[i] = mock(ProducerListener.class);
mRequestIds[i] = Integer.toString(i);
mResults[i] = mock(Object.class);
when(mProducerContexts[i].getListener()).thenReturn(mProducerListeners[i]);
when(mProducerContexts[i].getId()).thenReturn(mRequestIds[i]);
final int iFinal = i;
doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
mThrottlerConsumers[iFinal] =
(Consumer<Object>) invocation.getArguments()[0];
return null;
}
}).when(mInputProducer).produceResults(any(Consumer.class), eq(mProducerContexts[i]));
}
}
@Test
public void testThrottling() {
// First two requests are passed on immediately
mThrottlingProducer.produceResults(mConsumers[0], mProducerContexts[0]);
assertNotNull(mThrottlerConsumers[0]);
verify(mProducerListeners[0]).onProducerStart(mRequestIds[0], PRODUCER_NAME);
verify(mProducerListeners[0]).onProducerFinishWithSuccess(mRequestIds[0], PRODUCER_NAME, null);
mThrottlingProducer.produceResults(mConsumers[1], mProducerContexts[1]);
assertNotNull(mThrottlerConsumers[1]);
verify(mProducerListeners[1]).onProducerStart(mRequestIds[1], PRODUCER_NAME);
verify(mProducerListeners[1]).onProducerFinishWithSuccess(mRequestIds[1], PRODUCER_NAME, null);
// Third and fourth requests are queued up
mThrottlingProducer.produceResults(mConsumers[2], mProducerContexts[2]);
assertNull(mThrottlerConsumers[2]);
verify(mProducerListeners[2]).onProducerStart(mRequestIds[2], PRODUCER_NAME);
verify(mProducerListeners[2], never())
.onProducerFinishWithSuccess(mRequestIds[2], PRODUCER_NAME, null);
mThrottlingProducer.produceResults(mConsumers[3], mProducerContexts[3]);
assertNull(mThrottlerConsumers[3]);
verify(mProducerListeners[3]).onProducerStart(mRequestIds[3], PRODUCER_NAME);
verify(mProducerListeners[3], never())
.onProducerFinishWithSuccess(mRequestIds[3], PRODUCER_NAME, null);
// First request fails, third request is kicked off, fourth request remains in queue
mThrottlerConsumers[0].onFailure(mException);
verify(mConsumers[0]).onFailure(mException);
assertNotNull(mThrottlerConsumers[2]);
verify(mProducerListeners[2]).onProducerFinishWithSuccess(mRequestIds[2], PRODUCER_NAME, null);
assertNull(mThrottlerConsumers[3]);
verify(mProducerListeners[3], never())
.onProducerFinishWithSuccess(mRequestIds[3], PRODUCER_NAME, null);
// Fifth request is queued up
mThrottlingProducer.produceResults(mConsumers[4], mProducerContexts[4]);
assertNull(mThrottlerConsumers[4]);
verify(mProducerListeners[4]).onProducerStart(mRequestIds[4], PRODUCER_NAME);
verify(mProducerListeners[4], never())
.onProducerFinishWithSuccess(mRequestIds[4], PRODUCER_NAME, null);
// Second request gives intermediate result, no new request is kicked off
Object intermediateResult = mock(Object.class);
mThrottlerConsumers[1].onNewResult(intermediateResult, Consumer.NO_FLAGS);
verify(mConsumers[1]).onNewResult(intermediateResult, Consumer.NO_FLAGS);
assertNull(mThrottlerConsumers[3]);
assertNull(mThrottlerConsumers[4]);
// Third request finishes, fourth request is kicked off
mThrottlerConsumers[2].onNewResult(mResults[2], Consumer.IS_LAST);
verify(mConsumers[2]).onNewResult(mResults[2], Consumer.IS_LAST);
assertNotNull(mThrottlerConsumers[3]);
verify(mProducerListeners[3]).onProducerFinishWithSuccess(mRequestIds[3], PRODUCER_NAME, null);
assertNull(mThrottlerConsumers[4]);
// Second request is cancelled, fifth request is kicked off
mThrottlerConsumers[1].onCancellation();
verify(mConsumers[1]).onCancellation();
assertNotNull(mThrottlerConsumers[4]);
verify(mProducerListeners[4]).onProducerFinishWithSuccess(mRequestIds[4], PRODUCER_NAME, null);
// Fourth and fifth requests finish
mThrottlerConsumers[3].onNewResult(mResults[3], Consumer.IS_LAST);
mThrottlerConsumers[4].onNewResult(mResults[4], Consumer.IS_LAST);
}
@Test
public void testNoThrottlingAfterRequestsFinish() {
// First two requests are passed on immediately
mThrottlingProducer.produceResults(mConsumers[0], mProducerContexts[0]);
assertNotNull(mThrottlerConsumers[0]);
verify(mProducerListeners[0]).onProducerStart(mRequestIds[0], PRODUCER_NAME);
verify(mProducerListeners[0]).onProducerFinishWithSuccess(mRequestIds[0], PRODUCER_NAME, null);
mThrottlingProducer.produceResults(mConsumers[1], mProducerContexts[1]);
assertNotNull(mThrottlerConsumers[1]);
verify(mProducerListeners[1]).onProducerStart(mRequestIds[1], PRODUCER_NAME);
verify(mProducerListeners[1]).onProducerFinishWithSuccess(mRequestIds[1], PRODUCER_NAME, null);
// First two requests finish
mThrottlerConsumers[0].onNewResult(mResults[3], Consumer.IS_LAST);
mThrottlerConsumers[1].onNewResult(mResults[4], Consumer.IS_LAST);
// Next two requests are passed on immediately
mThrottlingProducer.produceResults(mConsumers[2], mProducerContexts[2]);
assertNotNull(mThrottlerConsumers[2]);
verify(mProducerListeners[2]).onProducerStart(mRequestIds[2], PRODUCER_NAME);
verify(mProducerListeners[2]).onProducerFinishWithSuccess(mRequestIds[2], PRODUCER_NAME, null);
mThrottlingProducer.produceResults(mConsumers[3], mProducerContexts[3]);
assertNotNull(mThrottlerConsumers[3]);
verify(mProducerListeners[3]).onProducerStart(mRequestIds[3], PRODUCER_NAME);
verify(mProducerListeners[3]).onProducerFinishWithSuccess(mRequestIds[3], PRODUCER_NAME, null);
// Next two requests finish
mThrottlerConsumers[2].onNewResult(mResults[3], Consumer.IS_LAST);
mThrottlerConsumers[3].onNewResult(mResults[4], Consumer.IS_LAST);
}
}