001: /*
002: * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007.
003: *
004: * Licensed under the Aduna BSD-style license.
005: */
006: package org.openrdf.query.parser.sparql;
007:
008: import java.io.IOException;
009: import java.io.InputStream;
010: import java.io.InputStreamReader;
011: import java.net.URL;
012: import java.util.ArrayList;
013: import java.util.HashSet;
014: import java.util.LinkedHashSet;
015: import java.util.List;
016: import java.util.Set;
017:
018: import junit.framework.TestCase;
019: import junit.framework.TestSuite;
020:
021: import org.slf4j.Logger;
022: import org.slf4j.LoggerFactory;
023:
024: import info.aduna.io.IOUtil;
025: import info.aduna.iteration.Iterations;
026: import info.aduna.text.StringUtil;
027:
028: import org.openrdf.model.Resource;
029: import org.openrdf.model.Statement;
030: import org.openrdf.model.URI;
031: import org.openrdf.model.Value;
032: import org.openrdf.model.util.ModelUtil;
033: import org.openrdf.query.BindingSet;
034: import org.openrdf.query.BooleanQuery;
035: import org.openrdf.query.Dataset;
036: import org.openrdf.query.GraphQuery;
037: import org.openrdf.query.GraphQueryResult;
038: import org.openrdf.query.MalformedQueryException;
039: import org.openrdf.query.Query;
040: import org.openrdf.query.QueryEvaluationException;
041: import org.openrdf.query.QueryLanguage;
042: import org.openrdf.query.QueryResultUtil;
043: import org.openrdf.query.TupleQuery;
044: import org.openrdf.query.TupleQueryResult;
045: import org.openrdf.query.dawg.DAWGTestResultSetUtil;
046: import org.openrdf.query.impl.DatasetImpl;
047: import org.openrdf.query.impl.MutableTupleQueryResult;
048: import org.openrdf.query.resultio.BooleanQueryResultFormat;
049: import org.openrdf.query.resultio.BooleanQueryResultParserRegistry;
050: import org.openrdf.query.resultio.QueryResultIO;
051: import org.openrdf.query.resultio.TupleQueryResultFormat;
052: import org.openrdf.repository.Repository;
053: import org.openrdf.repository.RepositoryConnection;
054: import org.openrdf.repository.RepositoryException;
055: import org.openrdf.repository.dataset.DatasetRepository;
056: import org.openrdf.repository.sail.SailRepository;
057: import org.openrdf.repository.util.RDFInserter;
058: import org.openrdf.rio.RDFFormat;
059: import org.openrdf.rio.RDFParser;
060: import org.openrdf.rio.Rio;
061: import org.openrdf.rio.RDFParser.DatatypeHandling;
062: import org.openrdf.rio.helpers.StatementCollector;
063: import org.openrdf.sail.memory.MemoryStore;
064:
065: public class SPARQLQueryTest extends TestCase {
066:
067: /*-----------*
068: * Constants *
069: *-----------*/
070:
071: static final Logger logger = LoggerFactory
072: .getLogger(SPARQLQueryTest.class);
073:
074: protected final String testURI;
075:
076: protected final String queryFileURL;
077:
078: protected final String resultFileURL;
079:
080: protected final Dataset dataset;
081:
082: /*-----------*
083: * Variables *
084: *-----------*/
085:
086: private Repository dataRep;
087:
088: /*--------------*
089: * Constructors *
090: *--------------*/
091:
092: public SPARQLQueryTest(String testURI, String name,
093: String queryFileURL, String resultFileURL, Dataset dataSet) {
094: super (name);
095:
096: this .testURI = testURI;
097: this .queryFileURL = queryFileURL;
098: this .resultFileURL = resultFileURL;
099: this .dataset = dataSet;
100: }
101:
102: /*---------*
103: * Methods *
104: *---------*/
105:
106: @Override
107: protected void setUp() throws Exception {
108: dataRep = createRepository();
109:
110: if (dataset != null) {
111: uploadDataset(dataset);
112: }
113: }
114:
115: protected Repository createRepository() throws RepositoryException {
116: Repository dataRep = new DatasetRepository(new SailRepository(
117: new MemoryStore()));
118: dataRep.initialize();
119: return dataRep;
120: }
121:
122: @Override
123: protected void tearDown() throws Exception {
124: dataRep.shutDown();
125: }
126:
127: @Override
128: protected void runTest() throws Exception {
129: RepositoryConnection con = dataRep.getConnection();
130: try {
131: String queryString = readQueryString();
132: Query query = con.prepareQuery(QueryLanguage.SPARQL,
133: queryString, queryFileURL);
134: if (dataset != null) {
135: query.setDataset(dataset);
136: }
137:
138: if (query instanceof TupleQuery) {
139: TupleQueryResult queryResult = ((TupleQuery) query)
140: .evaluate();
141:
142: TupleQueryResult expectedResult = readExpectedTupleQueryResult();
143: compareTupleQueryResults(queryResult, expectedResult);
144:
145: // Graph queryGraph = RepositoryUtil.asGraph(queryResult);
146: // Graph expectedGraph = readExpectedTupleQueryResult();
147: // compareGraphs(queryGraph, expectedGraph);
148: } else if (query instanceof GraphQuery) {
149: GraphQueryResult gqr = ((GraphQuery) query).evaluate();
150: Set<Statement> queryResult = Iterations.asSet(gqr);
151:
152: Set<Statement> expectedResult = readExpectedGraphQueryResult();
153:
154: compareGraphs(queryResult, expectedResult);
155: } else if (query instanceof BooleanQuery) {
156: boolean queryResult = ((BooleanQuery) query).evaluate();
157: boolean expectedResult = readExpectedBooleanQueryResult();
158: assertEquals(expectedResult, queryResult);
159: } else {
160: throw new RuntimeException("Unexpected query type: "
161: + query.getClass());
162: }
163: } finally {
164: con.close();
165: }
166: }
167:
168: private void compareTupleQueryResults(TupleQueryResult queryResult,
169: TupleQueryResult expectedResult) throws Exception {
170: // Create MutableTupleQueryResult to be able to re-iterate over the
171: // results
172: MutableTupleQueryResult queryResultTable = new MutableTupleQueryResult(
173: queryResult);
174: MutableTupleQueryResult expectedResultTable = new MutableTupleQueryResult(
175: expectedResult);
176:
177: if (!QueryResultUtil.equals(queryResultTable,
178: expectedResultTable)) {
179: queryResultTable.beforeFirst();
180: expectedResultTable.beforeFirst();
181:
182: /*
183: * StringBuilder message = new StringBuilder(128);
184: * message.append("\n============ "); message.append(getName());
185: * message.append(" =======================\n");
186: * message.append("Expected result: \n"); while
187: * (expectedResultTable.hasNext()) {
188: * message.append(expectedResultTable.next()); message.append("\n"); }
189: * message.append("============="); StringUtil.appendN('=',
190: * getName().length(), message);
191: * message.append("========================\n"); message.append("Query
192: * result: \n"); while (queryResultTable.hasNext()) {
193: * message.append(queryResultTable.next()); message.append("\n"); }
194: * message.append("============="); StringUtil.appendN('=',
195: * getName().length(), message);
196: * message.append("========================\n");
197: */
198:
199: List<BindingSet> queryBindings = Iterations
200: .asList(queryResultTable);
201: List<BindingSet> expectedBindings = Iterations
202: .asList(expectedResultTable);
203:
204: List<BindingSet> missingBindings = new ArrayList<BindingSet>(
205: expectedBindings);
206: missingBindings.removeAll(queryBindings);
207:
208: List<BindingSet> unexpectedBindings = new ArrayList<BindingSet>(
209: queryBindings);
210: unexpectedBindings.removeAll(expectedBindings);
211:
212: StringBuilder message = new StringBuilder(128);
213: message.append("\n============ ");
214: message.append(getName());
215: message.append(" =======================\n");
216:
217: if (!missingBindings.isEmpty()) {
218: message.append("Missing bindings: \n");
219: for (BindingSet bs : missingBindings) {
220: message.append(bs);
221: message.append("\n");
222: }
223:
224: message.append("=============");
225: StringUtil.appendN('=', getName().length(), message);
226: message.append("========================\n");
227: }
228:
229: if (!unexpectedBindings.isEmpty()) {
230: message.append("Unexpected bindings: \n");
231: for (BindingSet bs : unexpectedBindings) {
232: message.append(bs);
233: message.append("\n");
234: }
235:
236: message.append("=============");
237: StringUtil.appendN('=', getName().length(), message);
238: message.append("========================\n");
239: }
240:
241: logger.error(message.toString());
242: fail(message.toString());
243: }
244: }
245:
246: private void compareGraphs(Set<Statement> queryResult,
247: Set<Statement> expectedResult) throws Exception {
248: if (!ModelUtil.equals(expectedResult, queryResult)) {
249: // Don't use RepositoryUtil.difference, it reports incorrect diffs
250: /*
251: * Collection<? extends Statement> unexpectedStatements =
252: * RepositoryUtil.difference(queryResult, expectedResult); Collection<?
253: * extends Statement> missingStatements =
254: * RepositoryUtil.difference(expectedResult, queryResult);
255: * StringBuilder message = new StringBuilder(128);
256: * message.append("\n=======Diff: "); message.append(getName());
257: * message.append("========================\n"); if
258: * (!unexpectedStatements.isEmpty()) { message.append("Unexpected
259: * statements in result: \n"); for (Statement st :
260: * unexpectedStatements) { message.append(st.toString());
261: * message.append("\n"); } message.append("============="); for (int i =
262: * 0; i < getName().length(); i++) { message.append("="); }
263: * message.append("========================\n"); } if
264: * (!missingStatements.isEmpty()) { message.append("Statements missing
265: * in result: \n"); for (Statement st : missingStatements) {
266: * message.append(st.toString()); message.append("\n"); }
267: * message.append("============="); for (int i = 0; i <
268: * getName().length(); i++) { message.append("="); }
269: * message.append("========================\n"); }
270: */
271: StringBuilder message = new StringBuilder(128);
272: message.append("\n============ ");
273: message.append(getName());
274: message.append(" =======================\n");
275: message.append("Expected result: \n");
276: for (Statement st : expectedResult) {
277: message.append(st.toString());
278: message.append("\n");
279: }
280: message.append("=============");
281: StringUtil.appendN('=', getName().length(), message);
282: message.append("========================\n");
283:
284: message.append("Query result: \n");
285: for (Statement st : queryResult) {
286: message.append(st.toString());
287: message.append("\n");
288: }
289: message.append("=============");
290: StringUtil.appendN('=', getName().length(), message);
291: message.append("========================\n");
292:
293: logger.error(message.toString());
294: fail(message.toString());
295: }
296: }
297:
298: private void uploadDataset(Dataset dataset) throws Exception {
299: RepositoryConnection con = dataRep.getConnection();
300: try {
301: // Merge default and named graphs to filter duplicates
302: Set<URI> graphURIs = new HashSet<URI>();
303: graphURIs.addAll(dataset.getDefaultGraphs());
304: graphURIs.addAll(dataset.getNamedGraphs());
305:
306: for (Resource graphURI : graphURIs) {
307: upload(((URI) graphURI), graphURI);
308: }
309: } finally {
310: con.close();
311: }
312: }
313:
314: private void upload(URI graphURI, Resource context)
315: throws Exception {
316: RepositoryConnection con = dataRep.getConnection();
317: con.setAutoCommit(false);
318: try {
319: RDFFormat rdfFormat = Rio.getParserFormatForFileName(
320: graphURI.toString(), RDFFormat.TURTLE);
321: RDFParser rdfParser = Rio.createParser(rdfFormat, dataRep
322: .getValueFactory());
323: rdfParser.setVerifyData(false);
324: rdfParser.setDatatypeHandling(DatatypeHandling.IGNORE);
325: // rdfParser.setPreserveBNodeIDs(true);
326:
327: RDFInserter rdfInserter = new RDFInserter(con);
328: rdfInserter.enforceContext(context);
329: rdfParser.setRDFHandler(rdfInserter);
330:
331: URL graphURL = new URL(graphURI.toString());
332: InputStream in = graphURL.openStream();
333: try {
334: rdfParser.parse(in, graphURI.toString());
335: } finally {
336: in.close();
337: }
338:
339: con.setAutoCommit(true);
340: } finally {
341: con.close();
342: }
343: }
344:
345: private String readQueryString() throws IOException {
346: InputStream stream = new URL(queryFileURL).openStream();
347: try {
348: return IOUtil.readString(new InputStreamReader(stream,
349: "UTF-8"));
350: } finally {
351: stream.close();
352: }
353: }
354:
355: private TupleQueryResult readExpectedTupleQueryResult()
356: throws Exception {
357: TupleQueryResultFormat tqrFormat = QueryResultIO
358: .getParserFormatForFileName(resultFileURL);
359:
360: if (tqrFormat != null) {
361: InputStream in = new URL(resultFileURL).openStream();
362: try {
363: TupleQueryResult tqr = QueryResultIO.parse(in,
364: tqrFormat);
365: // return RepositoryUtil.asGraph(tqr);
366: return tqr;
367: } finally {
368: in.close();
369: }
370: } else {
371: Set<Statement> resultGraph = readExpectedGraphQueryResult();
372: return DAWGTestResultSetUtil
373: .toTupleQueryResult(resultGraph);
374: }
375: }
376:
377: private boolean readExpectedBooleanQueryResult() throws Exception {
378: BooleanQueryResultFormat bqrFormat = BooleanQueryResultParserRegistry
379: .getInstance().getFileFormatForFileName(resultFileURL);
380:
381: if (bqrFormat != null) {
382: InputStream in = new URL(resultFileURL).openStream();
383: try {
384: return QueryResultIO.parse(in, bqrFormat);
385: } finally {
386: in.close();
387: }
388: } else {
389: Set<Statement> resultGraph = readExpectedGraphQueryResult();
390: return DAWGTestResultSetUtil
391: .toBooleanQueryResult(resultGraph);
392: }
393: }
394:
395: private Set<Statement> readExpectedGraphQueryResult()
396: throws Exception {
397: RDFFormat rdfFormat = Rio
398: .getParserFormatForFileName(resultFileURL);
399:
400: if (rdfFormat != null) {
401: RDFParser parser = Rio.createParser(rdfFormat);
402: parser.setDatatypeHandling(DatatypeHandling.IGNORE);
403: parser.setPreserveBNodeIDs(true);
404:
405: Set<Statement> result = new LinkedHashSet<Statement>();
406: parser.setRDFHandler(new StatementCollector(result));
407:
408: InputStream in = new URL(resultFileURL).openStream();
409: try {
410: parser.parse(in, resultFileURL);
411: } finally {
412: in.close();
413: }
414:
415: return result;
416: } else {
417: throw new RuntimeException(
418: "Unable to determine file type of results file");
419: }
420: }
421:
422: public static class Factory {
423:
424: public SPARQLQueryTest createSPARQLQueryTest(String testURI,
425: String name, String queryFileURL, String resultFileURL,
426: Dataset dataSet) {
427: return new SPARQLQueryTest(testURI, name, queryFileURL,
428: resultFileURL, dataSet);
429: }
430: }
431:
432: public static TestSuite suite(String manifestFileURL)
433: throws Exception {
434: return suite(manifestFileURL, new Factory());
435: }
436:
437: public static TestSuite suite(String manifestFileURL,
438: Factory factory) throws Exception {
439: logger.info("Building test suite for {}", manifestFileURL);
440:
441: TestSuite suite = new TestSuite();
442:
443: // Read manifest and create declared test cases
444: Repository manifestRep = new SailRepository(new MemoryStore());
445: manifestRep.initialize();
446: RepositoryConnection con = manifestRep.getConnection();
447:
448: con.add(new URL(manifestFileURL), manifestFileURL,
449: RDFFormat.TURTLE);
450:
451: suite
452: .setName(getManifestName(manifestRep, con,
453: manifestFileURL));
454:
455: // Extract test case information from the manifest file. Note that we only
456: // select those test cases that are mentioned in the list.
457: StringBuilder query = new StringBuilder(512);
458: query
459: .append(" SELECT DISTINCT testURI, testName, resultFile, action, queryFile, defaultGraph ");
460: query
461: .append(" FROM {} rdf:first {testURI} dawgt:approval {dawgt:Approved}; ");
462: query
463: .append(" mf:name {testName}; ");
464: query
465: .append(" mf:result {resultFile}; ");
466: query
467: .append(" mf:action {action} qt:query {queryFile}; ");
468: query
469: .append(" [qt:data {defaultGraph}] ");
470: query.append(" USING NAMESPACE ");
471: query
472: .append(" mf = <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#>, ");
473: query
474: .append(" dawgt = <http://www.w3.org/2001/sw/DataAccess/tests/test-dawg#>, ");
475: query
476: .append(" qt = <http://www.w3.org/2001/sw/DataAccess/tests/test-query#>");
477: TupleQuery testCaseQuery = con.prepareTupleQuery(
478: QueryLanguage.SERQL, query.toString());
479:
480: query.setLength(0);
481: query.append(" SELECT graph ");
482: query.append(" FROM {action} qt:graphData {graph} ");
483: query.append(" USING NAMESPACE ");
484: query
485: .append(" qt = <http://www.w3.org/2001/sw/DataAccess/tests/test-query#>");
486: TupleQuery namedGraphsQuery = con.prepareTupleQuery(
487: QueryLanguage.SERQL, query.toString());
488:
489: logger.debug("evaluating query..");
490: TupleQueryResult testCases = testCaseQuery.evaluate();
491: while (testCases.hasNext()) {
492: BindingSet bindingSet = testCases.next();
493:
494: String testURI = bindingSet.getValue("testURI").toString();
495: String testName = bindingSet.getValue("testName")
496: .toString();
497: String resultFile = bindingSet.getValue("resultFile")
498: .toString();
499: String queryFile = bindingSet.getValue("queryFile")
500: .toString();
501: URI defaultGraphURI = (URI) bindingSet
502: .getValue("defaultGraph");
503: Value action = bindingSet.getValue("action");
504:
505: logger.debug("found test case : {}", testName);
506:
507: // Query named graphs
508: namedGraphsQuery.setBinding("action", action);
509: TupleQueryResult namedGraphs = namedGraphsQuery.evaluate();
510:
511: DatasetImpl dataset = null;
512:
513: if (defaultGraphURI != null || namedGraphs.hasNext()) {
514: dataset = new DatasetImpl();
515:
516: if (defaultGraphURI != null) {
517: dataset.addDefaultGraph(defaultGraphURI);
518: }
519:
520: while (namedGraphs.hasNext()) {
521: BindingSet graphBindings = namedGraphs.next();
522: URI namedGraphURI = (URI) graphBindings
523: .getValue("graph");
524: dataset.addNamedGraph(namedGraphURI);
525: }
526: }
527:
528: suite.addTest(factory.createSPARQLQueryTest(testURI,
529: testName, queryFile, resultFile, dataset));
530: }
531:
532: testCases.close();
533: con.close();
534:
535: manifestRep.shutDown();
536: logger.info("Created test suite with " + suite.countTestCases()
537: + " test cases.");
538: return suite;
539: }
540:
541: protected static String getManifestName(Repository manifestRep,
542: RepositoryConnection con, String manifestFileURL)
543: throws QueryEvaluationException, RepositoryException,
544: MalformedQueryException {
545: // Try to extract suite name from manifest file
546: TupleQuery manifestNameQuery = con
547: .prepareTupleQuery(QueryLanguage.SERQL,
548: "SELECT ManifestName FROM {ManifestURL} rdfs:label {ManifestName}");
549: manifestNameQuery.setBinding("ManifestURL", manifestRep
550: .getValueFactory().createURI(manifestFileURL));
551: TupleQueryResult manifestNames = manifestNameQuery.evaluate();
552: try {
553: if (manifestNames.hasNext()) {
554: return manifestNames.next().getValue("ManifestName")
555: .stringValue();
556: }
557: } finally {
558: manifestNames.close();
559: }
560:
561: // Derive name from manifest URL
562: int lastSlashIdx = manifestFileURL.lastIndexOf('/');
563: int secLastSlashIdx = manifestFileURL.lastIndexOf('/',
564: lastSlashIdx - 1);
565: return manifestFileURL.substring(secLastSlashIdx + 1,
566: lastSlashIdx);
567: }
568: }
|