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 <[email protected]> Authored: Sat Nov 7 14:17:17 2015 -0500 Committer: Josh Elser <[email protected]> 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> + * @Parameters + * public static Object[][] data() { + * IteratorTestInput input = createIteratorInput(); + * IteratorTestOutput expectedOutput = createIteratorOuput(); + * List<IteratorTestCase> 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>
