/* Copyright (c) 2001-2015, 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";
public static
String LS =
System.
getProperty("line.separator");
public static
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.
*/
public static 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;
}
}