/* * 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.List; import java.util.concurrent.Executor; import com.facebook.common.executors.CallerThreadExecutor; import com.facebook.common.internal.Lists; import com.facebook.common.internal.Supplier; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.verification.VerificationMode; import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class DataSourceTestUtils { public static final boolean CLOSED = true; public static final boolean NOT_CLOSED = false; public static final boolean FINISHED = true; public static final boolean NOT_FINISHED = false; public static final boolean WITH_RESULT = true; public static final boolean WITHOUT_RESULT = false; public static final boolean FAILED = true; public static final boolean NOT_FAILED = false; public static final boolean LAST = true; public static final boolean INTERMEDIATE = false; public static final int NO_INTERACTIONS = 0; public static final int ON_NEW_RESULT = 1; public static final int ON_FAILURE = 2; public static final int ON_CANCELLATION = 3; public static VerificationMode optional() { return atLeast(0); } public static void setState( DataSource<Object> dataSource, boolean isClosed, boolean isFinished, boolean hasResult, Object value, boolean hasFailed, Throwable failureCause) { when(dataSource.isClosed()).thenReturn(isClosed); when(dataSource.isFinished()).thenReturn(isFinished); when(dataSource.hasResult()).thenReturn(hasResult); when(dataSource.getResult()).thenReturn(value); when(dataSource.hasFailed()).thenReturn(hasFailed); when(dataSource.getFailureCause()).thenReturn(failureCause); } public static <T> void verifyState( DataSource<T> dataSource, boolean isClosed, boolean isFinished, boolean hasResult, T result, boolean hasFailed, Throwable failureCause) { assertEquals("isClosed", isClosed, dataSource.isClosed()); assertEquals("isFinished", isFinished, dataSource.isFinished()); assertEquals("hasResult", hasResult, dataSource.hasResult()); assertSame("getResult", result, dataSource.getResult()); assertEquals("hasFailed", hasFailed, dataSource.hasFailed()); assertSame("failureCause", failureCause, dataSource.getFailureCause()); } public static class AbstractDataSourceSupplier { protected DataSource<Object> mSrc1; protected DataSource<Object> mSrc2; protected DataSource<Object> mSrc3; protected Supplier<DataSource<Object>> mDataSourceSupplier1; protected Supplier<DataSource<Object>> mDataSourceSupplier2; protected Supplier<DataSource<Object>> mDataSourceSupplier3; protected DataSubscriber<Object> mDataSubscriber; protected Executor mExecutor; protected InOrder mInOrder; protected List<Supplier<DataSource<Object>>> mSuppliers; protected Supplier<DataSource<Object>> mDataSourceSupplier; public void setUp() { mSrc1 = mock(DataSource.class); mSrc2 = mock(DataSource.class); mSrc3 = mock(DataSource.class); mDataSourceSupplier1 = mock(Supplier.class); mDataSourceSupplier2 = mock(Supplier.class); mDataSourceSupplier3 = mock(Supplier.class); when(mDataSourceSupplier1.get()).thenReturn(mSrc1); when(mDataSourceSupplier2.get()).thenReturn(mSrc2); when(mDataSourceSupplier3.get()).thenReturn(mSrc3); mDataSubscriber = mock(DataSubscriber.class); mExecutor = CallerThreadExecutor.getInstance(); mInOrder = inOrder( mSrc1, mSrc2, mSrc3, mDataSourceSupplier1, mDataSourceSupplier2, mDataSourceSupplier3, mDataSubscriber); mSuppliers = Lists.newArrayList( mDataSourceSupplier1, mDataSourceSupplier2, mDataSourceSupplier3); } protected void verifyNoMoreInteractionsAll() { verifyOptionals(mSrc1); verifyOptionals(mSrc2); verifyOptionals(mSrc3); mInOrder.verifyNoMoreInteractions(); verifyNoMoreInteractions( mSrc1, mSrc2, mSrc3, mDataSourceSupplier1, mDataSourceSupplier2, mDataSourceSupplier3, mDataSubscriber); } protected void verifyOptionals(DataSource<Object> underlyingDataSource) { mInOrder.verify(underlyingDataSource, optional()).isFinished(); mInOrder.verify(underlyingDataSource, optional()).hasResult(); mInOrder.verify(underlyingDataSource, optional()).hasFailed(); verify(underlyingDataSource, optional()).isFinished(); verify(underlyingDataSource, optional()).hasResult(); verify(underlyingDataSource, optional()).hasFailed(); } /** * Verifies that our mDataSourceSupplier got underlying data source and subscribed to it. * Subscriber is returned. */ protected DataSubscriber<Object> verifyGetAndSubscribe( Supplier<DataSource<Object>> dataSourceSupplier, DataSource<Object> underlyingDataSource, boolean expectMoreInteractions) { mInOrder.verify(dataSourceSupplier).get(); ArgumentCaptor<DataSubscriber> captor = ArgumentCaptor.forClass(DataSubscriber.class); mInOrder.verify(underlyingDataSource).subscribe(captor.capture(), any(Executor.class)); if (!expectMoreInteractions) { verifyNoMoreInteractionsAll(); } return captor.getValue(); } protected DataSubscriber<Object> verifyGetAndSubscribe( Supplier<DataSource<Object>> dataSourceSupplier, DataSource<Object> underlyingDataSource) { return verifyGetAndSubscribe(dataSourceSupplier, underlyingDataSource, false); } protected DataSubscriber<Object> verifyGetAndSubscribeM( Supplier<DataSource<Object>> dataSourceSupplier, DataSource<Object> underlyingDataSource) { return verifyGetAndSubscribe(dataSourceSupplier, underlyingDataSource, true); } /** * Verifies that data source provided by our mDataSourceSupplier notified mDataSubscriber. */ protected void verifySubscriber( DataSource<Object> dataSource, DataSource<Object> underlyingDataSource, int expected) { switch (expected) { case NO_INTERACTIONS: verifyNoMoreInteractionsAll(); break; case ON_NEW_RESULT: mInOrder.verify(mDataSubscriber).onNewResult(dataSource); verifyNoMoreInteractionsAll(); break; case ON_FAILURE: mInOrder.verify(underlyingDataSource).getFailureCause(); mInOrder.verify(mDataSubscriber).onFailure(dataSource); verifyNoMoreInteractionsAll(); break; case ON_CANCELLATION: verify(mDataSubscriber).onCancellation(dataSource); verifyNoMoreInteractionsAll(); break; } } /** * Verifies the state of the data source provided by our mDataSourceSupplier. */ protected void verifyState( DataSource<Object> dataSource, @Nullable DataSource<Object> dataSourceWithResult, boolean isClosed, boolean isFinished, boolean hasResult, Object result, boolean hasFailed, Throwable failureCause) { DataSourceTestUtils.verifyState( dataSource, isClosed, isFinished, hasResult, result, hasFailed, failureCause); // DataSourceTestUtils.verifyState will call dataSource.getResult() which should forward to // underlyingDataSource.getResult() if (dataSourceWithResult != null) { mInOrder.verify(dataSourceWithResult).getResult(); } verifyNoMoreInteractionsAll(); } /** * Verifies that the underlying data sources get closed when data source provided by * our mDataSourceSupplier gets closed. */ protected void testClose( DataSource<Object> dataSource, DataSource<Object>... underlyingDataSources) { dataSource.close(); if (underlyingDataSources != null) { for (DataSource<Object> underlyingDataSource : underlyingDataSources) { mInOrder.verify(underlyingDataSource, atLeastOnce()).close(); } } } /** * Gets data source from our mDataSourceSupplier and subscribes mDataSubscriber to it. * Obtained data source is returned. */ protected DataSource<Object> getAndSubscribe() { DataSource<Object> dataSource = mDataSourceSupplier.get(); dataSource.subscribe(mDataSubscriber, mExecutor); return dataSource; } /** Respond to subscriber with given data source and response. */ protected static <T> void respond( DataSubscriber<T> subscriber, DataSource<T> dataSource, int response) { switch (response) { case NO_INTERACTIONS: break; case ON_NEW_RESULT: subscriber.onNewResult(dataSource); break; case ON_FAILURE: subscriber.onFailure(dataSource); break; case ON_CANCELLATION: subscriber.onCancellation(dataSource); break; } } /** Schedule response on subscribe. */ protected static <T> void respondOnSubscribe( final DataSource<T> dataSource, final int response) { doAnswer( new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { DataSubscriber<T> subscriber = (DataSubscriber<T>) invocation.getArguments()[0]; respond(subscriber, dataSource, response); return subscriber; } }).when(dataSource).subscribe(any(DataSubscriber.class), any(Executor.class)); } } }