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