/* * 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.reasoner.test; import java.io.BufferedReader ; import java.io.FileReader ; import java.io.IOException ; import java.io.Reader ; import java.util.* ; import junit.framework.TestCase ; import org.apache.jena.graph.Factory ; import org.apache.jena.graph.Graph ; import org.apache.jena.graph.Node ; import org.apache.jena.graph.Triple ; import org.apache.jena.rdf.model.* ; import org.apache.jena.rdf.model.impl.PropertyImpl ; import org.apache.jena.rdf.model.impl.ResourceImpl ; import org.apache.jena.reasoner.InfGraph ; import org.apache.jena.reasoner.Reasoner ; import org.apache.jena.reasoner.ReasonerFactory ; import org.apache.jena.reasoner.TriplePattern ; import org.apache.jena.reasoner.rulesys.Node_RuleVariable ; import org.apache.jena.shared.JenaException ; import org.apache.jena.vocabulary.RDF ; import org.junit.Assert ; import org.slf4j.Logger ; import org.slf4j.LoggerFactory ; /** * A utility for loading a set of test reasoner problems and running defined * sets of listStatement operations and checking the results. * <p> * Each of the source, query and result models are specified in * different files. The files can be of type .rdf, .nt or .n3.</p> * <p> * A single manifest file defines the set of tests to run. Each test * specifies a name, source tbox file, source data file, query file and result file using * the properties "name", "source", "query" and "result" in the namespace * "http://www.hpl.hp.com/semweb/2003/query_tester#". The file names are * given as strings instead of URIs because the base directory for the test * files is subject to change. </p> * <p> * Within the query file each triple is treated as a triple pattern * to be searched for. Variables are indicated by resources in of the * form "var:x".</p> */ public class ReasonerTester { /** The namespace for the test specification schema */ public static final String NS = "http://www.hpl.hp.com/semweb/2003/query_tester#"; /** The base URI in which the files are purported to reside */ public static final String BASE_URI = "http://www.hpl.hp.com/semweb/2003/query_tester/"; /** The rdf class to which all tests belong */ public static final Resource testClass; /** The predicate defining the description of the test */ public static final Property descriptionP; /** The predicate defining the source tbox file for the test */ public static final Property tboxP; /** The predicate defining the source data file for the test */ public static final Property dataP; /** The predicate defining the query file for the test */ public static final Property queryP; /** The predicate defining the result file for the test */ public static final Property resultP; /** The base directory in which the test data is stored */ public static final String baseDir = "testing/reasoners/"; // Static initializer for the predicates static { descriptionP = new PropertyImpl(NS, "description"); tboxP = new PropertyImpl(NS, "tbox"); dataP = new PropertyImpl(NS, "data"); queryP = new PropertyImpl(NS, "query"); resultP = new PropertyImpl(NS, "result"); testClass = new ResourceImpl(NS, "Test"); } /** The rdf defining all the tests to be run */ protected Model testManifest; /** A cache of loaded source files, map from source name to Model */ protected Map<String, Model> sourceCache = new HashMap<>(); protected static Logger logger = LoggerFactory.getLogger(ReasonerTester.class); /** * Constructor. * @param manifest the name of the manifest file defining these * tests - relative to baseDir */ public ReasonerTester(String manifest) throws IOException { testManifest = loadFile(manifest, false); } /** * Utility to load a file in rdf/nt/n3 format as a Model. * @param file the file name, relative to baseDir * @param cache set to true if the file could be usefully cached * @return the loaded Model */ public Model loadFile(String file, boolean cache) throws IOException { if (cache && sourceCache.keySet().contains(file)) { return sourceCache.get(file); } String langType = "RDF/XML"; if (file.endsWith(".nt")) { langType = "N-TRIPLE"; } else if (file.endsWith("n3")) { langType = "N3"; } Model result = ModelFactory.createDefaultModel(); Reader reader = new BufferedReader(new FileReader(baseDir + file)); result.read(reader, BASE_URI + file, langType); if (cache) { sourceCache.put(file, result); } return result; } /** * Load the datafile given by the property name. * @param test the test being processed * @param predicate the property of the test giving the file name to load * @return a graph containing the file contents or an empty graph if the property * is not present * @throws IOException if the property is present but the file can't be found */ public Graph loadTestFile(Resource test, Property predicate) throws IOException { if (test.hasProperty(predicate)) { String fileName = test.getRequiredProperty(predicate).getObject().toString(); boolean cache = predicate.equals(tboxP) || predicate.equals(dataP); return loadFile(fileName, cache).getGraph(); } else { return Factory.createGraphMem(); } } /** * Convert a triple into a triple pattern by converting var resources into * wildcard variables. */ public static TriplePattern tripleToPattern(Triple t) { return new TriplePattern( nodeToPattern(t.getSubject()), nodeToPattern(t.getPredicate()), nodeToPattern(t.getObject())); } /** * Convert a node into a pattern node by converting var resources into wildcard * variables. */ public static Node nodeToPattern(Node n) { if (n.isURI() && n.toString().startsWith("var:")) { return Node_RuleVariable.WILD; // return Node.ANY; } else { return n; } } /** * Run all the tests in the manifest * @param reasonerF the factory for the reasoner to be tested * @param testcase the JUnit test case which is requesting this test * @param configuration optional configuration information * @return true if all the tests pass * @throws IOException if one of the test files can't be found * @throws JenaException if the test can't be found or fails internally */ public boolean runTests(ReasonerFactory reasonerF, TestCase testcase, Resource configuration) throws IOException { for ( String test : listTests() ) { if ( !runTest( test, reasonerF, testcase, configuration ) ) { return false; } } return true; } /** * Run all the tests in the manifest * @param reasoner the reasoner to be tested * @param testcase the JUnit test case which is requesting this test * @return true if all the tests pass * @throws IOException if one of the test files can't be found * @throws JenaException if the test can't be found or fails internally */ public boolean runTests(Reasoner reasoner, TestCase testcase) throws IOException { for ( String test : listTests() ) { if ( !runTest( test, reasoner, testcase ) ) { return false; } } return true; } /** * Return a list of all test names defined in the manifest for this test harness. */ public List<String> listTests() { List<String> testList = new ArrayList<>(); ResIterator tests = testManifest.listResourcesWithProperty(RDF.type, testClass); while (tests.hasNext()) { testList.add(tests.next().toString()); } return testList; } /** * Run a single designated test. * @param uri the uri of the test, as defined in the manifest file * @param reasonerF the factory for the reasoner to be tested * @param testcase the JUnit test case which is requesting this test * @param configuration optional configuration information * @return true if the test passes * @throws IOException if one of the test files can't be found * @throws JenaException if the test can't be found or fails internally */ public boolean runTest(String uri, ReasonerFactory reasonerF, TestCase testcase, Resource configuration) throws IOException { Reasoner reasoner = reasonerF.create(configuration); return runTest(uri, reasoner, testcase); } /** * Run a single designated test. * @param uri the uri of the test, as defined in the manifest file * @param reasoner the reasoner to be tested * @param testcase the JUnit test case which is requesting this test * @return true if the test passes * @throws IOException if one of the test files can't be found * @throws JenaException if the test can't be found or fails internally */ public boolean runTest(String uri, Reasoner reasoner, TestCase testcase) throws IOException { // Find the specification for the named test Resource test = testManifest.getResource(uri); if (!test.hasProperty(RDF.type, testClass)) { throw new JenaException("Can't find test: " + uri); } String description = test.getRequiredProperty(descriptionP).getObject().toString(); logger.debug("Reasoner test " + test.getURI() + " - " + description); // Construct the inferred graph Graph tbox = loadTestFile(test, tboxP); Graph data = loadTestFile(test, dataP); InfGraph graph = reasoner.bindSchema(tbox).bind(data); // Run each query triple and accumulate the results Graph queryG = loadTestFile(test, queryP); Graph resultG = Factory.createGraphMem(); Iterator<Triple> queries = queryG.find(null, null, null); while (queries.hasNext()) { TriplePattern query = tripleToPattern( queries.next() ); logger.debug("Query: " + query); Iterator<Triple> answers = graph.find(query.asTripleMatch()); while (answers.hasNext()) { Triple ans = answers.next(); logger.debug("ans: " + TriplePattern.simplePrintString(ans)); resultG.add(ans); } } // Check the total result set against the correct answer Graph correctG = loadTestFile(test, resultP); boolean correct = correctG.isIsomorphicWith(resultG); // Used in debugging the tests ... // Can't just leave it as a logger.debug because there are unit tests to which are supposed to given // a test failure which would then problem unwanted output. /* System.out.println("Reasoner test " + test.getURI() + " - " + description); if (!correct) { System.out.println("Missing triples:"); for (Iterator i = correctG.find(null, null, null); i.hasNext(); ) { Triple t = (Triple) i.next(); if (!resultG.contains(t)) { System.out.println(" " + t); } } System.out.println("Extra triples:"); for (Iterator i = resultG.find(null, null, null); i.hasNext(); ) { Triple t = (Triple) i.next(); if (!correctG.contains(t)) { System.out.println(" - " + t); } } } */ // ... end of debugging hack if (testcase != null) { Assert.assertTrue(description, correct); } return correct; } }