blob: 31b865121d632276dc28c3ebf583b832ee46dfe5 [file] [log] [blame]
/* Copyright (c) 2001-2010, 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.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.io.File;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.FileReader;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.lang.reflect.Method;
import org.hsqldb.test.TestUtil;
import org.hsqldb.lib.RCData;
/**
* @see #main
*/
class TestScriptRunner {
protected static final String DEFAULT_RCFILE = "testscriptrunner.rc";
static public String LS = System.getProperty("line.separator");
static public String SYNTAX_MSG =
"java " + TestScriptRunner.class.getName()
+ " [--optionalSwitches...] --urlid=URLID1 [script1.tsql [[--urlid=URLIDX] scriptY.tsql...]...]"
+ LS
+ " Specify one input file name as '-' to read from stdin." + LS
+ " No scripts specified will read from only stdin." + LS
+ " Simple single-threaded example with RC file '" + DEFAULT_RCFILE
+ "':" + LS
+ "java " + TestScriptRunner.class.getName()
+ "--urlid=URLID script1.tsql script2.tsql" + LS + LS
+ "OPTIONAL SWITCHES:" + LS
+ " --verbose Obviously..." + LS
+ " --threads Each script runs in a parallel thread (dflt. sequential)."
+ LS
+ " --rcfile=/path/to/file.rc (Defaults to '" + DEFAULT_RCFILE
+ "')" + LS
+ " --populate Use TestCacheSize class to populate one database" + LS
+ " --sqltool=URLID Invoke an interactive SqlTool session on given URLID" + LS
+ "(This last is useful for troubleshooting and interactive script dev).";
public boolean verbose = false;
public boolean threaded = false;
/**
* Executes specified SQL test scripts.
*
* Run <CODE>java org.hsqldb.util.TestScriptRunner</CODE> with no
* args to display syntax help.
*
* The TestCacheSize database population uses the database details
* as generated in TestCacheSize. It would be nice to get these
* from the RC file, but alas, TestCacheSize does much magical work
* based on components of the URL, for example. Therefore our user
* must make a URLID definition to match that generated by
* TestCacheSize for the DB type requested below. We must use that
* as the URLID for scripts (and/or SqlTool session) which we want
* to connect to the same database.
*/
static public void main(String[] sa) throws IOException, SQLException {
// Make a copy if argv so we can change it safely
int argIndex = 0;
boolean threaded = false;
boolean verbose = false;
boolean populate = false;
String rcFile = DEFAULT_RCFILE;
Map scriptFileMap = new HashMap(); // scriptname -> URLID
String currentUrlid = null;
String sqlToolUrlid = null;
Method sqlToolMainMethod = null;
try {
for (int i = 0; i < sa.length; i++) {
if (sa[i].equals("--verbose")) {
verbose = true;
continue;
}
if (sa[i].equals("--threads")) {
threaded = true;
continue;
}
if (sa[i].equals("--populate")) {
populate = true;
continue;
}
if (sa[i].startsWith("--rcfile=")) {
rcFile = sa[i].substring("--rcfile=".length());
continue;
}
if (sa[i].startsWith("--urlid=")) {
currentUrlid = sa[i].substring("--urlid=".length()); continue;
}
if (sa[i].startsWith("--sqltool=")) {
sqlToolUrlid = sa[i].substring("--sqltool=".length());
continue;
}
if (currentUrlid == null) {
throw new IllegalArgumentException(
"You must specify 'urlid' before script files.");
}
if (scriptFileMap.containsKey(sa[i]))
throw new IllegalArgumentException(
TestScriptRunner.class.getName()
+ " can't handle the same script name twice. "
+ "(Just copy or sym-link the script).");
scriptFileMap.put(sa[i], currentUrlid);
}
if (currentUrlid == null) throw new IllegalArgumentException();
if (scriptFileMap.size() < 1) {
scriptFileMap.put("-", currentUrlid);
}
} catch (IllegalArgumentException e) {
if (e.getMessage() != null) System.err.println(e.getMessage());
System.err.println(SYNTAX_MSG);
System.exit(2);
}
if (sqlToolUrlid != null) {
Class sqlToolClass = null;
try {
sqlToolClass = Class.forName("org.hsqldb.util.SqlTool");
} catch (Exception e) {
System.err.println("SqlTool class not accessible. "
+ "Re-run without '--sqltool' switch.");
System.exit(3);
}
try {
sqlToolMainMethod = sqlToolClass.
getMethod("objectMain", new Class[] {sa.getClass()} );
} catch (Exception e) {
System.err.println("SqlTool integration failure: " + e);
System.exit(3);
}
}
TestScriptRunner runner = new TestScriptRunner(rcFile, scriptFileMap);
runner.setVerbose(verbose);
runner.setThreaded(threaded);
TestCacheSize tcs = populate ? populate() : null;
runner.establishConnections();
boolean success = runner.runScripts();
if (sqlToolMainMethod != null) try {
sqlToolMainMethod.invoke(null, new Object[] { new String[] {
"--rcfile=" + rcFile, sqlToolUrlid }});
} catch (Exception e) {
System.err.println("SqlTool failed: " + e);
e.printStackTrace();
}
if (tcs != null) tcs.tearDown();
System.exit(success ? 0 : 1);
}
List scriptRuns = new ArrayList();
private class ScriptRun extends Thread {
private Reader reader;
private Connection conn = null;
private RCData rcdata;
private boolean success = false;
public ScriptRun(String name, Reader reader, RCData rcdata) {
super(name);
this.reader = reader;
this.rcdata = rcdata;
}
public boolean getSuccess() {
return success;
}
public void connect() throws SQLException {
if (conn != null) {
throw new IllegalStateException("Thread '" + getName()
+ "' has already been connected");
}
try {
conn = rcdata.getConnection();
} catch (Exception e) {
throw new RuntimeException(
"Failed to connect to get JDBC connection for '"
+ getName() + "'", e);
}
conn.setAutoCommit(false);
System.out.println("ScriptRun '" + getName() + "' connected with "
+ RCData.tiToString(conn.getTransactionIsolation()) + '.');
}
public void run() {
try {
TestUtil.testScript(conn, getName(), reader);
success = true;
} catch (TestUtil.TestRuntimeException tre) {
System.err.println("Script '" + getName() + "' failed");
} catch (IOException ioe) {
System.err.println("Aborting thread for script '" + getName()
+ "' due to: " + ioe);
throw new RuntimeException(ioe);
} catch (SQLException se) {
System.err.println("Aborting thread for script '" + getName()
+ "' due to: " + se);
throw new RuntimeException(se);
} finally { try {
conn.close();
} catch (SQLException se) {
System.err.println("Failed to close JDBC connection for '"
+ getName() + "': " + se);
} }
}
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public void setThreaded(boolean threaded) {
this.threaded = threaded;
}
public TestScriptRunner(String rcFileString, Map scriptFileMap)
throws IOException {
TestUtil.setAbortOnErr(true);
Map rcdataMap = new HashMap();
File rcFile = new File(rcFileString);
if (!rcFile.isFile())
throw new IllegalArgumentException(
"RC file '" + rcFileString + "' not a file");
String scriptPath, urlid;
Iterator it;
File file;
Reader reader = null;
it = scriptFileMap.values().iterator();
while (it.hasNext()) {
urlid = (String) it.next();
if (rcdataMap.containsKey(urlid)) continue;
try {
rcdataMap.put(urlid, new RCData(rcFile, urlid));
} catch (Exception e) {
throw new RuntimeException(
"Failed to instantiate RCData with file '"
+ rcFile + "' for urlid '" + urlid + "'", e);
}
}
it = scriptFileMap.keySet().iterator();
while (it.hasNext()) {
scriptPath = (String) it.next();
urlid = (String) scriptFileMap.get(scriptPath);
if (scriptPath.equals("-")) {
reader = new InputStreamReader(System.in);
} else {
file = new File(scriptPath);
if (!file.isFile()) throw new IOException("'" + file
+ "' is not a file");
if (!file.canRead()) throw new IOException("'" + file
+ "' is not readable");
reader = new FileReader(file);
}
scriptRuns.add(new ScriptRun(scriptPath,
reader, (RCData) rcdataMap.get(urlid)));
}
}
public void establishConnections() throws SQLException {
for (int i = 0; i < scriptRuns.size(); i++)
((ScriptRun) scriptRuns.get(i)).connect();
if (verbose) System.out.println(Integer.toString(scriptRuns.size())
+ " connection threads connected");
}
public boolean runScripts() {
ScriptRun scriptRun;
for (int i = 0; i < scriptRuns.size(); i++) {
scriptRun = (ScriptRun) scriptRuns.get(i);
if (verbose) System.out.print("Starting " + (++i) + " / "
+ scriptRuns.size() + "...");
scriptRun.start();
if (verbose) System.out.println(" +");
if (!threaded) try {
scriptRun.join();
} catch (InterruptedException ie) {
throw new RuntimeException(
"Interrupted while waiting for script '"
+ scriptRun.getName() + "' to execute", ie);
}
}
if (threaded) {
if (verbose)
System.out.println(
"All scripts started. Will now wait for them.");
for (int i = 0; i < scriptRuns.size(); i++) try {
((ScriptRun) scriptRuns.get(i)).join();
} catch (InterruptedException ie) {
throw new RuntimeException(
"Interrupted while waiting for script to execute", ie);
}
}
for (int i = 0; i < scriptRuns.size(); i++) {
if (!((ScriptRun) scriptRuns.get(i)).getSuccess()) return false;
}
return true;
}
/**
* Copied directly from TestCacheSize.main().
*
* My goal is to configure population of this database by a properties
* file, not by command line (which would just be too many settings
* along with the main settings), nor by System Properties (ditto).
* I see nothing in the TestCacheSize source code to allow loading by
* a properties file, however.
*/
static protected TestCacheSize populate() {
TestCacheSize test = new TestCacheSize();
/* Use all defaults
HsqlProperties props = HsqlProperties.argArrayToProps(argv, "test");
test.bigops = props.getIntegerProperty("test.bigops", test.bigops);
test.bigrows = test.bigops;
test.smallops = test.bigops / 8;
test.cacheScale = props.getIntegerProperty("test.scale",
test.cacheScale);
test.logType = props.getProperty("test.logtype", test.logType);
test.tableType = props.getProperty("test.tabletype", test.tableType);
test.nioMode = props.isPropertyTrue("test.nio", test.nioMode);
*/
test.filepath = "mem:test";
test.filedb = false;
test.shutdown = false;
test.setUp();
test.testFillUp();
//test.checkResults();
//System.out.println("total test time -- " + sw.elapsedTime() + " ms");
return test;
}
}