1 /* 2 * Copyright 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 androidx.recyclerview.widget; 18 19 import static androidx.recyclerview.widget.RecyclerView.ViewHolder; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertSame; 24 25 import android.support.test.filters.SmallTest; 26 import android.view.View; 27 28 import org.junit.Before; 29 import org.junit.Rule; 30 import org.junit.Test; 31 import org.junit.rules.TestWatcher; 32 import org.junit.runner.Description; 33 import org.junit.runner.RunWith; 34 import org.junit.runners.JUnit4; 35 import org.mockito.Mockito; 36 37 import java.util.ArrayList; 38 import java.util.LinkedList; 39 import java.util.List; 40 import java.util.Queue; 41 import java.util.Random; 42 import java.util.concurrent.atomic.AtomicInteger; 43 44 @RunWith(JUnit4.class) 45 @SmallTest 46 public class AdapterHelperTest { 47 48 private static final boolean DEBUG = false; 49 50 private boolean mCollectLogs = false; 51 52 private static final String TAG = "AHT"; 53 54 private List<MockViewHolder> mViewHolders; 55 56 private AdapterHelper mAdapterHelper; 57 58 private List<AdapterHelper.UpdateOp> mFirstPassUpdates, mSecondPassUpdates; 59 60 TestAdapter mTestAdapter; 61 62 private TestAdapter mPreProcessClone; 63 // we clone adapter pre-process to run operations to see result 64 65 private List<TestAdapter.Item> mPreLayoutItems; 66 67 private StringBuilder mLog = new StringBuilder(); 68 69 @Rule 70 public TestWatcher reportErrorLog = new TestWatcher() { 71 @Override 72 protected void failed(Throwable e, Description description) { 73 System.out.println(mLog.toString()); 74 } 75 76 @Override 77 protected void succeeded(Description description) { 78 } 79 }; 80 81 @Before cleanState()82 public void cleanState() { 83 mLog.setLength(0); 84 mPreLayoutItems = new ArrayList<>(); 85 mViewHolders = new ArrayList<>(); 86 mFirstPassUpdates = new ArrayList<>(); 87 mSecondPassUpdates = new ArrayList<>(); 88 mPreProcessClone = null; 89 mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { 90 @Override 91 public RecyclerView.ViewHolder findViewHolder(int position) { 92 for (ViewHolder vh : mViewHolders) { 93 if (vh.mPosition == position && !vh.isRemoved()) { 94 return vh; 95 } 96 } 97 return null; 98 } 99 100 @Override 101 public void offsetPositionsForRemovingInvisible(int positionStart, int itemCount) { 102 final int positionEnd = positionStart + itemCount; 103 for (ViewHolder holder : mViewHolders) { 104 if (holder.mPosition >= positionEnd) { 105 holder.offsetPosition(-itemCount, true); 106 } else if (holder.mPosition >= positionStart) { 107 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, true); 108 } 109 } 110 } 111 112 @Override 113 public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, 114 int itemCount) { 115 final int positionEnd = positionStart + itemCount; 116 for (ViewHolder holder : mViewHolders) { 117 if (holder.mPosition >= positionEnd) { 118 holder.offsetPosition(-itemCount, false); 119 } else if (holder.mPosition >= positionStart) { 120 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, false); 121 } 122 } 123 } 124 125 @Override 126 public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { 127 final int positionEnd = positionStart + itemCount; 128 for (ViewHolder holder : mViewHolders) { 129 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 130 holder.addFlags(ViewHolder.FLAG_UPDATE); 131 holder.addChangePayload(payload); 132 } 133 } 134 } 135 136 @Override 137 public void onDispatchFirstPass(AdapterHelper.UpdateOp updateOp) { 138 if (DEBUG) { 139 log("first pass:" + updateOp.toString()); 140 } 141 for (ViewHolder viewHolder : mViewHolders) { 142 for (int i = 0; i < updateOp.itemCount; i++) { 143 // events are dispatched before view holders are updated for consistency 144 assertFalse("update op should not match any existing view holders", 145 viewHolder.getLayoutPosition() == updateOp.positionStart + i); 146 } 147 } 148 149 mFirstPassUpdates.add(updateOp); 150 } 151 152 @Override 153 public void onDispatchSecondPass(AdapterHelper.UpdateOp updateOp) { 154 if (DEBUG) { 155 log("second pass:" + updateOp.toString()); 156 } 157 mSecondPassUpdates.add(updateOp); 158 } 159 160 @Override 161 public void offsetPositionsForAdd(int positionStart, int itemCount) { 162 for (ViewHolder holder : mViewHolders) { 163 if (holder != null && holder.mPosition >= positionStart) { 164 holder.offsetPosition(itemCount, false); 165 } 166 } 167 } 168 169 @Override 170 public void offsetPositionsForMove(int from, int to) { 171 final int start, end, inBetweenOffset; 172 if (from < to) { 173 start = from; 174 end = to; 175 inBetweenOffset = -1; 176 } else { 177 start = to; 178 end = from; 179 inBetweenOffset = 1; 180 } 181 for (ViewHolder holder : mViewHolders) { 182 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 183 continue; 184 } 185 if (holder.mPosition == from) { 186 holder.offsetPosition(to - from, false); 187 } else { 188 holder.offsetPosition(inBetweenOffset, false); 189 } 190 } 191 } 192 }, true); 193 } 194 log(String msg)195 void log(String msg) { 196 if (mCollectLogs) { 197 mLog.append(msg).append("\n"); 198 } else { 199 System.out.println(TAG + ":" + msg); 200 } 201 } 202 setupBasic(int count, int visibleStart, int visibleCount)203 void setupBasic(int count, int visibleStart, int visibleCount) { 204 if (DEBUG) { 205 log("setupBasic(" + count + "," + visibleStart + "," + visibleCount + ");"); 206 } 207 mTestAdapter = new TestAdapter(count, mAdapterHelper); 208 for (int i = 0; i < visibleCount; i++) { 209 addViewHolder(visibleStart + i); 210 } 211 mPreProcessClone = mTestAdapter.createCopy(); 212 } 213 addViewHolder(int position)214 private void addViewHolder(int position) { 215 MockViewHolder viewHolder = new MockViewHolder(); 216 viewHolder.mPosition = position; 217 viewHolder.mItem = mTestAdapter.mItems.get(position); 218 mViewHolders.add(viewHolder); 219 } 220 221 @Test testChangeAll()222 public void testChangeAll() throws Exception { 223 try { 224 setupBasic(5, 0, 3); 225 up(0, 5); 226 mAdapterHelper.preProcess(); 227 } catch (Throwable t) { 228 throw new Exception(mLog.toString()); 229 } 230 } 231 232 @Test testFindPositionOffsetInPreLayout()233 public void testFindPositionOffsetInPreLayout() { 234 setupBasic(50, 25, 10); 235 rm(24, 5); 236 mAdapterHelper.preProcess(); 237 // since 25 is invisible, we offset by one while checking 238 assertEquals("find position for view 23", 239 23, mAdapterHelper.findPositionOffset(23)); 240 assertEquals("find position for view 24", 241 -1, mAdapterHelper.findPositionOffset(24)); 242 assertEquals("find position for view 25", 243 -1, mAdapterHelper.findPositionOffset(25)); 244 assertEquals("find position for view 26", 245 -1, mAdapterHelper.findPositionOffset(26)); 246 assertEquals("find position for view 27", 247 -1, mAdapterHelper.findPositionOffset(27)); 248 assertEquals("find position for view 28", 249 24, mAdapterHelper.findPositionOffset(28)); 250 assertEquals("find position for view 29", 251 25, mAdapterHelper.findPositionOffset(29)); 252 } 253 254 @Test testNotifyAfterPre()255 public void testNotifyAfterPre() { 256 setupBasic(10, 2, 3); 257 add(2, 1); 258 mAdapterHelper.preProcess(); 259 add(3, 1); 260 mAdapterHelper.consumeUpdatesInOnePass(); 261 mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter); 262 mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter); 263 assertAdaptersEqual(mTestAdapter, mPreProcessClone); 264 } 265 266 @Test testSinglePass()267 public void testSinglePass() { 268 setupBasic(10, 2, 3); 269 add(2, 1); 270 rm(1, 2); 271 add(1, 5); 272 mAdapterHelper.consumeUpdatesInOnePass(); 273 assertDispatch(0, 3); 274 } 275 276 @Test testDeleteVisible()277 public void testDeleteVisible() { 278 setupBasic(10, 2, 3); 279 rm(2, 1); 280 preProcess(); 281 assertDispatch(0, 1); 282 } 283 284 @Test testDeleteInvisible()285 public void testDeleteInvisible() { 286 setupBasic(10, 3, 4); 287 rm(2, 1); 288 preProcess(); 289 assertDispatch(1, 0); 290 } 291 292 @Test testAddCount()293 public void testAddCount() { 294 setupBasic(0, 0, 0); 295 add(0, 1); 296 assertEquals(1, mAdapterHelper.mPendingUpdates.size()); 297 } 298 299 @Test testDeleteCount()300 public void testDeleteCount() { 301 setupBasic(1, 0, 0); 302 rm(0, 1); 303 assertEquals(1, mAdapterHelper.mPendingUpdates.size()); 304 } 305 306 @Test testAddProcess()307 public void testAddProcess() { 308 setupBasic(0, 0, 0); 309 add(0, 1); 310 preProcess(); 311 assertEquals(0, mAdapterHelper.mPendingUpdates.size()); 312 } 313 314 @Test testAddRemoveSeparate()315 public void testAddRemoveSeparate() { 316 setupBasic(10, 2, 2); 317 add(6, 1); 318 rm(5, 1); 319 preProcess(); 320 assertDispatch(1, 1); 321 } 322 323 @Test testScenario1()324 public void testScenario1() { 325 setupBasic(10, 3, 2); 326 rm(4, 1); 327 rm(3, 1); 328 rm(3, 1); 329 preProcess(); 330 assertDispatch(1, 2); 331 } 332 333 @Test testDivideDelete()334 public void testDivideDelete() { 335 setupBasic(10, 3, 4); 336 rm(2, 2); 337 preProcess(); 338 assertDispatch(1, 1); 339 } 340 341 @Test testScenario2()342 public void testScenario2() { 343 setupBasic(10, 3, 3); // 3-4-5 344 add(4, 2); // 3 a b 4 5 345 rm(0, 1); // (0) 3(2) a(3) b(4) 4(3) 5(4) 346 rm(1, 3); // (1,2) (x) a(1) b(2) 4(3) 347 preProcess(); 348 assertDispatch(2, 2); 349 } 350 351 @Test testScenario3()352 public void testScenario3() { 353 setupBasic(10, 2, 2); 354 rm(0, 5); 355 preProcess(); 356 assertDispatch(2, 1); 357 assertOps(mFirstPassUpdates, rmOp(0, 2), rmOp(2, 1)); 358 assertOps(mSecondPassUpdates, rmOp(0, 2)); 359 } 360 // TODO test MOVE then remove items in between. 361 // TODO test MOVE then remove it, make sure it is not dispatched 362 363 @Test testScenario4()364 public void testScenario4() { 365 setupBasic(5, 0, 5); 366 // 0 1 2 3 4 367 // 0 1 2 a b 3 4 368 // 0 2 a b 3 4 369 // 0 c d 2 a b 3 4 370 // 0 c d 2 a 4 371 // c d 2 a 4 372 // pre: 0 1 2 3 4 373 add(3, 2); 374 rm(1, 1); 375 add(1, 2); 376 rm(5, 2); 377 rm(0, 1); 378 preProcess(); 379 } 380 381 @Test testScenario5()382 public void testScenario5() { 383 setupBasic(5, 0, 5); 384 // 0 1 2 3 4 385 // 0 1 2 a b 3 4 386 // 0 1 b 3 4 387 // pre: 0 1 2 3 4 388 // pre w/ adap: 0 1 2 b 3 4 389 add(3, 2); 390 rm(2, 2); 391 preProcess(); 392 } 393 394 @Test testScenario6()395 public void testScenario6() { 396 // setupBasic(47, 19, 24); 397 // mv(11, 12); 398 // add(24, 16); 399 // rm(9, 3); 400 setupBasic(10, 5, 3); 401 mv(2, 3); 402 add(6, 4); 403 rm(4, 1); 404 preProcess(); 405 } 406 407 @Test testScenario8()408 public void testScenario8() { 409 setupBasic(68, 51, 13); 410 mv(22, 11); 411 mv(22, 52); 412 rm(37, 19); 413 add(12, 38); 414 preProcess(); 415 } 416 417 @Test testScenario9()418 public void testScenario9() { 419 setupBasic(44, 3, 7); 420 add(7, 21); 421 rm(31, 3); 422 rm(32, 11); 423 mv(29, 5); 424 mv(30, 32); 425 add(25, 32); 426 rm(15, 66); 427 preProcess(); 428 } 429 430 @Test testScenario10()431 public void testScenario10() { 432 setupBasic(14, 10, 3); 433 rm(4, 4); 434 add(5, 11); 435 mv(5, 18); 436 rm(2, 9); 437 preProcess(); 438 } 439 440 @Test testScenario11()441 public void testScenario11() { 442 setupBasic(78, 3, 64); 443 mv(34, 28); 444 add(1, 11); 445 rm(9, 74); 446 preProcess(); 447 } 448 449 @Test testScenario12()450 public void testScenario12() { 451 setupBasic(38, 9, 7); 452 rm(26, 3); 453 mv(29, 15); 454 rm(30, 1); 455 preProcess(); 456 } 457 458 @Test testScenario13()459 public void testScenario13() { 460 setupBasic(49, 41, 3); 461 rm(30, 13); 462 add(4, 10); 463 mv(3, 38); 464 mv(20, 17); 465 rm(18, 23); 466 preProcess(); 467 } 468 469 @Test testScenario14()470 public void testScenario14() { 471 setupBasic(24, 3, 11); 472 rm(2, 15); 473 mv(2, 1); 474 add(2, 34); 475 add(11, 3); 476 rm(10, 25); 477 rm(13, 6); 478 rm(4, 4); 479 rm(6, 4); 480 preProcess(); 481 } 482 483 @Test testScenario15()484 public void testScenario15() { 485 setupBasic(10, 8, 1); 486 mv(6, 1); 487 mv(1, 4); 488 rm(3, 1); 489 preProcess(); 490 } 491 492 @Test testScenario16()493 public void testScenario16() { 494 setupBasic(10, 3, 3); 495 rm(2, 1); 496 rm(1, 7); 497 rm(0, 1); 498 preProcess(); 499 } 500 501 @Test testScenario17()502 public void testScenario17() { 503 setupBasic(10, 8, 1); 504 mv(1, 0); 505 mv(5, 1); 506 rm(1, 7); 507 preProcess(); 508 } 509 510 @Test testScenario18()511 public void testScenario18() { 512 setupBasic(10, 1, 4); 513 add(2, 11); 514 rm(16, 1); 515 add(3, 1); 516 rm(9, 10); 517 preProcess(); 518 } 519 520 @Test testScenario19()521 public void testScenario19() { 522 setupBasic(10, 8, 1); 523 mv(9, 7); 524 mv(9, 3); 525 rm(5, 4); 526 preProcess(); 527 } 528 529 @Test testScenario20()530 public void testScenario20() { 531 setupBasic(10, 7, 1); 532 mv(9, 1); 533 mv(3, 9); 534 rm(7, 2); 535 preProcess(); 536 } 537 538 @Test testScenario21()539 public void testScenario21() { 540 setupBasic(10, 5, 2); 541 mv(1, 0); 542 mv(9, 1); 543 rm(2, 3); 544 preProcess(); 545 } 546 547 @Test testScenario22()548 public void testScenario22() { 549 setupBasic(10, 7, 2); 550 add(2, 16); 551 mv(20, 9); 552 rm(17, 6); 553 preProcess(); 554 } 555 556 @Test testScenario23()557 public void testScenario23() { 558 setupBasic(10, 5, 3); 559 mv(9, 6); 560 add(4, 15); 561 rm(21, 3); 562 preProcess(); 563 } 564 565 @Test testScenario24()566 public void testScenario24() { 567 setupBasic(10, 1, 6); 568 add(6, 5); 569 mv(14, 6); 570 rm(7, 6); 571 preProcess(); 572 } 573 574 @Test testScenario25()575 public void testScenario25() { 576 setupBasic(10, 3, 4); 577 mv(3, 9); 578 rm(5, 4); 579 preProcess(); 580 } 581 582 @Test testScenario25a()583 public void testScenario25a() { 584 setupBasic(10, 3, 4); 585 rm(6, 4); 586 mv(3, 5); 587 preProcess(); 588 } 589 590 @Test testScenario26()591 public void testScenario26() { 592 setupBasic(10, 4, 4); 593 rm(3, 5); 594 mv(2, 0); 595 mv(1, 0); 596 rm(1, 1); 597 mv(0, 2); 598 preProcess(); 599 } 600 601 @Test testScenario27()602 public void testScenario27() { 603 setupBasic(10, 0, 3); 604 mv(9, 4); 605 mv(8, 4); 606 add(7, 6); 607 rm(5, 5); 608 preProcess(); 609 } 610 611 @Test testScenerio28()612 public void testScenerio28() { 613 setupBasic(10, 4, 1); 614 mv(8, 6); 615 rm(8, 1); 616 mv(7, 5); 617 rm(3, 3); 618 rm(1, 4); 619 preProcess(); 620 } 621 622 @Test testScenerio29()623 public void testScenerio29() { 624 setupBasic(10, 6, 3); 625 mv(3, 6); 626 up(6, 2); 627 add(5, 5); 628 } 629 630 @Test testScenerio30()631 public void testScenerio30() { 632 mCollectLogs = true; 633 setupBasic(10, 3, 1); 634 rm(3, 2); 635 rm(2, 5); 636 preProcess(); 637 } 638 639 @Test testScenerio31()640 public void testScenerio31() { 641 mCollectLogs = true; 642 setupBasic(10, 3, 1); 643 rm(3, 1); 644 rm(2, 3); 645 preProcess(); 646 } 647 648 @Test testScenerio32()649 public void testScenerio32() { 650 setupBasic(10, 8, 1); 651 add(9, 2); 652 add(7, 39); 653 up(0, 39); 654 mv(36, 20); 655 add(1, 48); 656 mv(22, 98); 657 mv(96, 29); 658 up(36, 29); 659 add(60, 36); 660 add(127, 34); 661 rm(142, 22); 662 up(12, 69); 663 up(116, 13); 664 up(118, 19); 665 mv(94, 69); 666 up(98, 21); 667 add(89, 18); 668 rm(94, 70); 669 up(71, 8); 670 rm(54, 26); 671 add(2, 20); 672 mv(78, 84); 673 mv(56, 2); 674 mv(1, 79); 675 rm(76, 7); 676 rm(57, 12); 677 rm(30, 27); 678 add(24, 13); 679 add(21, 5); 680 rm(11, 27); 681 rm(32, 1); 682 up(0, 5); 683 mv(14, 9); 684 rm(15, 12); 685 up(19, 1); 686 rm(7, 1); 687 mv(10, 4); 688 up(4, 3); 689 rm(16, 1); 690 up(13, 5); 691 up(2, 8); 692 add(10, 19); 693 add(15, 42); 694 preProcess(); 695 } 696 697 @Test testScenerio33()698 public void testScenerio33() throws Throwable { 699 try { 700 mCollectLogs = true; 701 setupBasic(10, 7, 1); 702 mv(0, 6); 703 up(0, 7); 704 preProcess(); 705 } catch (Throwable t) { 706 throw new Throwable(t.getMessage() + "\n" + mLog.toString()); 707 } 708 } 709 710 @Test testScenerio34()711 public void testScenerio34() { 712 setupBasic(10, 6, 1); 713 mv(9, 7); 714 rm(5, 2); 715 up(4, 3); 716 preProcess(); 717 } 718 719 @Test testScenerio35()720 public void testScenerio35() { 721 setupBasic(10, 4, 4); 722 mv(1, 4); 723 up(2, 7); 724 up(0, 1); 725 preProcess(); 726 } 727 728 @Test testScenerio36()729 public void testScenerio36() { 730 setupBasic(10, 7, 2); 731 rm(4, 1); 732 mv(1, 6); 733 up(4, 4); 734 preProcess(); 735 } 736 737 @Test testScenerio37()738 public void testScenerio37() throws Throwable { 739 try { 740 mCollectLogs = true; 741 setupBasic(10, 5, 2); 742 mv(3, 6); 743 rm(4, 4); 744 rm(3, 2); 745 preProcess(); 746 } catch (Throwable t) { 747 throw new Throwable(t.getMessage() + "\n" + mLog.toString()); 748 } 749 } 750 751 @Test testScenerio38()752 public void testScenerio38() { 753 setupBasic(10, 2, 2); 754 add(0, 24); 755 rm(26, 4); 756 rm(1, 24); 757 preProcess(); 758 } 759 760 @Test testScenerio39()761 public void testScenerio39() { 762 setupBasic(10, 7, 1); 763 mv(0, 2); 764 rm(8, 1); 765 rm(2, 6); 766 preProcess(); 767 } 768 769 @Test testScenerio40()770 public void testScenerio40() { 771 setupBasic(10, 5, 3); 772 rm(5, 4); 773 mv(0, 5); 774 rm(2, 3); 775 preProcess(); 776 } 777 778 @Test testScenerio41()779 public void testScenerio41() { 780 setupBasic(10, 7, 2); 781 mv(4, 9); 782 rm(0, 6); 783 rm(0, 1); 784 preProcess(); 785 } 786 787 @Test testScenerio42()788 public void testScenerio42() { 789 setupBasic(10, 6, 2); 790 mv(5, 9); 791 rm(5, 1); 792 rm(2, 6); 793 preProcess(); 794 } 795 796 @Test testScenerio43()797 public void testScenerio43() { 798 setupBasic(10, 1, 6); 799 mv(6, 8); 800 rm(3, 5); 801 up(3, 1); 802 preProcess(); 803 } 804 805 @Test testScenerio44()806 public void testScenerio44() { 807 setupBasic(10, 5, 2); 808 mv(6, 4); 809 mv(4, 1); 810 rm(5, 3); 811 preProcess(); 812 } 813 814 @Test testScenerio45()815 public void testScenerio45() { 816 setupBasic(10, 4, 2); 817 rm(1, 4); 818 preProcess(); 819 } 820 821 @Test testScenerio46()822 public void testScenerio46() { 823 setupBasic(10, 4, 3); 824 up(6, 1); 825 mv(8, 0); 826 rm(2, 7); 827 preProcess(); 828 } 829 830 @Test testMoveAdded()831 public void testMoveAdded() { 832 setupBasic(10, 2, 2); 833 add(3, 5); 834 mv(4, 2); 835 preProcess(); 836 } 837 838 @Test testPayloads()839 public void testPayloads() { 840 setupBasic(10, 2, 2); 841 up(3, 3, "payload"); 842 preProcess(); 843 assertOps(mFirstPassUpdates, upOp(4, 2, "payload")); 844 assertOps(mSecondPassUpdates, upOp(3, 1, "payload")); 845 } 846 847 @Test testRandom()848 public void testRandom() throws Throwable { 849 mCollectLogs = true; 850 Random random = new Random(System.nanoTime()); 851 for (int i = 0; i < 100; i++) { 852 try { 853 log("running random test " + i); 854 randomTest(random, Math.max(40, 10 + nextInt(random, i))); 855 } catch (Throwable t) { 856 throw new Throwable("failure at random test " + i + "\n" + t.getMessage() 857 + "\n" + mLog.toString(), t); 858 } 859 } 860 } 861 randomTest(Random random, int opCount)862 private void randomTest(Random random, int opCount) { 863 cleanState(); 864 if (DEBUG) { 865 log("randomTest"); 866 } 867 final int count = 10;// + nextInt(random,100); 868 final int start = nextInt(random, count - 1); 869 final int layoutCount = Math.max(1, nextInt(random, count - start)); 870 setupBasic(count, start, layoutCount); 871 872 while (opCount-- > 0) { 873 final int op = nextInt(random, 5); 874 switch (op) { 875 case 0: 876 if (mTestAdapter.mItems.size() > 1) { 877 int s = nextInt(random, mTestAdapter.mItems.size() - 1); 878 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 879 rm(s, len); 880 } 881 break; 882 case 1: 883 int s = mTestAdapter.mItems.size() == 0 ? 0 : 884 nextInt(random, mTestAdapter.mItems.size()); 885 add(s, nextInt(random, 50)); 886 break; 887 case 2: 888 if (mTestAdapter.mItems.size() >= 2) { 889 int from = nextInt(random, mTestAdapter.mItems.size()); 890 int to; 891 do { 892 to = nextInt(random, mTestAdapter.mItems.size()); 893 } while (to == from); 894 mv(from, to); 895 } 896 break; 897 case 3: 898 if (mTestAdapter.mItems.size() > 1) { 899 s = nextInt(random, mTestAdapter.mItems.size() - 1); 900 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 901 up(s, len); 902 } 903 break; 904 case 4: 905 if (mTestAdapter.mItems.size() > 1) { 906 s = nextInt(random, mTestAdapter.mItems.size() - 1); 907 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 908 up(s, len, Integer.toString(s)); 909 } 910 break; 911 } 912 } 913 preProcess(); 914 } 915 nextInt(Random random, int n)916 private int nextInt(Random random, int n) { 917 if (n == 0) { 918 return 0; 919 } 920 return random.nextInt(n); 921 } 922 assertOps(List<AdapterHelper.UpdateOp> actual, AdapterHelper.UpdateOp... expected)923 private void assertOps(List<AdapterHelper.UpdateOp> actual, 924 AdapterHelper.UpdateOp... expected) { 925 assertEquals(expected.length, actual.size()); 926 for (int i = 0; i < expected.length; i++) { 927 assertEquals(expected[i], actual.get(i)); 928 } 929 } 930 assertDispatch(int firstPass, int secondPass)931 private void assertDispatch(int firstPass, int secondPass) { 932 assertEquals(firstPass, mFirstPassUpdates.size()); 933 assertEquals(secondPass, mSecondPassUpdates.size()); 934 } 935 preProcess()936 private void preProcess() { 937 for (MockViewHolder vh : mViewHolders) { 938 final int ind = mTestAdapter.mItems.indexOf(vh.mItem); 939 assertEquals("actual adapter position should match", ind, 940 mAdapterHelper.applyPendingUpdatesToPosition(vh.mPosition)); 941 } 942 mAdapterHelper.preProcess(); 943 for (int i = 0; i < mPreProcessClone.mItems.size(); i++) { 944 TestAdapter.Item item = mPreProcessClone.mItems.get(i); 945 final int preLayoutIndex = mPreLayoutItems.indexOf(item); 946 final int endIndex = mTestAdapter.mItems.indexOf(item); 947 if (preLayoutIndex != -1) { 948 assertEquals("find position offset should work properly for existing elements" + i 949 + " at pre layout position " + preLayoutIndex + " and post layout position " 950 + endIndex, endIndex, mAdapterHelper.findPositionOffset(preLayoutIndex)); 951 } 952 } 953 // make sure visible view holders still have continuous positions 954 final StringBuilder vhLogBuilder = new StringBuilder(); 955 for (ViewHolder vh : mViewHolders) { 956 vhLogBuilder.append("\n").append(vh.toString()); 957 } 958 if (mViewHolders.size() > 0) { 959 final String vhLog = vhLogBuilder.toString(); 960 final int start = mViewHolders.get(0).getLayoutPosition(); 961 for (int i = 1; i < mViewHolders.size(); i++) { 962 assertEquals("view holder positions should be continious in pre-layout" + vhLog, 963 start + i, mViewHolders.get(i).getLayoutPosition()); 964 } 965 } 966 mAdapterHelper.consumePostponedUpdates(); 967 // now assert these two adapters have identical data. 968 mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter); 969 mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter); 970 assertAdaptersEqual(mTestAdapter, mPreProcessClone); 971 } 972 assertAdaptersEqual(TestAdapter a1, TestAdapter a2)973 private void assertAdaptersEqual(TestAdapter a1, TestAdapter a2) { 974 assertEquals(a1.mItems.size(), a2.mItems.size()); 975 for (int i = 0; i < a1.mItems.size(); i++) { 976 TestAdapter.Item item = a1.mItems.get(i); 977 assertSame(item, a2.mItems.get(i)); 978 assertEquals(0, item.getUpdateCount()); 979 } 980 assertEquals(0, a1.mPendingAdded.size()); 981 assertEquals(0, a2.mPendingAdded.size()); 982 } 983 op(int cmd, int start, int count)984 private AdapterHelper.UpdateOp op(int cmd, int start, int count) { 985 return new AdapterHelper.UpdateOp(cmd, start, count, null); 986 } 987 op(int cmd, int start, int count, Object payload)988 private AdapterHelper.UpdateOp op(int cmd, int start, int count, Object payload) { 989 return new AdapterHelper.UpdateOp(cmd, start, count, payload); 990 } 991 rmOp(int start, int count)992 private AdapterHelper.UpdateOp rmOp(int start, int count) { 993 return op(AdapterHelper.UpdateOp.REMOVE, start, count); 994 } 995 upOp(int start, int count, @SuppressWarnings ("SameParameterValue") Object payload)996 private AdapterHelper.UpdateOp upOp(int start, int count, @SuppressWarnings 997 ("SameParameterValue") Object payload) { 998 return op(AdapterHelper.UpdateOp.UPDATE, start, count, payload); 999 } 1000 add(int start, int count)1001 void add(int start, int count) { 1002 if (DEBUG) { 1003 log("add(" + start + "," + count + ");"); 1004 } 1005 mTestAdapter.add(start, count); 1006 } 1007 isItemLaidOut(int pos)1008 private boolean isItemLaidOut(int pos) { 1009 for (ViewHolder viewHolder : mViewHolders) { 1010 if (viewHolder.mOldPosition == pos) { 1011 return true; 1012 } 1013 } 1014 return false; 1015 } 1016 mv(int from, int to)1017 private void mv(int from, int to) { 1018 if (DEBUG) { 1019 log("mv(" + from + "," + to + ");"); 1020 } 1021 mTestAdapter.move(from, to); 1022 } 1023 rm(int start, int count)1024 private void rm(int start, int count) { 1025 if (DEBUG) { 1026 log("rm(" + start + "," + count + ");"); 1027 } 1028 for (int i = start; i < start + count; i++) { 1029 if (!isItemLaidOut(i)) { 1030 TestAdapter.Item item = mTestAdapter.mItems.get(i); 1031 mPreLayoutItems.remove(item); 1032 } 1033 } 1034 mTestAdapter.remove(start, count); 1035 } 1036 up(int start, int count)1037 void up(int start, int count) { 1038 if (DEBUG) { 1039 log("up(" + start + "," + count + ");"); 1040 } 1041 mTestAdapter.update(start, count); 1042 } 1043 up(int start, int count, Object payload)1044 void up(int start, int count, Object payload) { 1045 if (DEBUG) { 1046 log("up(" + start + "," + count + "," + payload + ");"); 1047 } 1048 mTestAdapter.update(start, count, payload); 1049 } 1050 1051 static class TestAdapter { 1052 1053 List<Item> mItems; 1054 1055 final AdapterHelper mAdapterHelper; 1056 1057 Queue<Item> mPendingAdded; 1058 TestAdapter(int initialCount, AdapterHelper container)1059 public TestAdapter(int initialCount, AdapterHelper container) { 1060 mItems = new ArrayList<>(); 1061 mAdapterHelper = container; 1062 mPendingAdded = new LinkedList<>(); 1063 for (int i = 0; i < initialCount; i++) { 1064 mItems.add(new Item()); 1065 } 1066 } 1067 add(int index, int count)1068 public void add(int index, int count) { 1069 for (int i = 0; i < count; i++) { 1070 Item item = new Item(); 1071 mPendingAdded.add(item); 1072 mItems.add(index + i, item); 1073 } 1074 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1075 AdapterHelper.UpdateOp.ADD, index, count, null 1076 )); 1077 } 1078 move(int from, int to)1079 public void move(int from, int to) { 1080 mItems.add(to, mItems.remove(from)); 1081 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1082 AdapterHelper.UpdateOp.MOVE, from, to, null 1083 )); 1084 } 1085 remove(int index, int count)1086 public void remove(int index, int count) { 1087 for (int i = 0; i < count; i++) { 1088 mItems.remove(index); 1089 } 1090 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1091 AdapterHelper.UpdateOp.REMOVE, index, count, null 1092 )); 1093 } 1094 update(int index, int count)1095 public void update(int index, int count) { 1096 update(index, count, null); 1097 } 1098 update(int index, int count, Object payload)1099 public void update(int index, int count, Object payload) { 1100 for (int i = 0; i < count; i++) { 1101 mItems.get(index + i).update(payload); 1102 } 1103 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1104 AdapterHelper.UpdateOp.UPDATE, index, count, payload 1105 )); 1106 } 1107 createCopy()1108 TestAdapter createCopy() { 1109 TestAdapter adapter = new TestAdapter(0, mAdapterHelper); 1110 adapter.mItems.addAll(mItems); 1111 return adapter; 1112 } 1113 applyOps(List<AdapterHelper.UpdateOp> updates, TestAdapter dataSource)1114 void applyOps(List<AdapterHelper.UpdateOp> updates, 1115 TestAdapter dataSource) { 1116 for (AdapterHelper.UpdateOp op : updates) { 1117 switch (op.cmd) { 1118 case AdapterHelper.UpdateOp.ADD: 1119 for (int i = 0; i < op.itemCount; i++) { 1120 mItems.add(op.positionStart + i, dataSource.consumeNextAdded()); 1121 } 1122 break; 1123 case AdapterHelper.UpdateOp.REMOVE: 1124 for (int i = 0; i < op.itemCount; i++) { 1125 mItems.remove(op.positionStart); 1126 } 1127 break; 1128 case AdapterHelper.UpdateOp.UPDATE: 1129 for (int i = 0; i < op.itemCount; i++) { 1130 mItems.get(op.positionStart + i).handleUpdate(op.payload); 1131 } 1132 break; 1133 case AdapterHelper.UpdateOp.MOVE: 1134 mItems.add(op.itemCount, mItems.remove(op.positionStart)); 1135 break; 1136 } 1137 } 1138 } 1139 consumeNextAdded()1140 private Item consumeNextAdded() { 1141 return mPendingAdded.remove(); 1142 } 1143 1144 public static class Item { 1145 1146 private static AtomicInteger itemCounter = new AtomicInteger(); 1147 1148 @SuppressWarnings("unused") 1149 private final int id; 1150 1151 private int mVersionCount = 0; 1152 1153 private ArrayList<Object> mPayloads = new ArrayList<>(); 1154 Item()1155 public Item() { 1156 id = itemCounter.incrementAndGet(); 1157 } 1158 update(Object payload)1159 public void update(Object payload) { 1160 mPayloads.add(payload); 1161 mVersionCount++; 1162 } 1163 handleUpdate(Object payload)1164 void handleUpdate(Object payload) { 1165 assertSame(payload, mPayloads.get(0)); 1166 mPayloads.remove(0); 1167 mVersionCount--; 1168 } 1169 getUpdateCount()1170 int getUpdateCount() { 1171 return mVersionCount; 1172 } 1173 } 1174 } 1175 1176 static class MockViewHolder extends RecyclerView.ViewHolder { 1177 TestAdapter.Item mItem; 1178 MockViewHolder()1179 MockViewHolder() { 1180 super(Mockito.mock(View.class)); 1181 } 1182 1183 @Override toString()1184 public String toString() { 1185 return mItem == null ? "null" : mItem.toString(); 1186 } 1187 } 1188 } 1189