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