| /* 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; |
| } |
| } |