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