/* * 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.datasource; import javax.annotation.Nullable; import java.util.concurrent.Executor; import org.robolectric.RobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import static com.facebook.datasource.DataSourceTestUtils.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @RunWith(RobolectricTestRunner.class) public class AbstractDataSourceTest { public interface Value { public void close(); } private static class FakeAbstractDataSource extends AbstractDataSource<Value> { @Override public boolean setResult(@Nullable Value value, boolean isLast) { return super.setResult(value, isLast); } @Override public boolean setFailure(Throwable throwable) { return super.setFailure(throwable); } @Override public boolean setProgress(float progress) { return super.setProgress(progress); } @Override public void closeResult(Value result) { result.close(); } } private Executor mExecutor1; private Executor mExecutor2; private DataSubscriber<Value> mDataSubscriber1; private DataSubscriber<Value> mDataSubscriber2; private FakeAbstractDataSource mDataSource; @Before public void setUp() { mExecutor1 = mock(Executor.class); mExecutor2 = mock(Executor.class); mDataSubscriber1 = mock(DataSubscriber.class); mDataSubscriber2 = mock(DataSubscriber.class); mDataSource = new FakeAbstractDataSource(); } private void verifyExecutor(Executor executor) { ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); verify(executor).execute(captor.capture()); Runnable runnable = captor.getValue(); assertNotNull(runnable); runnable.run(); } private void verifySubscribers(int expected) { switch (expected) { case NO_INTERACTIONS: verifyZeroInteractions(mExecutor1, mDataSubscriber1); verifyZeroInteractions(mExecutor2, mDataSubscriber2); break; case ON_NEW_RESULT: verifyExecutor(mExecutor1); verify(mDataSubscriber1).onNewResult(mDataSource); verifyExecutor(mExecutor2); verify(mDataSubscriber2).onNewResult(mDataSource); break; case ON_FAILURE: verifyExecutor(mExecutor1); verify(mDataSubscriber1).onFailure(mDataSource); verifyExecutor(mExecutor2); verify(mDataSubscriber2).onFailure(mDataSource); break; case ON_CANCELLATION: verifyExecutor(mExecutor1); verify(mDataSubscriber1).onCancellation(mDataSource); verifyExecutor(mExecutor2); verify(mDataSubscriber2).onCancellation(mDataSource); break; } reset(mExecutor1, mExecutor2, mDataSubscriber1, mDataSubscriber2); } private void subscribe() { mDataSource.subscribe(mDataSubscriber1, mExecutor1); mDataSource.subscribe(mDataSubscriber2, mExecutor2); } @Test public void testInitialState() { verifyState(mDataSource, NOT_CLOSED, NOT_FINISHED, WITHOUT_RESULT, null, NOT_FAILED, null); } @Test public void testLifeCycle_LastResult_Close() { subscribe(); // last result Value value = mock(Value.class); mDataSource.setResult(value, LAST); verifySubscribers(ON_NEW_RESULT); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITH_RESULT, value, NOT_FAILED, null); // close mDataSource.close(); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, CLOSED, FINISHED, WITHOUT_RESULT, null, NOT_FAILED, null); } @Test public void testLifeCycle_Failure_Close() { subscribe(); // failure Throwable throwable = mock(Throwable.class); mDataSource.setFailure(throwable); verifySubscribers(ON_FAILURE); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITHOUT_RESULT, null, FAILED, throwable); // close mDataSource.close(); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, CLOSED, FINISHED, WITHOUT_RESULT, null, FAILED, throwable); } @Test public void testLifeCycle_IntermediateResult_LastResult_Close() { subscribe(); // intermediate result Value value1 = mock(Value.class); mDataSource.setResult(value1, INTERMEDIATE); verifySubscribers(ON_NEW_RESULT); verifyState(mDataSource, NOT_CLOSED, NOT_FINISHED, WITH_RESULT, value1, NOT_FAILED, null); // last result Value value = mock(Value.class); mDataSource.setResult(value, LAST); verifySubscribers(ON_NEW_RESULT); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITH_RESULT, value, NOT_FAILED, null); // close mDataSource.close(); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, CLOSED, FINISHED, WITHOUT_RESULT, null, NOT_FAILED, null); } @Test public void testLifeCycle_IntermediateResult_Failure_Close() { subscribe(); // intermediate result Value value1 = mock(Value.class); mDataSource.setResult(value1, INTERMEDIATE); verifySubscribers(ON_NEW_RESULT); verifyState(mDataSource, NOT_CLOSED, NOT_FINISHED, WITH_RESULT, value1, NOT_FAILED, null); // failure Throwable throwable = mock(Throwable.class); mDataSource.setFailure(throwable); verifySubscribers(ON_FAILURE); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITH_RESULT, value1, FAILED, throwable); // close mDataSource.close(); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, CLOSED, FINISHED, WITHOUT_RESULT, null, FAILED, throwable); } @Test public void testLifeCycle_AfterSuccess() { subscribe(); // success Value value = mock(Value.class); mDataSource.setResult(value, LAST); verifySubscribers(ON_NEW_RESULT); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITH_RESULT, value, NOT_FAILED, null); // try intermediate mDataSource.setResult(mock(Value.class), INTERMEDIATE); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITH_RESULT, value, NOT_FAILED, null); // try last mDataSource.setResult(mock(Value.class), LAST); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITH_RESULT, value, NOT_FAILED, null); // try failure mDataSource.setFailure(mock(Throwable.class)); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITH_RESULT, value, NOT_FAILED, null); } @Test public void testLifeCycle_AfterFailure() { subscribe(); // failure Throwable throwable = mock(Throwable.class); mDataSource.setFailure(throwable); verifySubscribers(ON_FAILURE); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITHOUT_RESULT, null, FAILED, throwable); // try intermediate mDataSource.setResult(mock(Value.class), INTERMEDIATE); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITHOUT_RESULT, null, FAILED, throwable); // try last mDataSource.setResult(mock(Value.class), LAST); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITHOUT_RESULT, null, FAILED, throwable); // try failure mDataSource.setFailure(mock(Throwable.class)); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, NOT_CLOSED, FINISHED, WITHOUT_RESULT, null, FAILED, throwable); } @Test public void testLifeCycle_AfterClose() { subscribe(); // close mDataSource.close(); verifySubscribers(ON_CANCELLATION); verifyState(mDataSource, CLOSED, NOT_FINISHED, WITHOUT_RESULT, null, NOT_FAILED, null); // try intermediate mDataSource.setResult(mock(Value.class), INTERMEDIATE); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, CLOSED, NOT_FINISHED, WITHOUT_RESULT, null, NOT_FAILED, null); // try last mDataSource.setResult(mock(Value.class), LAST); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, CLOSED, NOT_FINISHED, WITHOUT_RESULT, null, NOT_FAILED, null); // try failure mDataSource.setFailure(mock(Throwable.class)); verifySubscribers(NO_INTERACTIONS); verifyState(mDataSource, CLOSED, NOT_FINISHED, WITHOUT_RESULT, null, NOT_FAILED, null); } @Test public void testSubscribe_InProgress_WithoutResult() { subscribe(); verifySubscribers(NO_INTERACTIONS); } @Test public void testSubscribe_InProgress_WithResult() { mDataSource.setResult(mock(Value.class), INTERMEDIATE); subscribe(); verifySubscribers(ON_NEW_RESULT); } @Test public void testSubscribe_Finished_WithoutResult() { mDataSource.setResult(null, LAST); subscribe(); verifySubscribers(ON_NEW_RESULT); } @Test public void testSubscribe_Finished_WithResult() { mDataSource.setResult(mock(Value.class), LAST); subscribe(); verifySubscribers(ON_NEW_RESULT); } @Test public void testSubscribe_Failed_WithoutResult() { mDataSource.setFailure(mock(Throwable.class)); subscribe(); verifySubscribers(ON_FAILURE); } @Test public void testSubscribe_Failed_WithResult() { mDataSource.setResult(mock(Value.class), INTERMEDIATE); mDataSource.setFailure(mock(Throwable.class)); subscribe(); verifySubscribers(ON_FAILURE); } @Test public void testSubscribe_Closed_AfterSuccess() { mDataSource.setResult(mock(Value.class), LAST); mDataSource.close(); subscribe(); verifySubscribers(NO_INTERACTIONS); } @Test public void testSubscribe_Closed_AfterFailure() { mDataSource.setFailure(mock(Throwable.class)); mDataSource.close(); subscribe(); verifySubscribers(NO_INTERACTIONS); } @Test public void testCloseResult() { Value value1 = mock(Value.class); mDataSource.setResult(value1, false); Value value2 = mock(Value.class); mDataSource.setResult(value2, false); verify(value1).close(); verify(value2, never()).close(); Value value3 = mock(Value.class); mDataSource.setResult(value3, false); verify(value2).close(); verify(value3, never()).close(); mDataSource.close(); verify(value3).close(); } }