You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by cl...@apache.org on 2015/05/23 08:58:19 UTC

[09/25] jena git commit: Added initial contract tests added testing_framework

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/GraphHelper.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/GraphHelper.java b/jena-core/src/test/java/org/apache/jena/testing_framework/GraphHelper.java
new file mode 100644
index 0000000..c208e8d
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/GraphHelper.java
@@ -0,0 +1,508 @@
+/*
+ * 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 static test helpers.  Generally included as a static.
+ */
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.apache.jena.graph.Factory;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.GraphUtil;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.shared.JenaException;
+import org.apache.jena.shared.PrefixMapping;
+import org.apache.jena.util.CollectionFactory;
+import org.apache.jena.util.IteratorCollection;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+public class GraphHelper extends TestUtils {
+
+	/**
+	 * Answer a Node as described by <code>x</code>; a shorthand for
+	 * <code>Node.create(x)</code>, which see.
+	 */
+	public static Node node(String x) {
+		return NodeCreateUtils.create(x);
+	}
+
+	/**
+	 * Answer a set containing the elements from the iterator <code>it</code>; a
+	 * shorthand for <code>IteratorCollection.iteratorToSet(it)</code>, which
+	 * see.
+	 */
+	public static <T> Set<T> iteratorToSet(Iterator<? extends T> it) {
+		return IteratorCollection.iteratorToSet(it);
+	}
+
+	/**
+	 * Answer a list containing the elements from the iterator <code>it</code>,
+	 * in order; a shorthand for
+	 * <code>IteratorCollection.iteratorToList(it)</code>, which see.
+	 */
+	public static <T> List<T> iteratorToList(Iterator<? extends T> it) {
+		return IteratorCollection.iteratorToList(it);
+	}
+
+	/**
+	 * Answer a set of the nodes described (as per <code>node()</code>) by the
+	 * space-separated substrings of <code>nodes</code>.
+	 */
+	public static Set<Node> nodeSet(String nodes) {
+		Set<Node> result = CollectionFactory.createHashedSet();
+		StringTokenizer st = new StringTokenizer(nodes);
+		while (st.hasMoreTokens())
+			result.add(node(st.nextToken()));
+		return result;
+	}
+
+	/**
+	 * Answer a set of the elements of <code>Foo</code>.
+	 */
+	public static <T> Set<T> arrayToSet(T[] A) {
+		return CollectionFactory.createHashedSet(Arrays.asList(A));
+	}
+
+	/**
+	 * Answer a triple described by the three space-separated node descriptions
+	 * in <code>fact</code>; a shorthand for <code>Triple.create(fact)</code>,
+	 * which see.
+	 */
+	public static Triple triple(String fact) {
+		return NodeCreateUtils.createTriple(fact);
+	}
+
+	/**
+	 * Answer a triple described by the three space-separated node descriptions
+	 * in <code>fact</code>, using prefix-mappings from <code>pm</code>; a
+	 * shorthand for <code>Triple.create(pm, fact)</code>, which see.
+	 */
+	public static Triple triple(PrefixMapping pm, String fact) {
+		return NodeCreateUtils.createTriple(pm, fact);
+	}
+
+	/**
+	 * Answer an array of triples; each triple is described by one of the
+	 * semi-separated substrings of <code>facts</code>, as per
+	 * <code>triple</code> with prefix-mapping <code>Extended</code>.
+	 */
+	public static Triple[] tripleArray(String facts) {
+		ArrayList<Triple> al = new ArrayList<Triple>();
+		StringTokenizer semis = new StringTokenizer(facts, ";");
+		while (semis.hasMoreTokens())
+			al.add(triple(PrefixMapping.Extended, semis.nextToken()));
+		return al.toArray(new Triple[al.size()]);
+	}
+
+	/**
+	 * Answer a set of triples where the elements are described by the
+	 * semi-separated substrings of <code>facts</code>, as per
+	 * <code>triple</code>.
+	 */
+	public static Set<Triple> tripleSet(String facts) {
+		Set<Triple> result = new HashSet<Triple>();
+		StringTokenizer semis = new StringTokenizer(facts, ";");
+		while (semis.hasMoreTokens())
+			result.add(triple(semis.nextToken()));
+		return result;
+	}
+
+	/**
+	 * Answer a list of nodes, where the nodes are described by the
+	 * space-separated substrings of <code>items</code> as per
+	 * <code>node()</code>.
+	 */
+	public static List<Node> nodeList(String items) {
+		ArrayList<Node> nl = new ArrayList<Node>();
+		StringTokenizer nodes = new StringTokenizer(items);
+		while (nodes.hasMoreTokens())
+			nl.add(node(nodes.nextToken()));
+		return nl;
+	}
+
+	/**
+	 * Answer an array of nodes, where the nodes are described by the
+	 * space-separated substrings of <code>items</code> as per
+	 */
+	public static Node[] nodeArray(String items) {
+		List<Node> nl = nodeList(items);
+		return nl.toArray(new Node[nl.size()]);
+	}
+
+	/**
+	 * Answer the graph <code>g</code> after adding to it every triple encoded
+	 * in <code>s</code> in the fashion of <code>tripleArray</code>, a
+	 * semi-separated sequence of space-separated node descriptions.
+	 */
+	public static Graph graphAdd(Graph g, String s) {
+		StringTokenizer semis = new StringTokenizer(s, ";");
+		while (semis.hasMoreTokens())
+			g.add(triple(PrefixMapping.Extended, semis.nextToken()));
+		return g;
+	}
+
+	/**
+	 * Like graphAdd but does it within a transaction if supported
+	 * 
+	 * @param g
+	 *            The graph to add to
+	 * @param s
+	 *            The string describing the graph
+	 * @return The populated graph.
+	 */
+	public static Graph graphAddTxn(Graph g, String s) {
+		txnBegin(g);
+		StringTokenizer semis = new StringTokenizer(s, ";");
+		while (semis.hasMoreTokens())
+			g.add(triple(PrefixMapping.Extended, semis.nextToken()));
+		txnCommit(g);
+		return g;
+	}
+
+	/**
+	 * Used to create a graph with values.
+	 * 
+	 * @param g
+	 *            The newly created graph
+	 * @param s
+	 *            The string representing the graph data.
+	 * @return The populated graph
+	 */
+	public static Graph graphWith(Graph g, String s) {
+		return graphAddTxn(g, s);
+	}
+
+	/**
+	 * Answer a new memory-based graph with Extended prefixes.
+	 */
+	public static Graph memGraph() {
+		Graph result = Factory.createGraphMem();
+		result.getPrefixMapping().setNsPrefixes(PrefixMapping.Extended);
+		return result;
+	}
+
+	/**
+	 * Answer a new memory-based graph with initial contents as described by
+	 * <code>s</code> in the fashion of <code>graphAdd()</code>. Not
+	 * over-ridable; do not use for abstraction.
+	 */
+	public static Graph graphWith(String s) {
+		return graphWith(memGraph(), s);
+	}
+
+	/**
+	 * Assert that the graph <code>g</code> is isomorphic to the graph described
+	 * by <code>template</code> in the fashion of <code>graphWith</code>.
+	 */
+	public static void assertEqualsTemplate(String title, Graph g,
+			String template) {
+		assertIsomorphic(title, graphWith(template), g);
+	}
+
+	/**
+	 * Assert that the supplied graph <code>got</code> is isomorphic with the
+	 * the desired graph <code>expected</code>; if not, display a readable
+	 * description of both graphs.
+	 */
+	public static void assertIsomorphic(String title, Graph expected, Graph got) {
+		if (!expected.isIsomorphicWith(got)) {
+			Map<Node, Object> map = CollectionFactory.createHashedMap();
+			fail(title + ": wanted " + nice(expected, map) + "\nbut got "
+					+ nice(got, map));
+		}
+	}
+
+	/**
+	 * Answer a string which is a newline-separated list of triples (as produced
+	 * by niceTriple) in the graph <code>g</code>. The map <code>bnodes</code>
+	 * maps already-seen bnodes to their "nice" strings.
+	 */
+	public static String nice(Graph g, Map<Node, Object> bnodes) {
+		StringBuffer b = new StringBuffer(g.size() * 100);
+		ExtendedIterator<Triple> it = GraphUtil.findAll(g);
+		while (it.hasNext())
+			niceTriple(b, bnodes, it.next());
+		return b.toString();
+	}
+
+	/**
+	 * Append to the string buffer <code>b</code> a "nice" representation of the
+	 * triple <code>t</code> on a new line, using (and updating)
+	 * <code>bnodes</code> to supply "nice" strings for any blank nodes.
+	 */
+	protected static void niceTriple(StringBuffer b, Map<Node, Object> bnodes,
+			Triple t) {
+		b.append("\n    ");
+		appendNode(b, bnodes, t.getSubject());
+		appendNode(b, bnodes, t.getPredicate());
+		appendNode(b, bnodes, t.getObject());
+	}
+
+	/**
+	 * Foo counter for new bnode strings; it starts at 1000 so as to make the
+	 * bnode strings more uniform (at least for the first 9000 bnodes).
+	 */
+	protected static int bnc = 1000;
+
+	/**
+	 * Append to the string buffer <code>b</code> a space followed by the "nice"
+	 * representation of the node <code>n</code>. If <code>n</code> is a bnode,
+	 * re-use any existing string for it from <code>bnodes</code> or make a new
+	 * one of the form <i>_bNNNN</i> with NNNN a new integer.
+	 */
+	protected static void appendNode(StringBuffer b, Map<Node, Object> bnodes,
+			Node n) {
+		b.append(' ');
+		if (n.isBlank()) {
+			Object already = bnodes.get(n);
+			if (already == null)
+				bnodes.put(n, already = "_b" + bnc++);
+			b.append(already);
+		} else
+			b.append(nice(n));
+	}
+
+	// protected static Graph graphWithTxn(IProducer<? extends Graph> producer,
+	// String s) {
+	// Graph g = producer.newInstance();
+	// txnBegin(g);
+	// try {
+	// graphAdd(g, s);
+	// txnCommit(g);
+	// } catch (Exception e) {
+	// txnRollback(g);
+	// fail(e.getMessage());
+	// }
+	// return g;
+	// }
+
+	/**
+	 * Answer the "nice" representation of this node, the string returned by
+	 * <code>n.toString(PrefixMapping.Extended,true)</code>.
+	 */
+	protected static String nice(Node n) {
+		return n.toString(PrefixMapping.Extended, true);
+	}
+
+	/**
+	 * Assert that the computed graph <code>got</code> is isomorphic with the
+	 * desired graph <code>expected</code>; if not, fail with a default message
+	 * (and pretty output of the graphs).
+	 */
+	public static void assertIsomorphic(Graph expected, Graph got) {
+		assertIsomorphic("graphs must be isomorphic", expected, got);
+	}
+
+	/**
+	 * Assert that the graph <code>g</code> must contain the triple described by
+	 * <code>s</code>; if not, fail with pretty output of both graphs and a
+	 * message containing <code>name</code>.
+	 */
+	public static void assertContains(String name, String s, Graph g) {
+		assertTrue(name + " must contain " + s, g.contains(triple(s)));
+	}
+
+	/**
+	 * Assert that the graph <code>g</code> contains all the triples described
+	 * by the string <code>s</code; if not, fail with a message containing
+	 * <code>name</code>.
+	 */
+	public static void assertContainsAll(String name, Graph g, String s) {
+		StringTokenizer semis = new StringTokenizer(s, ";");
+		while (semis.hasMoreTokens())
+			assertContains(name, semis.nextToken(), g);
+	}
+
+	/**
+	 * Assert that the graph <code>g</code> does not contain the triple
+	 * described by <code>s<code>; if it does, fail with a message containing
+        <code>name</code>.
+	 */
+	public static void assertOmits(String name, Graph g, String s) {
+		assertFalse(name + " must not contain " + s, g.contains(triple(s)));
+	}
+
+	/**
+	 * Assert that the graph <code>g</code> contains none of the triples
+	 * described by <code>s</code> in the usual way; otherwise, fail with a
+	 * message containing <code>name</code>.
+	 */
+	public static void assertOmitsAll(String name, Graph g, String s) {
+		StringTokenizer semis = new StringTokenizer(s, ";");
+		while (semis.hasMoreTokens())
+			assertOmits(name, g, semis.nextToken());
+	}
+
+	/**
+	 * Assert that <code>g</code> contains the triple described by
+	 * <code>fact</code> in the usual way.
+	 */
+	public static boolean contains(Graph g, String fact) {
+		return g.contains(triple(fact));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains every triple in <code>triples</code>.
+	 */
+	public static void testContains(Graph g, Triple[] triples) {
+		for (int i = 0; i < triples.length; i += 1)
+			assertTrue("contains " + triples[i], g.contains(triples[i]));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains every triple in <code>triples</code>.
+	 */
+	public static void testContains(Graph g, List<Triple> triples) {
+		for (int i = 0; i < triples.size(); i += 1)
+			assertTrue(g.contains(triples.get(i)));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains every triple in <code>it</code>.
+	 */
+	public static void testContains(Graph g, Iterator<Triple> it) {
+		while (it.hasNext())
+			assertTrue(g.contains(it.next()));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains every triple in <code>other</code>.
+	 */
+	public static void testContains(Graph g, Graph other) {
+		testContains(g, GraphUtil.findAll(other));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains none of the triples in
+	 * <code>triples</code>.
+	 */
+	public static void testOmits(Graph g, Triple[] triples) {
+		for (int i = 0; i < triples.length; i += 1)
+			assertFalse("", g.contains(triples[i]));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains none of the triples in
+	 * <code>triples</code>.
+	 */
+	public static void testOmits(Graph g, List<Triple> triples) {
+		for (int i = 0; i < triples.size(); i += 1)
+			assertFalse("", g.contains(triples.get(i)));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains none of the triples in
+	 * <code>it</code>.
+	 */
+	public static void testOmits(Graph g, Iterator<Triple> it) {
+		while (it.hasNext())
+			assertFalse("", g.contains(it.next()));
+	}
+
+	/**
+	 * Assert that <code>g</code> contains none of the triples in
+	 * <code>other</code>.
+	 */
+	public static void testOmits(Graph g, Graph other) {
+		testOmits(g, GraphUtil.findAll(other));
+	}
+
+	/**
+	 * Answer an instance of <code>graphClass</code>. If <code>graphClass</code>
+	 * has a constructor that takes a <code>ReificationStyle</code> argument,
+	 * then that constructor is run on <code>style</code> to get the instance.
+	 * Otherwise, if it has a # constructor that takes an argument of
+	 * <code>wrap</code>'s class before the <code>ReificationStyle</code>, that
+	 * constructor is used; this allows non-static inner classes to be used for
+	 * <code>graphClass</code>, with <code>wrap</code> being the outer class
+	 * instance. If no suitable constructor exists, a JenaException is thrown.
+	 * 
+	 * @param wrap
+	 *            the outer class instance if graphClass is an inner class
+	 * @param graphClass
+	 *            a class implementing Graph
+	 * @return an instance of graphClass with the given style
+	 * @throws RuntimeException
+	 *             or JenaException if construction fails
+	 */
+	public static Graph getGraph(Object wrap, Class<? extends Graph> graphClass) {
+		try {
+			Constructor<?> cons = getConstructor(graphClass, new Class[] {});
+			if (cons != null)
+				return (Graph) cons.newInstance(new Object[] {});
+			Constructor<?> cons2 = getConstructor(graphClass,
+					new Class[] { wrap.getClass() });
+			if (cons2 != null)
+				return (Graph) cons2.newInstance(new Object[] { wrap });
+			throw new JenaException("no suitable graph constructor found for "
+					+ graphClass);
+		} catch (RuntimeException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new JenaException(e);
+		}
+	}
+
+	/**
+	 * Begin a transaction on the graph if transactions are supported.
+	 * 
+	 * @param g
+	 */
+	public static void txnBegin(Graph g) {
+		if (g.getTransactionHandler().transactionsSupported()) {
+			g.getTransactionHandler().begin();
+		}
+	}
+
+	/**
+	 * Commit the transaction on the graph if transactions are supported.
+	 * 
+	 * @param g
+	 */
+	public static void txnCommit(Graph g) {
+		if (g.getTransactionHandler().transactionsSupported()) {
+			g.getTransactionHandler().commit();
+		}
+	}
+
+	/**
+	 * Rollback (abort) the transaction on the graph if transactions are
+	 * supported.
+	 * 
+	 * @param g
+	 */
+	public static void txnRollback(Graph g) {
+		if (g.getTransactionHandler().transactionsSupported()) {
+			g.getTransactionHandler().abort();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/GraphProducerInterface.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/GraphProducerInterface.java b/jena-core/src/test/java/org/apache/jena/testing_framework/GraphProducerInterface.java
new file mode 100644
index 0000000..8c33eb1
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/GraphProducerInterface.java
@@ -0,0 +1,41 @@
+/*
+    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;
+
+/**
+ * Creates the graph for testing. Implementations must track the creation of
+ * graphs created with newGraph and close them when closeGraphs is called.
+ * 
+ */
+//public interface GraphProducerInterface<T> {
+//
+//	/**
+//	 * Returns a new Graph to take part in the test.
+//	 * 
+//	 * @return The graph implementation to test.
+//	 */
+//	public abstract Graph newGraph();
+//
+//	/**
+//	 * provides a hook to close down graphs. When called all graphs created by
+//	 * the newGraph() method should be closed. Note that some graphs may have
+//	 * been closed during the test, so graphs should be tested for being closed
+//	 * prior to closing.
+//	 */
+//	public abstract void closeGraphs();
+// }

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/IContainerProducer.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/IContainerProducer.java b/jena-core/src/test/java/org/apache/jena/testing_framework/IContainerProducer.java
new file mode 100644
index 0000000..41bc6df
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/IContainerProducer.java
@@ -0,0 +1,19 @@
+package org.apache.jena.testing_framework;
+
+import org.apache.jena.rdf.model.Container;
+import org.apache.jena.rdf.model.Resource;
+
+public interface IContainerProducer<T extends Container> extends
+		IResourceProducer<T> {
+
+	/**
+	 * The Resource identifying the continer type. e.g. RDF.seq
+	 */
+	Resource getContainerType();
+
+	/**
+	 * The class of the continaer. e.g. Seq.class
+	 */
+	Class<? extends Container> getContainerClass();
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/IIteratorProducer.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/IIteratorProducer.java b/jena-core/src/test/java/org/apache/jena/testing_framework/IIteratorProducer.java
new file mode 100644
index 0000000..3178692
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/IIteratorProducer.java
@@ -0,0 +1,42 @@
+package org.apache.jena.testing_framework;
+
+import java.util.List;
+
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+public interface IIteratorProducer<T> {
+
+	/**
+	 * Get a new instance of the iterator.
+	 * 
+	 * @return
+	 */
+	public ExtendedIterator<T> newInstance();
+
+	/**
+	 * Clean up after a test
+	 */
+	public void cleanUp();
+
+	/**
+	 * The list of items found in the iterator. Does not have to be in order.
+	 * 
+	 * @return
+	 */
+	public List<T> getList();
+
+	/**
+	 * True if delete is supported by the iterator
+	 * 
+	 * @return
+	 */
+	public boolean supportsDelete();
+
+	/**
+	 * True if this is an iterator on a copy so that delete works but getting a
+	 * new copy for the iterator test will return the original list.
+	 * 
+	 * @return
+	 */
+	public boolean isCopy();
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/INodeProducer.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/INodeProducer.java b/jena-core/src/test/java/org/apache/jena/testing_framework/INodeProducer.java
new file mode 100644
index 0000000..2532c32
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/INodeProducer.java
@@ -0,0 +1,39 @@
+/*
+    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;
+
+import org.xenei.junit.contract.IProducer;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.RDFNode;
+
+/**
+ * An abstract implementation of the IProducer<RDFNode> interface.
+ * 
+ * This class handles tracking of the created graphs and closing them. It also
+ * provides a callback for the implementing class to perform extra cleanup when
+ * the graph is closed.
+ * 
+ */
+public interface INodeProducer<T extends RDFNode> extends IProducer<T> {
+
+	abstract public T newInstance(String uri);
+
+	abstract public Model getModel();
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/IResourceProducer.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/IResourceProducer.java b/jena-core/src/test/java/org/apache/jena/testing_framework/IResourceProducer.java
new file mode 100644
index 0000000..ef9eead
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/IResourceProducer.java
@@ -0,0 +1,10 @@
+package org.apache.jena.testing_framework;
+
+import org.apache.jena.rdf.model.Resource;
+
+public interface IResourceProducer<X extends Resource> extends INodeProducer<X> {
+	/**
+	 * Returns true if the Resource implementation supports non URI values
+	 */
+	boolean supportsAnonymous();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/IStatementProducer.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/IStatementProducer.java b/jena-core/src/test/java/org/apache/jena/testing_framework/IStatementProducer.java
new file mode 100644
index 0000000..8830948
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/IStatementProducer.java
@@ -0,0 +1,45 @@
+/*
+    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;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.Statement;
+
+/**
+ * An abstract implementation of the IProducer<RDFNode> interface.
+ * 
+ * This class handles tracking of the created graphs and closing them. It also
+ * provides a callback for the implementing class to perform extra cleanup when
+ * the graph is closed.
+ * 
+ */
+public interface IStatementProducer<T extends Statement> {
+
+	abstract public T newInstance(String fact);
+
+	abstract public T newInstance(Resource s, Property p, RDFNode o);
+
+	abstract public Model getModel();
+
+	abstract public void cleanUp();
+
+	abstract public AbstractModelProducer<Model> getModelProducer();
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/ITripleStoreProducer.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/ITripleStoreProducer.java b/jena-core/src/test/java/org/apache/jena/testing_framework/ITripleStoreProducer.java
new file mode 100644
index 0000000..fe4e25d
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/ITripleStoreProducer.java
@@ -0,0 +1,35 @@
+/*
+    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;
+
+import org.apache.jena.graph.impl.TripleStore;
+
+/**
+ * Creates the graph for testing
+ * 
+ */
+public interface ITripleStoreProducer {
+
+	/**
+	 * Returns a TripleStore to take part in the test. Must be overridden in a
+	 * subclass.
+	 * 
+	 * @return The TripleStore implementation to test.
+	 */
+	public abstract TripleStore newTripleStore();
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/ModelHelper.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/ModelHelper.java b/jena-core/src/test/java/org/apache/jena/testing_framework/ModelHelper.java
new file mode 100644
index 0000000..2f35203
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/ModelHelper.java
@@ -0,0 +1,409 @@
+/*
+ * 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;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.rdf.model.Literal;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.ModelFactory;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+import org.apache.jena.rdf.model.Statement;
+import org.apache.jena.rdf.model.StmtIterator;
+import org.apache.jena.shared.PrefixMapping;
+import org.apache.jena.util.CollectionFactory;
+
+import static org.junit.Assert.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.junit.Assert;
+import org.xenei.junit.contract.IProducer;
+
+/**
+ * provides useful functionality for testing models, eg building small models
+ * from strings, testing equality, etc.
+ * 
+ * Currently this class extends GraphHelper and thus TestCase.
+ */
+public class ModelHelper extends GraphHelper {
+
+	private static Model builderModel;
+
+	static {
+		builderModel = ModelFactory.createDefaultModel();
+		builderModel.setNsPrefixes(PrefixMapping.Extended);
+	}
+
+	protected static final Model empty = ModelFactory.createDefaultModel();
+
+	protected static Model extendedModel(IProducer<Model> producer) {
+		Model result = producer.newInstance();
+		result.setNsPrefixes(PrefixMapping.Extended);
+		return result;
+	}
+
+	protected static String nice(RDFNode n) {
+		return nice(n.asNode());
+	}
+
+	public static Statement statement(String fact) {
+		StringTokenizer st = new StringTokenizer(fact);
+		Resource sub = resource(st.nextToken());
+		Property pred = property(st.nextToken());
+		RDFNode obj = rdfNode(st.nextToken());
+		return builderModel.createStatement(sub, pred, obj);
+	}
+
+	public static Statement statement(Resource s, Property p, RDFNode o) {
+		return builderModel.createStatement(s, p, o);
+	}
+
+	public static RDFNode rdfNode(Model m, String s) {
+		return m.asRDFNode(NodeCreateUtils.create(s));
+	}
+
+	public static RDFNode rdfNode(String s) {
+		return rdfNode(builderModel, s);
+	}
+
+	public static <T extends RDFNode> T rdfNode(String s, Class<T> c) {
+		return rdfNode(s).as(c);
+	}
+
+	public static Resource resource() {
+		return ResourceFactory.createResource();
+	}
+
+	public static Resource resource(String s) {
+		return (Resource) rdfNode(s);
+	}
+
+	// public static Resource resource(Model m, String s) {
+	// return (Resource) rdfNode(m, s);
+	// }
+
+	public static Property property(String s) {
+		return rdfNode(s).as(Property.class);
+	}
+
+	public static Property property(Model m, String s) {
+		return rdfNode(m, s).as(Property.class);
+	}
+
+	public static Literal literal(String s, String lang) {
+		return builderModel.createLiteral(s, lang);
+	}
+
+	public static Literal literal(String s) {
+		return rdfNode(s).as(Literal.class);
+	}
+
+	// /**
+	// * Create an array of Statements parsed from a semi-separated string.
+	// *
+	// * @param lockModel
+	// * a model to serve as a statement factory
+	// * @param facts
+	// * a sequence of semicolon-separated "S P O" facts
+	// * @return a Statement[] of the (S P O) statements from the string
+	// */
+	// public static Statement[] statements(Model m, String facts) {
+	// ArrayList<Statement> sl = new ArrayList<Statement>();
+	// StringTokenizer st = new StringTokenizer(facts, ";");
+	// while (st.hasMoreTokens())
+	// sl.add(statement(m, st.nextToken()));
+	// return sl.toArray(new Statement[sl.size()]);
+	// }
+
+	/**
+	 * Create an array of Statements parsed from a semi-separated string.
+	 * 
+	 * @param lockModel
+	 *            a model to serve as a statement factory
+	 * @param facts
+	 *            a sequence of semicolon-separated "S P O" facts
+	 * @return a Statement[] of the (S P O) statements from the string
+	 */
+	public static Statement[] statements(String facts) {
+		ArrayList<Statement> sl = new ArrayList<Statement>();
+		StringTokenizer st = new StringTokenizer(facts, ";");
+		while (st.hasMoreTokens())
+			sl.add(statement(st.nextToken()));
+		return sl.toArray(new Statement[sl.size()]);
+	}
+
+	/**
+	 * Create an array of Resources from a whitespace-separated string
+	 * 
+	 * @param items
+	 *            a whitespace-separated sequence to feed to resource
+	 * @return a Resource[] of the parsed resources
+	 */
+	public static Resource[] resources(String items) {
+		ArrayList<Resource> rl = new ArrayList<Resource>();
+		StringTokenizer st = new StringTokenizer(items);
+		while (st.hasMoreTokens())
+			rl.add(resource(st.nextToken()));
+		return rl.toArray(new Resource[rl.size()]);
+	}
+
+	/**
+	 * Answer the set of resources given by the space-separated
+	 * <code>items</code> string. Each resource specification is interpreted as
+	 * per <code>resource</code>.
+	 */
+	public static Set<Resource> resourceSet(String items) {
+		Set<Resource> result = new HashSet<Resource>();
+		StringTokenizer st = new StringTokenizer(items);
+		while (st.hasMoreTokens())
+			result.add(resource(st.nextToken()));
+		return result;
+	}
+
+	/**
+	 * add to a model all the statements expressed by a string.
+	 * 
+	 * Does not do any transaction manipulation.
+	 * 
+	 * @param m
+	 *            the model to be updated
+	 * @param facts
+	 *            a sequence of semicolon-separated "S P O" facts
+	 * @return the updated model
+	 */
+	public static Model modelAdd(Model m, String facts) {
+		StringTokenizer semis = new StringTokenizer(facts, ";");
+
+		while (semis.hasMoreTokens()) {
+			StringTokenizer st = new StringTokenizer(semis.nextToken());
+			Resource sub = resource(st.nextToken());
+			Property pred = property(st.nextToken());
+			RDFNode obj = rdfNode(st.nextToken());
+			m.add(sub, pred, obj);
+		}
+
+		return m;
+	}
+
+	/**
+	 * create a memory based model with extended prefixes and initialises it
+	 * with statements parsed from a string.
+	 * 
+	 * does all insertions in a transaction.
+	 * 
+	 * @param facts
+	 * @return
+	 */
+	public static Model memModel(String facts) {
+		Model model = ModelFactory.createMemModelMaker().createFreshModel();
+		model.setNsPrefixes(PrefixMapping.Extended);
+		txnBegin(model);
+		modelAdd(model, facts);
+		txnCommit(model);
+		return model;
+	}
+
+	/**
+	 * Creates a model with extended prefixes and initialises it with statements
+	 * parsed from a string.
+	 * 
+	 * does all insertions in a transaction.
+	 * 
+	 * @param facts
+	 *            a string in semicolon-separated "S P O" format
+	 * @return a model containing those facts
+	 */
+	public static Model modelWithStatements(
+			IProducer<? extends Model> producer, String facts) {
+		Model m = createModel(producer);
+		txnBegin(m);
+		modelAdd(m, facts);
+		txnCommit(m);
+		return m;
+	}
+
+	/**
+	 * Creates a model with extended prefixes and initialises it with statements
+	 * parsed from the statement iterator.
+	 * 
+	 * does all insertions in a transaction.
+	 * 
+	 * @param facts
+	 *            a string in semicolon-separated "S P O" format
+	 * @return a model containing those facts
+	 */
+	public static Model modelWithStatements(
+			IProducer<? extends Model> producer, final StmtIterator it) {
+		Model m = createModel(producer);
+		txnBegin(m);
+		while (it.hasNext()) {
+			m.add(it.nextStatement());
+		}
+		txnCommit(m);
+		return m;
+	}
+
+	/**
+	 * make a model and give it Extended prefixes
+	 */
+	public static Model createModel(IProducer<? extends Model> producer) {
+		Model result = producer.newInstance();
+		result.setNsPrefixes(PrefixMapping.Extended);
+		return result;
+	}
+
+	/**
+	 * test that two models are isomorphic and fail if they are not.
+	 * 
+	 * @param title
+	 *            a String appearing at the beginning of the failure message
+	 * @param wanted
+	 *            the model value that is expected
+	 * @param got
+	 *            the model value to check
+	 * @exception if
+	 *                the models are not isomorphic
+	 */
+	public static void assertIsoModels(String title, Model wanted, Model got) {
+		if (wanted.isIsomorphicWith(got) == false) {
+			Map<Node, Object> map = CollectionFactory.createHashedMap();
+			fail(title + ": expected " + nice(wanted.getGraph(), map)
+					+ "\n but had " + nice(got.getGraph(), map));
+		}
+	}
+
+	public static void assertContainsAll(final Model model, final Model model2) {
+		for (final StmtIterator s = model2.listStatements(); s.hasNext();) {
+			Assert.assertTrue(model.contains(s.nextStatement()));
+		}
+	}
+
+	public static void assertSameStatements(final Model model,
+			final Model model2) {
+		assertContainsAll(model, model2);
+		assertContainsAll(model2, model);
+	}
+
+	public static Property prop(final String uri) {
+		return ResourceFactory.createProperty("eh:/" + uri);
+	}
+
+	public static Resource res(final String uri) {
+		return ResourceFactory.createResource("eh:/" + uri);
+	}
+
+	/**
+	 * Fail if the two models are not isomorphic. See
+	 * assertIsoModels(String,Model,Model).
+	 */
+	public static void assertIsoModels(Model wanted, Model got) {
+		assertIsoModels("models must be isomorphic", wanted, got);
+	}
+
+	public static final boolean tvBoolean = true;
+	public static final byte tvByte = 1;
+	public static final short tvShort = 2;
+	public static final int tvInt = -1;
+	public static final long tvLong = -2;
+	public static final char tvChar = '!';
+	public static final float tvFloat = (float) 123.456;
+	public static final double tvDouble = -123.456;
+	public static final String tvString = "test 12 string";
+	public static final double dDelta = 0.000000005;
+
+	public static final float fDelta = 0.000005f;
+
+	public static final Object tvLitObj = new LitTestObj(1234);
+	public static final LitTestObj tvObject = new LitTestObj(12345);
+
+	public static class LitTestObj {
+		protected long content;
+
+		public LitTestObj(final long l) {
+			content = l;
+		}
+
+		public LitTestObj(final String s) {
+			content = Long.parseLong(s.substring(1, s.length() - 1));
+		}
+
+		@Override
+		public boolean equals(final Object o) {
+			return (o instanceof LitTestObj)
+					&& (content == ((LitTestObj) o).content);
+		}
+
+		@Override
+		public int hashCode() {
+			return (int) (content ^ (content >> 32));
+		}
+
+		@Override
+		public String toString() {
+			return "[" + Long.toString(content) + "]";
+		}
+
+		public long getContent() {
+			return content;
+		}
+	}
+
+	/**
+	 * Begin a transaction on the model if transactions are supported.
+	 * 
+	 * @param m
+	 */
+	public static Model txnBegin(Model m) {
+		if (m.supportsTransactions()) {
+			return m.begin();
+		}
+		return m;
+	}
+
+	/**
+	 * Commit the transaction on the model if transactions are supported.
+	 * 
+	 * @param m
+	 */
+	public static Model txnCommit(Model m) {
+		if (m.supportsTransactions()) {
+			return m.commit();
+		}
+		return m;
+	}
+
+	/**
+	 * Rollback (abort) the transaction on the model if transactions are
+	 * supported.
+	 * 
+	 * @param m
+	 */
+	public static Model txnRollback(Model m) {
+		if (m.supportsTransactions()) {
+			return m.abort();
+		}
+		return m;
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/NodeCreateUtils.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/NodeCreateUtils.java b/jena-core/src/test/java/org/apache/jena/testing_framework/NodeCreateUtils.java
new file mode 100644
index 0000000..83d42eb
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/NodeCreateUtils.java
@@ -0,0 +1,177 @@
+/*
+ * 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;
+
+import java.util.StringTokenizer;
+
+import org.apache.jena.datatypes.xsd.XSDDatatype;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.graph.impl.LiteralLabel;
+import org.apache.jena.graph.impl.LiteralLabelFactory;
+import org.apache.jena.rdf.model.AnonId;
+import org.apache.jena.shared.*;
+
+/**
+ * Creating nodes from string specifications.
+ */
+public class NodeCreateUtils {
+	/**
+	 * Returns a Node described by the string, primarily for testing purposes.
+	 * The string represents a URI, a numeric literal, a string literal, a bnode
+	 * label, or a variable.
+	 * <ul>
+	 * <li>'some text' :: a string literal with that text
+	 * <li>'some text'someLanguage:: a string literal with that text and
+	 * language
+	 * <li>'some text'someURI:: a typed literal with that text and datatype
+	 * <li>digits :: a literal [OF WHAT TYPE] with that [numeric] value
+	 * <li>_XXX :: a bnode with an AnonId built from _XXX
+	 * <li>?VVV :: a variable with name VVV
+	 * <li>&PPP :: to be done
+	 * <li>name:stuff :: the URI; name may be expanded using the Extended map
+	 * </ul>
+	 * 
+	 * @param x
+	 *            the string describing the node
+	 * @return a node of the appropriate type with the appropriate label
+	 */
+	public static Node create(String x) {
+		return create(PrefixMapping.Extended, x);
+	}
+
+	/**
+	 * Returns a Node described by the string, primarily for testing purposes.
+	 * The string represents a URI, a numeric literal, a string literal, a bnode
+	 * label, or a variable.
+	 * <ul>
+	 * <li>'some text' :: a string literal with that text
+	 * <li>'some text'someLanguage:: a string literal with that text and
+	 * language
+	 * <li>'some text'someURI:: a typed literal with that text and datatype
+	 * <li>digits :: a literal [OF WHAT TYPE] with that [numeric] value
+	 * <li>_XXX :: a bnode with an AnonId built from _XXX
+	 * <li>?VVV :: a variable with name VVV
+	 * <li>&PPP :: to be done
+	 * <li>name:stuff :: the URI; name may be expanded using the Extended map
+	 * </ul>
+	 * 
+	 * @param pm
+	 *            the PrefixMapping for translating pre:X strings
+	 * @param x
+	 *            the string encoding the node to create
+	 * @return a node with the appropriate type and label
+	 */
+	public static Node create(PrefixMapping pm, String x) {
+		if (x.equals(""))
+			throw new JenaException(
+					"Node.create does not accept an empty string as argument");
+		char first = x.charAt(0);
+		if (first == '\'' || first == '\"')
+			return NodeFactory.createLiteral(newString(pm, first, x));
+		if (Character.isDigit(first))
+			return NodeFactory.createLiteral(x, "", XSDDatatype.XSDinteger);
+		if (first == '_')
+			return NodeFactory.createAnon(new AnonId(x));
+		if (x.equals("??"))
+			return Node.ANY;
+		if (first == '?')
+			return NodeFactory.createVariable(x.substring(1));
+		if (first == '&')
+			return NodeFactory.createURI("q:" + x.substring(1));
+		int colon = x.indexOf(':');
+		String d = pm.getNsPrefixURI("");
+		return colon < 0 ? NodeFactory.createURI((d == null ? "eh:/" : d) + x)
+				: NodeFactory.createURI(pm.expandPrefix(x));
+	}
+
+	public static String unEscape(String spelling) {
+		if (spelling.indexOf('\\') < 0)
+			return spelling;
+		StringBuffer result = new StringBuffer(spelling.length());
+		int start = 0;
+		while (true) {
+			int b = spelling.indexOf('\\', start);
+			if (b < 0)
+				break;
+			result.append(spelling.substring(start, b));
+			result.append(unEscape(spelling.charAt(b + 1)));
+			start = b + 2;
+		}
+		result.append(spelling.substring(start));
+		return result.toString();
+	}
+
+	public static char unEscape(char ch) {
+		switch (ch) {
+		case '\\':
+		case '\"':
+		case '\'':
+			return ch;
+		case 'n':
+			return '\n';
+		case 's':
+			return ' ';
+		case 't':
+			return '\t';
+		default:
+			return 'Z';
+		}
+	}
+
+	public static LiteralLabel literal(PrefixMapping pm, String spelling,
+			String langOrType) {
+		String content = unEscape(spelling);
+		int colon = langOrType.indexOf(':');
+		return colon < 0 ? LiteralLabelFactory.create(content, langOrType,
+				false) : LiteralLabelFactory.createLiteralLabel(content, "",
+				NodeFactory.getType(pm.expandPrefix(langOrType)));
+	}
+
+	public static LiteralLabel newString(PrefixMapping pm, char quote,
+			String nodeString) {
+		int close = nodeString.lastIndexOf(quote);
+		return literal(pm, nodeString.substring(1, close),
+				nodeString.substring(close + 1));
+	}
+
+	/**
+	 * Utility factory as for create(String), but allowing the PrefixMapping to
+	 * be specified explicitly.
+	 */
+	public static Triple createTriple(PrefixMapping pm, String fact) {
+		StringTokenizer st = new StringTokenizer(fact);
+		Node sub = create(pm, st.nextToken());
+		Node pred = create(pm, st.nextToken());
+		Node obj = create(pm, st.nextToken());
+		return Triple.create(sub, pred, obj);
+	}
+
+	/**
+	 * Utility factory method for creating a triple based on the content of an
+	 * "S P O" string. The S, P, O are processed by Node.create, see which for
+	 * details of the supported syntax. This method exists to support test code.
+	 * Nodes are interpreted using the Standard prefix mapping.
+	 */
+
+	public static Triple createTriple(String fact) {
+		return createTriple(PrefixMapping.Standard, fact);
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/NodeProducerInterface.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/NodeProducerInterface.java b/jena-core/src/test/java/org/apache/jena/testing_framework/NodeProducerInterface.java
new file mode 100644
index 0000000..c80d3f4
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/NodeProducerInterface.java
@@ -0,0 +1,32 @@
+/*
+    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;
+
+/**
+ * Creates the graph for testing. Implementations must track the creation of
+ * graphs created with newGraph and close them when closeGraphs is called.
+ * 
+ */
+//public interface NodeProducerInterface {
+//
+//	/**
+//	 * Returns a new anonymous RDFNode in an model.
+//	 */
+//	public abstract RDFNode newRDFNode();
+//
+// }

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/TestFileData.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/TestFileData.java b/jena-core/src/test/java/org/apache/jena/testing_framework/TestFileData.java
new file mode 100644
index 0000000..7b46366
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/TestFileData.java
@@ -0,0 +1,380 @@
+package org.apache.jena.testing_framework;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.rdf.model.AnonId;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.ModelFactory;
+import org.apache.jena.graph.Factory;
+
+/**
+ * Class that produces RDF and TTL data, a Graph and a Model that all contain
+ * the same data. This is used for various tests where files are read/written
+ * 
+ */
+public class TestFileData {
+
+	public static final String NS = "uri:urn:x-rdf:test#";
+
+	private static Map<String, String[]> rdfData = new HashMap<String, String[]>();
+	private static Map<String, String[]> ttlData = new HashMap<String, String[]>();
+
+	static {
+		rdfData.put(
+				"", // default set must be equiv to TTL default
+				new String[] {
+						"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
+						"<rdf:RDF",
+						"  xmlns:u=\"uri:\"",
+						"  xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"",
+						String.format("  xmlns:ex=\"%s\">", NS),
+						String.format(
+								"  <rdf:Description rdf:about=\"%ssubject\">",
+								NS),
+						String.format(
+								"    <ex:predicate rdf:resource=\"%sobject\"/>",
+								NS), "  </rdf:Description>",
+						"  <rdf:Description rdf:about=\"uri:e\">",
+						"    <u:p5>verify base works</u:p5>",
+						"  </rdf:Description>", "</rdf:RDF>" });
+		rdfData.put(
+				"realtiveURI", // has relative URI in description rdf:about
+				new String[] {
+						"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
+						"<rdf:RDF",
+						"  xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"",
+						"  xmlns:ex=\"http://example.com/\">",
+						"  <rdf:Description rdf:about=\"http://example.com/subject\">",
+						"    <ex:predicate rdf:resource=\"http://example.com/object\"/>",
+						"  </rdf:Description>",
+						"  <rdf:Description rdf:about=\"e\">",
+						"    <ex:p5>verify base works</ex:p5>",
+						"  </rdf:Description>", "</rdf:RDF>" });
+		rdfData.put(
+				"OntologyList0",
+				new String[] {
+						"<?xml version='1.0' encoding='ISO-8859-1'?>",
+						"<!DOCTYPE rdf:RDF [",
+						"    <!ENTITY rdf   'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>",
+						"    <!ENTITY rdfs  'http://www.w3.org/2000/01/rdf-schema#'>",
+						"]>",
+						"<rdf:RDF",
+						"    xmlns:rdf   =\"&rdf;\"",
+						"    xmlns:rdfs  =\"&rdfs;\"",
+						String.format("    xml:base    =\"%s\"",
+								NS.substring(0, NS.length() - 1)),
+						String.format("    xmlns       =\"%s\"", NS), ">",
+						"<rdf:Description rdf:ID=\"root\">",
+						"   <p rdf:parseType=\"Collection\">", "   </p>",
+						"</rdf:Description>", "</rdf:RDF>" });
+		rdfData.put(
+				"OntologyList1",
+				new String[] {
+						"<?xml version='1.0' encoding='ISO-8859-1'?>",
+						"<!DOCTYPE rdf:RDF [",
+						"    <!ENTITY rdf   'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>",
+						"    <!ENTITY rdfs  'http://www.w3.org/2000/01/rdf-schema#'>",
+						"]>",
+						"<rdf:RDF",
+						"    xmlns:rdf   =\"&rdf;\"",
+						"    xmlns:rdfs  =\"&rdfs;\"",
+						String.format("    xml:base    =\"%s\"",
+								NS.substring(0, NS.length() - 1)),
+						String.format("    xmlns       =\"%s\"", NS), ">",
+						"<rdf:Description rdf:ID=\"root\">",
+						"   <p rdf:parseType=\"Collection\">",
+						"    <rdf:Description rdf:ID=\"a\" />", "   </p>",
+						"</rdf:Description>", "</rdf:RDF>" });
+		rdfData.put(
+				"OntologyList2",
+				new String[] {
+						"<?xml version='1.0' encoding='ISO-8859-1'?>",
+						"<!DOCTYPE rdf:RDF [",
+						"    <!ENTITY rdf   'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>",
+						"    <!ENTITY rdfs  'http://www.w3.org/2000/01/rdf-schema#'>",
+						"]>",
+						"<rdf:RDF",
+						"    xmlns:rdf   =\"&rdf;\"",
+						"    xmlns:rdfs  =\"&rdfs;\"",
+						String.format("    xml:base    =\"%s\"",
+								NS.substring(0, NS.length() - 1)),
+						String.format("    xmlns       =\"%s\"", NS), ">",
+						"<rdf:Description rdf:ID=\"root\">",
+						"   <p rdf:parseType=\"Collection\">",
+						"    <rdf:Description rdf:ID=\"a\" />",
+						"    <rdf:Description rdf:ID=\"b\" />", "   </p>",
+						"</rdf:Description>", "</rdf:RDF>" });
+		rdfData.put(
+				"OntologyList3",
+				new String[] {
+						"<?xml version='1.0' encoding='ISO-8859-1'?>",
+						"<!DOCTYPE rdf:RDF [",
+						"    <!ENTITY rdf   'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>",
+						"    <!ENTITY rdfs  'http://www.w3.org/2000/01/rdf-schema#'>",
+						"]>",
+						"<rdf:RDF",
+						"    xmlns:rdf   =\"&rdf;\"",
+						"    xmlns:rdfs  =\"&rdfs;\"",
+						String.format("    xml:base    =\"%s\"",
+								NS.substring(0, NS.length() - 1)),
+						String.format("    xmlns       =\"%s\"", NS), ">",
+						"<rdf:Description rdf:ID=\"root\">",
+						"   <p rdf:parseType=\"Collection\">",
+						"    <rdf:Description rdf:ID=\"a\" />",
+						"    <rdf:Description rdf:ID=\"b\" />",
+						"    <rdf:Description rdf:ID=\"c\" />", "   </p>",
+						"</rdf:Description>", "</rdf:RDF>" });
+		rdfData.put(
+				"OntologyList4",
+				new String[] {
+						"<?xml version='1.0' encoding='ISO-8859-1'?>",
+						"<!DOCTYPE rdf:RDF [",
+						"    <!ENTITY rdf   'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>",
+						"    <!ENTITY rdfs  'http://www.w3.org/2000/01/rdf-schema#'>",
+						"]>",
+						"<rdf:RDF",
+						"    xmlns:rdf   =\"&rdf;\"",
+						"    xmlns:rdfs  =\"&rdfs;\"",
+						String.format("    xml:base    =\"%s\"",
+								NS.substring(0, NS.length() - 1)),
+						String.format("    xmlns       =\"%s\"", NS), ">",
+						"<rdf:Description rdf:ID=\"root\">",
+						"   <p rdf:parseType=\"Collection\">",
+						"    <rdf:Description rdf:ID=\"a\" />",
+						"    <rdf:Description rdf:ID=\"b\" />",
+						"    <rdf:Description rdf:ID=\"c\" />",
+						"    <rdf:Description rdf:ID=\"d\" />", "   </p>",
+						"</rdf:Description>", "</rdf:RDF>" });
+		rdfData.put(
+				"OntologyList5",
+				new String[] {
+						"<?xml version='1.0' encoding='ISO-8859-1'?>",
+						"<!DOCTYPE rdf:RDF [",
+						"    <!ENTITY rdf   'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>",
+						"    <!ENTITY rdfs  'http://www.w3.org/2000/01/rdf-schema#'>",
+						"]>",
+						"<rdf:RDF",
+						"    xmlns:rdf   =\"&rdf;\"",
+						"    xmlns:rdfs  =\"&rdfs;\"",
+						String.format("    xml:base    =\"%s\"",
+								NS.substring(0, NS.length() - 1)),
+						String.format("    xmlns       =\"%s\"", NS), ">",
+						"<rdf:Description rdf:ID=\"root\">",
+						"   <p rdf:parseType=\"Collection\">",
+						"    <rdf:Description rdf:ID=\"a\" />",
+						"    <rdf:Description rdf:ID=\"b\" />",
+						"    <rdf:Description rdf:ID=\"c\" />",
+						"    <rdf:Description rdf:ID=\"d\" />",
+						"    <rdf:Description rdf:ID=\"e\" />", "   </p>",
+						"</rdf:Description>", "</rdf:RDF>" });
+
+		ttlData.put(
+				"", // default set must be equiv to RDF default and must be
+					// parsable as N-TRIPLE
+				new String[] {
+						String.format("<%ssubject> <%spredicate> <%sobject> .",
+								NS, NS, NS), "<e> <p5> \"verify base works\" ." });
+
+	}
+
+	private static String toDataString(String[] lines) {
+		String eol = System.getProperty("line.separator");
+		StringBuilder sb = new StringBuilder();
+		for (String l : lines) {
+			sb.append(l).append(eol);
+		}
+		return sb.toString();
+	}
+
+	public static Graph getGraph() {
+		
+		Graph g = Factory.createGraphMem();
+
+		g.add(new Triple(NodeFactory.createURI("http://example.com/subject"),
+				NodeFactory.createURI("http://example.com/predicate"),
+				NodeFactory.createURI("http://example.com/object")));
+
+		g.add(new Triple(NodeFactory.createAnon(AnonId.create("a")),
+				NodeFactory.createURI("http://example.com/p1"), NodeFactory
+						.createAnon(AnonId.create("b"))));
+
+		g.add(new Triple(NodeFactory.createAnon(AnonId.create("b")),
+				NodeFactory.createURI("http://example.com/p2"), NodeFactory
+						.createLiteral("foo")));
+
+		g.add(new Triple(NodeFactory.createURI("http://example.com/ns/e"),
+				NodeFactory.createURI("http://example.com/ns/p5"), NodeFactory
+						.createLiteral("verify base works")));
+
+		return g;
+	}
+
+	public static Model getModel() {
+		return ModelFactory.createModelForGraph(getGraph());
+	}
+
+	public static Model populateRDFModel(Model model, String name)
+			throws IOException {
+		ModelHelper.txnBegin(model);
+		model.read(getRDFInput(name), "http://example.com/test/");
+		ModelHelper.txnCommit(model);
+		return model;
+	}
+
+	public static Model getRDFModel(String name) throws IOException {
+		return populateRDFModel(ModelFactory.createDefaultModel(), name);
+	}
+
+	private static String[] getRDFData(String name) throws IOException {
+		String[] data = rdfData.get(name);
+		if (data == null) {
+			throw new IOException("Can not find RDF data " + name);
+		}
+		return data;
+	}
+
+	public static InputStream getRDFInput(String name) throws IOException {
+		return new ByteArrayInputStream(toDataString(getRDFData(name))
+				.getBytes());
+	}
+
+	public static InputStream getRDFInput() throws IOException {
+		return getRDFInput("");
+	}
+
+	private static String[] getTTLData(String name) throws IOException {
+		String[] data = ttlData.get(name);
+		if (data == null) {
+			throw new IOException("Can not find TTL data " + name);
+		}
+		return data;
+	}
+
+	public static InputStream getTTLInput(String name) throws IOException {
+		return new ByteArrayInputStream(toDataString(getTTLData(name))
+				.getBytes());
+	}
+
+	public static InputStream getTTLInput() throws IOException {
+		return getTTLInput("");
+	}
+
+	public static Reader getRDFReader(String name) throws IOException {
+
+		return new StringReader(toDataString(getRDFData(name)));
+	}
+
+	public static Reader getRDFReader() throws IOException {
+
+		return getRDFReader("");
+	}
+
+	public static Reader getTTLReader() throws IOException {
+		return getTTLReader("");
+	}
+
+	public static Reader getTTLReader(String name) throws IOException {
+		return new StringReader(toDataString(getTTLData(name)));
+	}
+
+	public static String getRDFName(String name) throws IOException {
+		return createFile(toDataString(getRDFData(name)), ".rdf");
+	}
+
+	public static String getRDFName() throws IOException {
+		return getRDFName("");
+	}
+
+	private static String createFile(String data, String extension)
+			throws IOException {
+		File f = File.createTempFile("tfd", extension);
+		f.deleteOnExit();
+		FileOutputStream fos = new FileOutputStream(f);
+		// fos.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes());
+		// fos.write(System.getProperty("line.separator").getBytes());
+		fos.write(data.getBytes());
+		fos.close();
+		return f.toURI().toURL().toExternalForm();
+	}
+
+	public static String getTTLName() throws IOException {
+		return getTTLName("");
+	}
+
+	public static String getTTLName(String name) throws IOException {
+		return createFile(toDataString(getTTLData(name)), ".ttl");
+	}
+
+	@Test
+	public void testEquality() throws Exception {
+		Model ttl = ModelFactory.createDefaultModel().read(getTTLInput(), NS,
+				"TTL");
+		Model rdf = ModelFactory.createDefaultModel().read(getRDFInput(), NS,
+				"RDF/XML-ABBREV");
+
+		assertTrue(ttl.isIsomorphicWith(rdf));
+		assertTrue(rdf.isIsomorphicWith(ttl));
+	}
+
+	public static void main(String... argv) throws Exception {
+		// //Model model = ModelFactory.createDefaultModel() ;
+		// //String x = "<s> <p> 'verify it works' ." ;
+		//
+		//
+		// //Reader sr = getTTLReader();
+		// //model.read(sr, "http://example/", "TTL") ;
+		// //model.read(sr, "", "TTL") ;
+		// //model.read( getRDFInput() );
+		// Model ttl = ModelFactory.createDefaultModel().read( getTTLInput(),
+		// "", "TTL");
+		// Model rdf = ModelFactory.createDefaultModel().read( getRDFInput(),
+		// "", "RDF/XML-ABBREV");
+		//
+		// ttl.write(System.out, "RDF/XML-ABBREV") ;
+		// System.out.println("-----") ;
+		// // model.setNsPrefix("ex", "http://example/") ;
+		// rdf.write(System.out, "N-TRIPLES") ;
+		// System.out.println("-----") ;
+		// System.out.println( getTTLName() );
+		// System.out.println( "ttl iso rdf: "+ttl.isIsomorphicWith(rdf));
+		//
+		// System.out.println( getRDFName() );
+		// System.out.println( "rdf iso ttl: "+rdf.isIsomorphicWith(ttl));
+
+		String[] lines = { "<rdf:RDF",
+				"  xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">",
+				"  <rdf:Description rdf:about=\"e\">",
+				"    <p5>verify base works</p5>", "  </rdf:Description>",
+				"</rdf:RDF>" };
+
+		String eol = System.getProperty("line.separator");
+		StringBuilder sb = new StringBuilder();
+		for (String l : lines) {
+			sb.append(l).append(eol);
+		}
+
+		Model model = ModelFactory.createDefaultModel();
+
+		StringReader sr = new StringReader(sb.toString());
+		model.read(sr, "http://example/");
+		model.write(System.out, "N-TRIPLES");
+		System.out.println("-----");
+		model.setNsPrefix("ex", "http://example/");
+		model.write(System.out, "RDF/XML-ABBREV", "http://another/");
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/TestUtils.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/TestUtils.java b/jena-core/src/test/java/org/apache/jena/testing_framework/TestUtils.java
new file mode 100644
index 0000000..78d4bab
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/TestUtils.java
@@ -0,0 +1,320 @@
+/*
+ * 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;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.*;
+
+import org.apache.jena.util.CollectionFactory;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.WrappedIterator;
+
+/**
+ * Foo basis for Jena test cases which provides assertFalse and assertDiffer.
+ * Often the logic of the names is clearer than using a negation.
+ */
+public class TestUtils {
+	// do not instantiate
+	protected TestUtils() {
+		throw new UnsupportedOperationException("Do not instantiate TestUtils");
+	};
+
+	/**
+	 * assert that the two objects must be unequal according to .equals().
+	 * 
+	 * @param title
+	 *            a labelling string for the assertion failure text
+	 * @param x
+	 *            an object to test; the subject of a .equals()
+	 * @param y
+	 *            the other object; the argument of the .equals()
+	 */
+	public static void assertDiffer(String title, Object x, Object y) {
+		if (x == null ? y == null : x.equals(y))
+			fail((title == null ? "objects should be different, but both were: "
+					: title)
+					+ x);
+	}
+
+	/**
+	 * assert that the two objects must be unequal according to .equals().
+	 * 
+	 * @param x
+	 *            an object to test; the subject of a .equals()
+	 * @param y
+	 *            the other object; the argument of the .equals()
+	 */
+	public static void assertDiffer(Object x, Object y) {
+		assertDiffer(null, x, y);
+	}
+
+	/**
+	 * assert that the object <code>x</code> must be of the class
+	 * <code>expected</code>.
+	 */
+	public static void assertInstanceOf(Class<?> expected, Object x) {
+		if (x == null)
+			fail("expected instance of " + expected + ", but had null");
+		if (!expected.isInstance(x))
+			fail("expected instance of " + expected + ", but had instance of "
+					+ x.getClass());
+	}
+
+	/**
+	 * Answer a Set formed from the elements of the List <code>L</code>.
+	 */
+	public static <T> Set<T> listToSet(List<T> L) {
+		return CollectionFactory.createHashedSet(L);
+	}
+
+	/**
+	 * Answer a List of the substrings of <code>s</code> that are separated by
+	 * spaces.
+	 */
+	public static List<String> listOfStrings(String s) {
+		List<String> result = new ArrayList<String>();
+		StringTokenizer st = new StringTokenizer(s);
+		while (st.hasMoreTokens())
+			result.add(st.nextToken());
+		return result;
+	}
+
+	/**
+	 * Answer a Set of the substrings of <code>s</code> that are separated by
+	 * spaces.
+	 */
+	public static Set<String> setOfStrings(String s) {
+		Set<String> result = new HashSet<String>();
+		StringTokenizer st = new StringTokenizer(s);
+		while (st.hasMoreTokens())
+			result.add(st.nextToken());
+		return result;
+	}
+
+	/**
+	 * Answer a list containing the single object <code>x</code>.
+	 */
+	public static <T> List<T> listOfOne(T x) {
+		List<T> result = new ArrayList<T>();
+		result.add(x);
+		return result;
+	}
+
+	/**
+	 * Answer a Set containing the single object <code>x</code>.
+	 */
+	public static <T> Set<T> setOfOne(T x) {
+		Set<T> result = new HashSet<T>();
+		result.add(x);
+		return result;
+	}
+
+	/**
+	 * Answer a fresh list which is the concatenation of <code>L</code> then
+	 * <code>R</code>. Neither <code>L</code> nor <code>R</code> is updated.
+	 */
+	public static <T> List<T> append(List<? extends T> L, List<? extends T> R) {
+		List<T> result = new ArrayList<T>(L);
+		result.addAll(R);
+		return result;
+	}
+
+	/**
+	 * Answer an iterator over the space-separated substrings of <code>s</code>.
+	 */
+	protected static ExtendedIterator<String> iteratorOfStrings(String s) {
+		return WrappedIterator.create(listOfStrings(s).iterator());
+	}
+
+	/**
+	 * Answer the constructor of the class <code>c</code> which takes arguments
+	 * of the type(s) in <code>args</code>, or <code>null</code> if there isn't
+	 * one.
+	 */
+	public static Constructor<?> getConstructor(Class<?> c, Class<?>[] args) {
+		try {
+			return c.getConstructor(args);
+		} catch (NoSuchMethodException e) {
+			return null;
+		}
+	}
+
+	/**
+	 * Answer true iff <code>subClass</code> is the same class as
+	 * <code>superClass</code>, if its superclass <i>is</i>
+	 * <code>superClass</code>, or if one of its interfaces hasAsInterface that
+	 * class.
+	 */
+	public static boolean hasAsParent(Class<?> subClass, Class<?> superClass) {
+		if (subClass == superClass || subClass.getSuperclass() == superClass)
+			return true;
+		Class<?>[] is = subClass.getInterfaces();
+		for (int i = 0; i < is.length; i += 1)
+			if (hasAsParent(is[i], superClass))
+				return true;
+		return false;
+	}
+
+	/**
+	 * Fail unless <code>subClass</code> has <code>superClass</code> as a
+	 * parent, either a superclass or an implemented (directly or not)
+	 * interface.
+	 */
+	public static void assertHasParent(Class<?> subClass, Class<?> superClass) {
+		if (hasAsParent(subClass, superClass) == false)
+			fail("" + subClass + " should have " + superClass + " as a parent");
+	}
+
+	/**
+	 * Tests o1.equals( o2 ) && o2.equals(o1) && o1.hashCode() == o2.hashCode()
+	 * 
+	 * @param o1
+	 * @param o2
+	 */
+	public static void assertEquivalent(Object o1, Object o2) {
+		assertEquals(o1, o2);
+		assertEquals(o2, o1);
+		assertEquals(o1.hashCode(), o2.hashCode());
+	}
+
+	/**
+	 * Tests o1.equals( o2 ) && o2.equals(o1) && o1.hashCode() == o2.hashCode()
+	 * 
+	 * @param o1
+	 * @param o2
+	 */
+	public static void assertEquivalent(String msg, Object o1, Object o2) {
+		assertEquals(msg, o1, o2);
+		assertEquals(msg, o2, o1);
+		assertEquals(msg, o1.hashCode(), o2.hashCode());
+	}
+
+	/**
+	 * Tests o1.equals( o2 ) && o2.equals(o1) && o1.hashCode() == o2.hashCode()
+	 * 
+	 * @param o1
+	 * @param o2
+	 */
+	public static void assertNotEquivalent(String msg, Object o1, Object o2) {
+		assertNotEquals(msg, o1, o2);
+		assertNotEquals(msg, o2, o1);
+	}
+
+	private static URL getURL(String fn) {
+		URL u = TestUtils.class.getClassLoader().getResource(fn);
+		if (u == null) {
+			throw new RuntimeException(new FileNotFoundException(fn));
+		}
+		return u;
+	}
+
+	public static String getFileName(String fn) {
+
+		try {
+			return getURL(fn).toURI().toString();
+		} catch (URISyntaxException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static InputStream getInputStream(String fn) throws IOException {
+		return getURL(fn).openStream();
+	}
+
+	// FIXME this is to be removed when testing is complete
+	public static void logAssertEquals(Class<?> clazz, String msg, Object obj1,
+			Object obj2) {
+		if (obj1 == null && obj2 == null) {
+			return;
+		}
+
+		if (obj1 != null) {
+			if (obj1 == obj2) {
+				return;
+			}
+			if (obj1.equals(obj2)) {
+				return;
+			}
+		}
+		LoggerFactory.getLogger(clazz).warn(
+				String.format("%s expected: %s got: %s", msg, obj1, obj2));
+		System.out.println(String.format("[%sWARNING] %s expected: %s got: %s",
+				clazz, msg, obj1, obj2));
+	}
+
+	// FIXME this is to be removed when testing is complete
+	public static void logAssertTrue(Class<?> clazz, String msg, boolean value) {
+		if (value) {
+			return;
+		}
+
+		LoggerFactory.getLogger(clazz).warn(String.format("%s", msg));
+		System.out.println(String.format("[%s WARNING] %s ", clazz, msg));
+	}
+
+	// FIXME this is to be removed when testing is complete
+	public static void logAssertFalse(Class<?> clazz, String msg, boolean value) {
+		if (!value) {
+			return;
+		}
+
+		LoggerFactory.getLogger(clazz).warn(String.format("%s", msg));
+		System.out.println(String.format("[%s WARNING] %s ", clazz, msg));
+	}
+
+	// FIXME this is to be removed when testing is complete
+	public static void logAssertSame(Class<?> clazz, String msg, Object obj1,
+			Object obj2) {
+		if (obj1 == null && obj2 == null) {
+			return;
+		}
+
+		if (obj1 != null) {
+			if (obj1 == obj2) {
+				return;
+			}
+		}
+		LoggerFactory.getLogger(clazz).warn(
+				String.format("%s expected: %s got: %s", msg, obj1, obj2));
+		System.out
+				.println(String.format("[%s WARNING] %s expected: %s got: %s",
+						clazz, msg, obj1, obj2));
+	}
+
+	public static String safeName(String s) {
+		// Safe from Eclipse
+		s = s.replace('(', '[');
+		s = s.replace(')', ']');
+		return s;
+
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/Manifest.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/Manifest.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/Manifest.java
new file mode 100644
index 0000000..79d2fce
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/Manifest.java
@@ -0,0 +1,228 @@
+/*
+ * 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.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.jena.n3.IRIResolver;
+import org.apache.jena.rdf.model.*;
+import org.apache.jena.util.FileManager;
+import org.apache.jena.vocabulary.RDF;
+import org.apache.jena.vocabulary.RDFS;
+import org.apache.jena.vocabulary.TestManifest;
+import org.apache.jena.vocabulary.TestManifestX;
+
+/**
+ * A test manifest for a single manifest file.
+ */
+
+public class Manifest {
+	// This class does not know about JUnit.
+	private static Logger log = LoggerFactory.getLogger(Manifest.class);
+	private Model manifest;
+	private String manifestName;
+	private String filename;
+	private List<String> includedFiles = new ArrayList<String>();
+	private Resource manifestRes = null;
+
+	public Manifest(String fn) {
+		log.debug("Manifest = " + fn);
+		filename = IRIResolver.resolveGlobal(fn);
+		log.debug("         = " + filename);
+		manifest = FileManager.get().loadModel(filename);
+		parseIncludes();
+		parseManifest();
+	}
+
+	public String getName() {
+		return manifestName;
+	}
+
+	public Iterator<String> includedManifests() {
+		return includedFiles.iterator();
+	}
+
+	private void parseManifest() {
+		StmtIterator manifestStmts = manifest.listStatements(null, RDF.type,
+				TestManifest.Manifest);
+		if (!manifestStmts.hasNext()) {
+			log.warn("No manifest in manifest file: " + filename);
+			return;
+		}
+
+		Statement manifestItemStmt = manifestStmts.nextStatement();
+		if (manifestStmts.hasNext()) {
+			log.warn("Multiple manifests in manifest file: " + filename);
+			return;
+		}
+
+		manifestRes = manifestItemStmt.getSubject();
+		manifestName = getLiteral(manifestRes, RDFS.label);
+		if (manifestName == null)
+			manifestName = getLiteral(manifestRes, RDFS.comment);
+		manifestStmts.close();
+	}
+
+	// For every test item (does not recurse)
+	public void apply(ManifestItemHandler gen) {
+
+		StmtIterator manifestStmts = manifest.listStatements(null, RDF.type,
+				TestManifest.Manifest);
+
+		for (; manifestStmts.hasNext();) {
+			Statement manifestItemStmt = manifestStmts.nextStatement();
+			Resource manifestRes = manifestItemStmt.getSubject();
+
+			// For each item in this manifest
+			StmtIterator listIter = manifestRes
+					.listProperties(TestManifest.entries);
+			for (; listIter.hasNext();) {
+				// List head
+				Resource listItem = listIter.nextStatement().getResource();
+				for (; !listItem.equals(RDF.nil);) {
+					ManifestItem item = new ManifestItem(listItem
+							.getRequiredProperty(RDF.first).getResource());
+					gen.processManifestItem(item);
+					// Move to next list item
+					listItem = listItem.getRequiredProperty(RDF.rest)
+							.getResource();
+				}
+			}
+			listIter.close();
+		}
+		manifestStmts.close();
+	}
+
+	// -------- included manifests
+	private void parseIncludes() {
+		parseIncludes(TestManifest.include);
+		parseIncludes(TestManifestX.include);
+	}
+
+	private void parseIncludes(Property property) {
+		StmtIterator includeStmts = manifest.listStatements(null, property,
+				(RDFNode) null);
+
+		for (; includeStmts.hasNext();) {
+			Statement s = includeStmts.nextStatement();
+			if (!(s.getObject() instanceof Resource)) {
+				log.warn("Include: not a Resource" + s);
+				continue;
+			}
+			Resource r = s.getResource();
+			parseOneIncludesList(r);
+		}
+		includeStmts.close();
+	}
+
+	private void parseOneIncludesList(Resource r) {
+		if (r == null)
+			return;
+
+		if (r.equals(RDF.nil))
+			return;
+
+		if (!r.isAnon()) {
+			String uri = r.getURI();
+			if (includedFiles.contains(uri))
+				return;
+			includedFiles.add(r.getURI());
+			return;
+		}
+
+		// BNnode => list
+		Resource listItem = r;
+		while (!listItem.equals(RDF.nil)) {
+			r = listItem.getRequiredProperty(RDF.first).getResource();
+			parseOneIncludesList(r);
+			// Move on
+			listItem = listItem.getRequiredProperty(RDF.rest).getResource();
+		}
+	}
+
+	public static Resource getResource(Resource r, Property p) {
+		if (r == null)
+			return null;
+		if (!r.hasProperty(p))
+			return null;
+
+		RDFNode n = r.getProperty(p).getObject();
+		if (n instanceof Resource)
+			return (Resource) n;
+
+		throw new ManifestException("Manifest problem (not a Resource): " + n
+				+ " => " + p);
+	}
+
+	public static Collection<Resource> listResources(Resource r, Property p) {
+		if (r == null)
+			return null;
+		List<Resource> x = new ArrayList<Resource>();
+		StmtIterator sIter = r.listProperties(p);
+		for (; sIter.hasNext();) {
+			RDFNode n = sIter.next().getObject();
+			if (!(n instanceof Resource))
+				throw new ManifestException(
+						"Manifest problem (not a Resource): " + n + " => " + p);
+			x.add((Resource) n);
+		}
+		return x;
+	}
+
+	public static String getLiteral(Resource r, Property p) {
+		if (r == null)
+			return null;
+		if (!r.hasProperty(p))
+			return null;
+
+		RDFNode n = r.getProperty(p).getObject();
+		if (n instanceof Literal)
+			return ((Literal) n).getLexicalForm();
+
+		throw new ManifestException("Manifest problem (not a Literal): " + n
+				+ " => " + p);
+	}
+
+	public static String getLiteralOrURI(Resource r, Property p) {
+		if (r == null)
+			return null;
+
+		if (!r.hasProperty(p))
+			return null;
+
+		RDFNode n = r.getProperty(p).getObject();
+		if (n instanceof Literal)
+			return ((Literal) n).getLexicalForm();
+
+		if (n instanceof Resource) {
+			Resource r2 = (Resource) n;
+			if (!r2.isAnon())
+				return r2.getURI();
+		}
+
+		throw new ManifestException("Manifest problem: " + n + " => " + p);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestException.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestException.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestException.java
new file mode 100644
index 0000000..14a3d44
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestException.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+/**
+ * TestException a root exception for all (intentional) exceptions in tests
+ * setup, not a failure of the test itself (e.g. manifest problems)
+ */
+
+public class ManifestException extends RuntimeException {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 5601201233175898449L;
+
+	public ManifestException() {
+		super();
+	}
+
+	public ManifestException(Throwable cause) {
+		super(cause);
+	}
+
+	public ManifestException(String msg) {
+		super(msg);
+	}
+
+	public ManifestException(String msg, Throwable cause) {
+		super(msg, cause);
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestFile.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestFile.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestFile.java
new file mode 100644
index 0000000..d7493bb
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestFile.java
@@ -0,0 +1,38 @@
+/*
+ * 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.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to declare the manifest file for the test.
+ * 
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ManifestFile {
+	/**
+	 * The file that the annotated class should read
+	 */
+	String value();
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItem.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItem.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItem.java
new file mode 100644
index 0000000..81999bd
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItem.java
@@ -0,0 +1,53 @@
+package org.apache.jena.testing_framework.manifest;
+
+import org.apache.jena.n3.turtle.TurtleTestVocab;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.vocabulary.RDF;
+import org.apache.jena.vocabulary.TestManifest;
+
+public class ManifestItem {
+	private Resource entry;
+
+	public ManifestItem(Resource entry) {
+		this.entry = entry;
+	}
+
+	public Resource getEntry() {
+		return entry;
+	}
+
+	public String getTestName() {
+		return Manifest.getLiteral(entry, TestManifest.name);
+	}
+
+	public Resource getAction() {
+		return Manifest.getResource(entry, TestManifest.action);
+	}
+
+	public Resource getResult() {
+		return Manifest.getResource(entry, TestManifest.result);
+	}
+
+	public Resource getType() {
+		return Manifest.getResource(entry, RDF.type);
+	}
+
+	public Resource getOutput() {
+		Resource result = getResult();
+		return result == null ? null : Manifest.getResource(result,
+				TurtleTestVocab.output);
+	}
+
+	public Resource getInput() {
+		Resource action = getAction();
+		return action == null ? null : Manifest.getResource(action,
+				TurtleTestVocab.input);
+	}
+
+	public String getUriString() {
+		Resource action = getAction();
+		Resource inputIRIr = action == null ? null : Manifest.getResource(
+				action, TurtleTestVocab.inputIRI);
+		return (inputIRIr == null) ? null : inputIRIr.getURI();
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItemHandler.java
----------------------------------------------------------------------
diff --git a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItemHandler.java b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItemHandler.java
new file mode 100644
index 0000000..08ce69f
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestItemHandler.java
@@ -0,0 +1,35 @@
+/*
+ * 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.List;
+
+import org.junit.runner.Runner;
+import org.junit.runners.model.InitializationError;
+
+public interface ManifestItemHandler {
+	/**
+	 * Handle an item in a manifest
+	 * 
+	 * @throws InitializationError
+	 */
+	public void processManifestItem(ManifestItem item);
+
+	public void setTestRunnerList(List<Runner> runners);
+}