1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.wm;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertSame;
25 import static org.junit.Assert.assertTrue;
26 
27 import android.os.SystemClock;
28 import android.platform.test.annotations.Presubmit;
29 
30 import androidx.test.filters.MediumTest;
31 
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.Test;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.TimeUnit;
40 import java.util.concurrent.atomic.AtomicInteger;
41 
42 /**
43  * Build/Install/Run:
44  *  atest WmTests:PersisterQueueTests
45  */
46 @MediumTest
47 @Presubmit
48 public class PersisterQueueTests {
49     private static final long INTER_WRITE_DELAY_MS = 50;
50     private static final long PRE_TASK_DELAY_MS = 300;
51     // We allow at most 0.2s more than the expected timeout.
52     private static final long TIMEOUT_ALLOWANCE = 200;
53 
54     private final PersisterQueue mTarget =
55             new PersisterQueue(INTER_WRITE_DELAY_MS, PRE_TASK_DELAY_MS);
56 
57     private TestPersisterQueueListener mListener;
58     private TestWriteQueueItemFactory mFactory;
59 
60     @Before
setUp()61     public void setUp() throws Exception {
62         mListener = new TestPersisterQueueListener();
63         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
64         mTarget.addListener(mListener);
65 
66         mFactory = new TestWriteQueueItemFactory();
67 
68         mTarget.startPersisting();
69 
70         assertTrue("Target didn't call callback on start up.",
71                 mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
72     }
73 
74     @After
tearDown()75     public void tearDown() throws Exception {
76         mTarget.stopPersisting();
77         mTarget.removeListener(mListener);
78     }
79 
80     @Test
testCallCallbackOnStartUp()81     public void testCallCallbackOnStartUp() {
82         // The onPreProcessItem() must be called on start up.
83         assertEquals(1, mListener.mProbablyDoneResults.size());
84         // The last one must be called with probably done being true.
85         assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(0));
86     }
87 
88     @Test
testProcessOneItem()89     public void testProcessOneItem() throws Exception {
90         mFactory.setExpectedProcessedItemNumber(1);
91         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
92 
93         final long dispatchTime = SystemClock.uptimeMillis();
94         mTarget.addItem(mFactory.createItem(), false);
95         assertTrue("Target didn't process item enough times.",
96                 mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
97         assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
98         final long processDuration = SystemClock.uptimeMillis() - dispatchTime;
99         assertTrue("Target didn't wait enough time before processing item. duration: "
100                         + processDuration + "ms pretask delay: " + PRE_TASK_DELAY_MS + "ms",
101                 processDuration >= PRE_TASK_DELAY_MS);
102 
103         assertTrue("Target didn't call callback enough times.",
104                 mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
105         // Once before processing this item, once after that.
106         assertEquals(2, mListener.mProbablyDoneResults.size());
107         // The last one must be called with probably done being true.
108         assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(1));
109     }
110 
111     @Test
testProcessOneItem_Flush()112     public void testProcessOneItem_Flush() throws Exception {
113         mFactory.setExpectedProcessedItemNumber(1);
114         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
115 
116         final long dispatchTime = SystemClock.uptimeMillis();
117         mTarget.addItem(mFactory.createItem(), true);
118         assertTrue("Target didn't process item enough times.",
119                 mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE));
120         assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
121         final long processDuration = SystemClock.uptimeMillis() - dispatchTime;
122         assertTrue("Target didn't process item immediately when flushing. duration: "
123                         + processDuration + "ms pretask delay: "
124                         + PRE_TASK_DELAY_MS + "ms",
125                 processDuration < PRE_TASK_DELAY_MS);
126 
127         assertTrue("Target didn't call callback enough times.",
128                 mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE));
129         // Once before processing this item, once after that.
130         assertEquals(2, mListener.mProbablyDoneResults.size());
131         // The last one must be called with probably done being true.
132         assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(1));
133     }
134 
135     @Test
testProcessTwoItems()136     public void testProcessTwoItems() throws Exception {
137         mFactory.setExpectedProcessedItemNumber(2);
138         mListener.setExpectedOnPreProcessItemCallbackTimes(2);
139 
140         final long dispatchTime = SystemClock.uptimeMillis();
141         mTarget.addItem(mFactory.createItem(), false);
142         mTarget.addItem(mFactory.createItem(), false);
143         assertTrue("Target didn't call callback enough times.",
144                 mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS
145                         + TIMEOUT_ALLOWANCE));
146         assertEquals("Target didn't process all items.", 2, mFactory.getTotalProcessedItemCount());
147         final long processDuration = SystemClock.uptimeMillis() - dispatchTime;
148         assertTrue("Target didn't wait enough time before processing item. duration: "
149                         + processDuration + "ms pretask delay: " + PRE_TASK_DELAY_MS
150                         + "ms inter write delay: " + INTER_WRITE_DELAY_MS + "ms",
151                 processDuration >= PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS);
152         assertTrue("Target didn't call the onPreProcess callback enough times",
153                 mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
154         // Once before processing this item, once after that.
155         assertEquals(3, mListener.mProbablyDoneResults.size());
156         // The first one must be called with probably done being false.
157         assertFalse("The first probablyDone must be false.", mListener.mProbablyDoneResults.get(1));
158         // The last one must be called with probably done being true.
159         assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(2));
160     }
161 
162     @Test
testProcessTwoItems_OneAfterAnother()163     public void testProcessTwoItems_OneAfterAnother() throws Exception {
164         // First item
165         mFactory.setExpectedProcessedItemNumber(1);
166         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
167         long dispatchTime = SystemClock.uptimeMillis();
168         mTarget.addItem(mFactory.createItem(), false);
169         assertTrue("Target didn't process item enough times.",
170                 mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
171         long processDuration = SystemClock.uptimeMillis() - dispatchTime;
172         assertTrue("Target didn't wait enough time before processing item."
173                         + processDuration + "ms pretask delay: "
174                         + PRE_TASK_DELAY_MS + "ms",
175                 processDuration >= PRE_TASK_DELAY_MS);
176         assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
177         assertTrue("Target didn't call callback enough times.",
178                 mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
179 
180         // Second item
181         mFactory.setExpectedProcessedItemNumber(1);
182         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
183         dispatchTime = SystemClock.uptimeMillis();
184         // Synchronize on the instance to make sure we schedule the item after it starts to wait for
185         // task indefinitely.
186         synchronized (mTarget) {
187             mTarget.addItem(mFactory.createItem(), false);
188         }
189         assertTrue("Target didn't process item enough times.",
190                 mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
191         assertEquals("Target didn't process all items.", 2, mFactory.getTotalProcessedItemCount());
192         processDuration = SystemClock.uptimeMillis() - dispatchTime;
193         assertTrue("Target didn't wait enough time before processing item. Process time: "
194                         + processDuration + "ms pre task delay: "
195                         + PRE_TASK_DELAY_MS + "ms",
196                 processDuration >= PRE_TASK_DELAY_MS);
197 
198         assertTrue("Target didn't call callback enough times.",
199                 mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
200         // Once before processing this item, once after that.
201         assertEquals(3, mListener.mProbablyDoneResults.size());
202         // The last one must be called with probably done being true.
203         assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(2));
204     }
205 
206     @Test
testFindLastItemNotReturnDifferentType()207     public void testFindLastItemNotReturnDifferentType() {
208         synchronized (mTarget) {
209             mTarget.addItem(mFactory.createItem(), false);
210             assertNull(mTarget.findLastItem(TestItem::shouldKeepOnFilter,
211                     FilterableTestItem.class));
212         }
213     }
214 
215     @Test
testFindLastItemNotReturnMismatchItem()216     public void testFindLastItemNotReturnMismatchItem() {
217         synchronized (mTarget) {
218             mTarget.addItem(mFactory.createFilterableItem(false), false);
219             assertNull(mTarget.findLastItem(TestItem::shouldKeepOnFilter,
220                     FilterableTestItem.class));
221         }
222     }
223 
224     @Test
testFindLastItemReturnMatchedItem()225     public void testFindLastItemReturnMatchedItem() {
226         synchronized (mTarget) {
227             final FilterableTestItem item = mFactory.createFilterableItem(true);
228             mTarget.addItem(item, false);
229             assertSame(item, mTarget.findLastItem(TestItem::shouldKeepOnFilter,
230                     FilterableTestItem.class));
231         }
232     }
233 
234     @Test
testRemoveItemsNotRemoveDifferentType()235     public void testRemoveItemsNotRemoveDifferentType() throws Exception {
236         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
237         synchronized (mTarget) {
238             mTarget.addItem(mFactory.createItem(), false);
239             mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class);
240         }
241         assertTrue("Target didn't call callback enough times.",
242                 mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
243         assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
244     }
245 
246     @Test
testRemoveItemsNotRemoveMismatchedItem()247     public void testRemoveItemsNotRemoveMismatchedItem() throws Exception {
248         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
249         synchronized (mTarget) {
250             mTarget.addItem(mFactory.createFilterableItem(false), false);
251             mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class);
252         }
253         assertTrue("Target didn't call callback enough times.",
254                 mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
255         assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
256     }
257 
258     @Test
testUpdateLastOrAddItemUpdatesMatchedItem()259     public void testUpdateLastOrAddItemUpdatesMatchedItem() throws Exception {
260         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
261         final FilterableTestItem scheduledItem = mFactory.createFilterableItem(true);
262         final FilterableTestItem expected = mFactory.createFilterableItem(true);
263         synchronized (mTarget) {
264             mTarget.addItem(scheduledItem, false);
265             mTarget.updateLastOrAddItem(expected, false);
266         }
267 
268         assertSame(expected, scheduledItem.mUpdateFromItem);
269         assertTrue("Target didn't call callback enough times.",
270                 mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
271         assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
272     }
273 
274     @Test
testUpdateLastOrAddItemUpdatesAddItemWhenNoMatch()275     public void testUpdateLastOrAddItemUpdatesAddItemWhenNoMatch() throws Exception {
276         mListener.setExpectedOnPreProcessItemCallbackTimes(2);
277         final FilterableTestItem scheduledItem = mFactory.createFilterableItem(false);
278         final FilterableTestItem expected = mFactory.createFilterableItem(true);
279         synchronized (mTarget) {
280             mTarget.addItem(scheduledItem, false);
281             mTarget.updateLastOrAddItem(expected, false);
282         }
283 
284         assertNull(scheduledItem.mUpdateFromItem);
285         assertTrue("Target didn't call callback enough times.",
286                 mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS
287                         + TIMEOUT_ALLOWANCE));
288         assertEquals("Target didn't process item.", 2, mFactory.getTotalProcessedItemCount());
289     }
290 
291     @Test
testRemoveItemsRemoveMatchedItem()292     public void testRemoveItemsRemoveMatchedItem() throws Exception {
293         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
294         synchronized (mTarget) {
295             mTarget.addItem(mFactory.createItem(), false);
296             mTarget.addItem(mFactory.createFilterableItem(true), false);
297             mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class);
298         }
299         assertTrue("Target didn't call callback enough times.",
300                 mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
301         assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount());
302     }
303 
304     @Test
testFlushWaitSynchronously()305     public void testFlushWaitSynchronously() {
306         final long dispatchTime = SystemClock.uptimeMillis();
307         mTarget.addItem(mFactory.createItem(), false);
308         mTarget.addItem(mFactory.createItem(), false);
309         mTarget.flush();
310         assertEquals("Flush should wait until all items are processed before return.",
311                 2, mFactory.getTotalProcessedItemCount());
312         final long processTime = SystemClock.uptimeMillis() - dispatchTime;
313         assertWithMessage("Flush should trigger immediate flush without delays. processTime: "
314                 + processTime).that(processTime).isLessThan(TIMEOUT_ALLOWANCE);
315     }
316 
317     private static class TestWriteQueueItemFactory {
318         private final AtomicInteger mItemCount = new AtomicInteger(0);;
319         private CountDownLatch mLatch;
320 
getTotalProcessedItemCount()321         int getTotalProcessedItemCount() {
322             return mItemCount.get();
323         }
324 
setExpectedProcessedItemNumber(int countDown)325         void setExpectedProcessedItemNumber(int countDown) {
326             mLatch = new CountDownLatch(countDown);
327         }
328 
waitForAllExpectedItemsProcessed(long timeoutInMilliseconds)329         boolean waitForAllExpectedItemsProcessed(long timeoutInMilliseconds)
330                 throws InterruptedException {
331             return mLatch.await(timeoutInMilliseconds, TimeUnit.MILLISECONDS);
332         }
333 
createItem()334         TestItem createItem() {
335             return new TestItem(mItemCount, mLatch);
336         }
337 
createFilterableItem(boolean shouldKeepOnFilter)338         FilterableTestItem createFilterableItem(boolean shouldKeepOnFilter) {
339             return new FilterableTestItem(shouldKeepOnFilter, mItemCount, mLatch);
340         }
341     }
342 
343     private static class TestItem<T extends TestItem<T>>
344             implements PersisterQueue.WriteQueueItem<T> {
345         private AtomicInteger mItemCount;
346         private CountDownLatch mLatch;
347 
TestItem(AtomicInteger itemCount, CountDownLatch latch)348         TestItem(AtomicInteger itemCount, CountDownLatch latch) {
349             mItemCount = itemCount;
350             mLatch = latch;
351         }
352 
353         @Override
process()354         public void process() {
355             mItemCount.getAndIncrement();
356             if (mLatch != null) {
357                 // Count down the latch at the last step is necessary, as it's a kind of lock to the
358                 // next assert in many test cases.
359                 mLatch.countDown();
360             }
361         }
362 
shouldKeepOnFilter()363         boolean shouldKeepOnFilter() {
364             return true;
365         }
366     }
367 
368     private static class FilterableTestItem extends TestItem<FilterableTestItem> {
369         private boolean mShouldKeepOnFilter;
370 
371         private FilterableTestItem mUpdateFromItem;
372 
FilterableTestItem(boolean shouldKeepOnFilter, AtomicInteger mItemCount, CountDownLatch mLatch)373         private FilterableTestItem(boolean shouldKeepOnFilter, AtomicInteger mItemCount,
374                 CountDownLatch mLatch) {
375             super(mItemCount, mLatch);
376             mShouldKeepOnFilter = shouldKeepOnFilter;
377         }
378 
379         @Override
matches(FilterableTestItem item)380         public boolean matches(FilterableTestItem item) {
381             return item.mShouldKeepOnFilter;
382         }
383 
384         @Override
updateFrom(FilterableTestItem item)385         public void updateFrom(FilterableTestItem item) {
386             mUpdateFromItem = item;
387         }
388 
389         @Override
shouldKeepOnFilter()390         boolean shouldKeepOnFilter() {
391             return mShouldKeepOnFilter;
392         }
393     }
394 
395     private class TestPersisterQueueListener implements PersisterQueue.Listener {
396         CountDownLatch mCallbackLatch;
397         final List<Boolean> mProbablyDoneResults = new ArrayList<>();
398 
399         @Override
onPreProcessItem(boolean queueEmpty)400         public void onPreProcessItem(boolean queueEmpty) {
401             mProbablyDoneResults.add(queueEmpty);
402             mCallbackLatch.countDown();
403         }
404 
setExpectedOnPreProcessItemCallbackTimes(int countDown)405         void setExpectedOnPreProcessItemCallbackTimes(int countDown) {
406             mCallbackLatch = new CountDownLatch(countDown);
407         }
408 
waitForAllExpectedCallbackDone(long timeoutInMilliseconds)409         boolean waitForAllExpectedCallbackDone(long timeoutInMilliseconds)
410                 throws InterruptedException {
411             return mCallbackLatch.await(timeoutInMilliseconds, TimeUnit.MILLISECONDS);
412         }
413     }
414 }
415