/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.test;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;

import org.hsqldb.lib.ArraySort;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.LineGroupReader;
import org.hsqldb.lib.StopWatch;
import org.hsqldb.lib.StringComparator;
import org.hsqldb.lib.StringUtil;

Utility class providing methodes for submitting test statements or scripts to the database, comparing the results returned with the expected results. The test script format is compatible with existing scripts. Script writers be aware that you can't use stderr to distinguish error messages. This class writes error messages to stdout.
Author:Ewan Slater (ewanslater@users dot sourceforge.net), Fred Toussi (fredt@users dot sourceforge.net)
/** * Utility class providing methodes for submitting test statements or * scripts to the database, comparing the results returned with * the expected results. The test script format is compatible with existing * scripts. * * Script writers be aware that you can't use stderr to distinguish error * messages. This class writes error messages to stdout. * * @author Ewan Slater (ewanslater@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) */
public class TestUtil { /* * The executing scripts do have state. This class should be * redesigned with OOD. */ static private final SimpleDateFormat sdfYMDHMS = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); static private boolean abortOnErr = false; static final private String TIMESTAMP_VAR_STR = "${timestamp}"; static final String LS = System.getProperty("line.separator", "\n"); static final boolean oneSessionOnly = false; public static void main(String[] argv) { StopWatch sw = new StopWatch(true); TestUtil.testScripts("testrun/hsqldb", sw); System.out.println(sw.currentElapsedTimeToMessage("Total time :")); } public static void deleteDatabase(String path) { FileUtil.deleteOrRenameDatabaseFiles(path); } public static boolean delete(String file) { return new File(file).delete(); } public static void checkDatabaseFilesDeleted(String path) { File[] list = FileUtil.getDatabaseFileList(path); if (list.length != 0) { System.out.println("database files not deleted"); } }
Expand occurrences of "${timestamp}" in input to time stamps.
/** * Expand occurrences of "${timestamp}" in input to time stamps. */
static protected void expandStamps(StringBuilder sb) { int i = sb.indexOf(TIMESTAMP_VAR_STR); if (i < 1) { return; } String timestamp; synchronized (sdfYMDHMS) { timestamp = sdfYMDHMS.format(new java.util.Date()); } while (i > -1) { sb.replace(i, i + TIMESTAMP_VAR_STR.length(), timestamp); i = sb.indexOf(TIMESTAMP_VAR_STR); } } static void testScripts(String directory, StopWatch sw) { TestUtil.deleteDatabase("test1"); try { Class.forName("org.hsqldb.jdbc.JDBCDriver"); String url = "jdbc:hsqldb:test1;sql.enforce_strict_size=true"; String user = "sa"; String password = ""; Connection cConnection = null; String[] filelist; String absolute = new File(directory).getAbsolutePath(); filelist = new File(absolute).list(); ArraySort.sort((Object[]) filelist, filelist.length, new StringComparator()); for (int i = 0; i < filelist.length; i++) { String fname = filelist[i]; if (fname.startsWith("TestSelf") && fname.endsWith(".txt")) { long elapsed = sw.elapsedTime(); if (!oneSessionOnly || cConnection == null) { cConnection = DriverManager.getConnection(url, user, password); } print("Opened DB in " + (double) (sw.elapsedTime() - elapsed) / 1000 + " s"); testScript(cConnection, absolute + File.separator + fname); if (!oneSessionOnly) { cConnection.close(); } } } } catch (Exception e) { e.printStackTrace(); print("TestUtil init error: " + e.toString()); } } static void testScript(Connection aConnection, String aPath) { /* * This is a legacy wrapper method which purposefully inherits the sins * of the original. * No indication is given to the invoker of even RuntimeExceptions. */ File file = new File(aPath); try { TestUtil.testScript(aConnection, file.getAbsolutePath(), new FileReader(file)); } catch (Exception e) { e.printStackTrace(); System.out.println("test script file error: " + e.toString()); } }
Runs a preformatted script.

Where a result set is required, each line in the script will be interpreted as a seperate expected row in the ResultSet returned by the query. Within each row, fields should be delimited using either comma (the default), or a user defined delimiter which should be specified in the System property TestUtilFieldDelimiter

Params:
  • aConnection – Connection object for the database
  • sourceName – Identifies the script which failed
  • inReader – Source of commands to be tested
/** * Runs a preformatted script.<p> * * Where a result set is required, each line in the script will * be interpreted as a seperate expected row in the ResultSet * returned by the query. Within each row, fields should be delimited * using either comma (the default), or a user defined delimiter * which should be specified in the System property TestUtilFieldDelimiter * @param aConnection Connection object for the database * @param sourceName Identifies the script which failed * @param inReader Source of commands to be tested */
public static void testScript(Connection aConnection, String sourceName, Reader inReader) throws SQLException, IOException { Statement statement = aConnection.createStatement(); LineNumberReader reader = new LineNumberReader(inReader); LineGroupReader sqlReader = new LineGroupReader(reader); int startLine = 0; System.out.println("Opened test script file: " + sourceName); /** * we read the lines from the start of one section of the script "/*" * until the start of the next section, collecting the lines in the * list. * When a new section starts, we pass the list of lines * to the test method to be processed. */ try { while (true) { HsqlArrayList section = sqlReader.getSection(); startLine = sqlReader.getStartLineNumber(); if (section.size() == 0) { break; } testSection(statement, section, sourceName, startLine); } statement.close(); // The following catch blocks are just to report the source location // of the failure. } catch (SQLException se) { System.out.println("Error encountered at command beginning at " + sourceName + ':' + startLine); throw se; } catch (RuntimeException re) { System.out.println("Error encountered at command beginning at " + sourceName + ':' + startLine); throw re; } System.out.println("Processed " + reader.getLineNumber() + " lines from " + sourceName); }
Legacy wrapper
/** Legacy wrapper */
static void test(Statement stat, String s, int line) { TestUtil.test(stat, s, null, line); }
Performs a preformatted statement or group of statements and throws if the result does not match the expected one.
Params:
  • line – start line in the script file for this test
  • stat – Statement object used to access the database
  • sourceName – Identifies the script which failed
  • s – Contains the type, expected result and SQL for the test
/** * Performs a preformatted statement or group of statements and throws * if the result does not match the expected one. * @param line start line in the script file for this test * @param stat Statement object used to access the database * @param sourceName Identifies the script which failed * @param s Contains the type, expected result and SQL for the test */
static void test(Statement stat, String s, String sourceName, int line) { //maintain the interface for this method HsqlArrayList section = new HsqlArrayList(new String[8], 0); section.add(s); testSection(stat, section, sourceName, line); }
Method to save typing ;-) This method does not distinguish between normal and error output.
Params:
  • s – String to be printed
/** * Method to save typing ;-) * This method does not distinguish between normal and error output. * * @param s String to be printed */
static void print(String s) { System.out.println(s); }
Takes a discrete section of the test script, contained in the section vector, splits this into the expected result(s) and submits the statement to the database, comparing the results returned with the expected results. If the actual result differs from that expected, or an exception is thrown, then the appropriate message is printed.
Params:
  • stat – Statement object used to access the database
  • section – Vector of script lines containing a discrete section of script (i.e. test type, expected results, SQL for the statement).
  • line – line of the script file where this section started
/** * Takes a discrete section of the test script, contained in the * section vector, splits this into the expected result(s) and * submits the statement to the database, comparing the results * returned with the expected results. * If the actual result differs from that expected, or an * exception is thrown, then the appropriate message is printed. * @param stat Statement object used to access the database * @param section Vector of script lines containing a discrete * section of script (i.e. test type, expected results, * SQL for the statement). * @param line line of the script file where this section started */
private static void testSection(Statement stat, HsqlArrayList section, String scriptName, int line) { //create an appropriate instance of ParsedSection ParsedSection pSection = parsedSectionFactory(section); if (pSection == null) { //it was not possible to sucessfully parse the section System.out.println( "The section starting at " + scriptName + ':' + line + " could not be parsed, and so was not processed." + LS); return; } if (pSection instanceof IgnoreParsedSection) { System.out.println("At " + scriptName + ':' + line + ": " + pSection.getResultString()); return; } if (pSection instanceof DisplaySection || pSection instanceof WaitSection || pSection instanceof ProceedSection) { String s = pSection.getResultString(); if (s != null) { // May or may not want to report line number for these sections? System.out.println(pSection.getResultString()); } } if (pSection instanceof DisplaySection) { return; // Do not run test method for DisplaySections. } if (!pSection.test(stat)) { System.out.println("Section starting at " + scriptName + ':' + line + " returned an unexpected result: " + pSection.getTestResultString()); if (TestUtil.abortOnErr) { throw new TestRuntimeException(scriptName + ": " + line + "pSection"); } } }
Factory method to create appropriate parsed section class for the section
Params:
  • sectionLines – Vector containing the section of script
Returns:a ParesedSection object
/** * Factory method to create appropriate parsed section class for the section * @param sectionLines Vector containing the section of script * @return a ParesedSection object */
private static ParsedSection parsedSectionFactory( HsqlArrayList sectionLines) { //type of the section char type = ' '; //read the first line of the Vector... String topLine = (String) sectionLines.get(0); //...and check it for the type... if (topLine.startsWith("/*")) { type = topLine.charAt(2); //if the type code is UPPERCASE and system property IgnoreCodeCase //has been set to true, make the type code lowercase if ((Character.isUpperCase(type)) && (Boolean.getBoolean("IgnoreCodeCase"))) { type = Character.toLowerCase(type); } //if the type code is invalid return null if (!ParsedSection.isValidCode(type)) { return null; } } //then pass this to the constructor for the ParsedSection class that //corresponds to the value of type switch (type) { case 'u' : { ParsedSection section = new UpdateParsedSection(sectionLines); if (TestUtil.oneSessionOnly) { if (section.getSql().toUpperCase().contains("SHUTDOWN")) { section = new IgnoreParsedSection(sectionLines, type); } } return section; } case 's' : return new SilentParsedSection(sectionLines); case 'w' : return new WaitSection(sectionLines); case 'p' : return new ProceedSection(sectionLines); case 'r' : return new ResultSetParsedSection(sectionLines); case 'o' : return new ResultSetOutputParsedSection(sectionLines); case 'c' : return new CountParsedSection(sectionLines); case 'd' : return new DisplaySection(sectionLines); case 'e' : return new ExceptionParsedSection(sectionLines); case ' ' : { ParsedSection section = new BlankParsedSection(sectionLines); if (TestUtil.oneSessionOnly) { if (section.getSql().toUpperCase().contains("SHUTDOWN")) { section = new IgnoreParsedSection(sectionLines, type); } } return section; } default : //if we arrive here, then we should have a valid code, //since we validated it earlier, so return an //IgnoreParsedSection object return new IgnoreParsedSection(sectionLines, type); } }
This method should certainly be an instance method. Can't do that until make this entire class OO.
/** * This method should certainly be an instance method. * * Can't do that until make this entire class OO. */
public static void setAbortOnErr(boolean aoe) { abortOnErr = aoe; } static class TestRuntimeException extends RuntimeException { public TestRuntimeException(String s) { super(s); } public TestRuntimeException(Throwable t) { super(t); } public TestRuntimeException(String s, Throwable t) { super(s, t); } } }
Abstract inner class representing a parsed section of script. The specific ParsedSections for each type of test should inherit from this.
/** * Abstract inner class representing a parsed section of script. * The specific ParsedSections for each type of test should inherit from this. */
abstract class ParsedSection { static final String LS = System.getProperty("line.separator", "\n");
Type of this test.
See Also:
  • for allowed values
/** * Type of this test. * @see #isValidCode(char) for allowed values */
protected char type = ' ';
error message for this section
/** error message for this section */
String message = null;
contents of the section as an array of Strings, one for each line in the section.
/** contents of the section as an array of Strings, one for each line in the section. */
protected String[] lines = null;
number of the last row containing results in sectionLines
/** number of the last row containing results in sectionLines */
protected int resEndRow = 0;
SQL query to be submitted to the database.
/** SQL query to be submitted to the database. */
protected String sqlString = null;
Constructor when the section's input lines do not need to be parsed into SQL.
/** * Constructor when the section's input lines do not need to be parsed * into SQL. */
protected ParsedSection() {}
Common constructor functions for this family.
Params:
  • linesArray – Array of the script lines containing the section of script. database
/** * Common constructor functions for this family. * @param linesArray Array of the script lines containing the section of script. * database */
protected ParsedSection(HsqlArrayList linesArray) { //read the lines array backwards to get out the SQL String //using a StringBuilder for efficency until we've got the whole String StringBuilder sqlBuff = new StringBuilder(); int endIndex = 0; int k; String s = (String) linesArray.get(0); if (s.startsWith("/*")) { //if, after stripping out the declaration from topLine, the length of topLine //is greater than 0, then keep the rest of the line, as the first row. //Otherwise it will be discarded, and the offset (between the array and the vector) //set to 1. if (s.length() == 3) { lines = (String[]) linesArray.toArray(1, linesArray.size()); } else { lines = (String[]) linesArray.toArray(); lines[0] = lines[0].substring(3); } k = lines.length - 1; do { //check to see if the row contains the end of the result set if ((endIndex = lines[k].indexOf("*/")) != -1) { //then this is the end of the result set sqlBuff.insert(0, lines[k].substring(endIndex + 2)); lines[k] = lines[k].substring(0, endIndex); if (lines[k].length() == 0) { resEndRow = k - 1; } else { resEndRow = k; } break; } else { sqlBuff.insert(0, lines[k]); } k--; } while (k >= 0); } else { lines = (String[]) linesArray.toArray(); for (k = 0; k < lines.length; k++) { sqlBuff.append(lines[k]); sqlBuff.append(LS); } } //set sqlString value sqlString = sqlBuff.toString(); }
String representation of this ParsedSection
Returns:String representation of this ParsedSection
/** * String representation of this ParsedSection * @return String representation of this ParsedSection */
protected String getTestResultString() { StringBuilder b = new StringBuilder(); b.append(LS + "******" + LS); b.append("Type: "); b.append(getType()).append(LS); b.append("SQL: ").append(getSql()).append(LS); b.append("expected results:").append(LS); b.append(getResultString()).append(LS); //check to see if the message field has been populated if (getMessage() != null) { b.append(LS + "message:").append(LS); b.append(getMessage()).append(LS); } b.append("actual results:").append(LS); b.append(getActualResultString()); b.append(LS + "******" + LS); return b.toString(); }
returns a String representation of the expected result for the test
Returns:The expected result(s) for the test
/** * returns a String representation of the expected result for the test * @return The expected result(s) for the test */
protected abstract String getResultString();
returns a String representation of the actual result for the test
Returns:The expected result(s) for the test
/** * returns a String representation of the actual result for the test * @return The expected result(s) for the test */
protected String getActualResultString() { return ""; }
returns the error message for the section
Returns:message
/** * returns the error message for the section * * @return message */
protected String getMessage() { return message; }
returns the type of this section
Returns:type of this section
/** * returns the type of this section * @return type of this section */
protected char getType() { return type; }
returns the SQL statement for this section
Returns:SQL statement for this section
/** * returns the SQL statement for this section * @return SQL statement for this section */
protected String getSql() { return sqlString; }
performs the test contained in the section against the database.
Params:
  • aStatement – Statement object
Returns:true if the result(s) are as expected, otherwise false
/** * performs the test contained in the section against the database. * @param aStatement Statement object * @return true if the result(s) are as expected, otherwise false */
protected boolean test(Statement aStatement) { try { String sql = getSql(); aStatement.execute(sql); } catch (Exception x) { message = x.toString(); return false; } return true; }
Checks that the type code letter is valid
Params:
  • aCode – Lower-cased type code to validate.
Returns:true if the type code is valid, otherwise false.
/** * Checks that the type code letter is valid * @param aCode Lower-cased type code to validate. * @return true if the type code is valid, otherwise false. */
protected static boolean isValidCode(char aCode) { /* Allowed values for test codes are: * (note that UPPERCASE codes, while valid are only processed if the * system property IgnoreCodeCase has been set to true) * * 'u' - update * 'c' - count * 'e' - exception * 'r' - results * 'w' - wait * 'p' - proceed * 's' - silent * 'd' - display (No reason to use upper-case). * ' ' - not a test */ switch (aCode) { case ' ' : case 'r' : case 'o' : case 'e' : case 'c' : case 'u' : case 's' : case 'd' : case 'w' : case 'p' : return true; } return false; } }
Represents a ParsedSection for a ResultSet test
/** Represents a ParsedSection for a ResultSet test */
class ResultSetParsedSection extends ParsedSection { private String delim = System.getProperty("TestUtilFieldDelimiter", ","); private String[] expectedRows = null; private String[] actualRows = null;
constructs a new instance of ResultSetParsedSection, interpreting the supplied results as one or more lines of delimited field values
/** * constructs a new instance of ResultSetParsedSection, interpreting * the supplied results as one or more lines of delimited field values */
protected ResultSetParsedSection(HsqlArrayList linesArray) { super(linesArray); type = 'r'; //now we'll populate the expectedResults array expectedRows = new String[(resEndRow + 1)]; for (int i = 0; i <= resEndRow; i++) { int skip = StringUtil.skipSpaces(lines[i], 0); expectedRows[i] = lines[i].substring(skip); } } protected String getResultString() { StringBuilder printVal = new StringBuilder(); String[] expectedRows = getExpectedRows(); for (int i = 0; i < expectedRows.length; i++) { printVal.append(expectedRows[i]).append(LS); } return printVal.toString(); } protected String getActualResultString() { StringBuilder printVal = new StringBuilder(); String[] actualRows = getActualRows(); if (actualRows == null) { return "no result"; } for (int i = 0; i < actualRows.length; i++) { printVal.append(actualRows[i]).append(LS); } return printVal.toString(); } protected boolean test(Statement aStatement) { try { try { //execute the SQL aStatement.execute(getSql()); } catch (SQLException s) { throw new Exception("Expected a ResultSet, but got the error: " + s.getMessage()); } //check that update count != -1 if (aStatement.getUpdateCount() != -1) { throw new Exception( "Expected a ResultSet, but got an update count of " + aStatement.getUpdateCount()); } //iterate over the ResultSet HsqlArrayList list = new HsqlArrayList(new String[1][], 0); ResultSet results = aStatement.getResultSet(); int colCount = results.getMetaData().getColumnCount(); while (results.next()) { String[] row = new String[colCount]; for (int i = 0; i < colCount; i++) { row[i] = results.getString(i + 1); } list.add(row); } results.close(); actualRows = new String[list.size()]; for (int i = 0; i < list.size(); i++) { String[] row = (String[]) list.get(i); StringBuilder sb = new StringBuilder(); for (int j = 0; j < row.length; j++) { if (j > 0) { sb.append(','); } sb.append(row[j]); } actualRows[i] = sb.toString(); } String[] expectedRows = getExpectedRows(); int count = 0; for (; count < list.size(); count++) { if (count < expectedRows.length) { String[] expectedFields = StringUtil.split(expectedRows[count], delim); // handle ARRAY[val,val, val] commas for (int i = 0; i < expectedFields.length; i++) { if (expectedFields[i] == null) { expectedFields = (String[]) ArrayUtil.resizeArray( expectedFields, i); break; } if (expectedFields[i].startsWith("ARRAY[")) { if (expectedFields[i].endsWith("]")) { continue; } for (int j = i + 1; j < expectedFields.length; j++) { String part = expectedFields[j]; expectedFields[i] += delim + part; if (part.endsWith("]")) { ArrayUtil.adjustArray( ArrayUtil.CLASS_CODE_OBJECT, expectedFields, expectedFields.length, i + 1, i - j); break; } } } } //check that we have the number of columns expected... if (colCount == expectedFields.length) { //...and if so, check that the column values are as expected... int j = 0; for (int i = 0; i < expectedFields.length; i++) { j = i + 1; String actual = ((String[]) list.get(count))[i]; //...including null values... if (actual == null) { //..then we have a null //...check to see if we were expecting it... if (!expectedFields[i].equalsIgnoreCase( "NULL")) { message = "Expected row " + (count + 1) + " of the ResultSet to contain:" + LS + expectedRows[count] + LS + "but field " + j + " contained NULL"; break; } } else if (!actual.equals(expectedFields[i])) { //then the results are different message = "Expected row " + (count + 1) + " of the ResultSet to contain:" + LS + expectedRows[count] + LS + "but field " + j + " contained " + actual; break; } } } else { //we have the wrong number of columns message = "Expected the ResultSet to contain " + expectedFields.length + " fields, but it contained " + colCount + " fields."; } } if (message != null) { break; } } //check that we got as many rows as expected if (count != expectedRows.length) { if (message == null) { //we don't have the expected number of rows message = "Expected the ResultSet to contain " + expectedRows.length + " rows, but it contained " + count + " rows."; } } } catch (Exception x) { message = x.toString(); return false; } return message == null; } private String[] getExpectedRows() { return expectedRows; } private String[] getActualRows() { return actualRows; } }
Represents a ParsedSection for a ResultSet dump
/** Represents a ParsedSection for a ResultSet dump */
class ResultSetOutputParsedSection extends ParsedSection { private String delim = System.getProperty("TestUtilFieldDelimiter", ","); private String[] expectedRows = null;
constructs a new instance of ResultSetParsedSection, interpreting the supplied results as one or more lines of delimited field values
/** * constructs a new instance of ResultSetParsedSection, interpreting * the supplied results as one or more lines of delimited field values */
protected ResultSetOutputParsedSection(HsqlArrayList linesArray) { super(linesArray); type = 'o'; } protected String getResultString() { return ""; } protected boolean test(Statement aStatement) { try { try { //execute the SQL aStatement.execute(getSql()); } catch (SQLException s) { throw new Exception("Expected a ResultSet, but got the error: " + s.getMessage()); } //check that update count != -1 if (aStatement.getUpdateCount() != -1) { throw new Exception( "Expected a ResultSet, but got an update count of " + aStatement.getUpdateCount()); } //iterate over the ResultSet ResultSet results = aStatement.getResultSet(); StringBuilder printVal = new StringBuilder(); while (results.next()) { for (int j = 0; j < results.getMetaData().getColumnCount(); j++) { if (j != 0) { printVal.append(','); } printVal.append(results.getString(j + 1)); } printVal.append(LS); } throw new Exception(printVal.toString()); } catch (Exception x) { message = x.toString(); return false; } } private String[] getExpectedRows() { return expectedRows; } }
Represents a ParsedSection for an update test
/** Represents a ParsedSection for an update test */
class UpdateParsedSection extends ParsedSection { //expected update count int countWeWant; protected UpdateParsedSection(HsqlArrayList linesArray) { super(linesArray); type = 'u'; countWeWant = Integer.parseInt(lines[0]); } protected String getResultString() { return Integer.toString(getCountWeWant()); } private int getCountWeWant() { return countWeWant; } protected boolean test(Statement aStatement) { try { try { //execute the SQL aStatement.execute(getSql()); } catch (SQLException s) { throw new Exception("Expected an update count of " + getCountWeWant() + ", but got the error: " + s.getMessage()); } if (aStatement.getUpdateCount() != getCountWeWant()) { throw new Exception("Expected an update count of " + getCountWeWant() + ", but got an update count of " + aStatement.getUpdateCount() + "."); } } catch (Exception x) { message = x.toString(); return false; } return true; } } class WaitSection extends ParsedSection { /* Would love to have a setting to say whether multi-thread mode, * but the static design of TestUtil prevents that. * a W command will cause a non-threaded execution to wait forever. */ static private String W_SYNTAX_MSG = "Syntax of Wait commands:" + LS + " /*w 123*/ To Wait 123 milliseconds" + LS + " /*w false x*/ Wait until /*p*/ command in another script has executed" + LS + " /*w true x*/ Same, but the /*p*/ must not have executed yet";
Represents a ParsedSection for wait execution
/** Represents a ParsedSection for wait execution */
long sleepTime = -1; Waiter waiter = null; boolean enforceSequence = false; protected WaitSection(HsqlArrayList linesArray) { /* Can't user the super constructor, since it does funny things when * constructing the SQL Buffer, which we don't need. */ lines = (String[]) linesArray.toArray(); int closeCmd = lines[0].indexOf("*/"); String cmd = lines[0].substring(0, closeCmd); lines[0] = lines[0].substring(closeCmd + 2).trim(); String trimmed = cmd.trim(); if (trimmed.indexOf('e') < 0 && trimmed.indexOf('E') < 0) { // Does not contain "true" or "false" sleepTime = Long.parseLong(trimmed); } else { try { // Would like to use String.split(), but don't know if Java 4 // is allowed here. // Until we can use Java 4, prohibit tabs as white space. int index = trimmed.indexOf(' '); if (index < 0) { throw new IllegalArgumentException(); } enforceSequence = Boolean.valueOf(trimmed.substring(0, index)).booleanValue(); waiter = Waiter.getWaiter(trimmed.substring(index).trim()); } catch (IllegalArgumentException ie) { throw new IllegalArgumentException(W_SYNTAX_MSG); } } type = 'w'; } protected String getResultString() { StringBuilder sb = new StringBuilder(); if (lines.length == 1 && lines[0].trim().length() < 1) { return null; } for (int i = 0; i < lines.length; i++) { if (i > 0) { sb.append(LS); } sb.append("+ " + lines[i]); } TestUtil.expandStamps(sb); return sb.toString().trim(); } protected boolean test(Statement aStatement) { if (waiter == null) { try { //System.err.println("Sleeping for " + sleepTime + " ms."); Thread.sleep(sleepTime); } catch (InterruptedException ie) { throw new RuntimeException("Test sleep interrupted", ie); } } else { waiter.waitFor(enforceSequence); } return true; } } class ProceedSection extends ParsedSection { /* See comment above for WaitSection */ static private String P_SYNTAX_MSG = "Syntax of Proceed commands:" + LS + " /*p false x*/ /*p*/ command in another script may Proceed" + LS + " /*p true x*/ Same, but the /*w*/ must be waiting when we execute /*p*/" ;
Represents a ParsedSection for wait execution
/** Represents a ParsedSection for wait execution */
Waiter waiter = null; boolean enforceSequence = false; protected ProceedSection(HsqlArrayList linesArray) { /* Can't use the super constructor, since it does funny things when * constructing the SQL Buffer, which we don't need. */ lines = (String[]) linesArray.toArray(); int closeCmd = lines[0].indexOf("*/"); String cmd = lines[0].substring(0, closeCmd); lines[0] = lines[0].substring(closeCmd + 2).trim(); String trimmed = cmd.trim(); try { // Would like to use String.split(), but don't know if Java 4 // is allowed here. // Until we can use Java 4, prohibit tabs as white space. int index = trimmed.indexOf(' '); if (index < 0) { throw new IllegalArgumentException(); } enforceSequence = Boolean.valueOf(trimmed.substring(0, index)).booleanValue(); waiter = Waiter.getWaiter(trimmed.substring(index).trim()); } catch (IllegalArgumentException ie) { throw new IllegalArgumentException(P_SYNTAX_MSG); } type = 'p'; } protected String getResultString() { StringBuilder sb = new StringBuilder(); if (lines.length == 1 && lines[0].trim().length() < 1) { return ""; } for (int i = 0; i < lines.length; i++) { if (i > 0) { sb.append(LS); } sb.append("+ " + lines[i]); } TestUtil.expandStamps(sb); return sb.toString().trim(); } protected boolean test(Statement aStatement) { waiter.resume(enforceSequence); return true; } }
Represents a ParsedSection for silent execution
/** Represents a ParsedSection for silent execution */
class SilentParsedSection extends ParsedSection { protected SilentParsedSection(HsqlArrayList linesArray) { super(linesArray); type = 's'; } protected String getResultString() { return ""; } protected boolean test(Statement aStatement) { try { aStatement.execute(getSql()); } catch (Exception x) {} return true; } }
Represents a ParsedSection for a count test
/** Represents a ParsedSection for a count test */
class CountParsedSection extends ParsedSection { //expected row count private int countWeWant; protected CountParsedSection(HsqlArrayList linesArray) { super(linesArray); type = 'c'; countWeWant = Integer.parseInt(lines[0]); } protected String getResultString() { return Integer.toString(getCountWeWant()); } private int getCountWeWant() { return countWeWant; } protected boolean test(Statement aStatement) { try { //execute the SQL try { aStatement.execute(getSql()); } catch (SQLException s) { throw new Exception("Expected a ResultSet containing " + getCountWeWant() + " rows, but got the error: " + s.getMessage()); } //check that update count != -1 if (aStatement.getUpdateCount() != -1) { throw new Exception( "Expected a ResultSet, but got an update count of " + aStatement.getUpdateCount()); } //iterate over the ResultSet ResultSet results = aStatement.getResultSet(); int count = 0; while (results.next()) { count++; } //check that we got as many rows as expected if (count != getCountWeWant()) { //we don't have the expected number of rows throw new Exception("Expected the ResultSet to contain " + getCountWeWant() + " rows, but it contained " + count + " rows."); } } catch (Exception x) { message = x.toString(); return false; } return true; } }
Represents a ParsedSection for an Exception test
/** Represents a ParsedSection for an Exception test */
class ExceptionParsedSection extends ParsedSection { private String expectedState = null; private Throwable caught = null; protected ExceptionParsedSection(HsqlArrayList linesArray) { super(linesArray); expectedState = lines[0].trim(); if (expectedState.length() < 1) { expectedState = null; } type = 'e'; } protected String getResultString() { return (caught == null) ? "Nothing thrown" : caught.toString(); } protected boolean test(Statement aStatement) { try { aStatement.execute(getSql()); } catch (SQLException sqlX) { caught = sqlX; if (expectedState == null || expectedState.equalsIgnoreCase(sqlX.getSQLState())) { return true; } message = "SQLState '" + sqlX.getSQLState() + "' : " + sqlX.toString() + " instead of '" + expectedState + "'"; } catch (Exception x) { caught = x; message = x.toString(); } return false; } }
Represents a ParsedSection for a section with blank type
/** Represents a ParsedSection for a section with blank type */
class BlankParsedSection extends ParsedSection { protected BlankParsedSection(HsqlArrayList linesArray) { super(linesArray); type = ' '; } protected String getResultString() { return ""; } }
Represents a ParsedSection that is to be ignored
/** Represents a ParsedSection that is to be ignored */
class IgnoreParsedSection extends ParsedSection { protected IgnoreParsedSection(HsqlArrayList sectionLines, char aType) { /* Extremely ambiguous to use input parameter of same exact * variable name as the superclass member "lines". * Therefore, renaming to inLines. */ // Inefficient to parse this into SQL when we aren't going to use // it as SQL. Should probably just be removed to use the // super() constructor. super(sectionLines); type = aType; } protected String getResultString() { return "This section, of type '" + getType() + "' was ignored"; } }
Represents a Section to be Displayed, not executed
/** Represents a Section to be Displayed, not executed */
class DisplaySection extends ParsedSection { protected DisplaySection(HsqlArrayList sectionLines) { /* Can't user the super constructor, since it does funny things when * constructing the SQL Buffer, which we don't need. */ lines = (String[]) sectionLines.toArray(); int firstSlash = lines[0].indexOf('/'); lines[0] = lines[0].substring(firstSlash + 1).trim(); } protected String getResultString() { StringBuilder sb = new StringBuilder(); if (lines.length == 1 && lines[0].trim().length() < 1) { return null; } for (int i = 0; i < lines.length; i++) { if (i > 0) { sb.append(LS); } sb.append("+ " + lines[i]); } TestUtil.expandStamps(sb); return sb.toString().trim(); } }