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