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 junit.framework.AssertionFailedError;
20import junit.framework.TestResult;
21
22import org.junit.Before;
23import org.junit.Test;
24import org.junit.runner.RunWith;
25import org.junit.runners.JUnit4;
26
27import android.test.AndroidTestCase;
28import android.util.Log;
29import android.view.View;
30import android.widget.TextView;
31
32import java.util.ArrayList;
33import java.util.LinkedList;
34import java.util.List;
35import java.util.Queue;
36import java.util.Random;
37import java.util.concurrent.atomic.AtomicInteger;
38
39import static android.support.v7.widget.RecyclerView.*;
40
41@RunWith(JUnit4.class)
42public 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
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
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
186    void log(String msg) {
187        if (mCollectLogs) {
188            mLog.append(msg).append("\n");
189        } else {
190            Log.d(TAG, msg);
191        }
192    }
193
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
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
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
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
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
257    public void testDeleteVisible() {
258        setupBasic(10, 2, 3);
259        rm(2, 1);
260        preProcess();
261        assertDispatch(0, 1);
262    }
263
264    @Test
265    public void testDeleteInvisible() {
266        setupBasic(10, 3, 4);
267        rm(2, 1);
268        preProcess();
269        assertDispatch(1, 0);
270    }
271
272    @Test
273    public void testAddCount() {
274        setupBasic(0, 0, 0);
275        add(0, 1);
276        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
277    }
278
279    @Test
280    public void testDeleteCount() {
281        setupBasic(1, 0, 0);
282        rm(0, 1);
283        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
284    }
285
286    @Test
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
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
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
314    public void testDivideDelete() {
315        setupBasic(10, 3, 4);
316        rm(2, 2);
317        preProcess();
318        assertDispatch(1, 1);
319    }
320
321    @Test
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
555    public void testScenario25() {
556        setupBasic(10, 3, 4);
557        mv(3, 9);
558        rm(5, 4);
559        preProcess();
560    }
561
562    @Test
563    public void testScenario25a() {
564        setupBasic(10, 3, 4);
565        rm(6, 4);
566        mv(3, 5);
567        preProcess();
568    }
569
570    @Test
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
795    public void testScenerio45() {
796        setupBasic(10, 4, 2);
797        rm(1, 4);
798        preProcess();
799    }
800
801    @Test
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
811    public void testMoveAdded() {
812        setupBasic(10, 2, 2);
813        add(3, 5);
814        mv(4, 2);
815        preProcess();
816    }
817
818    @Test
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
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
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
896    int nextInt(Random random, int n) {
897        if (n == 0) {
898            return 0;
899        }
900        return random.nextInt(n);
901    }
902
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
911    void assertDispatch(int firstPass, int secondPass) {
912        assertEquals(firstPass, mFirstPassUpdates.size());
913        assertEquals(secondPass, mSecondPassUpdates.size());
914    }
915
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
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
964    AdapterHelper.UpdateOp op(int cmd, int start, int count) {
965        return new AdapterHelper.UpdateOp(cmd, start, count, null);
966    }
967
968    AdapterHelper.UpdateOp op(int cmd, int start, int count, Object payload) {
969        return new AdapterHelper.UpdateOp(cmd, start, count, payload);
970    }
971
972    AdapterHelper.UpdateOp addOp(int start, int count) {
973        return op(AdapterHelper.UpdateOp.ADD, start, count);
974    }
975
976    AdapterHelper.UpdateOp rmOp(int start, int count) {
977        return op(AdapterHelper.UpdateOp.REMOVE, start, count);
978    }
979
980    AdapterHelper.UpdateOp upOp(int start, int count, Object payload) {
981        return op(AdapterHelper.UpdateOp.UPDATE, start, count, payload);
982    }
983
984    void add(int start, int count) {
985        if (DEBUG) {
986            log("add(" + start + "," + count + ");");
987        }
988        mTestAdapter.add(start, count);
989    }
990
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
1000    private void mv(int from, int to) {
1001        if (DEBUG) {
1002            log("mv(" + from + "," + to + ");");
1003        }
1004        mTestAdapter.move(from, to);
1005    }
1006
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
1020    void up(int start, int count) {
1021        if (DEBUG) {
1022            log("up(" + start + "," + count + ");");
1023        }
1024        mTestAdapter.update(start, count);
1025    }
1026
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
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
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
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
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
1078        public void update(int index, int count) {
1079            update(index, count, null);
1080        }
1081
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
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
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
1125        private Item consumeNextAdded() {
1126            return mPendingAdded.remove();
1127        }
1128
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
1144            public Item() {
1145                id = itemCounter.incrementAndGet();
1146            }
1147
1148            public void update(Object payload) {
1149                mPayloads.add(payload);
1150                mVersionCount++;
1151            }
1152
1153            public void handleUpdate(Object payload) {
1154                assertSame(payload, mPayloads.get(0));
1155                mPayloads.remove(0);
1156                mVersionCount--;
1157            }
1158
1159            public int getUpdateCount() {
1160                return mVersionCount;
1161            }
1162        }
1163    }
1164
1165    void waitForDebugger() {
1166        android.os.Debug.waitForDebugger();
1167    }
1168
1169    static class MockViewHolder extends RecyclerView.ViewHolder {
1170        public Object mItem;
1171        public MockViewHolder(View itemView) {
1172            super(itemView);
1173        }
1174    }
1175}
1176