/******************************************************************************* * Copyright (c) 2004, 2007-2008 IBM Corporation and Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * File: $Source: /cvsroot/slrp/boca/com.ibm.adtech.boca.test/src/com/ibm/adtech/boca/test/AbstractBocaTest.java,v $ * Created by: Matthew Roy ( <a href="mailto:mroy@us.ibm.com">mroy@us.ibm.com </a>) * Created on: 9/17/2004 * Revision: $Id: AbstractTest.java 171 2007-07-31 14:11:17Z mroy $ * * Contributors: * IBM Corporation - initial API and implementation * Cambridge Semantics Incorporated - Fork to Anzo *******************************************************************************/ package org.openanzo.test; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeMap; import java.util.Map.Entry; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.openanzo.exceptions.AnzoException; import org.openanzo.exceptions.AnzoRuntimeException; import org.openanzo.glitter.query.PatternSolution; import org.openanzo.glitter.query.QueryResults; import org.openanzo.glitter.query.QueryType; import org.openanzo.glitter.query.SolutionSet; import org.openanzo.rdf.Bindable; import org.openanzo.rdf.Constants; import org.openanzo.rdf.Literal; import org.openanzo.rdf.RDFFormat; import org.openanzo.rdf.Statement; import org.openanzo.rdf.URI; import org.openanzo.rdf.Value; import org.openanzo.rdf.adapter.BasicNodeConverter; import org.openanzo.rdf.utils.ReadWriteUtils; import org.openanzo.rdf.utils.SmartEncodingInputStream; import org.openrdf.query.BindingSet; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.TupleQueryResultHandlerException; import org.openrdf.query.dawg.DAWGTestResultSetParseException; import org.openrdf.query.dawg.DAWGTestResultSetUtil; import org.openrdf.query.impl.MutableTupleQueryResult; import org.openrdf.query.impl.TupleQueryResultBuilder; import org.openrdf.query.resultio.BooleanQueryResultParser; import org.openrdf.query.resultio.QueryResultParseException; import org.openrdf.query.resultio.TupleQueryResultParser; import org.openrdf.query.resultio.sparqlxml.SPARQLBooleanXMLParser; import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLParser; import org.openrdf.rio.RDFHandlerException; import org.openrdf.rio.RDFParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Suite of query tests * * @author Lee Feigenbaum ( <a href="mailto:lee@cambridgesemantics.com">lee@cambridgesemantics.com </a>) */ public abstract class QueryTestSuiteBase extends AbstractTest { private static final Logger log = LoggerFactory.getLogger(QueryTestSuiteBase.class); protected static BasicNodeConverter converter = new BasicNodeConverter(); abstract protected QueryResults executeQuery(Set<URI> defaultGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, URI base) throws AnzoException; static protected HashMap<InputStream, Object> cachedStreamResults = new HashMap<InputStream, Object>(); protected void performQueryTest(QueryTest test) throws RDFHandlerException, QueryResultParseException, TupleQueryResultHandlerException, DAWGTestResultSetParseException, IOException, Exception { String query = readAndCacheStream(test.getQuery()); QueryType type = getQueryType(query); try { switch (type) { case SELECT: performSelectQueryTest(test.getBase(), test.getDefaultGraphs(), test.getNamedGraphs(), test.getNamedDatasets(), query, test.hasExpectedException() ? null : loadTupleQueryResult(test.getResults(), test.getResultRDFFormat(), test.getBase()), test.isForceIgnoreOrder(), test.isAllowAnagrams()); break; case CONSTRUCT: performConstructQueryTest(test.getBase(), test.getDefaultGraphs(), test.getNamedGraphs(), test.getNamedDatasets(), query, test.hasExpectedException() ? null : parseAnyRdf(test.getResults(), test.getResultRDFFormat(), test.getBase())); break; case ASK: performAskQueryTest(test.getBase(), test.getDefaultGraphs(), test.getNamedGraphs(), test.getNamedDatasets(), query, test.hasExpectedException() ? null : loadBooleanResult(test.getResults(), test.getResultRDFFormat(), test.getBase())); break; case DESCRIBE: performDescribeQueryTest(test.getBase(), test.getDefaultGraphs(), test.getNamedGraphs(), test.getNamedDatasets(), query, test.hasExpectedException() ? null : parseAnyRdf(test.getResults(), test.getResultRDFFormat(), test.getBase())); break; default: fail("Unknown query type."); } } catch (Exception e) { verifyExpectedException(test, e); } } protected void verifyExpectedException(QueryTest test, Exception caughtException) throws Exception { Exception expectedException = test.getExpectedException(); if (expectedException == null) { // This test isn't expecting an Exception so simply re-throw it so that the test fails throw caughtException; } // Do some verification of the expected errorCode and errorTags if the // expected exception is either an AnzoException or an AnzoRuntimeException. // We treat AnzoException and AnzoRuntimeException as interchangeable since the system // fires different ones depending on whether we are communicating over the network or to // a local query engine. long expectedErrorCode = -1; long expectedErrorTags = -1; long caughtErrorCode = -1; long caughtErrorTags = -1; if (expectedException instanceof AnzoException) { expectedErrorCode = ((AnzoException) expectedException).getErrorCode(); } if (caughtException instanceof AnzoException) { caughtErrorCode = ((AnzoException) caughtException).getErrorCode(); } if (expectedException instanceof AnzoRuntimeException) { expectedErrorCode = ((AnzoRuntimeException) expectedException).getErrorCode(); } if (caughtException instanceof AnzoRuntimeException) { caughtErrorCode = ((AnzoRuntimeException) caughtException).getErrorCode(); } if (expectedException instanceof AnzoException || expectedException instanceof AnzoRuntimeException) { if (caughtException instanceof AnzoException || caughtException instanceof AnzoRuntimeException) { if (expectedErrorCode != caughtErrorCode) { throw new Exception("Query tests's expected AnzoException or AnzoRuntimeException with errorCode '" + expectedErrorCode + "' but got errorCode '" + caughtErrorCode + "'.", caughtException); } if (expectedErrorTags != caughtErrorTags) { throw new Exception("Query tests's expected AnzoException or AnzoRuntimeException with errorTags '" + expectedErrorTags + "' but got errorTags '" + caughtErrorTags + "'.", caughtException); } } else { throw new Exception("Query test expected an AnzoException or AnzoRuntimeException but thrown exception was not an AnzoException or AnzoRuntimeException.", caughtException); } } else { // For the case where the exception isn't an AnzoException or an AnzoRuntimeException, the // best we can do is verify that as least the caught exception is an "instance of" the excepted exception. if (expectedException.getClass().isInstance(caughtException)) { throw new Exception("Query test's expected exception was of type " + expectedException.getClass().getName() + " while the thrown exception was of the incompatible type " + caughtException.getClass().getName(), caughtException); } } } protected String readAndCacheStream(InputStream stream) throws IOException { //System.err.println("** readAndCacheStream - start"); if (!cachedStreamResults.containsKey(stream)) { //System.err.println("** readAndCacheStream - reading from stream"); List<?> lines = IOUtils.readLines(stream); String s = ""; for (Object tmp : lines) s += tmp + "\n"; cachedStreamResults.put(stream, s); //if (s.equals("")) // System.err.println("** readAndCacheStream - read empty string from " + lines.size() + " lines"); } //System.err.println("** readAndCacheStream - end (" + ((String)cachedStreamResults.get(stream)).length() + ")"); return (String) cachedStreamResults.get(stream); } @SuppressWarnings("all") protected QueryType getQueryType(String query) { String lc = query.toLowerCase(); TreeMap<Integer, QueryType> m = new TreeMap<Integer, QueryType>(); m.put(Integer.valueOf(lc.indexOf("select")), QueryType.SELECT); m.put(Integer.valueOf(lc.indexOf("construct")), QueryType.CONSTRUCT); m.put(Integer.valueOf(lc.indexOf("ask")), QueryType.ASK); m.put(Integer.valueOf(lc.indexOf("describe")), QueryType.DESCRIBE); for (Entry<Integer, QueryType> e : m.entrySet()) { int index = e.getKey(); if (index > -1) { // determine if this index is inside a BASE or PREFIX clause. We do this by determining if // a colon immediately follows it or it is inside angle brackets int x; for (x = index + 3; Character.isJavaIdentifierPart(lc.charAt(x)); x++) ; //earliest the colon could be, if we're in ask if (lc.charAt(x) != ':') { int lt = lc.lastIndexOf('<', index); int gt = lc.lastIndexOf('>', index); if (lt == -1 || gt > lt) { // no < before us, or else there's a > after the last < return e.getValue(); } } } } fail("Unknown query type for query: " + query); return null; } @SuppressWarnings("unchecked") // ignore cast to Collection<Statement> since only used for testing protected Collection<Statement> parseAnyRdf(InputStream stream, org.openanzo.rdf.RDFFormat format, String base) throws AnzoException { if (!cachedStreamResults.containsKey(stream)) { cachedStreamResults.put(stream, ReadWriteUtils.loadStatements(SmartEncodingInputStream.createSmartReader(stream), format, base)); } return (Collection<Statement>) cachedStreamResults.get(stream); } @SuppressWarnings("unchecked") // ignore cast to Collection<Statement> since only used for testing protected Collection<org.openrdf.model.Statement> parseAnyRdfOpenRdf(InputStream stream, RDFFormat format, String base) throws AnzoException { if (!cachedStreamResults.containsKey(stream)) { cachedStreamResults.put(stream, ReadWriteUtils.loadStatements(SmartEncodingInputStream.createSmartReader(stream), format, base)); } return (Collection<org.openrdf.model.Statement>) cachedStreamResults.get(stream); } protected MutableTupleQueryResult loadTupleQueryResult(InputStream results, org.openanzo.rdf.RDFFormat format, String base) throws AnzoException, RDFHandlerException, IOException, QueryResultParseException, TupleQueryResultHandlerException, DAWGTestResultSetParseException, RDFParseException, QueryEvaluationException { if (!cachedStreamResults.containsKey(results)) { MutableTupleQueryResult tqr = null; if (format.equals(RDFFormat.SPARQL)) { // if it's not RDF, it's probably srx or srj TupleQueryResultParser parser = new SPARQLResultsXMLParser();//.createParser(TupleQueryResultFormat.SPARQL); TupleQueryResultBuilder tqrBuilder = new TupleQueryResultBuilder(); parser.setTupleQueryResultHandler(tqrBuilder); parser.parse(results); tqr = new MutableTupleQueryResult(tqrBuilder.getQueryResult()); } else { Collection<org.openrdf.model.Statement> rdf = parseAnyRdfOpenRdf(results, format, base); log.debug("expected: {}", rdf); tqr = new MutableTupleQueryResult(DAWGTestResultSetUtil.toTupleQueryResult(rdf)); } cachedStreamResults.put(results, tqr); } return (MutableTupleQueryResult) cachedStreamResults.get(results); } protected Boolean loadBooleanResult(InputStream results, RDFFormat format, String base) throws AnzoException, RDFHandlerException, IOException, QueryResultParseException, TupleQueryResultHandlerException, DAWGTestResultSetParseException, RDFParseException { if (!cachedStreamResults.containsKey(results)) { Boolean b = null; if (format.equals(RDFFormat.BOOLEANRESULT)) { // if it's not RDF, it's probably srx or srj BooleanQueryResultParser parser = new SPARQLBooleanXMLParser(); b = parser.parse(results); } else { Collection<org.openrdf.model.Statement> rdf = parseAnyRdfOpenRdf(results, format, base); b = DAWGTestResultSetUtil.toBooleanQueryResult(rdf); } cachedStreamResults.put(results, b); } return (Boolean) cachedStreamResults.get(results); } protected void performSelectQueryTest(String base, Set<URI> defaultGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, MutableTupleQueryResult expectedResults, boolean forceIgnoreOrder, boolean allowAnagrams) throws Exception { QueryResults actualResults = executeQuery(defaultGraphs, namedGraphs, namedDatasets, query, base != null ? Constants.valueFactory.createURI(base) : null); // Iterator<PatternSolution> iterator = actualResults.getSelectResults().iterator(); // while (iterator.hasNext()) { // PatternSolution patternSolution = (PatternSolution) iterator.next(); // System.err.println(patternSolution); // } assertEquals(QueryType.SELECT, actualResults.getQueryType()); SolutionSet solutionSet = actualResults.getSelectResults(); // ooo this is a bit of a cheat if (!query.matches("(?is:.*SELECT\\s+\\*.*)")) assertEquals(expectedResults.getBindingNames(), solutionSet.getBindingNames()); assertTupleQueryResultEquals(expectedResults, solutionSet, forceIgnoreOrder ? false : query.toUpperCase().contains("ORDER BY"), allowAnagrams); } protected void performConstructQueryTest(String base, Set<URI> defaultGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, Collection<Statement> expectedResults) throws Exception { QueryResults actualResults = executeQuery(defaultGraphs, namedGraphs, namedDatasets, query, base != null ? Constants.valueFactory.createURI(base) : null); assertTrue(actualResults.getQueryType() == QueryType.CONSTRUCT || actualResults.getQueryType() == QueryType.CONSTRUCT_QUADS); Collection<Statement> actualTriples = actualResults.getConstructResults(); // we want set semantics for comparison HashSet<Statement> expectedTripleSet = new HashSet<Statement>(expectedResults); HashSet<Statement> actualTripleSet = new HashSet<Statement>(actualTriples); assertEquals(expectedTripleSet, actualTripleSet); } protected void performAskQueryTest(String base, Set<URI> defaultGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, Boolean expectedResults) throws Exception { QueryResults actualResults = executeQuery(defaultGraphs, namedGraphs, namedDatasets, query, base != null ? Constants.valueFactory.createURI(base) : null); assertEquals(QueryType.ASK, actualResults.getQueryType()); assertEquals(expectedResults.booleanValue(), actualResults.getAskResults()); } protected void performDescribeQueryTest(String base, Set<URI> defaultGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, Collection<Statement> expectedResults) throws Exception { QueryResults actualResults = executeQuery(defaultGraphs, namedGraphs, namedDatasets, query, base != null ? Constants.valueFactory.createURI(base) : null); assertTrue(actualResults.getQueryType() == QueryType.DESCRIBE || actualResults.getQueryType() == QueryType.DESCRIBE_QUADS); Collection<Statement> actualTriples = actualResults.getDescribeResults(); // we want set semantics for comparison HashSet<Statement> expectedTripleSet = new HashSet<Statement>(expectedResults); HashSet<Statement> actualTripleSet = new HashSet<Statement>(actualTriples); assertEquals(expectedTripleSet, actualTripleSet); } private void assertTupleQueryResultEquals(MutableTupleQueryResult expected, SolutionSet actual, boolean ordered, boolean allowAnagrams) throws Exception { // this is laxer than it need be: SELECT * allows names in any order, but SELECT ?x ?y ?z mandates // the order Iterator<PatternSolution> actualIter = actual.iterator(); assertEquals(new HashSet<String>(expected.getBindingNames()), new HashSet<String>(actual.getBindingNames())); if (ordered) { // if it's ordered, we can just walk the lists in parallel expected.beforeFirst(); while (expected.hasNext()) { assertTrue(actualIter.hasNext()); BindingSet expectedPatternSolution = expected.next(); PatternSolution actualPatternSolution = actualIter.next(); assertTrue("Expected solution does not equal actual solution:\n\t" + converter.convert(expectedPatternSolution) + "\n\t\tvs.\n\t" + actualPatternSolution, areSolutionsEqual(converter.convert(expectedPatternSolution), actualPatternSolution, allowAnagrams)); } assertFalse(actualIter.hasNext()); } else { // otherwise, we can just build bags from each solution set and compare them //assertEquals(tqr2bag(expected), tqr2bag(actual)); // this doesn't work because different implementations have different .hashCodes, so // we do this the naive way LinkedList<PatternSolution> actualBindings = new LinkedList<PatternSolution>(); while (actualIter.hasNext()) actualBindings.add(actualIter.next()); int expectedCount = 0; expected.beforeFirst(); while (expected.hasNext()) { expectedCount++; BindingSet e = expected.next(); PatternSolution expect = converter.convert(e); boolean found = false; for (PatternSolution a : actualBindings) { if (areSolutionsEqual(expect, a, allowAnagrams)) { actualBindings.remove(a); found = true; break; } } assertTrue("Did not find expected bindings: \n\t" + expect + "\nActual bindings are:\n\t" + actualBindings, found); } assertEquals("Actual bindings found that were not expected. Expected were: \n\t[" + StringUtils.join(convertResults(expected), ", ") + "]\nActual bindings are:\n\t" + actualBindings, 0, actualBindings.size()); } } private boolean areSolutionsEqual(PatternSolution expected, PatternSolution actual, boolean allowAnagrams) { if (!allowAnagrams) return expected.equals(actual); Bindable[] domain = expected.getBoundDomain(true); boolean sameDomain = Arrays.equals(domain, actual.getBoundDomain(true)); if (!sameDomain) return false; for (Bindable b : domain) { Value expectedVal = expected.getBinding(b); Value actualVal = actual.getBinding(b); if (expectedVal == null && actualVal == null) continue; if (expectedVal == null || actualVal == null) return false; if (expectedVal.equals(actualVal)) continue; // allow anagrams here, but only for literals if (expectedVal instanceof Literal && actualVal instanceof Literal) { String expectedLabel = ((Literal) expectedVal).getLabel(); String actualLabel = ((Literal) actualVal).getLabel(); char[] expectedChars = expectedLabel.toCharArray(); char[] actualChars = actualLabel.toCharArray(); Arrays.sort(expectedChars); Arrays.sort(actualChars); return Arrays.equals(expectedChars, actualChars); } return false; } return true; } static Collection<BindingSet> convertResults(MutableTupleQueryResult results) { Collection<BindingSet> resultCollection = new ArrayList<BindingSet>(); while (results.hasNext()) { resultCollection.add(results.next()); } return resultCollection; } }