private void testLockMigration(int nodeThatPuts) throws Exception {
    Map<Object, DummyTransaction> key2Tx = new HashMap<Object, DummyTransaction>();
    for (int i = 0; i < NUM_KEYS; i++) {
      Object key = getKeyForCache(0);
      if (key2Tx.containsKey(key)) continue;

      dummyTm(nodeThatPuts).begin();
      cache(nodeThatPuts).put(key, key);
      DummyTransaction tx = dummyTm(nodeThatPuts).getTransaction();
      tx.runPrepare();
      dummyTm(nodeThatPuts).suspend();
      key2Tx.put(key, tx);

      assertLocked(0, key);
    }

    log.trace("Lock transfer happens here");

    addClusterEnabledCacheManager(dccc);
    waitForClusterToForm();

    Object migratedKey = null;
    ConsistentHash ch = advancedCache(2).getDistributionManager().getConsistentHash();
    for (Object key : key2Tx.keySet()) {
      if (ch.locatePrimaryOwner(key).equals(address(2))) {
        migratedKey = key;
        break;
      }
    }
    if (migratedKey == null) {
      log.trace("No key migrated to new owner.");
    } else {
      log.trace("migratedKey = " + migratedKey);
      dummyTm(2).begin();
      cache(2).put(migratedKey, "someValue");
      try {
        dummyTm(2).commit();
        fail("RollbackException should have been thrown here.");
      } catch (RollbackException e) {
        // expected
      }
    }

    log.trace("About to commit existing transactions.");

    log.trace("Committing the tx to the new node.");
    for (Transaction tx : key2Tx.values()) {
      tm(nodeThatPuts).resume(tx);
      dummyTm(nodeThatPuts).getTransaction().runCommitTx();
    }

    for (Object key : key2Tx.keySet()) {
      Object value =
          getValue(
              key); // make sure that data from the container, just to make sure all replicas are
                    // correctly set
      assertEquals(key, value);
    }
  }
  public void testPrimaryOwnerCrash() throws Exception {
    // cache 0 is the originator and backup, cache 1 is the primary owner
    StateSequencer ss = new StateSequencer();
    ss.logicalThread("main", "block_prepare", "crash_primary", "resume_prepare");

    tm(0).begin();
    cache(0).put("k", "v1");
    DummyTransaction tx1 = (DummyTransaction) tm(0).suspend();
    tx1.runPrepare();

    advanceOnInboundRpc(ss, cache(1), matchCommand(PrepareCommand.class).build())
        .before("block_prepare", "resume_prepare");

    Future<DummyTransaction> tx2Future =
        fork(
            () -> {
              tm(0).begin();
              cache(0).put("k", "v2");
              DummyTransaction tx2 = (DummyTransaction) tm(0).suspend();
              tx2.runPrepare();
              return tx2;
            });

    ss.enter("crash_primary");
    killMember(1);
    ss.exit("crash_primary");

    DummyTransaction tx2 = tx2Future.get(10, SECONDS);
    try {
      tx2.runCommit(false);
      fail("tx2 should not be able to commit");
    } catch (Exception e) {
      log.tracef(e, "Received expected exception");
    }

    tx1.runCommit(false);
  }
  protected void doTest(final SplitMode splitMode, boolean txFail, boolean discard)
      throws Exception {
    waitForClusterToForm(OPTIMISTIC_TX_CACHE_NAME);

    final KeyInfo keyInfo = createKeys(OPTIMISTIC_TX_CACHE_NAME);
    final Cache<Object, String> originator = cache(0, OPTIMISTIC_TX_CACHE_NAME);
    final FilterCollection filterCollection =
        createFilters(OPTIMISTIC_TX_CACHE_NAME, discard, getCommandClass(), splitMode);

    Future<Void> put =
        fork(
            () -> {
              final DummyTransactionManager transactionManager =
                  (DummyTransactionManager) originator.getAdvancedCache().getTransactionManager();
              transactionManager.begin();
              keyInfo.putFinalValue(originator);
              final DummyTransaction transaction = transactionManager.getTransaction();
              transaction.runPrepare();
              transaction.runCommit(forceRollback());
              transaction.throwRollbackExceptionIfAny();
              return null;
            });

    filterCollection.await(30, TimeUnit.SECONDS);
    splitMode.split(this);
    filterCollection.unblock();

    try {
      put.get();
      assertFalse(txFail);
    } catch (ExecutionException e) {
      assertTrue(txFail);
    }

    checkLocksDuringPartition(splitMode, keyInfo, discard);

    mergeCluster(OPTIMISTIC_TX_CACHE_NAME);
    finalAsserts(OPTIMISTIC_TX_CACHE_NAME, keyInfo, txFail ? INITIAL_VALUE : FINAL_VALUE);
  }