private void writeTestData(HBaseTestingUtil sourceCluster, TableName sourceTableName,
    HBaseTestingUtil targetCluster, TableName targetTableName, long... timestamps)
    throws Exception {
    final byte[] family = Bytes.toBytes("family");
    final byte[] column1 = Bytes.toBytes("c1");
    final byte[] column2 = Bytes.toBytes("c2");
    final byte[] value1 = Bytes.toBytes("val1");
    final byte[] value2 = Bytes.toBytes("val2");
    final byte[] value3 = Bytes.toBytes("val3");

    int numRows = 100;
    int sourceRegions = 10;
    int targetRegions = 6;
    if (ArrayUtils.isEmpty(timestamps)) {
      long current = EnvironmentEdgeManager.currentTime();
      timestamps = new long[] { current, current };
    }

    try (
      Table sourceTable =
        sourceCluster.createTable(sourceTableName, family, generateSplits(numRows, sourceRegions));
      Table targetTable = targetCluster.createTable(targetTableName, family,
        generateSplits(numRows, targetRegions))) {

      int rowIndex = 0;
      // a bunch of identical rows
      for (; rowIndex < 40; rowIndex++) {
        Put sourcePut = new Put(Bytes.toBytes(rowIndex));
        sourcePut.addColumn(family, column1, timestamps[0], value1);
        sourcePut.addColumn(family, column2, timestamps[0], value2);
        sourceTable.put(sourcePut);

        Put targetPut = new Put(Bytes.toBytes(rowIndex));
        targetPut.addColumn(family, column1, timestamps[1], value1);
        targetPut.addColumn(family, column2, timestamps[1], value2);
        targetTable.put(targetPut);
      }
      // some rows only in the source table
      // ROWSWITHDIFFS: 10
      // TARGETMISSINGROWS: 10
      // TARGETMISSINGCELLS: 20
      for (; rowIndex < 50; rowIndex++) {
        Put put = new Put(Bytes.toBytes(rowIndex));
        put.addColumn(family, column1, timestamps[0], value1);
        put.addColumn(family, column2, timestamps[0], value2);
        sourceTable.put(put);
      }
      // some rows only in the target table
      // ROWSWITHDIFFS: 10
      // SOURCEMISSINGROWS: 10
      // SOURCEMISSINGCELLS: 20
      for (; rowIndex < 60; rowIndex++) {
        Put put = new Put(Bytes.toBytes(rowIndex));
        put.addColumn(family, column1, timestamps[1], value1);
        put.addColumn(family, column2, timestamps[1], value2);
        targetTable.put(put);
      }
      // some rows with 1 missing cell in target table
      // ROWSWITHDIFFS: 10
      // TARGETMISSINGCELLS: 10
      for (; rowIndex < 70; rowIndex++) {
        Put sourcePut = new Put(Bytes.toBytes(rowIndex));
        sourcePut.addColumn(family, column1, timestamps[0], value1);
        sourcePut.addColumn(family, column2, timestamps[0], value2);
        sourceTable.put(sourcePut);

        Put targetPut = new Put(Bytes.toBytes(rowIndex));
        targetPut.addColumn(family, column1, timestamps[1], value1);
        targetTable.put(targetPut);
      }
      // some rows with 1 missing cell in source table
      // ROWSWITHDIFFS: 10
      // SOURCEMISSINGCELLS: 10
      for (; rowIndex < 80; rowIndex++) {
        Put sourcePut = new Put(Bytes.toBytes(rowIndex));
        sourcePut.addColumn(family, column1, timestamps[0], value1);
        sourceTable.put(sourcePut);

        Put targetPut = new Put(Bytes.toBytes(rowIndex));
        targetPut.addColumn(family, column1, timestamps[1], value1);
        targetPut.addColumn(family, column2, timestamps[1], value2);
        targetTable.put(targetPut);
      }
      // some rows differing only in timestamp
      // ROWSWITHDIFFS: 10
      // SOURCEMISSINGCELLS: 20
      // TARGETMISSINGCELLS: 20
      for (; rowIndex < 90; rowIndex++) {
        Put sourcePut = new Put(Bytes.toBytes(rowIndex));
        sourcePut.addColumn(family, column1, timestamps[0], column1);
        sourcePut.addColumn(family, column2, timestamps[0], value2);
        sourceTable.put(sourcePut);

        Put targetPut = new Put(Bytes.toBytes(rowIndex));
        targetPut.addColumn(family, column1, timestamps[1] + 1, column1);
        targetPut.addColumn(family, column2, timestamps[1] - 1, value2);
        targetTable.put(targetPut);
      }
      // some rows with different values
      // ROWSWITHDIFFS: 10
      // DIFFERENTCELLVALUES: 20
      for (; rowIndex < numRows; rowIndex++) {
        Put sourcePut = new Put(Bytes.toBytes(rowIndex));
        sourcePut.addColumn(family, column1, timestamps[0], value1);
        sourcePut.addColumn(family, column2, timestamps[0], value2);
        sourceTable.put(sourcePut);

        Put targetPut = new Put(Bytes.toBytes(rowIndex));
        targetPut.addColumn(family, column1, timestamps[1], value3);
        targetPut.addColumn(family, column2, timestamps[1], value3);
        targetTable.put(targetPut);
      }
    }
  }
Basic test for the SyncTable M/R tool
hbase-mapreduce/src/test/java/org/apache/hadoop/hbase/mapreduce/TestSyncTable.java