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