/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.client;

import static org.apache.hadoop.hbase.HBaseTestingUtil.countRows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.StartTestingClusterOption;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
import org.apache.hadoop.hbase.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.filter.WhileMatchFilter;
import org.apache.hadoop.hbase.testclassification.ClientTests;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.NonRepeatedEnvironmentEdge;
import org.apache.hadoop.hbase.util.TableDescriptorChecker;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;

/**
 * Base for TestFromClientSide* classes. Has common defines and utility used by all.
 */
@Category({ LargeTests.class, ClientTests.class })
@SuppressWarnings("deprecation")
@RunWith(Parameterized.class)
class FromClientSideBase {
  private static final Logger LOG = LoggerFactory.getLogger(FromClientSideBase.class);
  static HBaseTestingUtil TEST_UTIL;
  static byte[] ROW = Bytes.toBytes("testRow");
  static byte[] FAMILY = Bytes.toBytes("testFamily");
  static final byte[] INVALID_FAMILY = Bytes.toBytes("invalidTestFamily");
  static byte[] QUALIFIER = Bytes.toBytes("testQualifier");
  static byte[] VALUE = Bytes.toBytes("testValue");
  static int SLAVES = 1;

  // To keep the child classes happy.
  FromClientSideBase() {
  }

  /**
   * JUnit does not provide an easy way to run a hook after each parameterized run. Without that
   * there is no easy way to restart the test cluster after each parameterized run. Annotation
   * BeforeParam does not work either because it runs before parameterization and hence does not
   * have access to the test parameters (which is weird). This *hack* checks if the current instance
   * of test cluster configuration has the passed parameterized configs. In such a case, we can just
   * reuse the cluster for test and do not need to initialize from scratch. While this is a hack, it
   * saves a ton of time for the full test and de-flakes it.
   */
  protected static boolean isSameParameterizedCluster(Class<?> registryImpl, int numHedgedReqs) {
    if (TEST_UTIL == null) {
      return false;
    }
    Configuration conf = TEST_UTIL.getConfiguration();
    Class<?> confClass = conf.getClass(HConstants.CLIENT_CONNECTION_REGISTRY_IMPL_CONF_KEY,
      ZKConnectionRegistry.class);
    int hedgedReqConfig = conf.getInt(MasterRegistry.MASTER_REGISTRY_HEDGED_REQS_FANOUT_KEY,
      AbstractRpcBasedConnectionRegistry.HEDGED_REQS_FANOUT_DEFAULT);
    return confClass.getName().equals(registryImpl.getName()) && numHedgedReqs == hedgedReqConfig;
  }

  protected static final void initialize(Class<? extends ConnectionRegistry> registryImpl,
    int numHedgedReqs, Class<?>... cps) throws Exception {
    // initialize() is called for every unit test, however we only want to reset the cluster state
    // at the end of every parameterized run.
    if (isSameParameterizedCluster(registryImpl, numHedgedReqs)) {
      return;
    }
    // Uncomment the following lines if more verbosity is needed for
    // debugging (see HBASE-12285 for details).
    // ((Log4JLogger)RpcServer.LOG).getLogger().setLevel(Level.ALL);
    // ((Log4JLogger)RpcClient.LOG).getLogger().setLevel(Level.ALL);
    // ((Log4JLogger)ScannerCallable.LOG).getLogger().setLevel(Level.ALL);
    // make sure that we do not get the same ts twice, see HBASE-19731 for more details.
    EnvironmentEdgeManager.injectEdge(new NonRepeatedEnvironmentEdge());
    if (TEST_UTIL != null) {
      // We reached end of a parameterized run, clean up.
      TEST_UTIL.shutdownMiniCluster();
    }
    TEST_UTIL = new HBaseTestingUtil();
    Configuration conf = TEST_UTIL.getConfiguration();
    conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
      Arrays.stream(cps).map(Class::getName).toArray(String[]::new));
    conf.setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, true); // enable for below tests
    conf.setClass(HConstants.CLIENT_CONNECTION_REGISTRY_IMPL_CONF_KEY, registryImpl,
      ConnectionRegistry.class);
    Preconditions.checkArgument(numHedgedReqs > 0);
    conf.setInt(MasterRegistry.MASTER_REGISTRY_HEDGED_REQS_FANOUT_KEY, numHedgedReqs);
    StartTestingClusterOption.Builder builder = StartTestingClusterOption.builder();
    // Multiple masters needed only when hedged reads for master registry are enabled.
    builder.numMasters(numHedgedReqs > 1 ? 3 : 1).numRegionServers(SLAVES);
    TEST_UTIL.startMiniCluster(builder.build());
  }

  protected static void afterClass() throws Exception {
    if (TEST_UTIL != null) {
      TEST_UTIL.shutdownMiniCluster();
    }
  }

  protected void deleteColumns(Table ht, String value, String keyPrefix) throws IOException {
    ResultScanner scanner = buildScanner(keyPrefix, value, ht);
    Iterator<Result> it = scanner.iterator();
    int count = 0;
    while (it.hasNext()) {
      Result result = it.next();
      Delete delete = new Delete(result.getRow());
      delete.addColumn(Bytes.toBytes("trans-tags"), Bytes.toBytes("qual2"));
      ht.delete(delete);
      count++;
    }
    assertEquals("Did not perform correct number of deletes", 3, count);
  }

  protected int getNumberOfRows(String keyPrefix, String value, Table ht) throws Exception {
    ResultScanner resultScanner = buildScanner(keyPrefix, value, ht);
    Iterator<Result> scanner = resultScanner.iterator();
    int numberOfResults = 0;
    while (scanner.hasNext()) {
      Result result = scanner.next();
      System.out.println("Got back key: " + Bytes.toString(result.getRow()));
      for (Cell kv : result.rawCells()) {
        System.out.println("kv=" + kv.toString() + ", " + Bytes.toString(CellUtil.cloneValue(kv)));
      }
      numberOfResults++;
    }
    return numberOfResults;
  }

  protected ResultScanner buildScanner(String keyPrefix, String value, Table ht)
    throws IOException {
    // OurFilterList allFilters = new OurFilterList();
    FilterList allFilters = new FilterList(/* FilterList.Operator.MUST_PASS_ALL */);
    allFilters.addFilter(new PrefixFilter(Bytes.toBytes(keyPrefix)));
    SingleColumnValueFilter filter = new SingleColumnValueFilter(Bytes.toBytes("trans-tags"),
      Bytes.toBytes("qual2"), CompareOperator.EQUAL, Bytes.toBytes(value));
    filter.setFilterIfMissing(true);
    allFilters.addFilter(filter);

    // allFilters.addFilter(new
    // RowExcludingSingleColumnValueFilter(Bytes.toBytes("trans-tags"),
    // Bytes.toBytes("qual2"), CompareOp.EQUAL, Bytes.toBytes(value)));

    Scan scan = new Scan();
    scan.addFamily(Bytes.toBytes("trans-blob"));
    scan.addFamily(Bytes.toBytes("trans-type"));
    scan.addFamily(Bytes.toBytes("trans-date"));
    scan.addFamily(Bytes.toBytes("trans-tags"));
    scan.addFamily(Bytes.toBytes("trans-group"));
    scan.setFilter(allFilters);

    return ht.getScanner(scan);
  }

  protected void putRows(Table ht, int numRows, String value, String key) throws IOException {
    for (int i = 0; i < numRows; i++) {
      String row = key + "_" + HBaseCommonTestingUtil.getRandomUUID().toString();
      System.out.println(String.format("Saving row: %s, with value %s", row, value));
      Put put = new Put(Bytes.toBytes(row));
      put.setDurability(Durability.SKIP_WAL);
      put.addColumn(Bytes.toBytes("trans-blob"), null, Bytes.toBytes("value for blob"));
      put.addColumn(Bytes.toBytes("trans-type"), null, Bytes.toBytes("statement"));
      put.addColumn(Bytes.toBytes("trans-date"), null, Bytes.toBytes("20090921010101999"));
      put.addColumn(Bytes.toBytes("trans-tags"), Bytes.toBytes("qual2"), Bytes.toBytes(value));
      put.addColumn(Bytes.toBytes("trans-group"), null, Bytes.toBytes("adhocTransactionGroupId"));
      ht.put(put);
    }
  }

  protected void assertRowCount(final Table t, final int expected) throws IOException {
    assertEquals(expected, countRows(t, new Scan()));
  }

  /*
   * @return Scan with RowFilter that does LESS than passed key.
   */
  protected Scan createScanWithRowFilter(final byte[] key) {
    return createScanWithRowFilter(key, null, CompareOperator.LESS);
  }

  /*
   * @return Scan with RowFilter that does CompareOp op on passed key.
   */
  protected Scan createScanWithRowFilter(final byte[] key, final byte[] startRow,
    CompareOperator op) {
    // Make sure key is of some substance... non-null and > than first key.
    assertTrue(key != null && key.length > 0
      && Bytes.BYTES_COMPARATOR.compare(key, new byte[] { 'a', 'a', 'a' }) >= 0);
    LOG.info("Key=" + Bytes.toString(key));
    Scan s = startRow == null ? new Scan() : new Scan().withStartRow(startRow);
    Filter f = new RowFilter(op, new BinaryComparator(key));
    f = new WhileMatchFilter(f);
    s.setFilter(f);
    return s;
  }

  /**
   * Split table into multiple regions.
   * @param t Table to split.
   * @return Map of regions to servers.
   */
  protected List<HRegionLocation> splitTable(final Table t) throws IOException {
    // Split this table in two.
    Admin admin = TEST_UTIL.getAdmin();
    admin.split(t.getName());
    // Is it right closing this admin?
    admin.close();
    List<HRegionLocation> regions = waitOnSplit(t);
    assertTrue(regions.size() > 1);
    return regions;
  }

  /*
   * Wait on table split. May return because we waited long enough on the split and it didn't
   * happen. Caller should check.
   * @return Map of table regions; caller needs to check table actually split.
   */
  private List<HRegionLocation> waitOnSplit(final Table t) throws IOException {
    try (RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(t.getName())) {
      List<HRegionLocation> regions = locator.getAllRegionLocations();
      int originalCount = regions.size();
      for (int i = 0; i < TEST_UTIL.getConfiguration().getInt("hbase.test.retries", 30); i++) {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        regions = locator.getAllRegionLocations();
        if (regions.size() > originalCount) {
          break;
        }
      }
      return regions;
    }
  }

  protected Result getSingleScanResult(Table ht, Scan scan) throws IOException {
    ResultScanner scanner = ht.getScanner(scan);
    Result result = scanner.next();
    scanner.close();
    return result;
  }

  byte[][] makeNAscii(byte[] base, int n) {
    if (n > 256) {
      return makeNBig(base, n);
    }
    byte[][] ret = new byte[n][];
    for (int i = 0; i < n; i++) {
      byte[] tail = Bytes.toBytes(Integer.toString(i));
      ret[i] = Bytes.add(base, tail);
    }
    return ret;
  }

  protected byte[][] makeN(byte[] base, int n) {
    if (n > 256) {
      return makeNBig(base, n);
    }
    byte[][] ret = new byte[n][];
    for (int i = 0; i < n; i++) {
      ret[i] = Bytes.add(base, new byte[] { (byte) i });
    }
    return ret;
  }

  protected byte[][] makeNBig(byte[] base, int n) {
    byte[][] ret = new byte[n][];
    for (int i = 0; i < n; i++) {
      int byteA = (i % 256);
      int byteB = (i >> 8);
      ret[i] = Bytes.add(base, new byte[] { (byte) byteB, (byte) byteA });
    }
    return ret;
  }

  protected long[] makeStamps(int n) {
    long[] stamps = new long[n];
    for (int i = 0; i < n; i++) {
      stamps[i] = i + 1L;
    }
    return stamps;
  }

  protected static boolean equals(byte[] left, byte[] right) {
    if (left == null && right == null) {
      return true;
    }
    if (left == null && right.length == 0) {
      return true;
    }
    if (right == null && left.length == 0) {
      return true;
    }
    return Bytes.equals(left, right);
  }

  protected void assertKey(Cell key, byte[] row, byte[] family, byte[] qualifier, byte[] value) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(CellUtil.cloneRow(key)) + "]", equals(row, CellUtil.cloneRow(key)));
    assertTrue(
      "Expected family [" + Bytes.toString(family) + "] " + "Got family ["
        + Bytes.toString(CellUtil.cloneFamily(key)) + "]",
      equals(family, CellUtil.cloneFamily(key)));
    assertTrue(
      "Expected qualifier [" + Bytes.toString(qualifier) + "] " + "Got qualifier ["
        + Bytes.toString(CellUtil.cloneQualifier(key)) + "]",
      equals(qualifier, CellUtil.cloneQualifier(key)));
    assertTrue("Expected value [" + Bytes.toString(value) + "] " + "Got value ["
      + Bytes.toString(CellUtil.cloneValue(key)) + "]", equals(value, CellUtil.cloneValue(key)));
  }

  static void assertIncrementKey(Cell key, byte[] row, byte[] family, byte[] qualifier,
    long value) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(CellUtil.cloneRow(key)) + "]", equals(row, CellUtil.cloneRow(key)));
    assertTrue(
      "Expected family [" + Bytes.toString(family) + "] " + "Got family ["
        + Bytes.toString(CellUtil.cloneFamily(key)) + "]",
      equals(family, CellUtil.cloneFamily(key)));
    assertTrue(
      "Expected qualifier [" + Bytes.toString(qualifier) + "] " + "Got qualifier ["
        + Bytes.toString(CellUtil.cloneQualifier(key)) + "]",
      equals(qualifier, CellUtil.cloneQualifier(key)));
    assertEquals("Expected value [" + value + "] " + "Got value ["
      + Bytes.toLong(CellUtil.cloneValue(key)) + "]", Bytes.toLong(CellUtil.cloneValue(key)),
      value);
  }

  protected void assertNumKeys(Result result, int n) throws Exception {
    assertEquals("Expected " + n + " keys but got " + result.size(), result.size(), n);
  }

  protected void assertNResult(Result result, byte[] row, byte[][] families, byte[][] qualifiers,
    byte[][] values, int[][] idxs) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(result.getRow()) + "]", equals(row, result.getRow()));
    assertEquals("Expected " + idxs.length + " keys but result contains " + result.size(),
      result.size(), idxs.length);

    Cell[] keys = result.rawCells();

    for (int i = 0; i < keys.length; i++) {
      byte[] family = families[idxs[i][0]];
      byte[] qualifier = qualifiers[idxs[i][1]];
      byte[] value = values[idxs[i][2]];
      Cell key = keys[i];

      byte[] famb = CellUtil.cloneFamily(key);
      byte[] qualb = CellUtil.cloneQualifier(key);
      byte[] valb = CellUtil.cloneValue(key);
      assertTrue("(" + i + ") Expected family [" + Bytes.toString(family) + "] " + "Got family ["
        + Bytes.toString(famb) + "]", equals(family, famb));
      assertTrue("(" + i + ") Expected qualifier [" + Bytes.toString(qualifier) + "] "
        + "Got qualifier [" + Bytes.toString(qualb) + "]", equals(qualifier, qualb));
      assertTrue("(" + i + ") Expected value [" + Bytes.toString(value) + "] " + "Got value ["
        + Bytes.toString(valb) + "]", equals(value, valb));
    }
  }

  protected void assertNResult(Result result, byte[] row, byte[] family, byte[] qualifier,
    long[] stamps, byte[][] values, int start, int end) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(result.getRow()) + "]", equals(row, result.getRow()));
    int expectedResults = end - start + 1;
    assertEquals(expectedResults, result.size());

    Cell[] keys = result.rawCells();

    for (int i = 0; i < keys.length; i++) {
      byte[] value = values[end - i];
      long ts = stamps[end - i];
      Cell key = keys[i];

      assertTrue("(" + i + ") Expected family [" + Bytes.toString(family) + "] " + "Got family ["
        + Bytes.toString(CellUtil.cloneFamily(key)) + "]", CellUtil.matchingFamily(key, family));
      assertTrue(
        "(" + i + ") Expected qualifier [" + Bytes.toString(qualifier) + "] " + "Got qualifier ["
          + Bytes.toString(CellUtil.cloneQualifier(key)) + "]",
        CellUtil.matchingQualifier(key, qualifier));
      assertEquals("Expected ts [" + ts + "] " + "Got ts [" + key.getTimestamp() + "]", ts,
        key.getTimestamp());
      assertTrue("(" + i + ") Expected value [" + Bytes.toString(value) + "] " + "Got value ["
        + Bytes.toString(CellUtil.cloneValue(key)) + "]", CellUtil.matchingValue(key, value));
    }
  }

  /**
   * Validate that result contains two specified keys, exactly. It is assumed key A sorts before key
   * B.
   */
  protected void assertDoubleResult(Result result, byte[] row, byte[] familyA, byte[] qualifierA,
    byte[] valueA, byte[] familyB, byte[] qualifierB, byte[] valueB) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(result.getRow()) + "]", equals(row, result.getRow()));
    assertEquals("Expected two keys but result contains " + result.size(), 2, result.size());
    Cell[] kv = result.rawCells();
    Cell kvA = kv[0];
    assertTrue(
      "(A) Expected family [" + Bytes.toString(familyA) + "] " + "Got family ["
        + Bytes.toString(CellUtil.cloneFamily(kvA)) + "]",
      equals(familyA, CellUtil.cloneFamily(kvA)));
    assertTrue(
      "(A) Expected qualifier [" + Bytes.toString(qualifierA) + "] " + "Got qualifier ["
        + Bytes.toString(CellUtil.cloneQualifier(kvA)) + "]",
      equals(qualifierA, CellUtil.cloneQualifier(kvA)));
    assertTrue("(A) Expected value [" + Bytes.toString(valueA) + "] " + "Got value ["
      + Bytes.toString(CellUtil.cloneValue(kvA)) + "]", equals(valueA, CellUtil.cloneValue(kvA)));
    Cell kvB = kv[1];
    assertTrue(
      "(B) Expected family [" + Bytes.toString(familyB) + "] " + "Got family ["
        + Bytes.toString(CellUtil.cloneFamily(kvB)) + "]",
      equals(familyB, CellUtil.cloneFamily(kvB)));
    assertTrue(
      "(B) Expected qualifier [" + Bytes.toString(qualifierB) + "] " + "Got qualifier ["
        + Bytes.toString(CellUtil.cloneQualifier(kvB)) + "]",
      equals(qualifierB, CellUtil.cloneQualifier(kvB)));
    assertTrue("(B) Expected value [" + Bytes.toString(valueB) + "] " + "Got value ["
      + Bytes.toString(CellUtil.cloneValue(kvB)) + "]", equals(valueB, CellUtil.cloneValue(kvB)));
  }

  protected void assertSingleResult(Result result, byte[] row, byte[] family, byte[] qualifier,
    byte[] value) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(result.getRow()) + "]", equals(row, result.getRow()));
    assertEquals("Expected a single key but result contains " + result.size(), 1, result.size());
    Cell kv = result.rawCells()[0];
    assertTrue("Expected family [" + Bytes.toString(family) + "] " + "Got family ["
      + Bytes.toString(CellUtil.cloneFamily(kv)) + "]", equals(family, CellUtil.cloneFamily(kv)));
    assertTrue(
      "Expected qualifier [" + Bytes.toString(qualifier) + "] " + "Got qualifier ["
        + Bytes.toString(CellUtil.cloneQualifier(kv)) + "]",
      equals(qualifier, CellUtil.cloneQualifier(kv)));
    assertTrue("Expected value [" + Bytes.toString(value) + "] " + "Got value ["
      + Bytes.toString(CellUtil.cloneValue(kv)) + "]", equals(value, CellUtil.cloneValue(kv)));
  }

  protected void assertSingleResult(Result result, byte[] row, byte[] family, byte[] qualifier,
    long value) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(result.getRow()) + "]", equals(row, result.getRow()));
    assertEquals("Expected a single key but result contains " + result.size(), 1, result.size());
    Cell kv = result.rawCells()[0];
    assertTrue("Expected family [" + Bytes.toString(family) + "] " + "Got family ["
      + Bytes.toString(CellUtil.cloneFamily(kv)) + "]", equals(family, CellUtil.cloneFamily(kv)));
    assertTrue(
      "Expected qualifier [" + Bytes.toString(qualifier) + "] " + "Got qualifier ["
        + Bytes.toString(CellUtil.cloneQualifier(kv)) + "]",
      equals(qualifier, CellUtil.cloneQualifier(kv)));
    assertEquals("Expected value [" + value + "] " + "Got value ["
      + Bytes.toLong(CellUtil.cloneValue(kv)) + "]", value, Bytes.toLong(CellUtil.cloneValue(kv)));
  }

  protected void assertSingleResult(Result result, byte[] row, byte[] family, byte[] qualifier,
    long ts, byte[] value) {
    assertTrue("Expected row [" + Bytes.toString(row) + "] " + "Got row ["
      + Bytes.toString(result.getRow()) + "]", equals(row, result.getRow()));
    assertEquals("Expected a single key but result contains " + result.size(), 1, result.size());
    Cell kv = result.rawCells()[0];
    assertTrue("Expected family [" + Bytes.toString(family) + "] " + "Got family ["
      + Bytes.toString(CellUtil.cloneFamily(kv)) + "]", equals(family, CellUtil.cloneFamily(kv)));
    assertTrue(
      "Expected qualifier [" + Bytes.toString(qualifier) + "] " + "Got qualifier ["
        + Bytes.toString(CellUtil.cloneQualifier(kv)) + "]",
      equals(qualifier, CellUtil.cloneQualifier(kv)));
    assertEquals("Expected ts [" + ts + "] " + "Got ts [" + kv.getTimestamp() + "]", ts,
      kv.getTimestamp());
    assertTrue("Expected value [" + Bytes.toString(value) + "] " + "Got value ["
      + Bytes.toString(CellUtil.cloneValue(kv)) + "]", equals(value, CellUtil.cloneValue(kv)));
  }

  protected void assertEmptyResult(Result result) throws Exception {
    assertTrue("expected an empty result but result contains " + result.size() + " keys",
      result.isEmpty());
  }

  protected void assertNullResult(Result result) throws Exception {
    assertNull("expected null result but received a non-null result", result);
  }

  protected void getVersionRangeAndVerifyGreaterThan(Table ht, byte[] row, byte[] family,
    byte[] qualifier, long[] stamps, byte[][] values, int start, int end) throws IOException {
    Get get = new Get(row);
    get.addColumn(family, qualifier);
    get.readVersions(Integer.MAX_VALUE);
    get.setTimeRange(stamps[start + 1], Long.MAX_VALUE);
    Result result = ht.get(get);
    assertNResult(result, row, family, qualifier, stamps, values, start + 1, end);
  }

  protected void getVersionRangeAndVerify(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long[] stamps, byte[][] values, int start, int end) throws IOException {
    Get get = new Get(row);
    get.addColumn(family, qualifier);
    get.readVersions(Integer.MAX_VALUE);
    get.setTimeRange(stamps[start], stamps[end] + 1);
    Result result = ht.get(get);
    assertNResult(result, row, family, qualifier, stamps, values, start, end);
  }

  protected void getAllVersionsAndVerify(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long[] stamps, byte[][] values, int start, int end) throws IOException {
    Get get = new Get(row);
    get.addColumn(family, qualifier);
    get.readVersions(Integer.MAX_VALUE);
    Result result = ht.get(get);
    assertNResult(result, row, family, qualifier, stamps, values, start, end);
  }

  protected void scanVersionRangeAndVerifyGreaterThan(Table ht, byte[] row, byte[] family,
    byte[] qualifier, long[] stamps, byte[][] values, int start, int end) throws IOException {
    Scan scan = new Scan().withStartRow(row);
    scan.addColumn(family, qualifier);
    scan.readVersions(Integer.MAX_VALUE);
    scan.setTimeRange(stamps[start + 1], Long.MAX_VALUE);
    Result result = getSingleScanResult(ht, scan);
    assertNResult(result, row, family, qualifier, stamps, values, start + 1, end);
  }

  protected void scanVersionRangeAndVerify(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long[] stamps, byte[][] values, int start, int end) throws IOException {
    Scan scan = new Scan().withStartRow(row);
    scan.addColumn(family, qualifier);
    scan.readVersions(Integer.MAX_VALUE);
    scan.setTimeRange(stamps[start], stamps[end] + 1);
    Result result = getSingleScanResult(ht, scan);
    assertNResult(result, row, family, qualifier, stamps, values, start, end);
  }

  protected void scanAllVersionsAndVerify(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long[] stamps, byte[][] values, int start, int end) throws IOException {
    Scan scan = new Scan().withStartRow(row);
    scan.addColumn(family, qualifier);
    scan.readVersions(Integer.MAX_VALUE);
    Result result = getSingleScanResult(ht, scan);
    assertNResult(result, row, family, qualifier, stamps, values, start, end);
  }

  protected void getVersionAndVerify(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long stamp, byte[] value) throws Exception {
    Get get = new Get(row);
    get.addColumn(family, qualifier);
    get.setTimestamp(stamp);
    get.readVersions(Integer.MAX_VALUE);
    Result result = ht.get(get);
    assertSingleResult(result, row, family, qualifier, stamp, value);
  }

  protected void getVersionAndVerifyMissing(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long stamp) throws Exception {
    Get get = new Get(row);
    get.addColumn(family, qualifier);
    get.setTimestamp(stamp);
    get.readVersions(Integer.MAX_VALUE);
    Result result = ht.get(get);
    assertEmptyResult(result);
  }

  protected void scanVersionAndVerify(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long stamp, byte[] value) throws Exception {
    Scan scan = new Scan().withStartRow(row);
    scan.addColumn(family, qualifier);
    scan.setTimestamp(stamp);
    scan.readVersions(Integer.MAX_VALUE);
    Result result = getSingleScanResult(ht, scan);
    assertSingleResult(result, row, family, qualifier, stamp, value);
  }

  protected void scanVersionAndVerifyMissing(Table ht, byte[] row, byte[] family, byte[] qualifier,
    long stamp) throws Exception {
    Scan scan = new Scan().withStartRow(row);
    scan.addColumn(family, qualifier);
    scan.setTimestamp(stamp);
    scan.readVersions(Integer.MAX_VALUE);
    Result result = getSingleScanResult(ht, scan);
    assertNullResult(result);
  }

  protected void getTestNull(Table ht, byte[] row, byte[] family, byte[] value) throws Exception {
    Get get = new Get(row);
    get.addColumn(family, null);
    Result result = ht.get(get);
    assertSingleResult(result, row, family, null, value);

    get = new Get(row);
    get.addColumn(family, HConstants.EMPTY_BYTE_ARRAY);
    result = ht.get(get);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

    get = new Get(row);
    get.addFamily(family);
    result = ht.get(get);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

    get = new Get(row);
    result = ht.get(get);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

  }

  protected void getTestNull(Table ht, byte[] row, byte[] family, long value) throws Exception {
    Get get = new Get(row);
    get.addColumn(family, null);
    Result result = ht.get(get);
    assertSingleResult(result, row, family, null, value);

    get = new Get(row);
    get.addColumn(family, HConstants.EMPTY_BYTE_ARRAY);
    result = ht.get(get);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

    get = new Get(row);
    get.addFamily(family);
    result = ht.get(get);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

    get = new Get(row);
    result = ht.get(get);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);
  }

  protected void scanTestNull(Table ht, byte[] row, byte[] family, byte[] value) throws Exception {
    scanTestNull(ht, row, family, value, false);
  }

  protected void scanTestNull(Table ht, byte[] row, byte[] family, byte[] value,
    boolean isReversedScan) throws Exception {

    Scan scan = new Scan();
    scan.setReversed(isReversedScan);
    scan.addColumn(family, null);
    Result result = getSingleScanResult(ht, scan);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

    scan = new Scan();
    scan.setReversed(isReversedScan);
    scan.addColumn(family, HConstants.EMPTY_BYTE_ARRAY);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

    scan = new Scan();
    scan.setReversed(isReversedScan);
    scan.addFamily(family);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

    scan = new Scan();
    scan.setReversed(isReversedScan);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, row, family, HConstants.EMPTY_BYTE_ARRAY, value);

  }

  protected void singleRowGetTest(Table ht, byte[][] ROWS, byte[][] FAMILIES, byte[][] QUALIFIERS,
    byte[][] VALUES) throws Exception {
    // Single column from memstore
    Get get = new Get(ROWS[0]);
    get.addColumn(FAMILIES[4], QUALIFIERS[0]);
    Result result = ht.get(get);
    assertSingleResult(result, ROWS[0], FAMILIES[4], QUALIFIERS[0], VALUES[0]);

    // Single column from storefile
    get = new Get(ROWS[0]);
    get.addColumn(FAMILIES[2], QUALIFIERS[2]);
    result = ht.get(get);
    assertSingleResult(result, ROWS[0], FAMILIES[2], QUALIFIERS[2], VALUES[2]);

    // Single column from storefile, family match
    get = new Get(ROWS[0]);
    get.addFamily(FAMILIES[7]);
    result = ht.get(get);
    assertSingleResult(result, ROWS[0], FAMILIES[7], QUALIFIERS[7], VALUES[7]);

    // Two columns, one from memstore one from storefile, same family,
    // wildcard match
    get = new Get(ROWS[0]);
    get.addFamily(FAMILIES[4]);
    result = ht.get(get);
    assertDoubleResult(result, ROWS[0], FAMILIES[4], QUALIFIERS[0], VALUES[0], FAMILIES[4],
      QUALIFIERS[4], VALUES[4]);

    // Two columns, one from memstore one from storefile, same family,
    // explicit match
    get = new Get(ROWS[0]);
    get.addColumn(FAMILIES[4], QUALIFIERS[0]);
    get.addColumn(FAMILIES[4], QUALIFIERS[4]);
    result = ht.get(get);
    assertDoubleResult(result, ROWS[0], FAMILIES[4], QUALIFIERS[0], VALUES[0], FAMILIES[4],
      QUALIFIERS[4], VALUES[4]);

    // Three column, one from memstore two from storefile, different families,
    // wildcard match
    get = new Get(ROWS[0]);
    get.addFamily(FAMILIES[4]);
    get.addFamily(FAMILIES[7]);
    result = ht.get(get);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES,
      new int[][] { { 4, 0, 0 }, { 4, 4, 4 }, { 7, 7, 7 } });

    // Multiple columns from everywhere storefile, many family, wildcard
    get = new Get(ROWS[0]);
    get.addFamily(FAMILIES[2]);
    get.addFamily(FAMILIES[4]);
    get.addFamily(FAMILIES[6]);
    get.addFamily(FAMILIES[7]);
    result = ht.get(get);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES, new int[][] { { 2, 2, 2 },
      { 2, 4, 4 }, { 4, 0, 0 }, { 4, 4, 4 }, { 6, 6, 6 }, { 6, 7, 7 }, { 7, 7, 7 } });

    // Multiple columns from everywhere storefile, many family, wildcard
    get = new Get(ROWS[0]);
    get.addColumn(FAMILIES[2], QUALIFIERS[2]);
    get.addColumn(FAMILIES[2], QUALIFIERS[4]);
    get.addColumn(FAMILIES[4], QUALIFIERS[0]);
    get.addColumn(FAMILIES[4], QUALIFIERS[4]);
    get.addColumn(FAMILIES[6], QUALIFIERS[6]);
    get.addColumn(FAMILIES[6], QUALIFIERS[7]);
    get.addColumn(FAMILIES[7], QUALIFIERS[7]);
    get.addColumn(FAMILIES[7], QUALIFIERS[8]);
    result = ht.get(get);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES, new int[][] { { 2, 2, 2 },
      { 2, 4, 4 }, { 4, 0, 0 }, { 4, 4, 4 }, { 6, 6, 6 }, { 6, 7, 7 }, { 7, 7, 7 } });

    // Everything
    get = new Get(ROWS[0]);
    result = ht.get(get);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES, new int[][] { { 2, 2, 2 },
      { 2, 4, 4 }, { 4, 0, 0 }, { 4, 4, 4 }, { 6, 6, 6 }, { 6, 7, 7 }, { 7, 7, 7 }, { 9, 0, 0 } });

    // Get around inserted columns

    get = new Get(ROWS[1]);
    result = ht.get(get);
    assertEmptyResult(result);

    get = new Get(ROWS[0]);
    get.addColumn(FAMILIES[4], QUALIFIERS[3]);
    get.addColumn(FAMILIES[2], QUALIFIERS[3]);
    result = ht.get(get);
    assertEmptyResult(result);

  }

  protected void singleRowScanTest(Table ht, byte[][] ROWS, byte[][] FAMILIES, byte[][] QUALIFIERS,
    byte[][] VALUES) throws Exception {
    // Single column from memstore
    Scan scan = new Scan();
    scan.addColumn(FAMILIES[4], QUALIFIERS[0]);
    Result result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[0], FAMILIES[4], QUALIFIERS[0], VALUES[0]);

    // Single column from storefile
    scan = new Scan();
    scan.addColumn(FAMILIES[2], QUALIFIERS[2]);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[0], FAMILIES[2], QUALIFIERS[2], VALUES[2]);

    // Single column from storefile, family match
    scan = new Scan();
    scan.addFamily(FAMILIES[7]);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[0], FAMILIES[7], QUALIFIERS[7], VALUES[7]);

    // Two columns, one from memstore one from storefile, same family,
    // wildcard match
    scan = new Scan();
    scan.addFamily(FAMILIES[4]);
    result = getSingleScanResult(ht, scan);
    assertDoubleResult(result, ROWS[0], FAMILIES[4], QUALIFIERS[0], VALUES[0], FAMILIES[4],
      QUALIFIERS[4], VALUES[4]);

    // Two columns, one from memstore one from storefile, same family,
    // explicit match
    scan = new Scan();
    scan.addColumn(FAMILIES[4], QUALIFIERS[0]);
    scan.addColumn(FAMILIES[4], QUALIFIERS[4]);
    result = getSingleScanResult(ht, scan);
    assertDoubleResult(result, ROWS[0], FAMILIES[4], QUALIFIERS[0], VALUES[0], FAMILIES[4],
      QUALIFIERS[4], VALUES[4]);

    // Three column, one from memstore two from storefile, different families,
    // wildcard match
    scan = new Scan();
    scan.addFamily(FAMILIES[4]);
    scan.addFamily(FAMILIES[7]);
    result = getSingleScanResult(ht, scan);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES,
      new int[][] { { 4, 0, 0 }, { 4, 4, 4 }, { 7, 7, 7 } });

    // Multiple columns from everywhere storefile, many family, wildcard
    scan = new Scan();
    scan.addFamily(FAMILIES[2]);
    scan.addFamily(FAMILIES[4]);
    scan.addFamily(FAMILIES[6]);
    scan.addFamily(FAMILIES[7]);
    result = getSingleScanResult(ht, scan);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES, new int[][] { { 2, 2, 2 },
      { 2, 4, 4 }, { 4, 0, 0 }, { 4, 4, 4 }, { 6, 6, 6 }, { 6, 7, 7 }, { 7, 7, 7 } });

    // Multiple columns from everywhere storefile, many family, wildcard
    scan = new Scan();
    scan.addColumn(FAMILIES[2], QUALIFIERS[2]);
    scan.addColumn(FAMILIES[2], QUALIFIERS[4]);
    scan.addColumn(FAMILIES[4], QUALIFIERS[0]);
    scan.addColumn(FAMILIES[4], QUALIFIERS[4]);
    scan.addColumn(FAMILIES[6], QUALIFIERS[6]);
    scan.addColumn(FAMILIES[6], QUALIFIERS[7]);
    scan.addColumn(FAMILIES[7], QUALIFIERS[7]);
    scan.addColumn(FAMILIES[7], QUALIFIERS[8]);
    result = getSingleScanResult(ht, scan);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES, new int[][] { { 2, 2, 2 },
      { 2, 4, 4 }, { 4, 0, 0 }, { 4, 4, 4 }, { 6, 6, 6 }, { 6, 7, 7 }, { 7, 7, 7 } });

    // Everything
    scan = new Scan();
    result = getSingleScanResult(ht, scan);
    assertNResult(result, ROWS[0], FAMILIES, QUALIFIERS, VALUES, new int[][] { { 2, 2, 2 },
      { 2, 4, 4 }, { 4, 0, 0 }, { 4, 4, 4 }, { 6, 6, 6 }, { 6, 7, 7 }, { 7, 7, 7 }, { 9, 0, 0 } });

    // Scan around inserted columns

    scan = new Scan().withStartRow(ROWS[1]);
    result = getSingleScanResult(ht, scan);
    assertNullResult(result);

    scan = new Scan();
    scan.addColumn(FAMILIES[4], QUALIFIERS[3]);
    scan.addColumn(FAMILIES[2], QUALIFIERS[3]);
    result = getSingleScanResult(ht, scan);
    assertNullResult(result);
  }

  /**
   * Verify a single column using gets. Expects family and qualifier arrays to be valid for at least
   * the range: idx-2 < idx < idx+2
   */
  protected void getVerifySingleColumn(Table ht, byte[][] ROWS, int ROWIDX, byte[][] FAMILIES,
    int FAMILYIDX, byte[][] QUALIFIERS, int QUALIFIERIDX, byte[][] VALUES, int VALUEIDX)
    throws Exception {
    Get get = new Get(ROWS[ROWIDX]);
    Result result = ht.get(get);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    get = new Get(ROWS[ROWIDX]);
    get.addFamily(FAMILIES[FAMILYIDX]);
    result = ht.get(get);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    get = new Get(ROWS[ROWIDX]);
    get.addFamily(FAMILIES[FAMILYIDX - 2]);
    get.addFamily(FAMILIES[FAMILYIDX]);
    get.addFamily(FAMILIES[FAMILYIDX + 2]);
    result = ht.get(get);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    get = new Get(ROWS[ROWIDX]);
    get.addColumn(FAMILIES[FAMILYIDX], QUALIFIERS[0]);
    result = ht.get(get);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    get = new Get(ROWS[ROWIDX]);
    get.addColumn(FAMILIES[FAMILYIDX], QUALIFIERS[1]);
    get.addFamily(FAMILIES[FAMILYIDX]);
    result = ht.get(get);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    get = new Get(ROWS[ROWIDX]);
    get.addFamily(FAMILIES[FAMILYIDX]);
    get.addColumn(FAMILIES[FAMILYIDX + 1], QUALIFIERS[1]);
    get.addColumn(FAMILIES[FAMILYIDX - 2], QUALIFIERS[1]);
    get.addFamily(FAMILIES[FAMILYIDX - 1]);
    get.addFamily(FAMILIES[FAMILYIDX + 2]);
    result = ht.get(get);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

  }

  /**
   * Verify a single column using scanners. Expects family and qualifier arrays to be valid for at
   * least the range: idx-2 to idx+2 Expects row array to be valid for at least idx to idx+2
   */
  protected void scanVerifySingleColumn(Table ht, byte[][] ROWS, int ROWIDX, byte[][] FAMILIES,
    int FAMILYIDX, byte[][] QUALIFIERS, int QUALIFIERIDX, byte[][] VALUES, int VALUEIDX)
    throws Exception {
    Scan scan = new Scan();
    Result result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    scan = new Scan().withStartRow(ROWS[ROWIDX]);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    scan = new Scan().withStartRow(ROWS[ROWIDX]).withStopRow(ROWS[ROWIDX + 1], true);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    scan = new Scan().withStartRow(HConstants.EMPTY_START_ROW).withStopRow(ROWS[ROWIDX + 1], true);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    scan = new Scan();
    scan.addFamily(FAMILIES[FAMILYIDX]);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    scan = new Scan();
    scan.addColumn(FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX]);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    scan = new Scan();
    scan.addColumn(FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX + 1]);
    scan.addFamily(FAMILIES[FAMILYIDX]);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

    scan = new Scan();
    scan.addColumn(FAMILIES[FAMILYIDX - 1], QUALIFIERS[QUALIFIERIDX + 1]);
    scan.addColumn(FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX]);
    scan.addFamily(FAMILIES[FAMILYIDX + 1]);
    result = getSingleScanResult(ht, scan);
    assertSingleResult(result, ROWS[ROWIDX], FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX],
      VALUES[VALUEIDX]);

  }

  /**
   * Verify we do not read any values by accident around a single column Same requirements as
   * getVerifySingleColumn
   */
  protected void getVerifySingleEmpty(Table ht, byte[][] ROWS, int ROWIDX, byte[][] FAMILIES,
    int FAMILYIDX, byte[][] QUALIFIERS, int QUALIFIERIDX) throws Exception {
    Get get = new Get(ROWS[ROWIDX]);
    get.addFamily(FAMILIES[4]);
    get.addColumn(FAMILIES[4], QUALIFIERS[1]);
    Result result = ht.get(get);
    assertEmptyResult(result);

    get = new Get(ROWS[ROWIDX]);
    get.addFamily(FAMILIES[4]);
    get.addColumn(FAMILIES[4], QUALIFIERS[2]);
    result = ht.get(get);
    assertEmptyResult(result);

    get = new Get(ROWS[ROWIDX]);
    get.addFamily(FAMILIES[3]);
    get.addColumn(FAMILIES[4], QUALIFIERS[2]);
    get.addFamily(FAMILIES[5]);
    result = ht.get(get);
    assertEmptyResult(result);

    get = new Get(ROWS[ROWIDX + 1]);
    result = ht.get(get);
    assertEmptyResult(result);

  }

  protected void scanVerifySingleEmpty(Table ht, byte[][] ROWS, int ROWIDX, byte[][] FAMILIES,
    int FAMILYIDX, byte[][] QUALIFIERS, int QUALIFIERIDX) throws Exception {
    Scan scan = new Scan().withStartRow(ROWS[ROWIDX + 1]);
    Result result = getSingleScanResult(ht, scan);
    assertNullResult(result);

    scan = new Scan().withStartRow(ROWS[ROWIDX + 1]).withStopRow(ROWS[ROWIDX + 2], true);
    result = getSingleScanResult(ht, scan);
    assertNullResult(result);

    scan = new Scan().withStartRow(HConstants.EMPTY_START_ROW).withStopRow(ROWS[ROWIDX]);
    result = getSingleScanResult(ht, scan);
    assertNullResult(result);

    scan = new Scan();
    scan.addColumn(FAMILIES[FAMILYIDX], QUALIFIERS[QUALIFIERIDX + 1]);
    scan.addFamily(FAMILIES[FAMILYIDX - 1]);
    result = getSingleScanResult(ht, scan);
    assertNullResult(result);

  }
}
