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