001: /******************************************************************
002: * File: QueryTester.java
003: * Created by: Dave Reynolds
004: * Created on: 19-Jan-03
005: *
006: * (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
007: * [See end of file]
008: * $Id: ReasonerTester.java,v 1.31 2008/01/02 12:08:31 andy_seaborne Exp $
009: *****************************************************************/package com.hp.hpl.jena.reasoner.test;
010:
011: import com.hp.hpl.jena.rdf.model.*;
012: import com.hp.hpl.jena.graph.*;
013: import com.hp.hpl.jena.rdf.model.impl.*;
014: import com.hp.hpl.jena.reasoner.*;
015: import com.hp.hpl.jena.reasoner.rulesys.Node_RuleVariable;
016: import com.hp.hpl.jena.vocabulary.RDF;
017:
018: import com.hp.hpl.jena.shared.*;
019:
020: import junit.framework.TestCase;
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023:
024: import java.util.*;
025: import java.io.*;
026:
027: /**
028: * A utility for loading a set of test reasoner problems and running defined
029: * sets of listStatement operations and checking the results.
030: * <p>
031: * Each of the source, query and result models are specified in
032: * different files. The files can be of type .rdf, .nt or .n3.</p>
033: * <p>
034: * A single manifest file defines the set of tests to run. Each test
035: * specifies a name, source tbox file, source data file, query file and result file using
036: * the properties "name", "source", "query" and "result" in the namespace
037: * "http://www.hpl.hp.com/semweb/2003/query_tester#". The file names are
038: * given as strings instead of URIs because the base directory for the test
039: * files is subject to change. </p>
040: * <p>
041: * Within the query file each triple is treated as a triple pattern
042: * to be searched for. Variables are indicated by resources in of the
043: * form "var:x".</p>
044: *
045: * @author <a href="mailto:der@hplb.hpl.hp.com">Dave Reynolds</a>
046: * @version $Revision: 1.31 $ on $Date: 2008/01/02 12:08:31 $
047: */
048: public class ReasonerTester {
049:
050: /** The namespace for the test specification schema */
051: public static final String NS = "http://www.hpl.hp.com/semweb/2003/query_tester#";
052:
053: /** The base URI in which the files are purported to reside */
054: public static final String BASE_URI = "http://www.hpl.hp.com/semweb/2003/query_tester/";
055:
056: /** The rdf class to which all tests belong */
057: public static final Resource testClass;
058:
059: /** The predicate defining the description of the test */
060: public static final Property descriptionP;
061:
062: /** The predicate defining the source tbox file for the test */
063: public static final Property tboxP;
064:
065: /** The predicate defining the source data file for the test */
066: public static final Property dataP;
067:
068: /** The predicate defining the query file for the test */
069: public static final Property queryP;
070:
071: /** The predicate defining the result file for the test */
072: public static final Property resultP;
073:
074: /** The base directory in which the test data is stored */
075: public static final String baseDir = "testing/reasoners/";
076:
077: // Static initializer for the predicates
078: static {
079: descriptionP = new PropertyImpl(NS, "description");
080: tboxP = new PropertyImpl(NS, "tbox");
081: dataP = new PropertyImpl(NS, "data");
082: queryP = new PropertyImpl(NS, "query");
083: resultP = new PropertyImpl(NS, "result");
084: testClass = new ResourceImpl(NS, "Test");
085: }
086:
087: /** The rdf defining all the tests to be run */
088: protected Model testManifest;
089:
090: /** A cache of loaded source files, map from source name to Model */
091: protected Map sourceCache = new HashMap();
092:
093: protected static Log logger = LogFactory
094: .getLog(ReasonerTester.class);
095:
096: /**
097: * Constructor.
098: * @param manifest the name of the manifest file defining these
099: * tests - relative to baseDir
100: */
101: public ReasonerTester(String manifest) throws IOException {
102: testManifest = loadFile(manifest, false);
103: }
104:
105: /**
106: * Utility to load a file in rdf/nt/n3 format as a Model.
107: * @param file the file name, relative to baseDir
108: * @param cache set to true if the file could be usefully cached
109: * @return the loaded Model
110: */
111: public Model loadFile(String file, boolean cache)
112: throws IOException {
113: if (cache && sourceCache.keySet().contains(file)) {
114: return (Model) sourceCache.get(file);
115: }
116: String langType = "RDF/XML";
117: if (file.endsWith(".nt")) {
118: langType = "N-TRIPLE";
119: } else if (file.endsWith("n3")) {
120: langType = "N3";
121: }
122: Model result = ModelFactory.createDefaultModel();
123: Reader reader = new BufferedReader(new FileReader(baseDir
124: + file));
125: result.read(reader, BASE_URI + file, langType);
126: if (cache) {
127: sourceCache.put(file, result);
128: }
129: return result;
130: }
131:
132: /**
133: * Load the datafile given by the property name.
134: * @param test the test being processed
135: * @param predicate the property of the test giving the file name to load
136: * @return a graph containing the file contents or an empty graph if the property
137: * is not present
138: * @throws IOException if the property is present but the file can't be found
139: */
140: public Graph loadTestFile(Resource test, Property predicate)
141: throws IOException {
142: if (test.hasProperty(predicate)) {
143: String fileName = test.getRequiredProperty(predicate)
144: .getObject().toString();
145: boolean cache = predicate.equals(tboxP)
146: || predicate.equals(dataP);
147: return loadFile(fileName, cache).getGraph();
148: } else {
149: return Factory.createGraphMem();
150: }
151: }
152:
153: /**
154: * Convert a triple into a triple pattern by converting var resources into
155: * wildcard variables.
156: */
157: public static TriplePattern tripleToPattern(Triple t) {
158: return new TriplePattern(nodeToPattern(t.getSubject()),
159: nodeToPattern(t.getPredicate()), nodeToPattern(t
160: .getObject()));
161: }
162:
163: /**
164: * Convert a node into a pattern node by converting var resources into wildcard
165: * variables.
166: */
167: public static Node nodeToPattern(Node n) {
168: if (n.isURI() && n.toString().startsWith("var:")) {
169: return Node_RuleVariable.WILD;
170: // return Node.ANY;
171: } else {
172: return n;
173: }
174: }
175:
176: /**
177: * Run all the tests in the manifest
178: * @param reasonerF the factory for the reasoner to be tested
179: * @param testcase the JUnit test case which is requesting this test
180: * @param configuration optional configuration information
181: * @return true if all the tests pass
182: * @throws IOException if one of the test files can't be found
183: * @throws RDFException if the test can't be found or fails internally
184: */
185: public boolean runTests(ReasonerFactory reasonerF,
186: TestCase testcase, Resource configuration)
187: throws IOException {
188: for (Iterator i = listTests().iterator(); i.hasNext();) {
189: String test = (String) i.next();
190: if (!runTest(test, reasonerF, testcase, configuration))
191: return false;
192: }
193: return true;
194: }
195:
196: /**
197: * Run all the tests in the manifest
198: * @param reasoner the reasoner to be tested
199: * @param testcase the JUnit test case which is requesting this test
200: * @return true if all the tests pass
201: * @throws IOException if one of the test files can't be found
202: * @throws RDFException if the test can't be found or fails internally
203: */
204: public boolean runTests(Reasoner reasoner, TestCase testcase)
205: throws IOException {
206: for (Iterator i = listTests().iterator(); i.hasNext();) {
207: String test = (String) i.next();
208: if (!runTest(test, reasoner, testcase))
209: return false;
210: }
211: return true;
212: }
213:
214: /**
215: * Return a list of all test names defined in the manifest for this test harness.
216: */
217: public List listTests() {
218: List testList = new ArrayList();
219: ResIterator tests = testManifest.listResourcesWithProperty(
220: RDF.type, testClass);
221: while (tests.hasNext()) {
222: testList.add(tests.next().toString());
223: }
224: return testList;
225: }
226:
227: /**
228: * Run a single designated test.
229: * @param uri the uri of the test, as defined in the manifest file
230: * @param reasonerF the factory for the reasoner to be tested
231: * @param testcase the JUnit test case which is requesting this test
232: * @param configuration optional configuration information
233: * @return true if the test passes
234: * @throws IOException if one of the test files can't be found
235: * @throws RDFException if the test can't be found or fails internally
236: */
237: public boolean runTest(String uri, ReasonerFactory reasonerF,
238: TestCase testcase, Resource configuration)
239: throws IOException {
240: Reasoner reasoner = reasonerF.create(configuration);
241: return runTest(uri, reasoner, testcase);
242: }
243:
244: /**
245: * Run a single designated test.
246: * @param uri the uri of the test, as defined in the manifest file
247: * @param reasoner the reasoner to be tested
248: * @param testcase the JUnit test case which is requesting this test
249: * @return true if the test passes
250: * @throws IOException if one of the test files can't be found
251: * @throws RDFException if the test can't be found or fails internally
252: */
253: public boolean runTest(String uri, Reasoner reasoner,
254: TestCase testcase) throws IOException {
255: // Find the specification for the named test
256: Resource test = testManifest.getResource(uri);
257: if (!test.hasProperty(RDF.type, testClass)) {
258: throw new JenaException("Can't find test: " + uri);
259: }
260:
261: String description = test.getRequiredProperty(descriptionP)
262: .getObject().toString();
263: logger.debug("Reasoner test " + test.getURI() + " - "
264: + description);
265:
266: // Construct the inferred graph
267: Graph tbox = loadTestFile(test, tboxP);
268: Graph data = loadTestFile(test, dataP);
269: InfGraph graph = reasoner.bindSchema(tbox).bind(data);
270:
271: // Run each query triple and accumulate the results
272: Graph queryG = loadTestFile(test, queryP);
273: Graph resultG = Factory.createGraphMem();
274:
275: Iterator queries = queryG.find(null, null, null);
276: while (queries.hasNext()) {
277: TriplePattern query = tripleToPattern((Triple) queries
278: .next());
279: logger.debug("Query: " + query);
280: Iterator answers = graph.find(query.asTripleMatch());
281: while (answers.hasNext()) {
282: Triple ans = (Triple) answers.next();
283: logger.debug("ans: "
284: + TriplePattern.simplePrintString(ans));
285: resultG.add(ans);
286: }
287: }
288:
289: // Check the total result set against the correct answer
290: Graph correctG = loadTestFile(test, resultP);
291: boolean correct = correctG.isIsomorphicWith(resultG);
292: // Used in debugging the tests ...
293: // Can't just leave it as a logger.debug because there are unit tests to which are supposed to given
294: // a test failure which would then problem unwanted output.
295: /*
296: System.out.println("Reasoner test " + test.getURI() + " - " + description);
297: if (!correct) {
298: System.out.println("Missing triples:");
299: for (Iterator i = correctG.find(null, null, null); i.hasNext(); ) {
300: Triple t = (Triple) i.next();
301: if (!resultG.contains(t)) {
302: System.out.println(" " + t);
303: }
304: }
305: System.out.println("Extra triples:");
306: for (Iterator i = resultG.find(null, null, null); i.hasNext(); ) {
307: Triple t = (Triple) i.next();
308: if (!correctG.contains(t)) {
309: System.out.println(" - " + t);
310: }
311: }
312:
313: }
314: */
315: // ... end of debugging hack
316: if (testcase != null) {
317: TestCase.assertTrue(description, correct);
318: }
319: return correct;
320: }
321:
322: }
323:
324: /*
325: (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
326: All rights reserved.
327:
328: Redistribution and use in source and binary forms, with or without
329: modification, are permitted provided that the following conditions
330: are met:
331:
332: 1. Redistributions of source code must retain the above copyright
333: notice, this list of conditions and the following disclaimer.
334:
335: 2. Redistributions in binary form must reproduce the above copyright
336: notice, this list of conditions and the following disclaimer in the
337: documentation and/or other materials provided with the distribution.
338:
339: 3. The name of the author may not be used to endorse or promote products
340: derived from this software without specific prior written permission.
341:
342: THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
343: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
344: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
345: IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
346: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
347: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
348: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
349: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
350: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
351: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
352: */
|