@Test
  public void testV1CodecV2Compat() throws Exception {

    long now = System.currentTimeMillis();

    // NOTE: set visibilityUpperBound to 0 as this is expected default for decoding older version
    // that doesn't store it
    TreeMap<Long, TransactionManager.InProgressTx> inProgress =
        Maps.newTreeMap(
            ImmutableSortedMap.of(
                16L, new TransactionManager.InProgressTx(0L, now + 1000),
                17L, new TransactionManager.InProgressTx(0L, now + 1000)));

    TransactionSnapshot snapshot =
        new TransactionSnapshot(
            now,
            15,
            18,
            Lists.newArrayList(5L, 7L),
            inProgress,
            ImmutableMap.<Long, Set<ChangeId>>of(
                17L,
                Sets.newHashSet(
                    new ChangeId(Bytes.toBytes("ch1")), new ChangeId(Bytes.toBytes("ch2")))),
            ImmutableMap.<Long, Set<ChangeId>>of(
                16L,
                Sets.newHashSet(
                    new ChangeId(Bytes.toBytes("ch2")), new ChangeId(Bytes.toBytes("ch3")))));

    Configuration configV1 = HBaseConfiguration.create();
    configV1.setStrings(
        TxConstants.Persist.CFG_TX_SNAPHOT_CODEC_CLASSES, SnapshotCodecV1.class.getName());

    SnapshotCodecProvider codecV1 = new SnapshotCodecProvider(configV1);

    // encoding with codec of v1
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
      codecV1.encode(out, snapshot);
    } finally {
      out.close();
    }

    // decoding
    Configuration configV1V2 = HBaseConfiguration.create();
    configV1V2.setStrings(
        TxConstants.Persist.CFG_TX_SNAPHOT_CODEC_CLASSES,
        SnapshotCodecV1.class.getName(),
        SnapshotCodecV2.class.getName());
    SnapshotCodecProvider codecV1V2 = new SnapshotCodecProvider(configV1V2);
    TransactionSnapshot decoded = codecV1V2.decode(new ByteArrayInputStream(out.toByteArray()));

    assertEquals(snapshot, decoded);
  }
  /**
   * In-progress LONG transactions written with DefaultSnapshotCodec will not have the type
   * serialized as part of the data. Since these transactions also contain a non-negative
   * expiration, we need to ensure we reset the type correctly when the snapshot is loaded.
   */
  @Test
  public void testV2ToTephraV3Compatibility() throws Exception {
    long now = System.currentTimeMillis();
    long nowWritePointer = now * TxConstants.MAX_TX_PER_MS;
    /*
     * Snapshot consisting of transactions at:
     */
    long tInvalid = nowWritePointer - 5; // t1 - invalid
    long readPtr = nowWritePointer - 4; // t2 - here and earlier committed
    long tLong = nowWritePointer - 3; // t3 - in-progress LONG
    long tCommitted = nowWritePointer - 2; // t4 - committed, changeset (r1, r2)
    long tShort =
        nowWritePointer - 1; // t5 - in-progress SHORT, canCommit called, changeset (r3, r4)

    TreeMap<Long, TransactionManager.InProgressTx> inProgress =
        Maps.newTreeMap(
            ImmutableSortedMap.of(
                tLong,
                    new TransactionManager.InProgressTx(
                        readPtr,
                        TransactionManager.getTxExpirationFromWritePointer(
                            tLong, TxConstants.Manager.DEFAULT_TX_LONG_TIMEOUT),
                        TransactionType.LONG),
                tShort,
                    new TransactionManager.InProgressTx(
                        readPtr, now + 1000, TransactionType.SHORT)));

    TransactionSnapshot snapshot =
        new TransactionSnapshot(
            now,
            readPtr,
            nowWritePointer,
            Lists.newArrayList(tInvalid), // invalid
            inProgress,
            ImmutableMap.<Long, Set<ChangeId>>of(
                tShort,
                Sets.newHashSet(
                    new ChangeId(new byte[] {'r', '3'}), new ChangeId(new byte[] {'r', '4'}))),
            ImmutableMap.<Long, Set<ChangeId>>of(
                tCommitted,
                Sets.newHashSet(
                    new ChangeId(new byte[] {'r', '1'}), new ChangeId(new byte[] {'r', '2'}))));

    Configuration conf1 = new Configuration();
    conf1.set(TxConstants.Persist.CFG_TX_SNAPHOT_CODEC_CLASSES, SnapshotCodecV2.class.getName());
    SnapshotCodecProvider provider1 = new SnapshotCodecProvider(conf1);

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
      provider1.encode(out, snapshot);
    } finally {
      out.close();
    }

    TransactionSnapshot snapshot2 = provider1.decode(new ByteArrayInputStream(out.toByteArray()));
    assertEquals(snapshot.getReadPointer(), snapshot2.getReadPointer());
    assertEquals(snapshot.getWritePointer(), snapshot2.getWritePointer());
    assertEquals(snapshot.getInvalid(), snapshot2.getInvalid());
    // in-progress transactions will have missing types
    assertNotEquals(snapshot.getInProgress(), snapshot2.getInProgress());
    assertEquals(snapshot.getCommittingChangeSets(), snapshot2.getCommittingChangeSets());
    assertEquals(snapshot.getCommittedChangeSets(), snapshot2.getCommittedChangeSets());

    // after fixing in-progress, full snapshot should match
    Map<Long, TransactionManager.InProgressTx> fixedInProgress =
        TransactionManager.txnBackwardsCompatCheck(
            TxConstants.Manager.DEFAULT_TX_LONG_TIMEOUT, 10000L, snapshot2.getInProgress());
    assertEquals(snapshot.getInProgress(), fixedInProgress);
    assertEquals(snapshot, snapshot2);
  }