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
17package android.support.v7.widget;
18
19import static android.support.v7.widget.RecyclerView.ViewHolder;
20
21import android.support.test.filters.SmallTest;
22import android.test.AndroidTestCase;
23import android.util.Log;
24import android.view.View;
25import android.widget.TextView;
26
27import junit.framework.AssertionFailedError;
28import junit.framework.TestResult;
29
30import org.junit.Before;
31import org.junit.Test;
32import org.junit.runner.RunWith;
33import org.junit.runners.JUnit4;
34
35import java.util.ArrayList;
36import java.util.LinkedList;
37import java.util.List;
38import java.util.Queue;
39import java.util.Random;
40import java.util.concurrent.atomic.AtomicInteger;
41
42@RunWith(JUnit4.class)
43@SmallTest
44public 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
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
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
188    void log(String msg) {
189        if (mCollectLogs) {
190            mLog.append(msg).append("\n");
191        } else {
192            Log.d(TAG, msg);
193        }
194    }
195
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
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
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
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
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
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
271    public void testDeleteVisible() {
272        setupBasic(10, 2, 3);
273        rm(2, 1);
274        preProcess();
275        assertDispatch(0, 1);
276    }
277
278    @Test
279    public void testDeleteInvisible() {
280        setupBasic(10, 3, 4);
281        rm(2, 1);
282        preProcess();
283        assertDispatch(1, 0);
284    }
285
286    @Test
287    public void testAddCount() {
288        setupBasic(0, 0, 0);
289        add(0, 1);
290        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
291    }
292
293    @Test
294    public void testDeleteCount() {
295        setupBasic(1, 0, 0);
296        rm(0, 1);
297        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
298    }
299
300    @Test
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
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
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
328    public void testDivideDelete() {
329        setupBasic(10, 3, 4);
330        rm(2, 2);
331        preProcess();
332        assertDispatch(1, 1);
333    }
334
335    @Test
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
569    public void testScenario25() {
570        setupBasic(10, 3, 4);
571        mv(3, 9);
572        rm(5, 4);
573        preProcess();
574    }
575
576    @Test
577    public void testScenario25a() {
578        setupBasic(10, 3, 4);
579        rm(6, 4);
580        mv(3, 5);
581        preProcess();
582    }
583
584    @Test
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
809    public void testScenerio45() {
810        setupBasic(10, 4, 2);
811        rm(1, 4);
812        preProcess();
813    }
814
815    @Test
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
825    public void testMoveAdded() {
826        setupBasic(10, 2, 2);
827        add(3, 5);
828        mv(4, 2);
829        preProcess();
830    }
831
832    @Test
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
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
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
910    int nextInt(Random random, int n) {
911        if (n == 0) {
912            return 0;
913        }
914        return random.nextInt(n);
915    }
916
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
925    void assertDispatch(int firstPass, int secondPass) {
926        assertEquals(firstPass, mFirstPassUpdates.size());
927        assertEquals(secondPass, mSecondPassUpdates.size());
928    }
929
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
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
978    AdapterHelper.UpdateOp op(int cmd, int start, int count) {
979        return new AdapterHelper.UpdateOp(cmd, start, count, null);
980    }
981
982    AdapterHelper.UpdateOp op(int cmd, int start, int count, Object payload) {
983        return new AdapterHelper.UpdateOp(cmd, start, count, payload);
984    }
985
986    AdapterHelper.UpdateOp addOp(int start, int count) {
987        return op(AdapterHelper.UpdateOp.ADD, start, count);
988    }
989
990    AdapterHelper.UpdateOp rmOp(int start, int count) {
991        return op(AdapterHelper.UpdateOp.REMOVE, start, count);
992    }
993
994    AdapterHelper.UpdateOp upOp(int start, int count, Object payload) {
995        return op(AdapterHelper.UpdateOp.UPDATE, start, count, payload);
996    }
997
998    void add(int start, int count) {
999        if (DEBUG) {
1000            log("add(" + start + "," + count + ");");
1001        }
1002        mTestAdapter.add(start, count);
1003    }
1004
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
1014    private void mv(int from, int to) {
1015        if (DEBUG) {
1016            log("mv(" + from + "," + to + ");");
1017        }
1018        mTestAdapter.move(from, to);
1019    }
1020
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
1034    void up(int start, int count) {
1035        if (DEBUG) {
1036            log("up(" + start + "," + count + ");");
1037        }
1038        mTestAdapter.update(start, count);
1039    }
1040
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
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
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
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
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
1092        public void update(int index, int count) {
1093            update(index, count, null);
1094        }
1095
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
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
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
1139        private Item consumeNextAdded() {
1140            return mPendingAdded.remove();
1141        }
1142
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
1158            public Item() {
1159                id = itemCounter.incrementAndGet();
1160            }
1161
1162            public void update(Object payload) {
1163                mPayloads.add(payload);
1164                mVersionCount++;
1165            }
1166
1167            public void handleUpdate(Object payload) {
1168                assertSame(payload, mPayloads.get(0));
1169                mPayloads.remove(0);
1170                mVersionCount--;
1171            }
1172
1173            public int getUpdateCount() {
1174                return mVersionCount;
1175            }
1176        }
1177    }
1178
1179    void waitForDebugger() {
1180        android.os.Debug.waitForDebugger();
1181    }
1182
1183    static class MockViewHolder extends RecyclerView.ViewHolder {
1184        public Object mItem;
1185        public MockViewHolder(View itemView) {
1186            super(itemView);
1187        }
1188
1189        @Override
1190        public String toString() {
1191            return mItem == null ? "null" : mItem.toString();
1192        }
1193    }
1194}
1195