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