accumulo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From els...@apache.org
Subject [1/3] accumulo git commit: ACCUMULO-626 Create an harness for testing iterators
Date Mon, 14 Dec 2015 05:29:52 GMT
Repository: accumulo
Updated Branches:
  refs/heads/master 24edc84a9 -> c7b27e0ae


ACCUMULO-626 Create an harness for testing iterators

Provides test cases that can test any iterator. Includes
a JUnit4 runner. Adds user manual documentation.

Closes apache/accumulo#50


Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo
Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/b332873c
Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/b332873c
Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/b332873c

Branch: refs/heads/master
Commit: b332873cf02a32e68f0b90ce9650c07ea78f02ec
Parents: 24edc84
Author: Josh Elser <elserj@apache.org>
Authored: Sat Nov 7 14:17:17 2015 -0500
Committer: Josh Elser <elserj@apache.org>
Committed: Mon Dec 14 00:29:05 2015 -0500

----------------------------------------------------------------------
 .../main/asciidoc/accumulo_user_manual.asciidoc |   2 +
 .../asciidoc/chapters/iterator_test_harness.txt | 110 ++++++++++++++
 iterator-test-harness/.gitignore                |  26 ++++
 iterator-test-harness/pom.xml                   |  51 +++++++
 .../iteratortest/IteratorTestCaseFinder.java    |  78 ++++++++++
 .../iteratortest/IteratorTestInput.java         |  88 +++++++++++
 .../iteratortest/IteratorTestOutput.java        | 152 +++++++++++++++++++
 .../iteratortest/IteratorTestReport.java        |  76 ++++++++++
 .../iteratortest/IteratorTestRunner.java        |  95 ++++++++++++
 .../accumulo/iteratortest/IteratorTestUtil.java |  44 ++++++
 .../iteratortest/SimpleKVReusingIterator.java   |  87 +++++++++++
 .../environments/SimpleIteratorEnvironment.java |  80 ++++++++++
 .../junit4/BaseJUnit4IteratorTest.java          |  99 ++++++++++++
 .../testcases/DeepCopyTestCase.java             |  63 ++++++++
 .../testcases/InstantiationTestCase.java        |  51 +++++++
 .../testcases/IsolatedDeepCopiesTestCase.java   | 124 +++++++++++++++
 .../testcases/IteratorTestCase.java             |  49 ++++++
 .../testcases/MultipleHasTopCalls.java          |  87 +++++++++++
 .../testcases/OutputVerifyingTestCase.java      |  30 ++++
 .../iteratortest/testcases/ReSeekTestCase.java  | 110 ++++++++++++++
 .../iteratortest/WholeRowIteratorTest.java      | 147 ++++++++++++++++++
 .../framework/JUnitFrameworkTest.java           |  98 ++++++++++++
 .../src/test/resources/log4j.properties         |  24 +++
 pom.xml                                         |   1 +
 24 files changed, 1772 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/docs/src/main/asciidoc/accumulo_user_manual.asciidoc
----------------------------------------------------------------------
diff --git a/docs/src/main/asciidoc/accumulo_user_manual.asciidoc b/docs/src/main/asciidoc/accumulo_user_manual.asciidoc
index b62983a..9b503af 100644
--- a/docs/src/main/asciidoc/accumulo_user_manual.asciidoc
+++ b/docs/src/main/asciidoc/accumulo_user_manual.asciidoc
@@ -43,6 +43,8 @@ include::chapters/table_configuration.txt[]
 
 include::chapters/iterator_design.txt[]
 
+include::chapters/iterator_test_harness.txt[]
+
 include::chapters/table_design.txt[]
 
 include::chapters/high_speed_ingest.txt[]

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/docs/src/main/asciidoc/chapters/iterator_test_harness.txt
----------------------------------------------------------------------
diff --git a/docs/src/main/asciidoc/chapters/iterator_test_harness.txt b/docs/src/main/asciidoc/chapters/iterator_test_harness.txt
new file mode 100644
index 0000000..91ae53a
--- /dev/null
+++ b/docs/src/main/asciidoc/chapters/iterator_test_harness.txt
@@ -0,0 +1,110 @@
+// 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.
+
+== Iterator Testing
+
+Iterators, while extremely powerful, are notoriously difficult to test. While the API defines
+the methods an Iterator must implement and each method's functionality, the actual invocation
+of these methods by Accumulo TabletServers can be surprisingly difficult to mimic in unit tests.
+
+The Apache Accumulo "Iterator Test Harness" is designed to provide a generalized testing framework
+for all Accumulo Iterators to leverage to identify common pitfalls in user-created Iterators.
+
+=== Framework Use
+
+The harness provides an abstract class for use with JUnit4. Users must define the following for this
+abstract class:
+
+  * A `SortedMap` of input data (`Key`-`Value` pairs)
+  * A `Range` to use in tests
+  * A `Map` of options (`String` to `String` pairs)
+  * A `SortedMap` of output data (`Key`-`Value` pairs)
+  * A list of `IteratorTestCase`s (these can be automatically discovered)
+
+The majority of effort a user must make is in creating the input dataset and the expected
+output dataset for the iterator being tested.
+
+=== Normal Test Outline
+
+Most iterator tests will follow the given outline:
+
+[source,java]
+----
+import java.util.List;
+import java.util.SortedMap;
+
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.iteratortest.IteratorTestCaseFinder;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.junit4.BaseJUnit4IteratorTest;
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+import org.junit.runners.Parameterized.Parameters;
+
+public class MyIteratorTest extends BaseJUnit4IteratorTest {
+
+  @Parameters
+  public static Object[][] parameters() {
+    final IteratorTestInput input = createIteratorInput();
+    final IteratorTestOutput output = createIteratorOutput();
+    final List<IteratorTestCase> testCases = IteratorTestCaseFinder.findAllTestCases();
+    return BaseJUnit4IteratorTest.createParameters(input, output, tests);
+  }
+
+  private static SortedMap<Key,Value> INPUT_DATA = createInputData();
+  private static SortedMap<Key,Value> OUTPUT_DATA = createOutputData();
+
+  private static SortedMap<Key,Value> createInputData() {
+    // TODO -- implement this method
+  }
+
+  private static SortedMap<Key,Value> createOutputData() {
+    // TODO -- implement this method
+  }
+
+  private static IteratorTestInput createIteratorInput() {
+    final Map<String,String> options = createIteratorOptions(); 
+    final Range range = createRange();
+    return new IteratorTestInput(MyIterator.class, options, range, INPUT_DATA);
+  }
+
+  private static Map<String,String> createIteratorOptions() {
+    // TODO -- implement this method
+    // Tip: Use INPUT_DATA if helpful in generating output
+  }
+
+  private static Range createRange() {
+    // TODO -- implement this method
+  }
+
+  private static IteratorTestOutput createIteratorOutput() {
+    return new IteratorTestOutput(OUTPUT_DATA);
+  }
+
+}
+----
+
+=== Limitations
+
+While the provided `IteratorTestCase`s should exercise common edge-cases in user iterators,
+there are still many limitations to the existing test harness. Some of them are:
+
+  * Can only specify a single iterator, not many (a "stack")
+  * No control over provided IteratorEnvironment for tests
+  * Exercising delete keys (especially with major compactions that do not include all files)
+
+These are left as future improvements to the harness.

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/.gitignore
----------------------------------------------------------------------
diff --git a/iterator-test-harness/.gitignore b/iterator-test-harness/.gitignore
new file mode 100644
index 0000000..e7d7fb1
--- /dev/null
+++ b/iterator-test-harness/.gitignore
@@ -0,0 +1,26 @@
+# 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.
+
+# Maven ignores
+/target/
+
+# IDE ignores
+/.settings/
+/.project
+/.classpath
+/.pydevproject
+/.idea
+/*.iml
+/target/

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/pom.xml
----------------------------------------------------------------------
diff --git a/iterator-test-harness/pom.xml b/iterator-test-harness/pom.xml
new file mode 100644
index 0000000..d54a086
--- /dev/null
+++ b/iterator-test-harness/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.accumulo</groupId>
+    <artifactId>accumulo-project</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>accumulo-iterator-test-harness</artifactId>
+  <name>Apache Accumulo Iterator Test Harness</name>
+  <description>A library for testing Apache Accumulo Iterators.</description>
+  <dependencies>
+    <!--TODO Don't force downstream users to have JUnit -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.accumulo</groupId>
+      <artifactId>accumulo-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.hadoop</groupId>
+      <artifactId>hadoop-client</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+  </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestCaseFinder.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestCaseFinder.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestCaseFinder.java
new file mode 100644
index 0000000..7546460
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestCaseFinder.java
@@ -0,0 +1,78 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.ClassPath;
+import com.google.common.reflect.ClassPath.ClassInfo;
+
+/**
+ * A class to ease finding published test cases.
+ */
+public class IteratorTestCaseFinder {
+  private static final Logger log = LoggerFactory.getLogger(IteratorTestCaseFinder.class);
+
+  /**
+   * Instantiates all test cases provided.
+   *
+   * @return A list of {@link IteratorTestCase}s.
+   */
+  public static List<IteratorTestCase> findAllTestCases() {
+    log.info("Searching {}", IteratorTestCase.class.getPackage().getName());
+    ClassPath cp;
+    try {
+      cp = ClassPath.from(IteratorTestCaseFinder.class.getClassLoader());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    ImmutableSet<ClassInfo> classes = cp.getTopLevelClasses(IteratorTestCase.class.getPackage().getName());
+
+    final List<IteratorTestCase> testCases = new ArrayList<>();
+    // final Set<Class<? extends IteratorTestCase>> classes = reflections.getSubTypesOf(IteratorTestCase.class);
+    for (ClassInfo classInfo : classes) {
+      Class<?> clz;
+      try {
+        clz = Class.forName(classInfo.getName());
+      } catch (Exception e) {
+        log.warn("Could not get class for " + classInfo.getName(), e);
+        continue;
+      }
+
+      if (clz.isInterface() || Modifier.isAbstract(clz.getModifiers()) || !IteratorTestCase.class.isAssignableFrom(clz)) {
+        log.debug("Skipping " + clz);
+        continue;
+      }
+
+      try {
+        testCases.add((IteratorTestCase) clz.newInstance());
+      } catch (IllegalAccessException | InstantiationException e) {
+        log.warn("Could not instantiate {}", clz, e);
+      }
+    }
+
+    return testCases;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java
new file mode 100644
index 0000000..943bb0d
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java
@@ -0,0 +1,88 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedMap;
+
+import org.apache.accumulo.core.client.IteratorSetting;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+
+/**
+ * The necessary user-input to invoke a test on a {@link SortedKeyValueIterator}.
+ */
+public class IteratorTestInput {
+
+  private final Class<? extends SortedKeyValueIterator<Key,Value>> iteratorClass;
+  private final Map<String,String> iteratorOptions;
+  private final Range range;
+  private final SortedMap<Key,Value> input;
+
+  /**
+   * Construct an instance of the test input.
+   *
+   * @param iteratorClass
+   *          The class for the iterator to test
+   * @param iteratorOptions
+   *          Options, if any, to provide to the iterator ({@link IteratorSetting}'s Map of properties)
+   * @param range
+   *          The Range of data to query ({@link Scanner#setRange(Range)})
+   * @param input
+   *          A sorted collection of Key-Value pairs acting as the table.
+   */
+  public IteratorTestInput(Class<? extends SortedKeyValueIterator<Key,Value>> iteratorClass, Map<String,String> iteratorOptions, Range range,
+      SortedMap<Key,Value> input) {
+    // Already immutable
+    this.iteratorClass = Objects.requireNonNull(iteratorClass);
+    // Make it immutable to the test
+    this.iteratorOptions = Collections.unmodifiableMap(Objects.requireNonNull(iteratorOptions));
+    // Already immutable
+    this.range = Objects.requireNonNull(range);
+    // Make it immutable to the test
+    this.input = Collections.unmodifiableSortedMap((Objects.requireNonNull(input)));
+  }
+
+  public Class<? extends SortedKeyValueIterator<Key,Value>> getIteratorClass() {
+    return iteratorClass;
+  }
+
+  public Map<String,String> getIteratorOptions() {
+    return iteratorOptions;
+  }
+
+  public Range getRange() {
+    return range;
+  }
+
+  public SortedMap<Key,Value> getInput() {
+    return input;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder(64);
+    sb.append("[iteratorClass=").append(iteratorClass).append(", iteratorOptions=").append(iteratorOptions).append(", range=").append(range)
+        .append(", input='").append(input).append("']");
+    return sb.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestOutput.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestOutput.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestOutput.java
new file mode 100644
index 0000000..0a19727
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestOutput.java
@@ -0,0 +1,152 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.SortedMap;
+
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+
+/**
+ * The expected results from invoking a {@link IteratorTestCase} on a {@link IteratorTestInput}. The output will be either a {@link SortedMap} of Keys and
+ * Values or an exception but never both. If one of these members is null, the other is guaranteed to be non-null.
+ */
+public class IteratorTestOutput {
+
+  /**
+   * An outcome about what happened during a test case.
+   */
+  public enum TestOutcome {
+    /**
+     * The IteratorTestCase proactively passed.
+     */
+    PASSED,
+    /**
+     * The IteratorTestCase proactively failed.
+     */
+    FAILED,
+    /**
+     * The IteratorTestCase completed, but the pass/fail should be determined by the other context.
+     */
+    COMPLETED
+  }
+
+  private final SortedMap<Key,Value> output;
+  private final Exception exception;
+  private final TestOutcome outcome;
+
+  public IteratorTestOutput(TestOutcome outcome) {
+    this.outcome = outcome;
+    if (outcome == TestOutcome.COMPLETED) {
+      throw new IllegalArgumentException("This constructor is only for use with PASSED and FAILED");
+    }
+    output = null;
+    exception = null;
+  }
+
+  /**
+   * Create an instance of the class.
+   *
+   * @param output
+   *          The sorted collection of Key-Value pairs generated by an Iterator.
+   */
+  public IteratorTestOutput(SortedMap<Key,Value> output) {
+    this.output = Collections.unmodifiableSortedMap(Objects.requireNonNull(output));
+    this.exception = null;
+    this.outcome = TestOutcome.COMPLETED;
+  }
+
+  public IteratorTestOutput(Exception e) {
+    this.output = null;
+    this.exception = Objects.requireNonNull(e);
+    this.outcome = TestOutcome.FAILED;
+  }
+
+  /**
+   * @return The outcome of the test.
+   */
+  public TestOutcome getTestOutcome() {
+    return outcome;
+  }
+
+  /**
+   * Returns the output from the iterator.
+   *
+   * @return The sorted Key-Value pairs from an iterator, null if an exception was thrown.
+   */
+  public SortedMap<Key,Value> getOutput() {
+    return output;
+  }
+
+  /**
+   * @return True if there is output, false if the output is null.
+   */
+  public boolean hasOutput() {
+    return null != output;
+  }
+
+  /**
+   * Returns the exception thrown by the iterator.
+   *
+   * @return The exception thrown by the iterator, null if no exception was thrown.
+   */
+  public Exception getException() {
+    return exception;
+  }
+
+  /**
+   * @return True if there is an exception, null if the iterator successfully generated Key-Value pairs.
+   */
+  public boolean hasException() {
+    return null != exception;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof IteratorTestOutput)) {
+      return false;
+    }
+
+    IteratorTestOutput other = (IteratorTestOutput) o;
+
+    if (outcome != other.outcome) {
+      return false;
+    }
+
+    if (hasOutput()) {
+      if (!other.hasOutput()) {
+        return false;
+      }
+      return output.equals(other.output);
+    }
+
+    if (!other.hasException()) {
+      return false;
+    }
+    return exception.equals(other.getException());
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder(64);
+    sb.append("[outcome=").append(outcome).append(", output='").append(output).append("', exception=").append(exception).append("]");
+    return sb.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestReport.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestReport.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestReport.java
new file mode 100644
index 0000000..a52e883
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestReport.java
@@ -0,0 +1,76 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.util.Objects;
+
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+
+/**
+ * A summary of the invocation of an {@link IteratorTestInput} over a {@link IteratorTestCase} with the expected {@link IteratorTestOutput}.
+ */
+public class IteratorTestReport {
+
+  private final IteratorTestInput input;
+  private final IteratorTestOutput expectedOutput;
+  private final IteratorTestCase testCase;
+  private final IteratorTestOutput actualOutput;
+
+  public IteratorTestReport(IteratorTestInput input, IteratorTestOutput expectedOutput, IteratorTestOutput actualOutput, IteratorTestCase testCase) {
+    this.input = Objects.requireNonNull(input);
+    this.expectedOutput = Objects.requireNonNull(expectedOutput);
+    this.testCase = Objects.requireNonNull(testCase);
+    this.actualOutput = Objects.requireNonNull(actualOutput);
+  }
+
+  public IteratorTestInput getInput() {
+    return input;
+  }
+
+  public IteratorTestOutput getExpectedOutput() {
+    return expectedOutput;
+  }
+
+  public IteratorTestCase getTestCase() {
+    return testCase;
+  }
+
+  public IteratorTestOutput getActualOutput() {
+    return actualOutput;
+  }
+
+  /**
+   * Evaluate whether the test passed or failed.
+   *
+   * @return True if the actual output matches the expected output, false otherwise.
+   */
+  public boolean didTestSucceed() {
+    return testCase.verify(expectedOutput, actualOutput);
+  }
+
+  public String getSummary() {
+    StringBuilder sb = new StringBuilder(64);
+    // @formatter:off
+    sb.append("IteratorTestReport Summary: \n")
+        .append("\tTest Case = ").append(testCase.getClass().getName())
+        .append("\tInput Data = '").append(input).append("'\n")
+        .append("\tExpected Output = '").append(expectedOutput).append("'\n")
+        .append("\tActual Output = '").append(actualOutput).append("'\n");
+    // @formatter:on
+    return sb.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestRunner.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestRunner.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestRunner.java
new file mode 100644
index 0000000..99825a4
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestRunner.java
@@ -0,0 +1,95 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A runner for invoking some tests over some input and expecting some output.
+ */
+public class IteratorTestRunner {
+  private static final Logger log = LoggerFactory.getLogger(IteratorTestRunner.class);
+
+  private final IteratorTestInput testInput;
+  private final IteratorTestOutput testOutput;
+  private final Collection<IteratorTestCase> testCases;
+
+  /**
+   * Construct an instance of the class.
+   *
+   * @param testInput
+   *          The input to the tests
+   * @param testOutput
+   *          The expected output given the input
+   * @param testCases
+   *          The test cases to invoke
+   */
+  public IteratorTestRunner(IteratorTestInput testInput, IteratorTestOutput testOutput, Collection<IteratorTestCase> testCases) {
+    this.testInput = testInput;
+    this.testOutput = testOutput;
+    this.testCases = testCases;
+  }
+
+  public IteratorTestInput getTestInput() {
+    return testInput;
+  }
+
+  public IteratorTestOutput getTestOutput() {
+    return testOutput;
+  }
+
+  public Collection<IteratorTestCase> getTestCases() {
+    return testCases;
+  }
+
+  /**
+   * Invokes each test case on the input, verifying the output.
+   *
+   * @return true if all tests passed, false
+   */
+  public List<IteratorTestReport> runTests() {
+    List<IteratorTestReport> testReports = new ArrayList<>(testCases.size());
+    for (IteratorTestCase testCase : testCases) {
+      log.info("Invoking {} on {}", testCase.getClass().getName(), testInput.getIteratorClass().getName());
+
+      IteratorTestOutput actualOutput = null;
+
+      try {
+        actualOutput = testCase.test(testInput);
+      } catch (Exception e) {
+        log.error("Failed to invoke {} on {}", testCase.getClass().getName(), testInput.getIteratorClass().getName(), e);
+        actualOutput = new IteratorTestOutput(e);
+      }
+
+      // Sanity-check on the IteratorTestCase implementation.
+      if (null == actualOutput) {
+        throw new IllegalStateException("IteratorTestCase implementations should always return a non-null IteratorTestOutput. " + testCase.getClass().getName()
+            + " did not!");
+      }
+
+      testReports.add(new IteratorTestReport(testInput, testOutput, actualOutput, testCase));
+    }
+
+    return testReports;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestUtil.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestUtil.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestUtil.java
new file mode 100644
index 0000000..4185846
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestUtil.java
@@ -0,0 +1,44 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.util.Objects;
+
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iterators.SortedMapIterator;
+import org.apache.accumulo.core.iterators.system.ColumnFamilySkippingIterator;
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+
+/**
+ * A collection of methods that are helpful to the development of {@link IteratorTestCase}s.
+ */
+public class IteratorTestUtil {
+
+  public static SortedKeyValueIterator<Key,Value> instantiateIterator(IteratorTestInput input) {
+    try {
+      return Objects.requireNonNull(input.getIteratorClass()).newInstance();
+    } catch (InstantiationException | IllegalAccessException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static SortedKeyValueIterator<Key,Value> createSource(IteratorTestInput input) {
+    return new SimpleKVReusingIterator(new ColumnFamilySkippingIterator(new SortedMapIterator(Objects.requireNonNull(input).getInput())));
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/SimpleKVReusingIterator.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/SimpleKVReusingIterator.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/SimpleKVReusingIterator.java
new file mode 100644
index 0000000..9174b69
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/SimpleKVReusingIterator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.accumulo.core.data.ByteSequence;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+
+/**
+ * Internally, Accumulo reuses the same instance of Key and Value to reduce the number of objects to be garbage collected. This iterator simulates that.
+ */
+public class SimpleKVReusingIterator implements SortedKeyValueIterator<Key,Value> {
+
+  private final SortedKeyValueIterator<Key,Value> source;
+  private final Key topKey = new Key();
+  private final Value topValue = new Value();
+
+  public SimpleKVReusingIterator(SortedKeyValueIterator<Key,Value> source) {
+    this.source = source;
+  }
+
+  @Override
+  public void init(SortedKeyValueIterator<Key,Value> source, Map<String,String> options, IteratorEnvironment env) throws IOException {
+    this.source.init(source, options, env);
+  }
+
+  @Override
+  public boolean hasTop() {
+    return source.hasTop();
+  }
+
+  @Override
+  public void next() throws IOException {
+    source.next();
+    load();
+  }
+
+  @Override
+  public void seek(Range range, Collection<ByteSequence> columnFamilies, boolean inclusive) throws IOException {
+    source.seek(range, columnFamilies, inclusive);
+    load();
+  }
+
+  @Override
+  public Key getTopKey() {
+    return topKey;
+  }
+
+  @Override
+  public Value getTopValue() {
+    return topValue;
+  }
+
+  @Override
+  public SortedKeyValueIterator<Key,Value> deepCopy(IteratorEnvironment env) {
+    SortedKeyValueIterator<Key,Value> newSource = source.deepCopy(env);
+    return new SimpleKVReusingIterator(newSource);
+  }
+
+  private void load() {
+    if (hasTop()) {
+      topKey.set(source.getTopKey());
+      topValue.set(source.getTopValue().get());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/environments/SimpleIteratorEnvironment.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/environments/SimpleIteratorEnvironment.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/environments/SimpleIteratorEnvironment.java
new file mode 100644
index 0000000..6204212
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/environments/SimpleIteratorEnvironment.java
@@ -0,0 +1,80 @@
+/*
+ * 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.accumulo.iteratortest.environments;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.client.admin.SamplerConfiguration;
+import org.apache.accumulo.core.conf.AccumuloConfiguration;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.security.Authorizations;
+
+/**
+ * A simple implementation of {@link IteratorEnvironment} which is unimplemented.
+ */
+public class SimpleIteratorEnvironment implements IteratorEnvironment {
+
+  @Override
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName) throws IOException {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public AccumuloConfiguration getConfig() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean isSamplingEnabled() {
+    return false;
+  }
+
+  @Override
+  public SamplerConfiguration getSamplerConfiguration() {
+    throw new UnsupportedOperationException();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/junit4/BaseJUnit4IteratorTest.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/junit4/BaseJUnit4IteratorTest.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/junit4/BaseJUnit4IteratorTest.java
new file mode 100644
index 0000000..66e9dbc
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/junit4/BaseJUnit4IteratorTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.accumulo.iteratortest.junit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.IteratorTestReport;
+import org.apache.accumulo.iteratortest.IteratorTestRunner;
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A base JUnit4 test class for users to leverage with the JUnit Parameterized Runner.
+ * <p>
+ * Users should extend this class and implement a static method using the {@code @Parameters} annotation.
+ * 
+ * <pre>
+ * &#064;Parameters
+ * public static Object[][] data() {
+ *   IteratorTestInput input = createIteratorInput();
+ *   IteratorTestOutput expectedOutput = createIteratorOuput();
+ *   List&lt;IteratorTestCase&gt; testCases = createTestCases();
+ *   return BaseJUnit4IteratorTest.createParameters(input, expectedOutput, testCases);
+ * }
+ * </pre>
+ * 
+ */
+@RunWith(Parameterized.class)
+public class BaseJUnit4IteratorTest {
+  private static final Logger log = LoggerFactory.getLogger(BaseJUnit4IteratorTest.class);
+
+  public final IteratorTestRunner runner;
+
+  public BaseJUnit4IteratorTest(IteratorTestInput input, IteratorTestOutput expectedOutput, IteratorTestCase testCase) {
+    this.runner = new IteratorTestRunner(input, expectedOutput, Collections.singleton(testCase));
+  }
+
+  /**
+   * A helper function to convert input, output and a list of test cases into a two-dimensional array for JUnit's Parameterized runner.
+   *
+   * @param input
+   *          The input
+   * @param output
+   *          The output
+   * @param testCases
+   *          A list of desired test cases to run.
+   * @return A two dimensional array suitable to pass as JUnit's parameters.
+   */
+  public static Object[][] createParameters(IteratorTestInput input, IteratorTestOutput output, Collection<IteratorTestCase> testCases) {
+    Object[][] parameters = new Object[testCases.size()][3];
+    Iterator<IteratorTestCase> testCaseIter = testCases.iterator();
+    for (int i = 0; testCaseIter.hasNext(); i++) {
+      final IteratorTestCase testCase = testCaseIter.next();
+      parameters[i] = new Object[] {input, output, testCase};
+    }
+    return parameters;
+  }
+
+  @Test
+  public void testIterator() {
+    List<IteratorTestReport> reports = runner.runTests();
+    assertEquals(1, reports.size());
+
+    IteratorTestReport report = reports.get(0);
+    assertNotNull(report);
+
+    assertTrue(report.getSummary(), report.didTestSucceed());
+
+    // Present for manual verification
+    log.trace("Expected: {}, Actual: {}", report.getExpectedOutput(), report.getActualOutput());
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/DeepCopyTestCase.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/DeepCopyTestCase.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/DeepCopyTestCase.java
new file mode 100644
index 0000000..1a608c1
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/DeepCopyTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * 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.accumulo.iteratortest.testcases;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.data.ByteSequence;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.IteratorTestUtil;
+import org.apache.accumulo.iteratortest.environments.SimpleIteratorEnvironment;
+
+/**
+ * Test case that verifies that an iterator can use the generated instance from {@code deepCopy}.
+ */
+public class DeepCopyTestCase extends OutputVerifyingTestCase {
+
+  @Override
+  public IteratorTestOutput test(IteratorTestInput testInput) {
+    final SortedKeyValueIterator<Key,Value> skvi = IteratorTestUtil.instantiateIterator(testInput);
+    final SortedKeyValueIterator<Key,Value> source = IteratorTestUtil.createSource(testInput);
+
+    try {
+      skvi.init(source, testInput.getIteratorOptions(), new SimpleIteratorEnvironment());
+
+      SortedKeyValueIterator<Key,Value> copy = skvi.deepCopy(new SimpleIteratorEnvironment());
+
+      copy.seek(testInput.getRange(), Collections.<ByteSequence> emptySet(), false);
+      return new IteratorTestOutput(consume(copy));
+    } catch (IOException e) {
+      return new IteratorTestOutput(e);
+    }
+  }
+
+  TreeMap<Key,Value> consume(SortedKeyValueIterator<Key,Value> skvi) throws IOException {
+    TreeMap<Key,Value> data = new TreeMap<>();
+    while (skvi.hasTop()) {
+      data.put(skvi.getTopKey(), skvi.getTopValue());
+      skvi.next();
+    }
+    return data;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/InstantiationTestCase.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/InstantiationTestCase.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/InstantiationTestCase.java
new file mode 100644
index 0000000..3bbfb7f
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/InstantiationTestCase.java
@@ -0,0 +1,51 @@
+/*
+ * 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.accumulo.iteratortest.testcases;
+
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput.TestOutcome;
+
+/**
+ * TestCase to assert that an Iterator has a no-args constructor.
+ */
+public class InstantiationTestCase implements IteratorTestCase {
+
+  @Override
+  public IteratorTestOutput test(IteratorTestInput testInput) {
+    Class<? extends SortedKeyValueIterator<Key,Value>> clz = testInput.getIteratorClass();
+
+    try {
+      // We should be able to instantiate the Iterator given the Class
+      @SuppressWarnings("unused")
+      SortedKeyValueIterator<Key,Value> iter = clz.newInstance();
+    } catch (Exception e) {
+      return new IteratorTestOutput(e);
+    }
+
+    return new IteratorTestOutput(TestOutcome.PASSED);
+  }
+
+  public boolean verify(IteratorTestOutput expected, IteratorTestOutput actual) {
+    // Ignore what the user provided as expected output, just check that we instantiated the iterator successfully.
+    return TestOutcome.PASSED == actual.getTestOutcome();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IsolatedDeepCopiesTestCase.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IsolatedDeepCopiesTestCase.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IsolatedDeepCopiesTestCase.java
new file mode 100644
index 0000000..b874962
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IsolatedDeepCopiesTestCase.java
@@ -0,0 +1,124 @@
+/*
+ * 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.accumulo.iteratortest.testcases;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.data.ByteSequence;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.IteratorTestUtil;
+import org.apache.accumulo.iteratortest.environments.SimpleIteratorEnvironment;
+
+/**
+ * Test case that verifies that copies do not impact one another.
+ */
+public class IsolatedDeepCopiesTestCase extends OutputVerifyingTestCase {
+
+  @Override
+  public IteratorTestOutput test(IteratorTestInput testInput) {
+    final SortedKeyValueIterator<Key,Value> skvi = IteratorTestUtil.instantiateIterator(testInput);
+    final SortedKeyValueIterator<Key,Value> source = IteratorTestUtil.createSource(testInput);
+
+    try {
+      skvi.init(source, testInput.getIteratorOptions(), new SimpleIteratorEnvironment());
+
+      SortedKeyValueIterator<Key,Value> copy1 = skvi.deepCopy(new SimpleIteratorEnvironment());
+      SortedKeyValueIterator<Key,Value> copy2 = copy1.deepCopy(new SimpleIteratorEnvironment());
+
+      skvi.seek(testInput.getRange(), Collections.<ByteSequence> emptySet(), false);
+      copy1.seek(testInput.getRange(), Collections.<ByteSequence> emptySet(), false);
+      copy2.seek(testInput.getRange(), Collections.<ByteSequence> emptySet(), false);
+
+      TreeMap<Key,Value> output = consumeMany(Arrays.asList(skvi, copy1, copy2));
+
+      return new IteratorTestOutput(output);
+    } catch (IOException e) {
+      return new IteratorTestOutput(e);
+    }
+  }
+
+  TreeMap<Key,Value> consumeMany(Collection<SortedKeyValueIterator<Key,Value>> iterators) throws IOException {
+    TreeMap<Key,Value> data = new TreeMap<>();
+    // All of the copies should have consistent results from concurrent use
+    while (allHasTop(iterators)) {
+      data.put(getTopKey(iterators), getTopValue(iterators));
+      next(iterators);
+    }
+
+    // All of the iterators should be consumed.
+    for (SortedKeyValueIterator<Key,Value> iter : iterators) {
+      if (iter.hasTop()) {
+        return null;
+      }
+    }
+
+    return data;
+  }
+
+  boolean allHasTop(Collection<SortedKeyValueIterator<Key,Value>> iterators) {
+    for (SortedKeyValueIterator<Key,Value> iter : iterators) {
+      if (!iter.hasTop()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  Key getTopKey(Collection<SortedKeyValueIterator<Key,Value>> iterators) {
+    boolean first = true;
+    Key topKey = null;
+    for (SortedKeyValueIterator<Key,Value> iter : iterators) {
+      if (first) {
+        topKey = iter.getTopKey();
+        first = false;
+      } else if (!topKey.equals(iter.getTopKey())) {
+        throw new IllegalStateException("Inconsistent keys between two iterators: " + topKey + " " + iter.getTopKey());
+      }
+    }
+
+    return topKey;
+  }
+
+  Value getTopValue(Collection<SortedKeyValueIterator<Key,Value>> iterators) {
+    boolean first = true;
+    Value topValue = null;
+    for (SortedKeyValueIterator<Key,Value> iter : iterators) {
+      if (first) {
+        topValue = iter.getTopValue();
+        first = false;
+      } else if (!topValue.equals(iter.getTopValue())) {
+        throw new IllegalStateException("Inconsistent values between two iterators: " + topValue + " " + iter.getTopValue());
+      }
+    }
+
+    return topValue;
+  }
+
+  void next(Collection<SortedKeyValueIterator<Key,Value>> iterators) throws IOException {
+    for (SortedKeyValueIterator<Key,Value> iter : iterators) {
+      iter.next();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IteratorTestCase.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IteratorTestCase.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IteratorTestCase.java
new file mode 100644
index 0000000..f7495af
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/IteratorTestCase.java
@@ -0,0 +1,49 @@
+/*
+ * 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.accumulo.iteratortest.testcases;
+
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+
+/**
+ * An interface that accepts some input for testing a {@link SortedKeyValueIterator}, runs the specific implementation of the test and returns the outcome from
+ * that iterator.
+ */
+public interface IteratorTestCase {
+
+  /**
+   * Run the implementation's test against the given input.
+   *
+   * @param testInput
+   *          The input to test.
+   * @return The output of the test with the input.
+   */
+  IteratorTestOutput test(IteratorTestInput testInput);
+
+  /**
+   * Compute whether or not the expected and actual output is a success or failure for this implementation.
+   *
+   * @param expected
+   *          The expected output from the user.
+   * @param actual
+   *          The actual output from the test
+   * @return True if the test case passes, false if it doesn't.
+   */
+  boolean verify(IteratorTestOutput expected, IteratorTestOutput actual);
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/MultipleHasTopCalls.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/MultipleHasTopCalls.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/MultipleHasTopCalls.java
new file mode 100644
index 0000000..34bf776
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/MultipleHasTopCalls.java
@@ -0,0 +1,87 @@
+/*
+ * 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.accumulo.iteratortest.testcases;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Random;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.data.ByteSequence;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.IteratorTestUtil;
+import org.apache.accumulo.iteratortest.environments.SimpleIteratorEnvironment;
+
+/**
+ * TestCase which asserts that multiple calls to {@link SortedKeyValueIterator#hasTop()} should not alter the internal state of the iterator and should not
+ * return different values due to multiple, sequential invocations.
+ * <p>
+ * This test case will call {@code hasTop()} multiple times, verifying that each call returns the same value as the first.
+ */
+public class MultipleHasTopCalls extends OutputVerifyingTestCase {
+
+  private final Random random;
+
+  public MultipleHasTopCalls() {
+    this.random = new Random();
+  }
+
+  @Override
+  public IteratorTestOutput test(IteratorTestInput testInput) {
+    final SortedKeyValueIterator<Key,Value> skvi = IteratorTestUtil.instantiateIterator(testInput);
+    final SortedKeyValueIterator<Key,Value> source = IteratorTestUtil.createSource(testInput);
+
+    try {
+      skvi.init(source, testInput.getIteratorOptions(), new SimpleIteratorEnvironment());
+      skvi.seek(testInput.getRange(), Collections.<ByteSequence> emptySet(), false);
+      return new IteratorTestOutput(consume(skvi));
+    } catch (IOException e) {
+      return new IteratorTestOutput(e);
+    }
+  }
+
+  TreeMap<Key,Value> consume(SortedKeyValueIterator<Key,Value> skvi) throws IOException {
+    TreeMap<Key,Value> data = new TreeMap<>();
+    while (skvi.hasTop()) {
+      // Check 1 to 5 times. If hasTop returned true, it should continue to return true.
+      for (int i = 0; i < random.nextInt(5) + 1; i++) {
+        if (!skvi.hasTop()) {
+          throw badStateException(true);
+        }
+      }
+      data.put(skvi.getTopKey(), skvi.getTopValue());
+      skvi.next();
+    }
+
+    // Check 1 to 5 times. Once hasTop returned false, it should continue to return false
+    for (int i = 0; i < random.nextInt(5) + 1; i++) {
+      if (skvi.hasTop()) {
+        throw badStateException(false);
+      }
+    }
+    return data;
+  }
+
+  IllegalStateException badStateException(boolean expectedState) {
+    return new IllegalStateException("Multiple sequential calls to hasTop should not alter the state or return value of the iterator. Expected '"
+        + expectedState + ", but got '" + !expectedState + "'.");
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/OutputVerifyingTestCase.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/OutputVerifyingTestCase.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/OutputVerifyingTestCase.java
new file mode 100644
index 0000000..5a46e4e
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/OutputVerifyingTestCase.java
@@ -0,0 +1,30 @@
+/*
+ * 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.accumulo.iteratortest.testcases;
+
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+
+/**
+ * Base {@link IteratorTestCase} implementation that performs verifiation on the expected and actual outcome.
+ */
+public abstract class OutputVerifyingTestCase implements IteratorTestCase {
+
+  public boolean verify(IteratorTestOutput expected, IteratorTestOutput actual) {
+    return expected.equals(actual);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/ReSeekTestCase.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/ReSeekTestCase.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/ReSeekTestCase.java
new file mode 100644
index 0000000..d539526
--- /dev/null
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/testcases/ReSeekTestCase.java
@@ -0,0 +1,110 @@
+/*
+ * 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.accumulo.iteratortest.testcases;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Random;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.data.ByteSequence;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.IteratorTestUtil;
+import org.apache.accumulo.iteratortest.environments.SimpleIteratorEnvironment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test case that verifies that an iterator can use the generated instance from {@code deepCopy}.
+ */
+public class ReSeekTestCase extends OutputVerifyingTestCase {
+  private static final Logger log = LoggerFactory.getLogger(ReSeekTestCase.class);
+
+  /**
+   * Let N be a random number between [0, RESEEK_INTERVAL). After every Nth entry "returned" to the client, recreate and reseek the iterator.
+   */
+  private static final int RESEEK_INTERVAL = 4;
+
+  private final Random random;
+
+  public ReSeekTestCase() {
+    this.random = new Random();
+  }
+
+  @Override
+  public IteratorTestOutput test(IteratorTestInput testInput) {
+    final SortedKeyValueIterator<Key,Value> skvi = IteratorTestUtil.instantiateIterator(testInput);
+    final SortedKeyValueIterator<Key,Value> source = IteratorTestUtil.createSource(testInput);
+
+    try {
+      skvi.init(source, testInput.getIteratorOptions(), new SimpleIteratorEnvironment());
+      skvi.seek(testInput.getRange(), Collections.<ByteSequence> emptySet(), false);
+      return new IteratorTestOutput(consume(skvi, testInput));
+    } catch (IOException e) {
+      return new IteratorTestOutput(e);
+    }
+  }
+
+  TreeMap<Key,Value> consume(SortedKeyValueIterator<Key,Value> skvi, IteratorTestInput testInput) throws IOException {
+    final TreeMap<Key,Value> data = new TreeMap<>();
+    final Range origRange = testInput.getRange();
+    int reseekCount = random.nextInt(RESEEK_INTERVAL);
+
+    int i = 0;
+    while (skvi.hasTop()) {
+      data.put(skvi.getTopKey(), skvi.getTopValue());
+
+      /*
+       * One of the trickiest cases in writing iterators:
+       * 
+       * After any result is returned from a TabletServer to the client, the Iterator in the TabletServer's memory may be torn down. To preserve the state and
+       * guarantee that all records are received, the TabletServer does remember the last Key it returned to the client. It will recreate the Iterator (stack),
+       * and seek it using an updated Range. This range's start key is set to the last Key returned, non-inclusive.
+       */
+      if (i % RESEEK_INTERVAL == reseekCount) {
+        // Last key
+        Key reSeekStartKey = skvi.getTopKey();
+
+        // Make a new instance of the iterator
+        skvi = IteratorTestUtil.instantiateIterator(testInput);
+        final SortedKeyValueIterator<Key,Value> sourceCopy = IteratorTestUtil.createSource(testInput);
+
+        skvi.init(sourceCopy, testInput.getIteratorOptions(), new SimpleIteratorEnvironment());
+
+        // The new range, resume where we left off (non-inclusive)
+        final Range newRange = new Range(reSeekStartKey, false, origRange.getEndKey(), origRange.isEndKeyInclusive());
+        log.debug("Re-seeking to {}", newRange);
+
+        // Seek there
+        skvi.seek(newRange, Collections.<ByteSequence> emptySet(), false);
+      } else {
+        // Every other time, it's a simple call to next()
+        skvi.next();
+      }
+
+      i++;
+    }
+
+    return data;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/WholeRowIteratorTest.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/WholeRowIteratorTest.java b/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/WholeRowIteratorTest.java
new file mode 100644
index 0000000..0b116f2
--- /dev/null
+++ b/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/WholeRowIteratorTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.accumulo.iteratortest;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.user.WholeRowIterator;
+import org.apache.accumulo.iteratortest.junit4.BaseJUnit4IteratorTest;
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+import org.apache.hadoop.io.Text;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Framework tests for {@link WholeRowIterator}.
+ */
+public class WholeRowIteratorTest extends BaseJUnit4IteratorTest {
+
+  @Parameters
+  public static Object[][] parameters() {
+    IteratorTestInput input = getIteratorInput();
+    IteratorTestOutput output = getIteratorOutput();
+    List<IteratorTestCase> tests = IteratorTestCaseFinder.findAllTestCases();
+    return BaseJUnit4IteratorTest.createParameters(input, output, tests);
+  }
+
+  private static final TreeMap<Key,Value> INPUT_DATA = createInputData();
+  private static final TreeMap<Key,Value> OUTPUT_DATA = createOutputData();
+
+  private static TreeMap<Key,Value> createInputData() {
+    TreeMap<Key,Value> data = new TreeMap<>();
+
+    data.put(new Key("1", "", "a"), new Value("1a".getBytes()));
+    data.put(new Key("1", "", "b"), new Value("1b".getBytes()));
+    data.put(new Key("1", "a", "a"), new Value("1aa".getBytes()));
+    data.put(new Key("1", "a", "b"), new Value("1ab".getBytes()));
+    data.put(new Key("1", "b", "a"), new Value("1ba".getBytes()));
+
+    data.put(new Key("2", "a", "a"), new Value("2aa".getBytes()));
+    data.put(new Key("2", "a", "b"), new Value("2ab".getBytes()));
+    data.put(new Key("2", "a", "c"), new Value("2ac".getBytes()));
+    data.put(new Key("2", "c", "c"), new Value("2cc".getBytes()));
+
+    data.put(new Key("3", "a", ""), new Value("3a".getBytes()));
+
+    data.put(new Key("4", "a", "b"), new Value("4ab".getBytes()));
+
+    data.put(new Key("5", "a", "a"), new Value("5aa".getBytes()));
+    data.put(new Key("5", "a", "b"), new Value("5ab".getBytes()));
+    data.put(new Key("5", "a", "c"), new Value("5ac".getBytes()));
+    data.put(new Key("5", "a", "d"), new Value("5ad".getBytes()));
+
+    data.put(new Key("6", "", "a"), new Value("6a".getBytes()));
+    data.put(new Key("6", "", "b"), new Value("6b".getBytes()));
+    data.put(new Key("6", "", "c"), new Value("6c".getBytes()));
+    data.put(new Key("6", "", "d"), new Value("6d".getBytes()));
+    data.put(new Key("6", "", "e"), new Value("6e".getBytes()));
+    data.put(new Key("6", "1", "a"), new Value("61a".getBytes()));
+    data.put(new Key("6", "1", "b"), new Value("61b".getBytes()));
+    data.put(new Key("6", "1", "c"), new Value("61c".getBytes()));
+    data.put(new Key("6", "1", "d"), new Value("61d".getBytes()));
+    data.put(new Key("6", "1", "e"), new Value("61e".getBytes()));
+
+    return data;
+  }
+
+  private static TreeMap<Key,Value> createOutputData() {
+    TreeMap<Key,Value> data = new TreeMap<>();
+
+    Text row = null;
+    List<Key> keys = new ArrayList<>();
+    List<Value> values = new ArrayList<>();
+
+    // Generate the output data from the input data
+    for (Entry<Key,Value> entry : INPUT_DATA.entrySet()) {
+      if (null == row) {
+        row = entry.getKey().getRow();
+      }
+
+      if (!row.equals(entry.getKey().getRow())) {
+        // Moved to the next row
+        try {
+          // Serialize and save
+          Value encoded = WholeRowIterator.encodeRow(keys, values);
+          data.put(new Key(row), encoded);
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+
+        // Empty the aggregated k-v's
+        keys = new ArrayList<>();
+        values = new ArrayList<>();
+        // Set the new current row
+        row = entry.getKey().getRow();
+      }
+
+      // Aggregate the current row
+      keys.add(entry.getKey());
+      values.add(entry.getValue());
+    }
+
+    if (!keys.isEmpty()) {
+      try {
+        Value encoded = WholeRowIterator.encodeRow(keys, values);
+        data.put(new Key(row), encoded);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    return data;
+  }
+
+  private static IteratorTestInput getIteratorInput() {
+    return new IteratorTestInput(WholeRowIterator.class, Collections.<String,String> emptyMap(), new Range(), INPUT_DATA);
+  }
+
+  private static IteratorTestOutput getIteratorOutput() {
+    return new IteratorTestOutput(OUTPUT_DATA);
+  }
+
+  public WholeRowIteratorTest(IteratorTestInput input, IteratorTestOutput expectedOutput, IteratorTestCase testCase) {
+    super(input, expectedOutput, testCase);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/framework/JUnitFrameworkTest.java
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/framework/JUnitFrameworkTest.java b/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/framework/JUnitFrameworkTest.java
new file mode 100644
index 0000000..133db62
--- /dev/null
+++ b/iterator-test-harness/src/test/java/org/apache/accumulo/iteratortest/framework/JUnitFrameworkTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.accumulo.iteratortest.framework;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.WrappingIterator;
+import org.apache.accumulo.iteratortest.IteratorTestInput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput;
+import org.apache.accumulo.iteratortest.IteratorTestOutput.TestOutcome;
+import org.apache.accumulo.iteratortest.junit4.BaseJUnit4IteratorTest;
+import org.apache.accumulo.iteratortest.testcases.IteratorTestCase;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * A Basic test asserting that the framework is functional.
+ */
+public class JUnitFrameworkTest extends BaseJUnit4IteratorTest {
+
+  /**
+   * An IteratorTestCase implementation that returns the original input without any external action.
+   */
+  public static class NoopIteratorTestCase implements IteratorTestCase {
+
+    @Override
+    public IteratorTestOutput test(IteratorTestInput testInput) {
+      return new IteratorTestOutput(TestOutcome.PASSED);
+    }
+
+    @Override
+    public boolean verify(IteratorTestOutput expected, IteratorTestOutput actual) {
+      // Always passes
+      return true;
+    }
+
+  }
+
+  @Parameters
+  public static Object[][] parameters() {
+    IteratorTestInput input = getIteratorInput();
+    IteratorTestOutput output = getIteratorOutput();
+    List<IteratorTestCase> tests = Collections.<IteratorTestCase> singletonList(new NoopIteratorTestCase());
+    return BaseJUnit4IteratorTest.createParameters(input, output, tests);
+  }
+
+  private static final TreeMap<Key,Value> DATA = createData();
+
+  private static TreeMap<Key,Value> createData() {
+    TreeMap<Key,Value> data = new TreeMap<>();
+    data.put(new Key("1", "a", ""), new Value("1a".getBytes()));
+    data.put(new Key("2", "a", ""), new Value("2a".getBytes()));
+    data.put(new Key("3", "a", ""), new Value("3a".getBytes()));
+    return data;
+  }
+
+  private static IteratorTestInput getIteratorInput() {
+    return new IteratorTestInput(IdentityIterator.class, Collections.<String,String> emptyMap(), new Range(), DATA);
+  }
+
+  private static IteratorTestOutput getIteratorOutput() {
+    return new IteratorTestOutput(DATA);
+  }
+
+  public JUnitFrameworkTest(IteratorTestInput input, IteratorTestOutput expectedOutput, IteratorTestCase testCase) {
+    super(input, expectedOutput, testCase);
+  }
+
+  /**
+   * Noop iterator implementation.
+   */
+  private static class IdentityIterator extends WrappingIterator {
+
+    @Override
+    public IdentityIterator deepCopy(IteratorEnvironment env) {
+      return new IdentityIterator();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/iterator-test-harness/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/iterator-test-harness/src/test/resources/log4j.properties b/iterator-test-harness/src/test/resources/log4j.properties
new file mode 100644
index 0000000..3b2c8e7
--- /dev/null
+++ b/iterator-test-harness/src/test/resources/log4j.properties
@@ -0,0 +1,24 @@
+# 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.
+
+log4j.rootLogger=INFO, CA
+log4j.appender.CA=org.apache.log4j.ConsoleAppender
+log4j.appender.CA.layout=org.apache.log4j.PatternLayout
+log4j.appender.CA.layout.ConversionPattern=%d{ISO8601} [%-8c{2}] %-5p: %m%n
+
+log4j.logger.org.apache.accumulo.core.client.impl.ServerClient=ERROR
+log4j.logger.org.apache.zookeeper=ERROR
+log4j.logger.org.apache.accumulo.iteratortest=DEBUG
+log4j.logger.org.apache.accumulo.iteratortest.testcases=DEBUG
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b332873c/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ba66b01..c18342e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,7 @@
     <module>docs</module>
     <module>examples/simple</module>
     <module>fate</module>
+    <module>iterator-test-harness</module>
     <module>maven-plugin</module>
     <module>minicluster</module>
     <module>proxy</module>


Mime
View raw message