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