SPARQLServiceEvaluationTest.java
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.query.parser.sparql;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.eclipse.rdf4j.common.io.IOUtil;
import org.eclipse.rdf4j.common.iteration.Iterations;
import org.eclipse.rdf4j.common.text.StringUtil;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.util.Literals;
import org.eclipse.rdf4j.model.util.Models;
import org.eclipse.rdf4j.model.vocabulary.FOAF;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.BooleanQuery;
import org.eclipse.rdf4j.query.GraphQuery;
import org.eclipse.rdf4j.query.GraphQueryResult;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.Query;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.QueryResults;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.query.dawg.DAWGTestResultSetUtil;
import org.eclipse.rdf4j.query.impl.MutableTupleQueryResult;
import org.eclipse.rdf4j.query.impl.TupleQueryResultBuilder;
import org.eclipse.rdf4j.query.resultio.QueryResultFormat;
import org.eclipse.rdf4j.query.resultio.QueryResultIO;
import org.eclipse.rdf4j.query.resultio.TupleQueryResultParser;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.http.HTTPRepository;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.helpers.StatementCollector;
import org.eclipse.rdf4j.sail.memory.MemoryStore;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test suite for evaluation of SPARQL queries involving SERVICE clauses. The test suite starts up an embedded Jetty
* server running RDF4J Server, which functions as the SPARQL endpoint to test against. The test is configured to
* execute the W3C service tests located in rdf4j-sparql-testsuite/src/main/resources/testcases-service
*
* @author Jeen Broekstra
* @author Andreas Schwarte
*/
public class SPARQLServiceEvaluationTest {
static final Logger logger = LoggerFactory.getLogger(SPARQLServiceEvaluationTest.class);
/**
* The maximal number of endpoints occurring in a (single) test case
*/
protected static final int MAX_ENDPOINTS = 3;
private SPARQLEmbeddedServer server;
private SailRepository localRepository;
private List<HTTPRepository> remoteRepositories;
@Rule
public TestName name = new TestName();
public SPARQLServiceEvaluationTest() {
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
// set up the server: the maximal number of endpoints must be known
List<String> repositoryIds = new ArrayList<>(MAX_ENDPOINTS);
for (int i = 1; i <= MAX_ENDPOINTS; i++) {
repositoryIds.add("endpoint" + i);
}
server = new SPARQLEmbeddedServer(repositoryIds);
try {
server.start();
} catch (Exception e) {
server.stop();
throw e;
}
remoteRepositories = new ArrayList<>(MAX_ENDPOINTS);
for (int i = 1; i <= MAX_ENDPOINTS; i++) {
HTTPRepository r = new HTTPRepository(getRepositoryUrl(i));
r.init();
remoteRepositories.add(r);
}
localRepository = new SailRepository(new MemoryStore());
}
/**
* Get the repository url, initialized repositories are called endpoint1 endpoint2 .. endpoint%MAX_ENDPOINTS%
*
* @param i the index of the repository, starting with 1
* @return
*/
protected String getRepositoryUrl(int i) {
return server.getRepositoryUrl("endpoint" + i);
}
/**
* Get the repository, initialized repositories are called endpoint1 endpoint2 .. endpoint%MAX_ENDPOINTS%
*
* @param i the index of the repository, starting with 1
* @return
*/
public HTTPRepository getRepository(int i) {
return remoteRepositories.get(i - 1);
}
/**
* Prepare a particular test, and load the specified data. Note: the repositories are cleared before loading data
*
* @param localData a local data file that is added to local repository, use null if there is no local data
* @param endpointData a list of endpoint data files, dataFile at index is loaded to endpoint%i%, use empty list for
* no remote data
* @throws Exception
*/
protected void prepareTest(String localData, List<String> endpointData) throws Exception {
if (endpointData.size() > MAX_ENDPOINTS) {
throw new RuntimeException(
"MAX_ENDPOINTs to low, " + endpointData.size() + " repositories needed. Adjust configuration");
}
if (localData != null) {
loadDataSet(localRepository, localData);
}
int i = 1; // endpoint id, start with 1
for (String s : endpointData) {
loadDataSet(getRepository(i++), s);
}
}
/**
* Load a dataset. Note: the repositories are cleared before loading data
*
* @param rep
* @param datasetFile
* @throws RDFParseException
* @throws RepositoryException
* @throws IOException
*/
protected void loadDataSet(Repository rep, String datasetFile)
throws RDFParseException, RepositoryException, IOException {
logger.debug("loading dataset...");
try (InputStream dataset = SPARQLServiceEvaluationTest.class.getResourceAsStream(datasetFile)) {
if (dataset == null) {
throw new IllegalArgumentException("Datasetfile " + datasetFile + " not found.");
}
try (RepositoryConnection con = rep.getConnection()) {
con.clear();
con.add(dataset, "",
Rio.getParserFormatForFileName(datasetFile).orElseThrow(Rio.unsupportedFormat(datasetFile)));
}
}
logger.debug("dataset loaded.");
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
try {
localRepository.shutDown();
} finally {
server.stop();
}
}
/**
* Verify that BIND clause alias from the SERVICE clause gets added to the result set.
*
* @see <a href="https://github.com/eclipse/rdf4j/issues/646">#646</a>
*/
@Test
public void testValuesBindClauseHandling() {
String query = "select * { service <" + getRepositoryUrl(1) + "> { Bind(1 as ?val) . VALUES ?x {1 2} . } }";
try (RepositoryConnection conn = localRepository.getConnection()) {
TupleQuery tq = conn.prepareTupleQuery(query);
TupleQueryResult tqr = tq.evaluate();
assertNotNull(tqr);
assertTrue(tqr.hasNext());
List<BindingSet> result = QueryResults.asList(tqr);
assertEquals(2, result.size());
for (BindingSet bs : result) {
assertTrue(bs.hasBinding("val"));
assertEquals(1, Literals.getIntValue(bs.getValue("val"), 0));
assertTrue(bs.hasBinding("x"));
int x = Literals.getIntValue(bs.getValue("x"), 0);
assertTrue(x == 1 || x == 2);
}
}
}
/**
* Verify that all relevant variable names from the SERVICE clause get added to the result set when a BIND clause is
* present.
*
* @see <a href="https://github.com/eclipse/rdf4j/issues/703">#703</a>
*/
@Test
public void testVariableNameHandling() throws Exception {
String query = "select * { service <" + getRepositoryUrl(1) + "> { ?s ?p ?o . Bind(str(?o) as ?val) . } }";
// add some data to the remote endpoint (we don't care about the exact contents)
prepareTest(null, List.of("/testcases-service/data13.ttl"));
try (RepositoryConnection conn = localRepository.getConnection()) {
TupleQuery tq = conn.prepareTupleQuery(query);
TupleQueryResult tqr = tq.evaluate();
assertNotNull(tqr);
assertTrue(tqr.hasNext());
List<BindingSet> result = QueryResults.asList(tqr);
assertTrue(!result.isEmpty());
for (BindingSet bs : result) {
assertTrue(bs.hasBinding("val"));
assertTrue(bs.hasBinding("s"));
assertTrue(bs.hasBinding("p"));
assertTrue(bs.hasBinding("o"));
}
}
}
@Test
public void testSimpleServiceQuery() throws RepositoryException {
// test setup
String EX_NS = "http://example.org/";
ValueFactory f = localRepository.getValueFactory();
IRI bob = f.createIRI(EX_NS, "bob");
IRI alice = f.createIRI(EX_NS, "alice");
IRI william = f.createIRI(EX_NS, "william");
// clears the repository and adds new data
try {
prepareTest("/testcases-service/simple-default-graph.ttl", List.of("/testcases-service/simple.ttl"));
} catch (Exception e1) {
fail(e1.getMessage());
}
StringBuilder qb = new StringBuilder();
qb.append(" SELECT * \n");
qb.append(" WHERE { \n");
qb.append(" SERVICE <" + getRepositoryUrl(1) + "> { \n");
qb.append(" ?X <" + FOAF.NAME + "> ?Y \n ");
qb.append(" } \n ");
qb.append(" ?X a <" + FOAF.PERSON + "> . \n");
qb.append(" } \n");
try (RepositoryConnection conn = localRepository.getConnection()) {
TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, qb.toString());
try (TupleQueryResult tqr = tq.evaluate()) {
assertNotNull(tqr);
assertTrue(tqr.hasNext());
int count = 0;
while (tqr.hasNext()) {
BindingSet bs = tqr.next();
count++;
Value x = bs.getValue("X");
Value y = bs.getValue("Y");
assertNotEquals(william, x);
assertTrue(bob.equals(x) || alice.equals(x));
if (bob.equals(x)) {
assertEquals(f.createLiteral("Bob"), y);
} else if (alice.equals(x)) {
assertEquals(f.createLiteral("Alice"), y);
}
}
assertEquals(2, count);
}
} catch (MalformedQueryException | QueryEvaluationException e) {
fail(e.getMessage());
}
}
@Test
public void test1() throws Exception {
prepareTest("/testcases-service/data01.ttl", List.of("/testcases-service/data01endpoint.ttl"));
execute("/testcases-service/service01.rq", "/testcases-service/service01.srx", false);
}
@Test
public void test2() throws Exception {
prepareTest(null,
Arrays.asList("/testcases-service/data02endpoint1.ttl", "/testcases-service/data02endpoint2.ttl"));
execute("/testcases-service/service02.rq", "/testcases-service/service02.srx", false);
}
@Test
public void test3() throws Exception {
prepareTest(null,
Arrays.asList("/testcases-service/data03endpoint1.ttl", "/testcases-service/data03endpoint2.ttl"));
execute("/testcases-service/service03.rq", "/testcases-service/service03.srx", false);
}
@Test
public void test4() throws Exception {
prepareTest("/testcases-service/data04.ttl", List.of("/testcases-service/data04endpoint.ttl"));
execute("/testcases-service/service04.rq", "/testcases-service/service04.srx", false);
}
@Test
public void test5() throws Exception {
prepareTest("/testcases-service/data05.ttl",
Arrays.asList("/testcases-service/data05endpoint1.ttl", "/testcases-service/data05endpoint2.ttl"));
execute("/testcases-service/service05.rq", "/testcases-service/service05.srx", false);
}
@Test
public void test6() throws Exception {
prepareTest(null, List.of("/testcases-service/data06endpoint1.ttl"));
execute("/testcases-service/service06.rq", "/testcases-service/service06.srx", false);
}
@Test
public void test7() throws Exception {
// clears the repository and adds new data + execute
prepareTest("/testcases-service/data07.ttl", Collections.<String>emptyList());
execute("/testcases-service/service07.rq", "/testcases-service/service07.srx", false);
}
@Test
public void test8() throws Exception {
/* test where the SERVICE expression is to be evaluated as ASK request */
prepareTest("/testcases-service/data08.ttl", List.of("/testcases-service/data08endpoint.ttl"));
execute("/testcases-service/service08.rq", "/testcases-service/service08.srx", false);
}
@Test
public void test9() throws Exception {
/* test where the service endpoint is bound at runtime through BIND */
prepareTest(null, List.of("/testcases-service/data09endpoint.ttl"));
execute("/testcases-service/service09.rq", "/testcases-service/service09.srx", false);
}
@Test
public void test10() throws Exception {
/* test how we deal with blank node */
prepareTest("/testcases-service/data10.ttl", List.of("/testcases-service/data10endpoint.ttl"));
execute("/testcases-service/service10.rq", "/testcases-service/service10.srx", false);
}
@Test
public void test11() throws Exception {
/* test vectored join with more intermediate results */
// clears the repository and adds new data + execute
prepareTest("/testcases-service/data11.ttl", List.of("/testcases-service/data11endpoint.ttl"));
execute("/testcases-service/service11.rq", "/testcases-service/service11.srx", false);
}
// test on remote DBpedia endpoint disabled. Only enable for manual testing,
// should not be enabled for
// Surefire or Hudson.
// /**
// * This is a manual test to see the Fallback in action. Query asks
// * DBpedia, which does not support BINDINGS
// *
// * @throws Exception
// */
// public void testFallbackWithDBpedia() throws Exception {
// /* test vectored join with more intermediate results */
// // clears the repository and adds new data + execute
// prepareTest("/testcases-service/data12.ttl",
// Collections.<String>emptyList());
// execute("/testcases-service/service12.rq",
// "/testcases-service/service12.srx", false);
// }
@Test
public void test13() throws Exception {
/* test for bug SES-899: cross product is required */
prepareTest(null, List.of("/testcases-service/data13.ttl"));
execute("/testcases-service/service13.rq", "/testcases-service/service13.srx", false);
}
@Test
public void testEmptyServiceBlock() throws Exception {
/* test for bug SES-900: nullpointer for empty service block */
prepareTest(null, List.of("/testcases-service/data13.ttl"));
execute("/testcases-service/service14.rq", "/testcases-service/service14.srx", false);
}
@Test
public void testNotProjectedCount() throws Exception {
/* test projection of subqueries - SES-1000 */
prepareTest(null, List.of("/testcases-service/data17endpoint1.ttl"));
execute("/testcases-service/service17.rq", "/testcases-service/service17.srx", false);
}
@Test
public void testNonAsciiCharHandling() throws Exception {
/* SES-1056 */
prepareTest(null, List.of("/testcases-service/data18endpoint1.rdf"));
execute("/testcases-service/service18.rq", "/testcases-service/service18.srx", false);
}
/**
* Execute a testcase, both queryFile and expectedResultFile must be files located on the class path.
*
* @param queryFile
* @param expectedResultFile
* @param checkOrder
* @throws Exception
*/
private void execute(String queryFile, String expectedResultFile, boolean checkOrder) throws Exception {
try (RepositoryConnection conn = localRepository.getConnection()) {
String queryString = readQueryString(queryFile);
Query query = conn.prepareQuery(QueryLanguage.SPARQL, queryString);
if (query instanceof TupleQuery) {
try (TupleQueryResult queryResult = ((TupleQuery) query).evaluate()) {
TupleQueryResult expectedResult = readExpectedTupleQueryResult(expectedResultFile);
compareTupleQueryResults(queryResult, expectedResult, checkOrder);
}
} else if (query instanceof GraphQuery) {
try (GraphQueryResult gqr = ((GraphQuery) query).evaluate()) {
Set<Statement> queryResult = Iterations.asSet(gqr);
Set<Statement> expectedResult = readExpectedGraphQueryResult(expectedResultFile);
compareGraphs(queryResult, expectedResult);
}
} else if (query instanceof BooleanQuery) {
// TODO implement if needed
throw new RuntimeException("Not yet supported " + query.getClass());
} else {
throw new RuntimeException("Unexpected query type: " + query.getClass());
}
}
}
/**
* Read the query string from the specified resource
*
* @param queryResource
* @return
* @throws RepositoryException
* @throws IOException
*/
private String readQueryString(String queryResource) throws RepositoryException, IOException {
try (InputStream stream = SPARQLServiceEvaluationTest.class.getResourceAsStream(queryResource)) {
return IOUtil.readString(new InputStreamReader(Objects.requireNonNull(stream), StandardCharsets.UTF_8));
}
}
/**
* Read the expected tuple query result from the specified resource
*
* @param resultFile
* @return
* @throws RepositoryException
* @throws IOException
*/
private TupleQueryResult readExpectedTupleQueryResult(String resultFile) throws Exception {
Optional<QueryResultFormat> tqrFormat = QueryResultIO.getParserFormatForFileName(resultFile);
if (tqrFormat.isPresent()) {
try (InputStream in = SPARQLServiceEvaluationTest.class.getResourceAsStream(resultFile)) {
TupleQueryResultParser parser = QueryResultIO.createTupleParser(tqrFormat.get());
parser.setValueFactory(SimpleValueFactory.getInstance());
TupleQueryResultBuilder qrBuilder = new TupleQueryResultBuilder();
parser.setQueryResultHandler(qrBuilder);
parser.parseQueryResult(in);
return qrBuilder.getQueryResult();
}
} else {
Set<Statement> resultGraph = readExpectedGraphQueryResult(resultFile);
return DAWGTestResultSetUtil.toTupleQueryResult(resultGraph);
}
}
/**
* Read the expected graph query result from the specified resource
*
* @param resultFile
* @return
* @throws Exception
*/
private Set<Statement> readExpectedGraphQueryResult(String resultFile) throws Exception {
RDFFormat rdfFormat = Rio.getParserFormatForFileName(resultFile).orElseThrow(Rio.unsupportedFormat(resultFile));
RDFParser parser = Rio.createParser(rdfFormat);
parser.setPreserveBNodeIDs(true);
parser.setValueFactory(SimpleValueFactory.getInstance());
Set<Statement> result = new LinkedHashSet<>();
parser.setRDFHandler(new StatementCollector(result));
try (InputStream in = SPARQLServiceEvaluationTest.class.getResourceAsStream(resultFile)) {
parser.parse(in, null); // TODO check
}
return result;
}
/**
* Compare two tuple query results
*
* @param queryResult
* @param expectedResult
* @param checkOrder
*/
private void compareTupleQueryResults(TupleQueryResult queryResult, TupleQueryResult expectedResult,
boolean checkOrder) {
// Create MutableTupleQueryResult to be able to re-iterate over the
// results
MutableTupleQueryResult queryResultTable = new MutableTupleQueryResult(queryResult);
MutableTupleQueryResult expectedResultTable = new MutableTupleQueryResult(expectedResult);
boolean resultsEqual;
resultsEqual = QueryResults.equals(queryResultTable, expectedResultTable);
if (checkOrder) {
// also check the order in which solutions occur.
queryResultTable.beforeFirst();
expectedResultTable.beforeFirst();
while (queryResultTable.hasNext()) {
BindingSet bs = queryResultTable.next();
BindingSet expectedBs = expectedResultTable.next();
if (!bs.equals(expectedBs)) {
resultsEqual = false;
break;
}
}
}
if (!resultsEqual) {
queryResultTable.beforeFirst();
expectedResultTable.beforeFirst();
/*
* StringBuilder message = new StringBuilder(128); message.append("\n============ ");
* message.append(name.getMethodName()); message.append(" =======================\n"); message.append(
* "Expected result: \n"); while (expectedResultTable.hasNext()) {
* message.append(expectedResultTable.next()); message.append("\n"); } message.append("=============");
* StringUtil.appendN('=', name.getMethodName().length(), message);
* message.append("========================\n"); message.append("Query result: \n"); while
* (queryResultTable.hasNext()) { message.append(queryResultTable.next()); message.append("\n"); }
* message.append("============="); StringUtil.appendN('=', name.getMethodName().length(), message);
* message.append("========================\n");
*/
List<BindingSet> queryBindings = Iterations.asList(queryResultTable);
List<BindingSet> expectedBindings = Iterations.asList(expectedResultTable);
List<BindingSet> missingBindings = new ArrayList<>(expectedBindings);
missingBindings.removeAll(queryBindings);
List<BindingSet> unexpectedBindings = new ArrayList<>(queryBindings);
unexpectedBindings.removeAll(expectedBindings);
StringBuilder message = new StringBuilder(128);
message.append("\n============ ");
message.append(name.getMethodName());
message.append(" =======================\n");
if (!missingBindings.isEmpty()) {
message.append("Missing bindings: \n");
for (BindingSet bs : missingBindings) {
message.append(bs);
message.append("\n");
}
message.append("=============");
StringUtil.appendN('=', name.getMethodName().length(), message);
message.append("========================\n");
}
if (!unexpectedBindings.isEmpty()) {
message.append("Unexpected bindings: \n");
for (BindingSet bs : unexpectedBindings) {
message.append(bs);
message.append("\n");
}
message.append("=============");
StringUtil.appendN('=', name.getMethodName().length(), message);
message.append("========================\n");
}
if (checkOrder && missingBindings.isEmpty() && unexpectedBindings.isEmpty()) {
message.append("Results are not in expected order.\n");
message.append(" =======================\n");
message.append("query result: \n");
for (BindingSet bs : queryBindings) {
message.append(bs);
message.append("\n");
}
message.append(" =======================\n");
message.append("expected result: \n");
for (BindingSet bs : expectedBindings) {
message.append(bs);
message.append("\n");
}
message.append(" =======================\n");
System.out.print(message.toString());
}
logger.error(message.toString());
fail(message.toString());
}
/*
* debugging only: print out result when test succeeds else { queryResultTable.beforeFirst(); List<BindingSet>
* queryBindings = Iterations.asList(queryResultTable); StringBuilder message = new StringBuilder(128);
* message.append("\n============ "); message.append(name.getMethodName()); message.append(
* " =======================\n"); message.append(" =======================\n"); message.append(
* "query result: \n"); for (BindingSet bs: queryBindings) { message.append(bs); message.append("\n"); }
* System.out.print(message.toString()); }
*/
}
/**
* Compare two graphs
*
* @param queryResult
* @param expectedResult
*/
private void compareGraphs(Set<Statement> queryResult, Set<Statement> expectedResult) {
if (!Models.isomorphic(expectedResult, queryResult)) {
// Don't use RepositoryUtil.difference, it reports incorrect diffs
/*
* Collection<? extends Statement> unexpectedStatements = RepositoryUtil.difference(queryResult,
* expectedResult); Collection<? extends Statement> missingStatements =
* RepositoryUtil.difference(expectedResult, queryResult); StringBuilder message = new StringBuilder(128);
* message.append("\n=======Diff: "); message.append(name.getMethodName());
* message.append("========================\n"); if (!unexpectedStatements.isEmpty()) {
* message.append("Unexpected statements in result: \n"); for (Statement st : unexpectedStatements) {
* message.append(st.toString()); message.append("\n"); } message.append("============="); for (int i = 0; i
* < name.getMethodName().length(); i++) { message.append("="); }
* message.append("========================\n"); } if (!missingStatements.isEmpty()) {
* message.append("Statements missing in result: \n"); for (Statement st : missingStatements) {
* message.append(st.toString()); message.append("\n"); } message.append("============="); for (int i = 0; i
* < name.getMethodName().length(); i++) { message.append("="); }
* message.append("========================\n"); }
*/
StringBuilder message = new StringBuilder(128);
message.append("\n============ ");
message.append(name.getMethodName());
message.append(" =======================\n");
message.append("Expected result: \n");
for (Statement st : expectedResult) {
message.append(st.toString());
message.append("\n");
}
message.append("=============");
StringUtil.appendN('=', name.getMethodName().length(), message);
message.append("========================\n");
message.append("Query result: \n");
for (Statement st : queryResult) {
message.append(st.toString());
message.append("\n");
}
message.append("=============");
StringUtil.appendN('=', name.getMethodName().length(), message);
message.append("========================\n");
logger.error(message.toString());
fail(message.toString());
}
}
}