Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 33f68ebf authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Clean-up PersisterQueue tests" into qt-dev

parents 784999f2 ba2519ca
Loading
Loading
Loading
Loading
+179 −107
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

/**
 * Build/Install/Run:
@@ -47,208 +46,222 @@ import java.util.function.Predicate;
 */
@MediumTest
@Presubmit
public class PersisterQueueTests implements PersisterQueue.Listener {
public class PersisterQueueTests {
    private static final long INTER_WRITE_DELAY_MS = 50;
    private static final long PRE_TASK_DELAY_MS = 300;
    // We allow at most 1s more than the expected timeout.
    private static final long TIMEOUT_ALLOWANCE = 100;

    private static final Predicate<MatchingTestItem> TEST_ITEM_PREDICATE = item -> item.mMatching;

    private AtomicInteger mItemCount;
    private CountDownLatch mSetUpLatch;
    private volatile CountDownLatch mLatch;
    private List<Boolean> mProbablyDoneResults;
    // We allow at most 0.2s more than the expected timeout.
    private static final long TIMEOUT_ALLOWANCE = 200;

    private final PersisterQueue mTarget =
            new PersisterQueue(INTER_WRITE_DELAY_MS, PRE_TASK_DELAY_MS);

    private TestPersisterQueueListener mListener;
    private TestWriteQueueItemFactory mFactory;

    @Before
    public void setUp() throws Exception {
        mItemCount = new AtomicInteger(0);
        mProbablyDoneResults = new ArrayList<>();
        mSetUpLatch = new CountDownLatch(1);
        mListener = new TestPersisterQueueListener();
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);
        mTarget.addListener(mListener);

        mFactory = new TestWriteQueueItemFactory();

        mTarget.addListener(this);
        mTarget.startPersisting();

        assertTrue("Target didn't call callback on start up.",
                mSetUpLatch.await(TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
                mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
    }

    @After
    public void tearDown() throws Exception {
        mTarget.stopPersisting();
        mTarget.removeListener(this);
        mTarget.removeListener(mListener);
    }

    @Test
    public void testCallCallbackOnStartUp() {
        // The onPreProcessItem() must be called on start up.
        assertEquals(1, mProbablyDoneResults.size());
        assertEquals(1, mListener.mProbablyDoneResults.size());
        // The last one must be called with probably done being true.
        assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(0));
        assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(0));
    }

    @Test
    public void testProcessOneItem() throws Exception {
        mLatch = new CountDownLatch(1);
        mFactory.setExpectedProcessedItemNumber(1);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);

        final long dispatchTime = SystemClock.uptimeMillis();
        mTarget.addItem(new TestItem(), false);
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process item.", 1, mItemCount.get());
        mTarget.addItem(mFactory.createItem(), false);
        assertTrue("Target didn't process item enough times.",
                mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
        final long processDuration = SystemClock.uptimeMillis() - dispatchTime;
        assertTrue("Target didn't wait enough time before processing item. duration: "
                        + processDuration + "ms pretask delay: " + PRE_TASK_DELAY_MS + "ms",
                processDuration >= PRE_TASK_DELAY_MS);

        assertTrue("Target didn't call callback enough times.",
                mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
        // Once before processing this item, once after that.
        assertEquals(2, mProbablyDoneResults.size());
        assertEquals(2, mListener.mProbablyDoneResults.size());
        // The last one must be called with probably done being true.
        assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(1));
        assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(1));
    }

    @Test
    public void testProcessOneItem_Flush() throws Exception {
        mLatch = new CountDownLatch(1);
        mFactory.setExpectedProcessedItemNumber(1);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);

        final long dispatchTime = SystemClock.uptimeMillis();
        mTarget.addItem(new TestItem(), true);
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process item.", 1, mItemCount.get());
        mTarget.addItem(mFactory.createItem(), true);
        assertTrue("Target didn't process item enough times.",
                mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
        final long processDuration = SystemClock.uptimeMillis() - dispatchTime;
        assertTrue("Target didn't process item immediately when flushing. duration: "
                        + processDuration + "ms pretask delay: "
                        + PRE_TASK_DELAY_MS + "ms",
                processDuration < PRE_TASK_DELAY_MS);

        assertTrue("Target didn't call callback enough times.",
                mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE));
        // Once before processing this item, once after that.
        assertEquals(2, mProbablyDoneResults.size());
        assertEquals(2, mListener.mProbablyDoneResults.size());
        // The last one must be called with probably done being true.
        assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(1));
        assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(1));
    }

    @Test
    public void testProcessTwoItems() throws Exception {
        mLatch = new CountDownLatch(2);
        mFactory.setExpectedProcessedItemNumber(2);
        mListener.setExpectedOnPreProcessItemCallbackTimes(2);

        final long dispatchTime = SystemClock.uptimeMillis();
        mTarget.addItem(new TestItem(), false);
        mTarget.addItem(new TestItem(), false);
        mTarget.addItem(mFactory.createItem(), false);
        mTarget.addItem(mFactory.createItem(), false);
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS + TIMEOUT_ALLOWANCE,
                        TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process all items.", 2, mItemCount.get());
                mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS
                        + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process all items.", 2, mFactory.getTotalProcessedItemCount());
        final long processDuration = SystemClock.uptimeMillis() - dispatchTime;
        assertTrue("Target didn't wait enough time before processing item. duration: "
                        + processDuration + "ms pretask delay: " + PRE_TASK_DELAY_MS
                        + "ms inter write delay: " + INTER_WRITE_DELAY_MS + "ms",
                processDuration >= PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS);

        assertTrue("Target didn't call the onPreProcess callback enough times",
                mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
        // Once before processing this item, once after that.
        assertEquals(3, mProbablyDoneResults.size());
        assertEquals(3, mListener.mProbablyDoneResults.size());
        // The first one must be called with probably done being false.
        assertFalse("The first probablyDone must be false.", mProbablyDoneResults.get(1));
        assertFalse("The first probablyDone must be false.", mListener.mProbablyDoneResults.get(1));
        // The last one must be called with probably done being true.
        assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(2));
        assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(2));
    }

    @Test
    @FlakyTest(bugId = 128526085)
    public void testProcessTwoItems_OneAfterAnother() throws Exception {
        // First item
        mLatch = new CountDownLatch(1);
        mFactory.setExpectedProcessedItemNumber(1);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);
        long dispatchTime = SystemClock.uptimeMillis();
        mTarget.addItem(new TestItem(), false);
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        mTarget.addItem(mFactory.createItem(), false);
        assertTrue("Target didn't process item enough times.",
                mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
        long processDuration = SystemClock.uptimeMillis() - dispatchTime;
        assertTrue("Target didn't wait enough time before processing item."
                        + processDuration + "ms pretask delay: "
                        + PRE_TASK_DELAY_MS + "ms",
                processDuration >= PRE_TASK_DELAY_MS);
        assertEquals("Target didn't process item.", 1, mItemCount.get());
        assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
        assertTrue("Target didn't call callback enough times.",
                mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));

        // Second item
        mLatch = new CountDownLatch(1);
        mFactory.setExpectedProcessedItemNumber(1);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);
        dispatchTime = SystemClock.uptimeMillis();
        // Synchronize on the instance to make sure we schedule the item after it starts to wait for
        // task indefinitely.
        synchronized (mTarget) {
            mTarget.addItem(new TestItem(), false);
            mTarget.addItem(mFactory.createItem(), false);
        }
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process all items.", 2, mItemCount.get());
        assertTrue("Target didn't process item enough times.",
                mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process all items.", 2, mFactory.getTotalProcessedItemCount());
        processDuration = SystemClock.uptimeMillis() - dispatchTime;
        assertTrue("Target didn't wait enough time before processing item. Process time: "
                        + processDuration + "ms pre task delay: "
                        + PRE_TASK_DELAY_MS + "ms",
                processDuration >= PRE_TASK_DELAY_MS);

        assertTrue("Target didn't call callback enough times.",
                mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
        // Once before processing this item, once after that.
        assertEquals(3, mProbablyDoneResults.size());
        assertEquals(3, mListener.mProbablyDoneResults.size());
        // The last one must be called with probably done being true.
        assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(2));
        assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(2));
    }

    @Test
    public void testFindLastItemNotReturnDifferentType() {
        synchronized (mTarget) {
            mTarget.addItem(new TestItem(), false);
            assertNull(mTarget.findLastItem(TEST_ITEM_PREDICATE, MatchingTestItem.class));
            mTarget.addItem(mFactory.createItem(), false);
            assertNull(mTarget.findLastItem(TestItem::shouldKeepOnFilter,
                    FilterableTestItem.class));
        }
    }

    @Test
    public void testFindLastItemNotReturnMismatchItem() {
        synchronized (mTarget) {
            mTarget.addItem(new MatchingTestItem(false), false);
            assertNull(mTarget.findLastItem(TEST_ITEM_PREDICATE, MatchingTestItem.class));
            mTarget.addItem(mFactory.createFilterableItem(false), false);
            assertNull(mTarget.findLastItem(TestItem::shouldKeepOnFilter,
                    FilterableTestItem.class));
        }
    }

    @Test
    public void testFindLastItemReturnMatchedItem() {
        synchronized (mTarget) {
            final MatchingTestItem item = new MatchingTestItem(true);
            final FilterableTestItem item = mFactory.createFilterableItem(true);
            mTarget.addItem(item, false);
            assertSame(item, mTarget.findLastItem(TEST_ITEM_PREDICATE, MatchingTestItem.class));
            assertSame(item, mTarget.findLastItem(TestItem::shouldKeepOnFilter,
                    FilterableTestItem.class));
        }
    }

    @Test
    public void testRemoveItemsNotRemoveDifferentType() throws Exception {
        mLatch = new CountDownLatch(1);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);
        synchronized (mTarget) {
            mTarget.addItem(new TestItem(), false);
            mTarget.removeItems(TEST_ITEM_PREDICATE, MatchingTestItem.class);
            mTarget.addItem(mFactory.createItem(), false);
            mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class);
        }
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process item.", 1, mItemCount.get());
                mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
    }

    @Test
    public void testRemoveItemsNotRemoveMismatchedItem() throws Exception {
        mLatch = new CountDownLatch(1);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);
        synchronized (mTarget) {
            mTarget.addItem(new MatchingTestItem(false), false);
            mTarget.removeItems(TEST_ITEM_PREDICATE, MatchingTestItem.class);
            mTarget.addItem(mFactory.createFilterableItem(false), false);
            mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class);
        }
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process item.", 1, mItemCount.get());
                mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
    }

    @Test
    public void testUpdateLastOrAddItemUpdatesMatchedItem() throws Exception {
        mLatch = new CountDownLatch(1);
        final MatchingTestItem scheduledItem = new MatchingTestItem(true);
        final MatchingTestItem expected = new MatchingTestItem(true);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);
        final FilterableTestItem scheduledItem = mFactory.createFilterableItem(true);
        final FilterableTestItem expected = mFactory.createFilterableItem(true);
        synchronized (mTarget) {
            mTarget.addItem(scheduledItem, false);
            mTarget.updateLastOrAddItem(expected, false);
@@ -256,15 +269,15 @@ public class PersisterQueueTests implements PersisterQueue.Listener {

        assertSame(expected, scheduledItem.mUpdateFromItem);
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process item.", 1, mItemCount.get());
                mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
    }

    @Test
    public void testUpdateLastOrAddItemUpdatesAddItemWhenNoMatch() throws Exception {
        mLatch = new CountDownLatch(2);
        final MatchingTestItem scheduledItem = new MatchingTestItem(false);
        final MatchingTestItem expected = new MatchingTestItem(true);
        mListener.setExpectedOnPreProcessItemCallbackTimes(2);
        final FilterableTestItem scheduledItem = mFactory.createFilterableItem(false);
        final FilterableTestItem expected = mFactory.createFilterableItem(true);
        synchronized (mTarget) {
            mTarget.addItem(scheduledItem, false);
            mTarget.updateLastOrAddItem(expected, false);
@@ -272,73 +285,132 @@ public class PersisterQueueTests implements PersisterQueue.Listener {

        assertNull(scheduledItem.mUpdateFromItem);
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS + TIMEOUT_ALLOWANCE,
                        TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process item.", 2, mItemCount.get());
                mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS
                        + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process item.", 2, mFactory.getTotalProcessedItemCount());
    }

    @Test
    public void testRemoveItemsRemoveMatchedItem() throws Exception {
        mLatch = new CountDownLatch(1);
        mListener.setExpectedOnPreProcessItemCallbackTimes(1);
        synchronized (mTarget) {
            mTarget.addItem(new TestItem(), false);
            mTarget.addItem(new MatchingTestItem(true), false);
            mTarget.removeItems(TEST_ITEM_PREDICATE, MatchingTestItem.class);
            mTarget.addItem(mFactory.createItem(), false);
            mTarget.addItem(mFactory.createFilterableItem(true), false);
            mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class);
        }
        assertTrue("Target didn't call callback enough times.",
                mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS));
        assertEquals("Target didn't process item.", 1, mItemCount.get());
                mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
        assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
    }

    @Test
    public void testFlushWaitSynchronously() {
        final long dispatchTime = SystemClock.uptimeMillis();
        mTarget.addItem(new TestItem(), false);
        mTarget.addItem(new TestItem(), false);
        mTarget.addItem(mFactory.createItem(), false);
        mTarget.addItem(mFactory.createItem(), false);
        mTarget.flush();
        assertEquals("Flush should wait until all items are processed before return.",
                2, mItemCount.get());
                2, mFactory.getTotalProcessedItemCount());
        final long processTime = SystemClock.uptimeMillis() - dispatchTime;
        assertWithMessage("Flush should trigger immediate flush without delays. processTime: "
                + processTime).that(processTime).isLessThan(TIMEOUT_ALLOWANCE);
    }

    @Override
    public void onPreProcessItem(boolean queueEmpty) {
        mProbablyDoneResults.add(queueEmpty);
    private static class TestWriteQueueItemFactory {
        private final AtomicInteger mItemCount = new AtomicInteger(0);;
        private CountDownLatch mLatch;

        final CountDownLatch latch = mLatch;
        if (latch != null) {
            latch.countDown();
        int getTotalProcessedItemCount() {
            return mItemCount.get();
        }

        mSetUpLatch.countDown();
        void setExpectedProcessedItemNumber(int countDown) {
            mLatch = new CountDownLatch(countDown);
        }

        boolean waitForAllExpectedItemsProcessed(long timeoutInMilliseconds)
                throws InterruptedException {
            return mLatch.await(timeoutInMilliseconds, TimeUnit.MILLISECONDS);
        }

        TestItem createItem() {
            return new TestItem(mItemCount, mLatch);
        }

        FilterableTestItem createFilterableItem(boolean shouldKeepOnFilter) {
            return new FilterableTestItem(shouldKeepOnFilter, mItemCount, mLatch);
        }
    }

    private static class TestItem<T extends TestItem<T>>
            implements PersisterQueue.WriteQueueItem<T> {
        private AtomicInteger mItemCount;
        private CountDownLatch mLatch;

        TestItem(AtomicInteger itemCount, CountDownLatch latch) {
            mItemCount = itemCount;
            mLatch = latch;
        }

    private class TestItem<T extends TestItem<T>> implements PersisterQueue.WriteQueueItem<T> {
        @Override
        public void process() {
            mItemCount.getAndIncrement();
            if (mLatch != null) {
                // Count down the latch at the last step is necessary, as it's a kind of lock to the
                // next assert in many test cases.
                mLatch.countDown();
            }
        }

    private class MatchingTestItem extends TestItem<MatchingTestItem> {
        private boolean mMatching;
        boolean shouldKeepOnFilter() {
            return true;
        }
    }

    private static class FilterableTestItem extends TestItem<FilterableTestItem> {
        private boolean mShouldKeepOnFilter;

        private MatchingTestItem mUpdateFromItem;
        private FilterableTestItem mUpdateFromItem;

        private MatchingTestItem(boolean matching) {
            mMatching = matching;
        private FilterableTestItem(boolean shouldKeepOnFilter, AtomicInteger mItemCount,
                CountDownLatch mLatch) {
            super(mItemCount, mLatch);
            mShouldKeepOnFilter = shouldKeepOnFilter;
        }

        @Override
        public boolean matches(MatchingTestItem item) {
            return item.mMatching;
        public boolean matches(FilterableTestItem item) {
            return item.mShouldKeepOnFilter;
        }

        @Override
        public void updateFrom(MatchingTestItem item) {
        public void updateFrom(FilterableTestItem item) {
            mUpdateFromItem = item;
        }

        @Override
        boolean shouldKeepOnFilter() {
            return mShouldKeepOnFilter;
        }
    }

    private class TestPersisterQueueListener implements PersisterQueue.Listener {
        CountDownLatch mCallbackLatch;
        final List<Boolean> mProbablyDoneResults = new ArrayList<>();

        @Override
        public void onPreProcessItem(boolean queueEmpty) {
            mProbablyDoneResults.add(queueEmpty);
            mCallbackLatch.countDown();
        }

        void setExpectedOnPreProcessItemCallbackTimes(int countDown) {
            mCallbackLatch = new CountDownLatch(countDown);
        }

        boolean waitForAllExpectedCallbackDone(long timeoutInMilliseconds)
                throws InterruptedException {
            return mCallbackLatch.await(timeoutInMilliseconds, TimeUnit.MILLISECONDS);
        }
    }
}