@Override
 public void run(TestResult result) {
   super.run(result);
   if (!result.wasSuccessful()) {
     result.addFailure(this, new AssertionFailedError(mLog.toString()));
   }
 }
 void log(String msg) {
   if (mCollectLogs) {
     mLog.append(msg).append("\n");
   } else {
     Log.d(TAG, msg);
   }
 }
 void preProcess() {
   for (MockViewHolder vh : mViewHolders) {
     final int ind = mTestAdapter.mItems.indexOf(vh.mItem);
     assertEquals(
         "actual adapter position should match",
         ind,
         mAdapterHelper.applyPendingUpdatesToPosition(vh.mPosition));
   }
   mAdapterHelper.preProcess();
   for (int i = 0; i < mPreProcessClone.mItems.size(); i++) {
     TestAdapter.Item item = mPreProcessClone.mItems.get(i);
     final int preLayoutIndex = mPreLayoutItems.indexOf(item);
     final int endIndex = mTestAdapter.mItems.indexOf(item);
     if (preLayoutIndex != -1) {
       assertEquals(
           "find position offset should work properly for existing elements"
               + i
               + " at pre layout position "
               + preLayoutIndex
               + " and post layout position "
               + endIndex,
           endIndex,
           mAdapterHelper.findPositionOffset(preLayoutIndex));
     }
   }
   // make sure visible view holders still have continuous positions
   final StringBuilder vhLogBuilder = new StringBuilder();
   for (ViewHolder vh : mViewHolders) {
     vhLogBuilder.append("\n").append(vh.toString());
   }
   if (mViewHolders.size() > 0) {
     final String vhLog = vhLogBuilder.toString();
     final int start = mViewHolders.get(0).getLayoutPosition();
     for (int i = 1; i < mViewHolders.size(); i++) {
       assertEquals(
           "view holder positions should be continious in pre-layout" + vhLog,
           start + i,
           mViewHolders.get(i).getLayoutPosition());
     }
   }
   mAdapterHelper.consumePostponedUpdates();
   // now assert these two adapters have identical data.
   mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter);
   mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter);
   assertAdaptersEqual(mTestAdapter, mPreProcessClone);
 }
 @Test
 public void testChangeAll() throws Exception {
   try {
     setupBasic(5, 0, 3);
     up(0, 5);
     mAdapterHelper.preProcess();
   } catch (Throwable t) {
     throw new Exception(mLog.toString());
   }
 }
 @Test
 public void testScenerio33() throws Throwable {
   try {
     mCollectLogs = true;
     setupBasic(10, 7, 1);
     mv(0, 6);
     up(0, 7);
     preProcess();
   } catch (Throwable t) {
     throw new Throwable(t.getMessage() + "\n" + mLog.toString());
   }
 }
 @Test
 public void testScenerio37() throws Throwable {
   try {
     mCollectLogs = true;
     setupBasic(10, 5, 2);
     mv(3, 6);
     rm(4, 4);
     rm(3, 2);
     preProcess();
   } catch (Throwable t) {
     throw new Throwable(t.getMessage() + "\n" + mLog.toString());
   }
 }
 @Test
 public void testRandom() throws Throwable {
   mCollectLogs = true;
   Random random = new Random(System.nanoTime());
   for (int i = 0; i < 100; i++) {
     try {
       Log.d(TAG, "running random test " + i);
       randomTest(random, Math.max(40, 10 + nextInt(random, i)));
     } catch (Throwable t) {
       throw new Throwable(
           "failure at random test " + i + "\n" + t.getMessage() + "\n" + mLog.toString(), t);
     }
   }
 }
  @Before
  public void cleanState() {
    mLog.setLength(0);
    mPreLayoutItems = new ArrayList<TestAdapter.Item>();
    mViewHolders = new ArrayList<MockViewHolder>();
    mFirstPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
    mSecondPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
    mPreProcessClone = null;
    mAdapterHelper =
        new AdapterHelper(
            new AdapterHelper.Callback() {
              @Override
              public RecyclerView.ViewHolder findViewHolder(int position) {
                for (ViewHolder vh : mViewHolders) {
                  if (vh.mPosition == position && !vh.isRemoved()) {
                    return vh;
                  }
                }
                return null;
              }

              @Override
              public void offsetPositionsForRemovingInvisible(int positionStart, int itemCount) {
                final int positionEnd = positionStart + itemCount;
                for (ViewHolder holder : mViewHolders) {
                  if (holder.mPosition >= positionEnd) {
                    holder.offsetPosition(-itemCount, true);
                  } else if (holder.mPosition >= positionStart) {
                    holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, true);
                  }
                }
              }

              @Override
              public void offsetPositionsForRemovingLaidOutOrNewView(
                  int positionStart, int itemCount) {
                final int positionEnd = positionStart + itemCount;
                for (ViewHolder holder : mViewHolders) {
                  if (holder.mPosition >= positionEnd) {
                    holder.offsetPosition(-itemCount, false);
                  } else if (holder.mPosition >= positionStart) {
                    holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, false);
                  }
                }
              }

              @Override
              public void markViewHoldersUpdated(int positionStart, int itemCount) {
                final int positionEnd = positionStart + itemCount;
                for (ViewHolder holder : mViewHolders) {
                  if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
                    holder.addFlags(ViewHolder.FLAG_UPDATE);
                  }
                }
              }

              @Override
              public void onDispatchFirstPass(AdapterHelper.UpdateOp updateOp) {
                if (DEBUG) {
                  log("first pass:"******"update op should not match any existing view holders",
                        viewHolder.getLayoutPosition() == updateOp.positionStart + i);
                  }
                }

                mFirstPassUpdates.add(updateOp);
              }

              @Override
              public void onDispatchSecondPass(AdapterHelper.UpdateOp updateOp) {
                if (DEBUG) {
                  log("second pass:" + updateOp.toString());
                }
                mSecondPassUpdates.add(updateOp);
              }

              @Override
              public void offsetPositionsForAdd(int positionStart, int itemCount) {
                for (ViewHolder holder : mViewHolders) {
                  if (holder != null && holder.mPosition >= positionStart) {
                    holder.offsetPosition(itemCount, false);
                  }
                }
              }

              @Override
              public void offsetPositionsForMove(int from, int to) {
                final int start, end, inBetweenOffset;
                if (from < to) {
                  start = from;
                  end = to;
                  inBetweenOffset = -1;
                } else {
                  start = to;
                  end = from;
                  inBetweenOffset = 1;
                }
                for (ViewHolder holder : mViewHolders) {
                  if (holder == null || holder.mPosition < start || holder.mPosition > end) {
                    continue;
                  }
                  if (holder.mPosition == from) {
                    holder.offsetPosition(to - from, false);
                  } else {
                    holder.offsetPosition(inBetweenOffset, false);
                  }
                }
              }
            },
            true);
  }