Java tutorial
/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.jboss.bqt.client.results.xml; import java.sql.Blob; import java.sql.Clob; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.SQLXML; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.jboss.bqt.client.ClientPlugin; import org.jboss.bqt.client.QueryTest; import org.jboss.bqt.client.TestProperties; import org.jboss.bqt.client.api.ExpectedResults; import org.jboss.bqt.client.results.ExpectedResultsHolder; import org.jboss.bqt.client.util.ListNestedSortComparator; import org.jboss.bqt.client.xml.TagNames; import org.jboss.bqt.client.xml.TagNames.Elements; import org.jboss.bqt.core.exception.QueryTestFailedException; import org.jboss.bqt.core.util.ExceptionUtil; import org.jboss.bqt.core.util.ObjectConverterUtil; import org.jboss.bqt.framework.TestCase; import org.jboss.bqt.framework.TestResult; public class XMLCompareResults { private static String newline = System.getProperty("line.separator"); //$NON-NLS-1$ private static double exceed_percent = -0.99999; private static long exec_minumin_time = -1; private XMLCompareResults(Properties props) { String exceed_per = props.getProperty(TestProperties.PROP_EXECUTE_EXCEED_PERCENT); String exec_min = props.getProperty(TestProperties.PROP_EXECUTE_TIME_MINEMUM); if (exceed_per != null && exceed_per.trim().length() > 0) { ClientPlugin.LOGGER .debug(" ======== " + TestProperties.PROP_EXECUTE_EXCEED_PERCENT + " is set to " + exceed_per); exceed_percent = Double.parseDouble(exceed_per); } if (exec_min != null && exec_min.trim().length() > 0) { ClientPlugin.LOGGER .debug(" ======== " + TestProperties.PROP_EXECUTE_EXCEED_PERCENT + " is set to " + exceed_per); exec_minumin_time = Long.parseLong(exec_min); } // if exceed percent was set and exec time was not, set exec time to minimum of 1 mil if (exceed_percent > 0 && exec_minumin_time < 0) exec_minumin_time = 1; } public static XMLCompareResults create(Properties properties) { return new XMLCompareResults(properties); } /** * Compare the results of a query with those that were expected. * @param testcase * @param expResults * @param resultSet * @param isOrdered * * @throws QueryTestFailedException * If comparison fails. */ public void compareResults(final TestCase testcase, ExpectedResults expResults, final ResultSet resultSet, final boolean isOrdered) throws QueryTestFailedException { final String eMsg = "CompareResults Error: "; //$NON-NLS-1$ // if (expResults.isExceptionExpected()) { // testcase.getTestResult().setStatus(TestResult.RESULT_STATE.TEST_EXPECTED_EXCEPTION); // } ExpectedResultsHolder expectedResults = (ExpectedResultsHolder) expResults; ExpectedResultsHolder actualResults = null; switch (testcase.getTestResult().getStatus()) { case TestResult.RESULT_STATE.TEST_EXCEPTION: if (!expResults.isExceptionExpected()) { throw new QueryTestFailedException(eMsg + "TestResult resulted in unexpected exception " //$NON-NLS-1$ + testcase.getTestResult().getExceptionMsg()); } // case TestResult.RESULT_STATE.TEST_EXPECTED_EXCEPTION: // // if (!expectedResults.isExceptionExpected()) { // // The actual exception was expected, but the expected results // // was not // throw new QueryTestFailedException( // eMsg // + "The actual result was an exception, but the Expected results wasn't an exception. Actual exception: '" //$NON-NLS-1$ // + testcase.getTestResult().getExceptionMsg() + "'"); //$NON-NLS-1$ // } testcase.getTestResult().setStatus(TestResult.RESULT_STATE.TEST_EXPECTED_EXCEPTION); // We got an exception that we expected - convert actual exception // to ResultsHolder actualResults = new ExpectedResultsHolder(TagNames.Elements.EXCEPTION, (QueryTest) testcase.getActualTest()); actualResults = convertException(testcase.getTestResult().getException(), actualResults); actualResults.setExecutionTime(testcase.getTestResult().getExecutionTime()); // // DEBUG: // ClientPlugin.LOGGER.info("*** EXPECTED EXC: Expected Results (holder): " + // expectedResults); // // DEBUG: // ClientPlugin.LOGGER.info("*** EXPECTED EXC: Actual Results (ResultSet): " + // actualResults); compareExceptions(actualResults, expectedResults, eMsg); break; default: // Convert results to ResultsHolder actualResults = new ExpectedResultsHolder(TagNames.Elements.QUERY_RESULTS, (QueryTest) testcase.getActualTest()); actualResults.setExecutionTime(testcase.getTestResult().getExecutionTime()); // // DEBUG: // ClientPlugin.LOGGER.info("*** 2 Expected Results (holder): " + // expectedResults); // // DEBUG: // ClientPlugin.LOGGER.info("*** 2 Actual Results (ResultSet): " + // actualResults); convertResults(resultSet, testcase.getTestResult().getUpdateCount(), actualResults); if (expectedResults.getRows().size() > 0) { compareResults(testcase, actualResults, expectedResults, eMsg, isOrdered); } else if (actualResults.getRows() != null && actualResults.getRows().size() > 0) { throw new QueryTestFailedException(eMsg + "Expected results indicated no results, but actual shows " //$NON-NLS-1$ + actualResults.getRows().size() + " rows."); } // DEBUG: // debugOut.println("*** Actual Results (holder): " + // actualResults); // Compare expected results with actual results, record by record break; } } /** * The use of REMOVE_PREFIX is a hack to strip down the message so that it matches * what is currently in use. * @param actualException * @param actualResults * @return ResultsHolder */ private static ExpectedResultsHolder convertException(final Throwable actualException, final ExpectedResultsHolder actualResults) { actualResults.setExceptionClassName(actualException.getClass().getName()); actualResults.setExceptionMsg(ExceptionUtil.getExceptionMessage(actualException)); return actualResults; } /** * Helper to convert results into records and record first batch response * time. * * @param results * @param batchSize * @param resultsHolder * Modified - results added by this method. * @return List of sorted results. * @throws QueryTestFailedException * replaced SQLException. */ private static final long convertResults(final ResultSet results, final long batchSize, ExpectedResultsHolder resultsHolder) throws QueryTestFailedException { long firstBatchResponseTime = 0; final List<List<Object>> records = new ArrayList<List<Object>>(); final List<String> columnTypeNames = new ArrayList<String>(); final List<String> columnTypes = new ArrayList<String>(); final ResultSetMetaData rsMetadata; final int colCount; if (results != null) { // Get column info try { rsMetadata = results.getMetaData(); colCount = rsMetadata.getColumnCount(); // Read types of all columns for (int col = 1; col <= colCount; col++) { columnTypeNames.add(rsMetadata.getColumnName(col)); columnTypes.add(rsMetadata.getColumnTypeName(col)); } } catch (SQLException qre) { throw new QueryTestFailedException("Can't get results metadata: " + qre.getMessage()); //$NON-NLS-1$ } // Get rows try { // Read all the rows for (int row = 0; results.next(); row++) { final List<Object> currentRecord = new ArrayList<Object>(colCount); // Read values for this row for (int col = 1; col <= colCount; col++) { currentRecord.add(results.getObject(col)); } records.add(currentRecord); // If this row is the (fetch size - 1)th row, record first batch // response time if (row == batchSize) { firstBatchResponseTime = System.currentTimeMillis(); } } } catch (SQLException qre) { throw new QueryTestFailedException("Can't get results: " + qre.getMessage()); //$NON-NLS-1$ } } // Set info on resultsHolder resultsHolder.setRows(records); resultsHolder.setIdentifiers(columnTypeNames); resultsHolder.setTypes(columnTypes); return firstBatchResponseTime; } /** * Added primarily for public access to the compare code for testing. * @param testCase * * @param actualResults * @param expectedResults * @param eMsg * @param isOrdered * @throws QueryTestFailedException */ private static void compareResults(final TestCase testCase, final ExpectedResultsHolder actualResults, final ExpectedResultsHolder expectedResults, final String eMsg, boolean isOrdered) throws QueryTestFailedException { // if (actualResults.isException() && expectedResults.isException()) { // // Compare exceptions // compareExceptions(actualResults, expectedResults, eMsg); // } else // if (actualResults.isResult() && expectedResults.isResult()) { // Compare results if (isOrdered == false && actualResults.hasRows() && expectedResults.hasRows()) { // If the results are not ordered, we can sort both // results and expected results to compare record for record // Otherwise, actual and expected results are already assumed // to be in same order // sort the sortedResults in ascending order final List actualRows = actualResults.getRows(); sortRecords(actualRows, true); actualResults.setRows(actualRows); // sort the expectedResults with ascending order final List expectedRows = expectedResults.getRows(); sortRecords(expectedRows, true); expectedResults.setRows(expectedRows); } compareResultSets(actualResults.getRows(), actualResults.getTypes(), actualResults.getIdentifiers(), expectedResults.getRows(), expectedResults.getTypes(), expectedResults.getIdentifiers(), eMsg); long a = actualResults.getExecutionTime(); long e = expectedResults.getExecutionTime(); if (exec_minumin_time > 0 && e > exec_minumin_time && a > e) { double allowediff = e * (exceed_percent / 100); ClientPlugin.LOGGER.info("EXEC MIN TIME: " + exec_minumin_time + " EXEC PER: " + exceed_percent + " expected exec time: " + expectedResults.getExecutionTime()); ClientPlugin.LOGGER.info(" expected exec time: " + e + " actual exec time: " + a); if ((a - allowediff) > e) { String msg = "Actual: " + a + " Expected: " + e + " Diff: " + (a - e) + " Allowed %: " + (exceed_percent / 100) + " (" + allowediff + ")"; QueryTestFailedException f = new QueryTestFailedException(eMsg + msg); //$NON-NLS-1$ testCase.getTestResult().setException(f); testCase.getTestResult().setExceptionMessage(msg); testCase.getTestResult().setStatus(TestResult.RESULT_STATE.TEST_EXECUTION_TIME_EXCEEDED_EXCEPTION); } } } /** * sort one result that is composed of records of all columns * @param records * @param ascending */ private static void sortRecords(List records, boolean ascending) { // if record's size == 0, don't need to sort if (records.size() != 0) { int nFields = ((List) records.get(0)).size(); int[] params = new int[(nFields > 3 ? 3 : nFields)]; for (int k = 0, j = 0; k < params.length; k++, j++) { params[j] = k; } if (nFields > 0) { Collections.sort(records, new ListNestedSortComparator(params, ascending)); } } } private static void compareExceptions(final ExpectedResultsHolder actualResults, final ExpectedResultsHolder expectedResults, String eMsg) throws QueryTestFailedException { final String expectedExceptionClass = expectedResults.getExceptionClassName(); final String expectedExceptionMsg = expectedResults.getExceptionMsg().toLowerCase(); final String actualExceptionClass = actualResults.getExceptionClassName(); final String actualExceptionMsg = actualResults.getExceptionMsg().toLowerCase(); if (actualExceptionClass == null) { // We didn't get an actual exception, we should have throw new QueryTestFailedException(eMsg + "Expected exception: " //$NON-NLS-1$ + expectedExceptionClass + " but got none."); //$NON-NLS-1$ } // Compare exception classes if (!expectedExceptionClass.equals(actualExceptionClass)) { throw new QueryTestFailedException(eMsg + "Got wrong exception, expected \"" //$NON-NLS-1$ + expectedExceptionClass + "\" but got \"" + //$NON-NLS-1$ actualExceptionClass + "\""); //$NON-NLS-1$ } // Compare exception messages if (expectedResults.isExceptionContains()) { if (actualExceptionMsg.indexOf(expectedExceptionMsg) > -1) { throw new QueryTestFailedException(eMsg + "Expected exception message " + expectedExceptionMsg //$NON-NLS-1$ + " is not contained in actual exception of " + actualExceptionMsg); } } else if (expectedResults.isExceptionStartsWith()) { if (!actualExceptionMsg.startsWith(expectedExceptionMsg)) { throw new QueryTestFailedException(eMsg + "Actual exception message " + actualExceptionMsg //$NON-NLS-1$ + " does not start with the expected exception of " + expectedExceptionMsg); } } else { if (!expectedExceptionMsg.equals(actualExceptionMsg)) { // Give it another chance by comparing w/o line separators if (!compareStrTokens(expectedExceptionMsg, actualExceptionMsg)) { throw new QueryTestFailedException( eMsg + "Got expected exception but with wrong message. Got " + actualExceptionMsg); //$NON-NLS-1$ } } } } private static boolean compareStrTokens(String expectedStr, String gotStr) { String[] expectedTokens = StringUtils.split(expectedStr, newline); String[] gotTokens = StringUtils.split(gotStr, newline); if (expectedTokens.length != gotTokens.length) return false; for (int i = 0; i < expectedTokens.length; i++) { String expected = expectedTokens[i]; String got = gotTokens[i]; if (!expected.equals(got)) { return false; } } return true; } /** * Compare actual results, identifiers and types with expected. <br> * <strong>Note </strong>: result list are expected to match element for * element.</br> * * @param actualResults * @param actualDatatypes * @param actualIdentifiers * @param expectedResults * @param expectedDatatypes * @param expectedIdentifiers * @param eMsg * @throws QueryTestFailedException * If comparison fails. */ private static void compareResultSets(final List actualResults, final List actualDatatypes, final List actualIdentifiers, final List expectedResults, final List expectedDatatypes, final List expectedIdentifiers, final String eMsg) throws QueryTestFailedException { // Compare column names and types compareIdentifiers(actualIdentifiers, expectedIdentifiers, actualDatatypes, expectedDatatypes); // Walk through records and compare actual against expected final int actualRowCount = actualResults.size(); final int expectedRowCount = expectedResults.size(); final int actualColumnCount = actualIdentifiers.size(); // Check for less records than in expected results if (actualRowCount < expectedRowCount) { throw new QueryTestFailedException(eMsg + "Expected " + expectedRowCount + //$NON-NLS-1$ " records but received only " + actualRowCount); //$NON-NLS-1$ } else if (actualRowCount > expectedRowCount) { // Check also for more records than expected throw new QueryTestFailedException(eMsg + "Expected " + expectedRowCount + //$NON-NLS-1$ " records but received " + actualRowCount); //$NON-NLS-1$ } // DEBUG: // debugOut.println("================== Compariing Rows ==================="); // Loop through rows for (int row = 0; row < actualRowCount; row++) { // Get actual record final List actualRecord = (List) actualResults.get(row); // Get expected record final List expectedRecord = (List) expectedResults.get(row); // DEBUG: // debugOut.println("Row: " + (row + 1)); // debugOut.println(" expectedRecord: " + expectedRecord); // debugOut.println(" actualRecord: " + actualRecord); // Loop through columns // Compare actual elements with expected elements column by column // in this row for (int col = 0; col < actualColumnCount; col++) { // Get actual value Object actualValue = actualRecord.get(col); // Get expected value Object expectedValue = expectedRecord.get(col); // DEBUG: // debugOut.println(" Col: " +(col +1) + ": expectedValue:[" + // expectedValue + "] actualValue:[" + actualValue + // "]"); // Compare these values if ((expectedValue == null && actualValue != null) || (actualValue == null && expectedValue != null)) { // Compare nulls throw new QueryTestFailedException(eMsg + "Value mismatch at row " + (row + 1) //$NON-NLS-1$ + " and column " + (col + 1) //$NON-NLS-1$ + ": expected = [" //$NON-NLS-1$ + (expectedValue != null ? expectedValue : "null") + "], actual = [" //$NON-NLS-1$ + (actualValue != null ? actualValue : "null") + "]"); //$NON-NLS-1$ } if (expectedValue == null && actualValue == null) { continue; } if (actualValue instanceof Blob || actualValue instanceof Clob || actualValue instanceof SQLXML) { if (actualValue instanceof Clob) { Clob c = (Clob) actualValue; try { actualValue = ObjectConverterUtil.convertToString(c.getAsciiStream()); } catch (Throwable e) { // TODO Auto-generated catch block throw new QueryTestFailedException(e); } } else if (actualValue instanceof Blob) { Blob b = (Blob) actualValue; try { byte[] ba = ObjectConverterUtil.convertToByteArray(b.getBinaryStream()); actualValue = String.valueOf(ba.length); // actualValue = // ObjectConverterUtil.convertToString(b.getBinaryStream()); } catch (Throwable e) { // TODO Auto-generated catch block throw new QueryTestFailedException(e); } } else if (actualValue instanceof SQLXML) { SQLXML s = (SQLXML) actualValue; try { actualValue = ObjectConverterUtil.convertToString(s.getBinaryStream()); } catch (Throwable e) { // TODO Auto-generated catch block throw new QueryTestFailedException(e); } } if (!(expectedValue instanceof String)) { expectedValue = expectedValue.toString(); } } // Compare values with equals if (!expectedValue.equals(actualValue)) { // DEBUG: if (expectedValue instanceof java.sql.Date) { expectedValue = expectedValue.toString(); actualValue = actualValue.toString(); } else if (expectedValue instanceof java.sql.Time) { expectedValue = expectedValue.toString(); actualValue = actualValue.toString(); } if (expectedValue instanceof String) { final String expectedString = (String) expectedValue; if (!(actualValue instanceof String)) { throw new QueryTestFailedException(eMsg + "Value (types) mismatch at row " + (row + 1) //$NON-NLS-1$ + " and column " + (col + 1) //$NON-NLS-1$ + ": expected = [" //$NON-NLS-1$ + expectedValue + ", (String) ], actual = [" //$NON-NLS-1$ + actualValue + ", (" + actualValue.getClass().getName() + ") ]"); //$NON-NLS-1$ } // Check for String difference assertStringsMatch(expectedString, (String) actualValue, (row + 1), (col + 1), eMsg); } else { throw new QueryTestFailedException(eMsg + "Value mismatch at row " + (row + 1) //$NON-NLS-1$ + " and column " + (col + 1) //$NON-NLS-1$ + ": expected = [" //$NON-NLS-1$ + expectedValue + "], actual = [" //$NON-NLS-1$ + actualValue + "]"); //$NON-NLS-1$ } } } // end loop through columns } // end loop through rows } private static void compareIdentifiers(List actualIdentifiers, List expectedIdentifiers, List actualDataTypes, List expectedDatatypes) throws QueryTestFailedException { // Check sizes if (expectedIdentifiers.size() != actualIdentifiers.size()) { throw new QueryTestFailedException( "Got incorrect number of columns, expected = " + expectedIdentifiers.size() + ", actual = " //$NON-NLS-1$ //$NON-NLS-2$ + actualIdentifiers.size()); } // Compare identifier lists only by short name for (int i = 0; i < actualIdentifiers.size(); i++) { String actualIdent = (String) actualIdentifiers.get(i); String expectedIdent = (String) expectedIdentifiers.get(i); String actualType = (String) actualDataTypes.get(i); String expectedType = (String) expectedDatatypes.get(i); // Get short name for each identifier String actualShort = getShortName(actualIdent); String expectedShort = getShortName(expectedIdent); if (!expectedShort.equalsIgnoreCase(actualShort)) { throw new QueryTestFailedException( "Got incorrect column name at column " + i + ", expected = " + expectedShort + " but got = " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + actualShort); } // if (actualType.equalsIgnoreCase("xml")) {//$NON-NLS-1$ // actualType = "string";//$NON-NLS-1$ // } // if (actualType.equalsIgnoreCase("clob")) {//$NON-NLS-1$ // actualType = "string";//$NON-NLS-1$ // } if (actualType.equalsIgnoreCase("blob")) { Class nodeType = (Class) TagNames.TYPE_MAP.get(actualType); actualType = nodeType.getSimpleName(); } if (!expectedType.equalsIgnoreCase(actualType)) { throw new QueryTestFailedException( "Got incorrect column type at column " + i + ", expected = " + expectedType + " but got = " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + actualType); } } } private static String getShortName(String ident) { int index = ident.lastIndexOf("."); //$NON-NLS-1$ if (index >= 0) { return ident.substring(index + 1); } return ident; } private static final int MISMATCH_OFFSET = 20; private static final int MAX_MESSAGE_SIZE = 50; private static void assertStringsMatch(final String expectedStr, final String actualStr, final int row, final int col, final String eMsg) throws QueryTestFailedException { // TODO: Replace stripCR() with XMLUnit comparison for XML results. // stripCR() is a workaround for comparing XML Queries // that have '\r'. String expected = stripCR(expectedStr).trim(); String actual = stripCR(actualStr).trim(); String locationText = ""; //$NON-NLS-1$ int mismatchIndex = -1; boolean isequal = Arrays.equals(expected.toCharArray(), actual.toCharArray()); // if (!expected.equals(actual)) { if (!isequal) { if (expected != null && actual != null) { int shortestStringLength = expected.length(); if (actual.length() < expected.length()) { shortestStringLength = actual.length(); } for (int i = 0; i < shortestStringLength; i++) { if (expected.charAt(i) != actual.charAt(i)) { locationText = " Strings do not match at character: " + (i + 1) + //$NON-NLS-1$ ". Expected [" + expected.charAt(i) + "] in " + expected + " - but got [" //$NON-NLS-2$//$NON-NLS-3$ + actual.charAt(i) + "] in " + actual; //$NON-NLS-1$ mismatchIndex = i; break; } } } String expectedPartOfMessage = expected; String actualPartOfMessage = actual; if (expected.length() + actual.length() > MAX_MESSAGE_SIZE) { expectedPartOfMessage = safeSubString(expected, mismatchIndex - MISMATCH_OFFSET, mismatchIndex + MISMATCH_OFFSET); actualPartOfMessage = safeSubString(actual, mismatchIndex - MISMATCH_OFFSET, mismatchIndex + MISMATCH_OFFSET); } String message = eMsg + " String mismatch at row " + row + //$NON-NLS-1$ " and column " + col + //$NON-NLS-1$ ". Expected: {0} but was: {1}" + locationText; //$NON-NLS-1$ message = MessageFormat.format(message, new Object[] { expectedPartOfMessage, actualPartOfMessage }); throw new QueryTestFailedException(message); } } private static String safeSubString(String text, int startIndex, int endIndex) { String prefix = "...'"; //$NON-NLS-1$ String suffix = "'..."; //$NON-NLS-1$ int actualStartIndex = startIndex; if (actualStartIndex < 0) { actualStartIndex = 0; prefix = "'"; //$NON-NLS-1$ } int actualEndIndex = endIndex; if (actualEndIndex > text.length() - 1) { actualEndIndex = text.length() - 1; if (actualEndIndex < 0) { actualEndIndex = 0; } } if (actualEndIndex == text.length() - 1 || text.length() == 0) { suffix = "'"; //$NON-NLS-1$ } return prefix + text.substring(actualStartIndex, actualEndIndex) + suffix; } private static String stripCR(final String text) { if (text.indexOf('\r') >= 0) { StringBuffer stripped = new StringBuffer(text.length()); int len = text.length(); for (int i = 0; i < len; i++) { char current = text.charAt(i); if (current != '\r') { stripped.append(current); } } return stripped.toString(); } return text; } }