http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java ---------------------------------------------------------------------- diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java new file mode 100644 index 0000000..7e19122 --- /dev/null +++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java @@ -0,0 +1,143 @@ +/* + * 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.jena.testing_framework.manifest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.ParentRunner; +import org.junit.runners.Suite; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.jena.shared.JenaException; + +/** + * Class that runs the Manifest annotated tests. + * + * Used with <code>@RunWith( ManifestSuite.class )</code> this class loads a + * manifest test suite file and adds the tests. + * <p> + * Tests annotated with <code>@RunWith( ManifestSuite.class )</code> must + * have a <code>ManifestFile</code> annotation specifying the path to the manifest file + * </p> + */ +public class ManifestSuite extends ParentRunner<Runner> { + private static final Logger LOG = LoggerFactory + .getLogger(ManifestSuite.class); + private final List<Runner> fRunners; + private final ManifestItemHandler itemHandler; + private final ManifestFile mf; + + /** + * Called reflectively on classes annotated with + * <code>@RunWith(Suite.class)</code> + * + * @param cls + * the root class + * @param builder + * builds runners for classes in the suite + * @throws Throwable + */ + public ManifestSuite(Class<? extends ManifestItemHandler> cls, + RunnerBuilder builder) throws Throwable { + super(cls); + + List<Throwable> errors = new ArrayList<Throwable>(); + + mf = cls.getAnnotation(ManifestFile.class); + if (mf == null) { + throw new IllegalStateException( + "ManifestSuite requries ManifestFile annotation"); + } + itemHandler = cls.newInstance(); + + Runner[] runner = new Runner[1]; + try { + runner[0] = oneManifest(new Manifest(mf.value()), + new ArrayList<Runner>()); + } catch (JenaException ex) { + runner[0] = new ErrorReportingRunner(null, ex); + } + + if (!errors.isEmpty()) { + throw new InitializationError(errors); + } + fRunners = Collections.unmodifiableList(Arrays.asList(runner)); + } + + private Runner oneManifest(final Manifest manifest, List<Runner> r) { + + // Recurse + for (Iterator<String> iter = manifest.includedManifests(); iter + .hasNext();) { + try { + r.add(oneManifest(new Manifest(iter.next()), + new ArrayList<Runner>())); + } catch (JenaException ex) { + r.add(new ErrorReportingRunner(null, ex)); + } + } + itemHandler.setTestRunnerList(r); + manifest.apply(itemHandler); + try { + return new Suite((Class<?>) null, r) { + + @Override + protected String getName() { + return manifest.getName(); + } + + }; + } catch (InitializationError e) { + return new ErrorReportingRunner(null, e); + } + } + + @Override + protected List<Runner> getChildren() { + return fRunners; + } + + @Override + protected Description describeChild(Runner child) { + return child.getDescription(); + } + + @Override + protected void runChild(Runner child, RunNotifier notifier) { + child.run(notifier); + } + + /** + * Returns a name used to describe this Runner + */ + @Override + protected String getName() { + return String.format("%s - %s", super.getName(), mf.value()); + } +}
http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java ---------------------------------------------------------------------- diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java new file mode 100644 index 0000000..cfde240 --- /dev/null +++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java @@ -0,0 +1,13 @@ +package org.apache.jena.testing_framework.manifest; + +public abstract class ManifestTest { + + protected ManifestItem manifestItem; + + public final void setManifestItem(ManifestItem manifestItem) { + this.manifestItem = manifestItem; + } + + abstract public void runTest(); + +} http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java ---------------------------------------------------------------------- diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java new file mode 100644 index 0000000..616b444 --- /dev/null +++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java @@ -0,0 +1,75 @@ +package org.apache.jena.testing_framework.manifest; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; + +public class ManifestTestRunner extends BlockJUnit4ClassRunner { + + private ManifestItem manifestItem; + + public ManifestTestRunner(ManifestItem manifestItem, + Class<? extends ManifestTest> cls) throws InitializationError { + super(cls); + this.manifestItem = manifestItem; + } + + /** + * Returns the name that describes {@code method} for {@link Description}s. + * Default implementation is the method's name + */ + @Override + protected String testName(FrameworkMethod method) { + return manifestItem.getTestName(); + } + + /** + * Returns the methods that run tests. Default implementation returns all + * methods annotated with {@code @Test} on this class and superclasses that + * are not overridden. + */ + @Override + protected List<FrameworkMethod> computeTestMethods() { + FrameworkMethod[] lst = new FrameworkMethod[1]; + + try { + lst[0] = new FrameworkMethod(getTestClass().getJavaClass() + .getMethod("runTest")) { + + @Override + public String getName() { + return manifestItem.getTestName(); + } + }; + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e); + } catch (SecurityException e) { + throw new IllegalStateException(e); + } + return Arrays.asList(lst); + } + + @Override + public Description getDescription() { + return Description.createTestDescription(this.getTestClass() + .getJavaClass(), manifestItem.getTestName(), new Annotation[0]); + } + + /** + * Returns a new fixture for running a test. Default implementation executes + * the test class's no-argument constructor (validation should have ensured + * one exists). + */ + @Override + protected Object createTest() throws Exception { + ManifestTest instance = (ManifestTest) super.createTest(); + instance.setManifestItem(manifestItem); + return instance; + } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java ---------------------------------------------------------------------- diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java b/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java new file mode 100644 index 0000000..96f3e42 --- /dev/null +++ b/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java @@ -0,0 +1,109 @@ +/* + 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.jena.testing_framework; + +/** + * Foo set of classes providing support for testing. + * <p> + * Testing guidelines/suggestions. + * </p><p> + * Interface tests are built so that developers may test that implementations meet the contract + * set out in the interface and accompanying documentation. + * </p> + * <h4>Producers</h4> + * <p> + * The test and suites use an instance of the [INTERFACE]ProducerInterface to create an instance + * of the the Object being tested. + * </p> + * <h4>Tests</h4> + * <p> + * Interface tests are noted as Abstract[INTERFACE]Test. Implementations of [INTERFACE] should + * create a concrete implementation of Abstract[INTERFACE]Test with an [INTERFACE]Producer to create + * instances of the Object. Passing the test indicates a compliance with the base interface + * definition. + * </p><p> + * In general to implement a test requires a few lines of code as is noted in the example below + * where the new Foo graph implementation is being tested.</p> + * <pre><code> + * public class FooGraphTest extends AbstractGraphTest { + * + * // the graph producer to use while running + * GraphProducerInterface graphProducer = new FooGraphTest.GraphProducer(); + * + * @Override + * protected GraphProducerInterface getGraphProducer() { + * return graphProducer; + * } + * + * // the implementation of the graph producer. + * public static class GraphProducer extends AbstractGraphProducer { + * + * @Override + * protected Graph createNewGraph() { + * return new FooGraph(); + * } + * } + * } + * </code></pre> + * <h4>Suites</h4> + * <p> + * Test suites are named as Abstract[INTERFACE]Suite. Suites contain several tests (see above) + * that exercise all of the tests for the components of the object under test. For example the + * graph suite includes tests for the graph itself, the reifier, finding literals, recursive + * subgraph extraction, event manager, and transactions. Running the suites is a bit more + * complicated then running the tests. + * </p> + * Suites are created using the JUnit 4 <code>@RunWith(Suite.class)</code and + * <code>@Suite.SuiteClasses({ })</code> annotations. This has several effects that the developer + * should know about:</p> + * <ul> + * <li>The suite class does not get instantiated during the run.</li> + * <li>The test class names must be known at coding time (not run time) as they are listed in the + * annotation.</li> + * <li>Configuration of the tests has to occur during the static initialization phase of class + * loading.</li> + * </ul> + * <p> + * To meet these requirements the AbstractGraphSuite has a static variable that holds the instance + * of the GraphProducerInterface and a number of local static implementations of the Abstract tests + * that implement the "getGraphProducer()" method by returning the static instance. The names of + * the local graphs are then used in the @Suite.SuiteClasses annotation. This makes creating an + * instance of the AbstractGraphSuite for a graph implementation fairly simple as is noted below. + * </p> + * <pre><code> + * public class FooGraphSuite extends AbstractGraphSuite { + * @BeforeClass + * public static void beforeClass() { + * setGraphProducer(new GraphProducer()); + * } + * + * public static class GraphProducer extends AbstractGraphProducer { + * @Override + * protected Graph createNewGraph() { + * return new FooGraph(); + * } + * } + * } + * </code></pre> + * <p> + * <b>Note:</b> that the beforeClass() method is annotated with @BeforeClass. the @BeforeClass + * causes it to be run once before any of the test methods in the class. This will set the static + * instance of the graph producer before the suite is run so that it is provided to the enclosed + * tests. + * </p> + */ http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java ---------------------------------------------------------------------- diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java new file mode 100644 index 0000000..65aff5b --- /dev/null +++ b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java @@ -0,0 +1,84 @@ +/* + * 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.jena.testing_framework.tuples; + +/** + * The unit found in a line of a tuple. Can be a string (quoted, possibly with + * the datatype, or unquoted) or a URI. + */ +public class TupleItem { + public static final int URI = 0; + public static final int STRING = 1; + public static final int UNKNOWN = 2; + public static final int UNQUOTED = 3; + public static final int ANON = 4; + + String rep; + String datatype; + String asFound; + int itemType; + + TupleItem(String value, String valAsFound, int type, String dt) { + rep = value; + asFound = valAsFound; + itemType = type; + datatype = dt; + } + + public int getType() { + return itemType; + } + + public boolean isURI() { + return itemType == URI; + } + + public boolean isString() { + return itemType == STRING; + } + + public boolean isUnknown() { + return itemType == UNKNOWN; + } + + public boolean isUnquoted() { + return itemType == UNQUOTED; + } + + public boolean isAnon() { + return itemType == ANON; + } + + public String get() { + return rep; + } + + public String getDT() { + return datatype; + } + + public String asQuotedString() { + return asFound; + } + + @Override + public String toString() { + return rep; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java ---------------------------------------------------------------------- diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java new file mode 100644 index 0000000..8e78879 --- /dev/null +++ b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java @@ -0,0 +1,274 @@ +/* + * 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.jena.testing_framework.tuples; + +import java.io.*; +import java.util.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TupleSet implements Iterator<List<TupleItem>> { + BufferedReader in; + public String line = null; + public int lineNumber = 0; + + static final char COMMENTCHAR = '#'; + List<TupleItem> current = null; + boolean finished = false; + + protected static Logger logger = LoggerFactory.getLogger(TupleSet.class); + + /** Creates new TupleSet */ + public TupleSet(Reader r) { + if (!(r instanceof BufferedReader)) + in = new BufferedReader(r); + else + in = (BufferedReader) r; + } + + @Override + public boolean hasNext() { + if (finished) + return false; + + if (current == null) + current = tuple(); + return current != null; + } + + @Override + public List<TupleItem> next() { + if (hasNext()) { + List<TupleItem> x = current; + current = null; + return x; + } else + return null; + } + + @Override + public void remove() { + throw new java.lang.UnsupportedOperationException("TupleSet.remove"); + } + + private List<TupleItem> tuple() { + + try { + lineNumber++; + line = in.readLine(); + } catch (IOException e) { + } + + if (line == null) { + finished = true; + return null; + } + + // System.out.println("Line: "+line) ; + List<TupleItem> tuple = new ArrayList<TupleItem>(); + int i = 0; + int j = 0; + boolean errorFound = false; + + tupleLoop: for (;;) { + // Move to beginning of next item. + i = skipwhitespace(line, j); + + if (i < 0) + break; + + int iStart = -2; // Points to the beginning of the item as found + int jStart = -2; // Points to the item without quotes + int iFinish = -2; // Points after the end of the item as found + int jFinish = -2; // Points after the end of the item without quotes + int dtStart = -2; // Points to start of datatype (after < quote) + int dtFinish = -2; // Points to end of datatype + int type = TupleItem.UNKNOWN; + + switch (line.charAt(i)) { + case COMMENTCHAR: + break tupleLoop; + case '<': + type = TupleItem.URI; + iStart = i; + jStart = i + 1; + int newPosn = parseURI(i, line); + if (newPosn < 0) { + errorFound = true; + break tupleLoop; + } + j = newPosn; + + iFinish = j + 1; + jFinish = j; + break; + case '"': + type = TupleItem.STRING; + iStart = i; + jStart = i + 1; + boolean inEscape = false; + for (j = i + 1; j < line.length(); j++) { + char ch = line.charAt(j); + if (inEscape) { + // ToDo: escape + inEscape = false; + continue; + } + // Not an escape + if (ch == '"') + break; + + if (ch == '\\') + inEscape = true; + if (ch == '\n' || ch == '\r') { + errorFound = true; + break tupleLoop; + + } + } + + // Malformed + if (j == line.length()) { + errorFound = true; + break tupleLoop; + } + + iFinish = j + 1; + jFinish = j; + // RDF literals may be followed by their type. + + if (j < line.length() - 3 && line.charAt(j + 1) == '^' + && line.charAt(j + 2) == '^' + && line.charAt(j + 3) == '<') { + dtFinish = parseURI(j + 3, line); + dtStart = j + 4; + if (dtFinish < 0) { + errorFound = true; + break tupleLoop; + } + j = dtFinish + 1; + // String dt = line.substring(dtStart, dtFinish) ; + // System.out.println("I see a datatype:"+dt) ; + } + + break; + case '_': + type = TupleItem.ANON; + iStart = i; + for (j = i + 1; j < line.length(); j++) { + char ch = line.charAt(j); + if (ch == ' ' || ch == '\t' || ch == '.') + break; + if (!Character.isLetterOrDigit(ch) && !(ch == '_') + && !(ch == ':')) { + errorFound = true; + break tupleLoop; + } + } + iFinish = j; + jStart = iStart; + jFinish = iFinish; + break; + case '.': + case '\n': + case '\r': + return tuple; + default: + type = TupleItem.UNQUOTED; + iStart = i; + jStart = i; + for (j = i + 1; j < line.length(); j++) { + char ch = line.charAt(j); + if (ch == ' ' || ch == '\t' || ch == '.') + break; + + // if ( ! Character.isLetterOrDigit(line.charAt(i)) ) + // { + // errorFound = true ; + // break tupleLoop; + // } + } + // Malformed + if (j == line.length() + 1) { + errorFound = true; + break tupleLoop; + } + iFinish = j; + jFinish = j; + break; + } + String item = line.substring(jStart, jFinish); + String literal = line.substring(iStart, iFinish); + String dt = null; + if (dtStart > 0) + dt = line.substring(dtStart, dtFinish); + + tuple.add(new TupleItem(item, literal, type, dt)); + j++; + // End of item. + } + // End of this line. + if (errorFound) { + logger.error("Error in TupleSet.tuple: " + line); + + String s = ""; + int k = 0; + for (; k < i; k++) + s = s + " "; + s = s + "^"; + for (; k < j - 1; k++) + s = s + " "; + s = s + "^"; + logger.error(s); + return null; + } + + if (tuple.size() == 0) { + // Nothing found : loop by tail recursion + return tuple(); + } + return tuple; + } + + private int skipwhitespace(String s, int i) { + for (; i < s.length(); i++) { + char ch = s.charAt(i); + // Horizonal whitespace + if (ch != ' ' && ch != '\t') + return i; + } + return -1; + } + + private int parseURI(int i, String line) { + int j; + for (j = i + 1; j < line.length(); j++) { + char ch = line.charAt(j); + if (ch == '>') + break; + if (ch == '\n' || ch == '\r') + return -1; + } + // Malformed + if (j == line.length()) + return -2; + return j; + } +}
