/*
 
Derby - Class org.apache.derbyTesting.functionTests.tests.replicationTests.ReplicationRun
 
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License.  You may obtain a copy of the License at
 
   http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 
 */
package org.apache.derbyTesting.functionTests.tests.replicationTests;


import org.apache.derbyTesting.functionTests.util.PrivilegedFileOpsForTests;
import org.apache.derby.drda.NetworkServerControl;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties;

import java.sql.*;
import java.io.*;
import org.apache.derby.jdbc.ClientDataSourceInterface;

import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestCase;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.NetworkServerTestSetup;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
 * Framework to run replication tests.
 * Subclass to create specific tests as 
 * in ReplicationRun_Local and ReplicationRun_Distributed.
 */

public class ReplicationRun extends BaseTestCase
{
    
    /**
     * Name of properties file defining the test environment
     * and replication tests to be run.
     * Located in <CODE>${user.dir}</CODE>
     */
    final static String REPLICATIONTEST_PROPFILE = "replicationtest.properties";
    
    final static String REPLICATION_MASTER_TIMED_OUT           = "XRE06";
    final static String REPLICATION_SLAVE_STARTED_OK           = "XRE08";
    final static String REPLICATION_DB_NOT_BOOTED              = "XRE11";
    final static String SLAVE_OPERATION_DENIED_WHILE_CONNECTED = "XRE41";
    final static String REPLICATION_SLAVE_SHUTDOWN_OK          = "XRE42";

    static String testUser = null;
    
    static String userDir = null;
    static String userHome = null; // Used when running distributed.
    
    static String dataEncryption = null;
      // Set to a legal encryption string to
      // create or connect to an encrypted db.
    
    static String masterServerHost = "localhost"; 
    static int masterServerPort = TestConfiguration.getCurrent().getPort(); // .. get current ports..
    static String slaveServerHost = "localhost";
    static int slaveServerPort = TestConfiguration.getCurrent().getNextAvailablePort();; // .. ..
    static String testClientHost = "localhost";
    static int slaveReplPort = TestConfiguration.getCurrent().getNextAvailablePort();;
    
    static String masterDatabasePath = null;
    static String slaveDatabasePath = null;
    static String replicatedDb = "test";
    
    static String bootLoad = ""; // The "test" to run when booting the master database.
    
    static String freezeDB = ""; // Preliminary: need to "manually" freeze db as part of initialization.
    static String unFreezeDB = ""; // Preliminary: need to "manually" unfreeze db as part of initialization.
    
    static boolean junitTest = true; // Set to false in replicationtest.properties
                                     // when running distributed using plain class w/main()
    static boolean runUnReplicated = false;
    
    static boolean simpleLoad = true;
    static int simpleLoadTuples = 1000;
    
    static int tuplesToInsertPerf = 10000;
    static int commitFreq = 0; // autocommit
    
    static String masterDbSubPath = "db_master";
    static String slaveDbSubPath = "db_slave";
    
    
    static String replicationTest = "";
    static String replicationVerify = "";
    
    static int THREADS = 0; // Number of threads and 
    static int MINUTES = 0; // minutes a StressMultiTest load should run.
                            // When ReplicationTestRunStress used as load...
    static String sqlLoadInit = "";
    
    final static String networkServerControl = "org.apache.derby.drda.NetworkServerControl";
    static String specialTestingJar = null;
    // None null if using e.g. your own modified tests.
    static String jvmVersion = null;
    static String masterJvmVersion = null;
    static String slaveJvmVersion = null;
    static String derbyVersion = null;
    static String derbyMasterVersion = null; // Needed for PoC. Remove when committed.
    static String derbySlaveVersion = null;  // Needed for PoC. Remove when committed.
    
    static String junit_jar = null; // Path for JUnit jar
    static String test_jars = null; // Path for derbyTesting.jar:junit_jar
    
    final static String FS = File.separator;
    final static String PS = File.pathSeparator;
    
    static boolean showSysinfo = false;
    
    static long sleepTime = 5000L; // millisecs.
    
    static final String DRIVER_CLASS_NAME = "org.apache.derby.jdbc.ClientDriver";
    static final String DB_PROTOCOL="jdbc:derby";
    
    static final String ALL_INTERFACES = "0.0.0.0";
    
    static String LF = null;
    
    static final String remoteShell = "/usr/bin/ssh -x"; // or /usr/bin/ssh ?

    Utils util = new Utils();
    
    State state = new State();

    static boolean localEnv = false; // true if all hosts have access to
                                             // same filesystem (NFS...)
    
    static String derbyProperties = null;

    String classPath = null; // Used in "localhost" testing.
    
    /** A Connection to the master database*/
    private Connection masterConn = null;
    /** A Connection to the slave database*/
    private Connection slaveConn = null;
    /** The exception thrown as a result of a startSlave connection attempt  */
    private volatile Exception startSlaveException = null;

    /**
     * List of threads that have been started by the tests and not explicitly
     * waited for. Wait for these to complete in {@link #tearDown()} so that
     * they don't interfere with subsequent test cases.
     */
    private ArrayList<Thread> helperThreads = new ArrayList<Thread>();

    private String db_uid = null;
    private String db_passwd = null;

    /**
     * Creates a new instance of ReplicationRun
     * @param testcaseName Identifying the test.
     */
    public ReplicationRun(String testcaseName)
    {
        super(testcaseName);
        
        LF = System.getProperties().getProperty("line.separator");
    }
    
    /**
     * Creates a new instance of ReplicationRun running with authentication.
     */
    public ReplicationRun( String testcaseName, String user, String password )
    {
        this( testcaseName );

        db_uid = user;
        db_passwd = password;
    }
    
    /**
     * Parent super()
     * @throws java.lang.Exception .
     */
    protected void setUp() throws Exception
    {
        super.setUp();
    }
    
    /**
     * Parent super()
     * @throws java.lang.Exception .
     */
    protected void tearDown() throws Exception
    {
        stopServer(jvmVersion, derbyVersion,
                slaveServerHost, slaveServerPort);
        
        stopServer(jvmVersion, derbyVersion,
                masterServerHost, masterServerPort);
        for (Thread t : helperThreads) {
            t.join();
        }
        helperThreads = null;

        close(masterConn);
        close(slaveConn);

        masterConn = null;
        slaveConn = null;
        startSlaveException = null;
        classPath = null;
        util = null;
        state = null;

        super.tearDown();
    }
    
    /** Close a connection. */
    private static void close(Connection conn) throws SQLException {
        if (conn != null && !conn.isClosed()) {
            conn.close();
        }
    }

    /**
     * Run the test. Extra logic in addition to BaseTestCase's similar logic,
     * to save derby.log and database files for replication directories if a
     * failure happens.
     */
    public void runBare() throws Throwable {

        try {

            super.runBare();

        } catch (Throwable running) {

            // Copy the master and slave's derby.log file and databases
            //
            PrintWriter stackOut = null;

            try {
                String failPath = PrivilegedFileOpsForTests.
                    getAbsolutePath(getFailureFolder());

                stackOut = new PrintWriter(
                        PrivilegedFileOpsForTests.getFileOutputStream(
                            new File(failPath, ERRORSTACKTRACEFILE), true));

                String[] replPaths = new String[]{masterDbSubPath,
                                                  slaveDbSubPath};

                for (int i=0; i < 2; i++) {
                    // Copy the derby.log file.
                    //
                    File origLog = new File(replPaths[i], DERBY_LOG);
                    File newLog = new File(failPath,
                                           replPaths[i] + "-" + DERBY_LOG);
                    PrivilegedFileOpsForTests.copy(origLog, newLog);

                    // Copy the database.
                    //
                    String dbName = TestConfiguration.getCurrent().
                        getDefaultDatabaseName();
                    File dbDir = new File(replPaths[i], dbName );
                    File newDbDir = new File(failPath,
                                             replPaths[i] + "-" + dbName);
                    PrivilegedFileOpsForTests.copy(dbDir,newDbDir);
                }
            } catch (IOException ioe) {
                // We need to throw the original exception so if there
                // is an exception saving the db or derby.log we will print it
                // and additionally try to log it to file.
                BaseTestCase.printStackTrace(ioe);
                if (stackOut != null) {
                    stackOut.println("Copying db_slave/db_master's " +
                                     DERBY_LOG + " or database failed:");
                    ioe.printStackTrace(stackOut);
                    stackOut.println();
                }
            } finally {
                if (stackOut != null) {
                    stackOut.close();
                }

                // Let JUnit take over
                throw running;
            }
        }
    }


    String useEncryption(boolean create)
    {
        String encryptionString = "";
        if ( dataEncryption != null)
        {
            if ( create ) encryptionString = ";dataEncryption=true";
            encryptionString = encryptionString+";"+dataEncryption;
        }
        return encryptionString;
    }
    
    //////////////////////////////////////////////////////////////
    ////
    //// The replication test framework (testReplication()):
    //// a) "clean" replication run starting master and slave servers,
    ////     preparing master and slave databases,
    ////     starting and stopping replication and doing
    ////     failover for a "normal"/"failure free" replication
    ////     test run.
    //// b)  Running (positive and negative) tests at the various states 
    ////     of replication to test what is and is not accepted compared to
    ////     the functional specification.
    //// c)  Adding additional load on master and slave servers in 
    ////     different states of replication.
    ////
    //////////////////////////////////////////////////////////////
    
    /* Template
    public void testReplication()
    throws Exception
    {
        util.DEBUG("WARNING: Define in subclass of ReplicationRun. "
                + "See ReplicationRun_Local for an example.");
    }
     */

    void connectPing(String fullDbPath, 
            String serverHost, int serverPort, 
            String testClientHost)
        throws Exception
    {
        
        String dbURL = serverURL( fullDbPath, serverHost, serverPort );
        Connection conn = null;
        String lastmsg = null;
        long sleeptime = 200L;
        boolean done = false;
        int count = 0;
        while ( !done )
        {
            try
            {
                Class.forName(DRIVER_CLASS_NAME); // Needed when running from classes!
                conn = DriverManager.getConnection(dbURL);
                done = true;
                util.DEBUG("Ping Got connection after " 
                        + count +" * "+ sleeptime + " ms.");
                conn.close();
            }
            catch ( SQLException se )
            {
                int errCode = se.getErrorCode();
                lastmsg = se.getMessage();
                String sState = se.getSQLState();
                String expectedState = "08004";
                lastmsg = errCode + " " + sState + " " + lastmsg 
                        + ". Expected: "+ expectedState;
                util.DEBUG("Got SQLException: " + lastmsg);
                if ( (errCode == 40000)
                && (sState.equalsIgnoreCase(expectedState) ) )
                {
                    if (count++ >= 600) {
                        // Have tried 600 * 200 ms == 2 minutes without
                        // success, so give up now.
                        fail("Failover did not succeed", se);
                    }
                    util.DEBUG("Failover not complete.");
                    Thread.sleep(sleeptime); // ms.
                }
                else
                {
                    fail("Connect failed", se);
                }
            }
        }
    }
    
    String showCurrentState(String ID, long waitTime,
            String fullDbPath, 
            String serverHost, int serverPort)
        throws Exception
    {
        int errCode = 0;
        String sState = "CONNECTED";
        String msg = null;
        Thread.sleep(waitTime); // .... until stable...
        try
        {
            ClientDataSourceInterface ds = configureDataSource(
                    fullDbPath,
                    serverHost,
                    serverPort,
                    useEncryption(false) );
            Connection conn = ds.getConnection();
            conn.close();
        }
        catch ( SQLException se )
        {
            errCode = se.getErrorCode();
            msg = se.getMessage();
            sState = se.getSQLState();
        }
        util.DEBUG(ID+": ["+serverHost+":"+serverPort+"/"+fullDbPath+"] "
                + errCode + " " + sState + " " + msg);
        return sState;
    }
    void waitForConnect(long sleepTime, int tries,
            String fullDbPath, 
            String serverHost, int serverPort)
        throws Exception
    {
        int count = 0;
        String msg = null;
        while (true)
        {
            try
            {
                ClientDataSourceInterface ds = configureDataSource(
                    fullDbPath, serverHost, serverPort, useEncryption(false) );
                Connection conn = ds.getConnection();
                util.DEBUG("Wait Got connection after " 
                        + (count-1) +" * "+ sleepTime + " ms.");
                conn.close();
                return;
            }
            catch ( SQLException se )
            {
                if (count++ > tries) {
                    fail("Could not connect in " + (tries * sleepTime) + " ms",
                          se);
                }
                msg = se.getErrorCode() + "' '" + se.getSQLState()
                        + "' '" + se.getMessage();
                util.DEBUG(count  + " got '" + msg +"'.");
                Thread.sleep(sleepTime); // ms. Sleep and try again...
            }
        }        
    }
    void waitForSQLState(String expectedState, 
            long sleepTime, int tries,
            String fullDbPath, 
            String serverHost, int serverPort)
        throws Exception
    {
        int count = 0;
        String msg = null;
        while (true)
        {
            try
            {
                ClientDataSourceInterface ds = configureDataSource(
                        fullDbPath,
                        serverHost,
                        serverPort,
                        useEncryption(false) );
                Connection conn = ds.getConnection();
                // Should never get here!
                conn.close();
                assertTrue("Expected SQLState'"+expectedState
                            + "', but got connection!",
                        false);
            }
            catch ( SQLException se )
            {
                int errCode = se.getErrorCode();
                msg = se.getMessage();
                String sState = se.getSQLState();
                msg = "'" + errCode + "' '" + sState + "' '" + msg +"'";
                util.DEBUG(count 
                        + ": SQLState expected '"+expectedState+"'," +
                        " got " + msg);
                if ( sState.equals(expectedState) )
                {
                    util.DEBUG("Reached SQLState '" + expectedState +"' in "
                            + (count-1)+"*"+sleepTime + "ms.");
                    return; // Got desired SQLState.
                }
                else if (count++ > tries)
                {
                    fail("SQLState '" + expectedState + "' was not reached in "
                            + (tries * sleepTime) + " ms", se);
                }
                else
                {
                    Thread.sleep(sleepTime); // ms. Sleep and try again...
                }
            }
        }
    }
    void shutdownDb(String jvmVersion, // Not yet used
            String serverHost, int serverPort, 
            String dbPath, String replicatedDb,
            String clientHost) // Not yet used
        throws Exception
    {
        String  dbURL = serverURL( dbPath+FS+replicatedDb, serverHost, serverPort );
        util.DEBUG("**** DriverManager.getConnection(\"" + dbURL+";shutdown=true\");");

        try{
            Class.forName(DRIVER_CLASS_NAME); // Needed when running from classes!
            DriverManager.getConnection(dbURL+";shutdown=true");
            fail("Database shutdown should throw exception");
        }
        catch (SQLException se)
        {
            BaseJDBCTestCase.assertSQLState("08006", se);
        }
        
    }
    
    ////////////////////////////////////////////////////////////////
    /* Utilities.... */
    void startServerMonitor(String slaveHost)
    {
        util.DEBUG("startServerMonitor(" + slaveHost + ") NOT YET IMPLEMENTED.");
    }
    
    void runTest(String replicationTest,
            String clientVM,
            String testClientHost,
            String serverHost, int serverPort,
            String dbName)
            throws Exception
    {
        util.DEBUG("runTest(" + replicationTest
                + ", " + clientVM
                + ", " + testClientHost
                + ", " + serverHost
                + ", " + serverPort
                + ", " + dbName
                + ") "
                );
        
        if ( replicationTest == null ) 
        {
            util.DEBUG("No replicationTest specified. Exitting.");
            return;
        } 
        
        if ( simpleLoad )
        {
            _testInsertUpdateDeleteOnMaster(serverHost, serverPort,
                dbName, simpleLoadTuples);
            return;
        }
        
        String URL = masterURL(dbName);
        String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbyTesting.jar"
                + PS + derbyVersion +FS+ "derbytools.jar";
        String testingClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbynet.jar" // WHY IS THIS NEEDED?
                // See TestConfiguration: startNetworkServer and stopNetworkServer
                + PS + test_jars;
        
        String clientJvm = ReplicationRun.getClientJavaExecutableName();

        final boolean isRemote = !testClientHost.equals("localhost");
        final boolean isIjTest = (replicationTest.indexOf(".sql") >= 0);

        ArrayList<String> cmd = new ArrayList<String>();

        // For remote tests, we need to specify the Java VM to use and the
        // classpath. For local tests, we'll just use the JVM and the classpath
        // BaseTestCase.execJavaCmd() gives us. Note that this means we cannot
        // vary versions when running locally.
        if (isRemote) {
            cmd.add(clientJvm);
            cmd.add("-classpath");
            cmd.add(isIjTest ? ijClassPath : testingClassPath);
        }
        
        util.DEBUG("replicationTest: " + replicationTest);
        if ( isIjTest )
        {
            cmd.add("-Dij.driver=" + DRIVER_CLASS_NAME);
            cmd.add("-Dij.connection.startTestClient=" + URL);
            cmd.add("org.apache.derby.tools.ij");
            cmd.add(replicationTest);
        }
        else
        { // JUnit or plain class w/main().
            cmd.add("-Dderby.tests.trace=true");
            cmd.add("-Dtest.serverHost=" + serverHost);  // Tell the test what server
            cmd.add("-Dtest.serverPort=" + serverPort);  // and port to connect to.
            cmd.add("-Dtest.inserts=" + tuplesToInsertPerf); // for SimplePerfTest
            cmd.add("-Dtest.commitFreq=" +  commitFreq); // for SimplePerfTest
            if (THREADS != 0 && MINUTES != 0) {
                // For StressMultiTestForReplLoad as load.
                cmd.add("-Dderby.tests.ThreadsMinutes="+THREADS+"x"+MINUTES);
            }
            cmd.add("-Dtest.dbPath=" + masterDbPath(dbName)); // OK?
            if (junitTest) {
                cmd.add("junit.textui.TestRunner");
            }
            cmd.add(replicationTest);
        }

        String[] command = util.toStringArray(cmd);
        
        long startTime = System.currentTimeMillis();
        String results = null;
        String workingDir = userHome; // Remember this is run on client against master..
        if ( !isRemote )
        {
            runUserCommandLocally(command, "runTest ", null);
        }
        else
        {
            // This doesn't work if path names contain spaces or other
            // characters with special meaning to the shell.
            // NOT Correct: ...Must be positioned where the properties file
            // is located.
            results = runUserCommandRemotely(
                    "cd " + workingDir + ";" + util.splice(command, ' '),
                    testClientHost, testUser, "runTest ");
        }
        util.DEBUG("Time: " + (System.currentTimeMillis() - startTime) / 1000.0);
        
    }
    void runTestOnSlave(String replicationTest,
            String clientVM,
            String testClientHost,
            String serverHost, int serverPort,
            String dbName)
            throws Exception
    {
        util.DEBUG("runTestOnSlave(" + replicationTest
                + ", " + clientVM
                + ", " + testClientHost
                + ", " + serverHost
                + ", " + serverPort
                + ", " + dbName
                + ") "
                );
        
        
        String URL = slaveURL(dbName);
        String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbyTesting.jar"
                + PS + derbyVersion +FS+ "derbytools.jar";
        String testingClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbynet.jar" // WHY IS THIS NEEDED?
                // See TestConfiguration: startNetworkServer and stopNetworkServer
                + PS + test_jars;
        
        String clientJvm = ReplicationRun.getSlaveJavaExecutableName();
        
        if ( replicationTest == null ) 
        {
            util.DEBUG("No replicationTest specified. Exitting.");
            return;
        } 
        
        final boolean isRemote = !serverHost.equals("localhost");
        final boolean isIjTest = (replicationTest.indexOf(".sql") >= 0);

        ArrayList<String> cmd = new ArrayList<String>();

        // For remote tests, we need to specify the Java VM to use and the
        // classpath. For local tests, we'll just use the JVM and the classpath
        // BaseTestCase.execJavaCmd() gives us. Note that this means we cannot
        // vary versions when running locally.
        if (isRemote) {
            cmd.add(clientJvm);
            cmd.add("-classpath");
            cmd.add(isIjTest ? ijClassPath : testingClassPath);
        }

        util.DEBUG("replicationTest: " + replicationTest);
        if ( isIjTest )
        {
            cmd.add("-Dij.driver=" + DRIVER_CLASS_NAME);
            cmd.add("-Dij.connection.startTestClient=" + URL);
            cmd.add("org.apache.derby.tools.ij");
            cmd.add(replicationTest);
        }
        else
        { // JUnit
            cmd.add("-Dderby.tests.trace=true");
            cmd.add("-Dtest.serverHost=" + serverHost);  // Tell the test what server
            cmd.add("-Dtest.serverPort=" + serverPort);  // and port to connect to.
            cmd.add("-Dtest.inserts=" + tuplesToInsertPerf); // for SimplePerfTest
            cmd.add("-Dtest.commitFreq=" +  commitFreq); // for SimplePerfTest
            cmd.add("-Dtest.dbPath=" + slaveDbPath(dbName)); // OK?
            cmd.add("junit.textui.TestRunner");
            cmd.add(replicationTest);
        }

        String[] command = util.toStringArray(cmd);
        
        long startTime = System.currentTimeMillis();
        String results = null;
        if ( !isRemote )
        {
            runUserCommandLocally(command, "runTestOnSlave ", null);
        }
        else
        {
            // This doesn't work if path names contain spaces or other
            // characters with special meaning to the shell.
            // Must be positioned where the properties file is located.
            results = runUserCommandRemotely(
                "cd " + userDir + ";" + util.splice(command, ' '),
                testClientHost, testUser, "runTestOnSlave ");
        }
        util.DEBUG("Time: " + (System.currentTimeMillis() - startTime) / 1000.0);
        
    }
    
    /*
     *
     * Should allow:
     * - Run load in separate thread.
     *
     */
    private void runLoad(String load,
            String clientVM,
            String testClientHost,
            String masterHost, int masterPort,
            String dbSubPath) // FIXME? Should we allow extra URL options?
            throws Exception
    {
        util.DEBUG("runLoad(" + load
                + ", " + clientVM
                + ", " + testClientHost
                + ", " + masterHost
                + ", " + masterPort
                + ", " + dbSubPath
                + ") "
                );
        
        
        String URL = masterLoadURL(dbSubPath);
        String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbyTesting.jar"
                // Needed for 'run resource 'createTestProcedures.subsql';' cases?
                // Nope? what is 'resource'?
                + PS + derbyVersion +FS+ "derbytools.jar";
        String testingClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbynet.jar" // WHY IS THIS NEEDED?
                // See TestConfiguration: startNetworkServer and stopNetworkServer
                + PS + test_jars;
        
        String clientJvm = ReplicationRun.getClientJavaExecutableName();

        final boolean isRemote = !masterHost.equals("localhost");
        final boolean isIjTest = (load.indexOf(".sql") >= 0);
        
        util.DEBUG("load: " + load);

        ArrayList<String> cmd = new ArrayList<String>();

        // For remote tests, we need to specify the Java VM to use and the
        // classpath. For local tests, we'll just use the JVM and the classpath
        // BaseTestCase.execJavaCmd() gives us. Note that this means we cannot
        // vary versions when running locally.
        if (isRemote) {
            cmd.add(clientJvm);
            cmd.add("-classpath");
            cmd.add(isIjTest ? ijClassPath : testingClassPath);
        }

        if ( isIjTest )
        {
            cmd.add("-Dij.driver=" + DRIVER_CLASS_NAME);
            cmd.add("-Dij.connection.startTestClient=" + URL);
            cmd.add("org.apache.derby.tools.ij");
            cmd.add(load);
        }
        else
        {
            /* BEGIN For junit: */
            cmd.add("-Dderby.tests.trace=true");
            cmd.add("junit.textui.TestRunner");
            cmd.add(load);
            /* END */
        }

        String[] command = util.toStringArray(cmd);

        if ( !isRemote )
        {
            runUserCommandInThreadLocally(command, dbSubPath,
                    "runLoad["+dbSubPath+"] ");
        }
        else
        {
            runUserCommandInThreadRemotely(util.splice(command, ' '),
                    testClientHost, testUser, "runLoad["+dbSubPath+"] ");
        }
        
    }
    
    private void runStateTest(String stateTest,
            String clientVM,
            String testClientHost,
            String masterHost, int masterPort, // serverHost?, serverPort?
            String dbSubPath) // FIXME? Should we allow extra URL options?
            throws Exception
    {
        util.DEBUG("runStateTest(" + stateTest
                + ", " + clientVM
                + ", " + testClientHost
                + ", " + masterHost
                + ", " + masterPort
                + ", " + dbSubPath
                + ") "
                );
        
        
        String URL = masterLoadURL(dbSubPath);
        String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbyTesting.jar"
                // Needed for 'run resource 'createTestProcedures.subsql';' cases?
                // Nope? what is 'resource'?
                + PS + derbyVersion +FS+ "derbytools.jar";
        String testingClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbynet.jar" // WHY IS THIS NEEDED?
                // See TestConfiguration: startNetworkServer and stopNetworkServer
                + PS + test_jars;
        
        String clientJvm = ReplicationRun.getClientJavaExecutableName();
        
        String command = null;
        
        if ( masterHost.equals("localhost") )
        { // Use full classpath when running locally. Can not vary server versions!
            ijClassPath = classPath;
            testingClassPath = classPath;
        }
        util.DEBUG("stateTest: " + stateTest);
        if ( stateTest.indexOf(".sql") >= 0 )
        {
            command = clientJvm 
                    + " -Dij.driver=" + DRIVER_CLASS_NAME
                    + " -Dij.connection.startTestClient=" + URL
                    + " -classpath " + ijClassPath + " org.apache.derby.tools.ij"
                    + " " + stateTest
                    ;
        }
        else
        {
            /* BEGIN For junit: */
            command = "cd "+ userDir +";" // Must be positioned where the properties file is located.
                    + clientJvm 
                    + " -Dderby.tests.trace=true"
                    // + " -Djava.security.policy=\"<NONE>\""  // Now using noSecurityManager decorator
                    + " -classpath " + testingClassPath
                    + " junit.textui.TestRunner"
                    + " " + stateTest
                    ;
            /* END */
        }
        
        /* String results = */
        runUserCommandRemotely(command,
                testClientHost, // masterHost,
                testUser,
                // dbSubPath,
                "runStateTest "); // ["+dbSubPath+"]
        
    }
    
    void bootMasterDatabase(String clientVM, 
            String dbSubPath,
            String dbName,
            String masterHost,  // Where the command is to be executed.
            int masterServerPort, // master server interface accepting client requests
            String load)
        throws Exception
    {
        // Should just do a "connect....;create=true" here, instead of copying in initMaster.
        
        String URL = masterURL(dbName)
                +";create=true"
                +useEncryption(true);

        {
            util.DEBUG("bootMasterDatabase getConnection("+URL+")");
            Class.forName(DRIVER_CLASS_NAME); // Needed when running from classes!
            Connection conn = DriverManager.getConnection(URL);
            conn.close();
        }
        
        // NB! should be done by startMaster. Preliminary needs to freeze db before copying to slave and setting replication mode.
        util.DEBUG("************************** DERBY-???? Preliminary needs to freeze db before copying to slave and setting replication mode.");
        
        {
             URL = masterURL(dbName);
            Class.forName(DRIVER_CLASS_NAME); // Needed when running from classes!
            util.DEBUG("bootMasterDatabase getConnection("+URL+")");
            Connection conn = DriverManager.getConnection(URL);
            Statement s = conn.createStatement();
            s.execute("call syscs_util.syscs_freeze_database()");
            conn.close();
        }
        
        if ( load != null )
        {
            runLoad(load,
                    clientVM, // jvmVersion,
                    testClientHost,
                    masterServerHost, masterServerPort,
                    dbSubPath+FS+dbName);
        }
        util.DEBUG("bootMasterDatabase done.");
    }

    /**
     * Set master db in replication master mode.
     */
    void startMaster(String clientVM,
            String dbName,
            String masterHost,  // Where the command is to be executed.
            int masterServerPort, // master server interface accepting client requests
            String slaveClientInterface, // Will be = slaveReplInterface = slaveHost if only one interface card used.
            int slaveServerPort, // masterPort, // Not used since slave don't accept client requests
            String slaveReplInterface, // slaveHost,
            int slaveReplPort) // slavePort)
            throws Exception
    {
        if ( masterHost.equalsIgnoreCase("localhost") )
        {
            startMaster_direct(dbName,
                    masterHost,  masterServerPort,
                    slaveReplInterface, slaveReplPort);
        }
        else
        {
            startMaster_ij(dbName,
                    masterHost, 
                    slaveReplInterface, slaveReplPort);
        }
    }

    private void startMaster_ij(String dbName,
            String masterHost, // Where the master db is run.
            String slaveReplInterface, // master server interface accepting client requests
            int slaveReplPort)
            throws Exception
    {
        
        String URL = masterURL(dbName)
                +";startMaster=true;slaveHost="+slaveReplInterface
                +";slavePort="+slaveReplPort;
        String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbytools.jar";
        if ( masterHost.equals("localhost") )
        { // Use full classpath when running locally. Can not vary server versions!
            ijClassPath = classPath;
        }
        
        String clientJvm = ReplicationRun.getMasterJavaExecutableName();
        
        String command = clientJvm
                + " -Dij.driver=" + DRIVER_CLASS_NAME
                + " -Dij.connection.startMaster=\"" + URL + "\""
                + " -classpath " + ijClassPath + " org.apache.derby.tools.ij"
                + " " + userHome + FS + "ij_dummy_script.sql"
                ;
        
        String results =
                runUserCommandRemotely(command,
                masterHost, // Must be run on the master!
                testUser,
                "startMaster_ij ");
        util.DEBUG(results);
    }
    private void startMaster_direct(String dbName,
            String masterHost,  // Where the master db is run.
            int masterServerPort, // master server interface accepting client requests
            
            String slaveReplInterface, // slaveHost,
            int slaveReplPort)
            throws Exception
    {
        
        String URL = masterURL(dbName)
                +";startMaster=true;slaveHost="+slaveReplInterface
                +";slavePort="+slaveReplPort;
                
            util.DEBUG("startMaster_direct getConnection("+URL+")");
            Connection conn = null;
            boolean done = false;
            int count = 0;
            while ( !done )
            {
                try
                {
                    /* On 1.5 locking of Drivermanager.class prevents
                     * using DriverManager.getConnection() concurrently
                     * in startMaster and startSlave!
                    Class.forName(DRIVER_CLASS_NAME); // Needed when running from classes!
                    conn = DriverManager.getConnection(URL);
                     */
                    String  connectionAttributes = "startMaster=true"
                        +";slaveHost="+slaveReplInterface
                        +";slavePort="+slaveReplPort
                        +useEncryption(false);
                    ClientDataSourceInterface ds = configureDataSource
                        ( masterDbPath( dbName ), masterHost, masterServerPort, connectionAttributes );
                    conn = ds.getConnection();
                    
                    done = true;
                    conn.close();
                    util.DEBUG("startMaster_direct connected in " + count + " * 100ms.");
                }
                catch ( SQLException se )
                {
                    int errCode = se.getErrorCode();
                    String msg = se.getMessage();
                    String sState = se.getSQLState();
                    String expectedState = "XRE04";
                    util.DEBUG("startMaster Got SQLException: " 
                            + errCode + " " + sState + " " + msg + ". Expected " + expectedState);
                    if ( (errCode == 40000)
                    && (sState.equalsIgnoreCase(expectedState) ) )
                    {
                        if (count++ > 1200) {
                            // Have tried for 1200 * 100 ms == 2 minutes
                            // without success. Give up.
                            fail("startMaster did not succeed", se);
                        }
                        util.DEBUG("Not ready to startMaster. "
                                +"Beware: Will also report "
                                + "'... got a fatal error for database '...../<dbname>'"
                                + " in master derby.log.");
                        Thread.sleep(100L); // ms.
                    }
                    else
                    {
                        if (REPLICATION_MASTER_TIMED_OUT.equals(sState)) // FIXME! CANNOT_START_MASTER_ALREADY_BOOTED
                        {
                            util.DEBUG("Master already started?");
                        }
                        util.DEBUG("startMaster_direct Got: "
                                +state+" Expected "+expectedState);
                        throw se;
                    }
                }
            }
            util.DEBUG("startMaster_direct exit.");
    }
    
    /**
     * Get a connection to the master database.
     * @return A connection to the master database
     */
    protected Connection getMasterConnection() throws SQLException {
        if (masterConn == null) {
            String url = masterURL(replicatedDb);
            masterConn = DriverManager.getConnection(url);
        }
        return masterConn;
    }
    
    /**
     * Get a connection to the slave database.
     * @return A connection to the slave database
     */
    protected Connection getSlaveConnection() throws SQLException {
        if (slaveConn == null) {
            String url = slaveURL(replicatedDb);
            slaveConn = DriverManager.getConnection(url);
        }
        return slaveConn;
    }


    /**
     * Execute SQL on the master database through a Statement
     * @param sql The sql that should be executed on the master database
     * @throws java.sql.SQLException thrown if an error occured while
     * executing the sql
     */
    protected void executeOnMaster(String sql) throws SQLException {
         Statement s = getMasterConnection().createStatement();
         s.execute(sql);
         s.close();
    }

    /**
     * Execute SQL on the slave database through a Statement
     * @param sql The sql that should be executed on the slave database
     * @throws java.sql.SQLException thrown if an error occured while
     * executing the sql
     */
    protected void executeOnSlave(String sql) throws SQLException {
         Statement s = getSlaveConnection().createStatement();
         s.execute(sql);
         s.close();
    }

    /**
     * Set slave db in replication slave mode
     */
    void startSlave(String clientVM,
            String dbName,
            String slaveClientInterface, // slaveHost, // Where the command is to be executed.
            int slaveServerPort,
            String slaveReplInterface,
            int slaveReplPort,
            String testClientHost)
    throws Exception
    {
        if ( testClientHost.equalsIgnoreCase("localhost") )
        {
            startSlave_direct(dbName,
                    slaveClientInterface, slaveServerPort,
                    slaveReplInterface,slaveReplPort);
        }
        else
        {
            startSlave_ij(
                    dbName,
                    slaveClientInterface,
                    slaveReplInterface, slaveReplPort);
        }
    }

    private void startSlave_ij(
            String dbName,
            String slaveHost,  // Where the slave db is run.
            String slaveReplInterface, // slaveHost,
            int slaveReplPort)
            throws Exception
    {
        
        String URL = slaveURL(dbName)
                +";startSlave=true;slaveHost="+slaveReplInterface
                +";slavePort="+slaveReplPort;
        String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbytools.jar";
        if ( slaveHost.equals("localhost") )
        { // Use full classpath when running locally. Can not vary server versions!
            ijClassPath = classPath;
        }
        
        String clientJvm = ReplicationRun.getSlaveJavaExecutableName();
        
        String command = clientJvm
                + " -Dij.driver=" + DRIVER_CLASS_NAME
                + " -Dij.connection.startSlave=\"" + URL + "\""
                + " -classpath " + ijClassPath + " org.apache.derby.tools.ij"
                + " " + userHome + FS + "ij_dummy_script.sql"
                ;
        
        runUserCommandInThreadRemotely(command,
                slaveHost, // Run on the slave.
                testUser,
                "startSlave_ij ");
        
    }
    private void startSlave_direct(String dbName,
            String slaveHost,  // Where the slave db is run.
            int slaveServerPort, // slave server interface accepting client requests
            String slaveReplInterface,
            int slaveReplPort)
            throws Exception
    {
        final String URL = slaveURL(dbName)
                +";startSlave=true;slaveHost="+slaveReplInterface
                +";slavePort="+slaveReplPort;
        
            util.DEBUG("startSlave_direct getConnection("+URL+")");
            
            final String fDbPath = slaveDbPath( dbName );
            final String fSlaveHost = slaveHost;
            final int fSlaveServerPort = slaveServerPort;
            final String fConnAttrs = "startSlave=true"
                                +";slaveHost="+slaveReplInterface
                                +";slavePort="+slaveReplPort
                                +useEncryption(false);
            Thread connThread = new Thread(
                    new Runnable()
            {
                public void run()
                {
                    startSlaveException = null;
                    Connection conn = null;
                    try {
                        // NB! WIll hang here until startMaster is executed!
                        /*On 1.5 locking of Drivermanager.class prevents
                         * using DriverManager.getConnection() concurrently
                         * in startMaster and startSlave!
                        Class.forName(DRIVER_CLASS_NAME); // Needed when running from classes!
                        conn = DriverManager.getConnection(URL);
                         */
                        ClientDataSourceInterface ds = configureDataSource(
                            fDbPath, fSlaveHost, fSlaveServerPort, fConnAttrs );
                        conn = ds.getConnection();
                        conn.close();
                    }
                    catch (SQLException se)
                    {
                        startSlaveException = se;
                    }
                    catch (Exception ex)
                    {
                        startSlaveException = ex;
                    }
                }
            }
            );
            connThread.start();
            registerThread(connThread);
            util.DEBUG("startSlave_direct exit.");
    }
    

    void failOver(String jvmVersion,
            String dbPath, String dbSubPath, String dbName,
            String host,  // Where the db is run.
            int serverPort,
            
            String testClientHost)
            throws Exception
    {
        if ( host.equalsIgnoreCase("localhost") )
        {
            failOver_direct(dbName);
        }
        else
        {
            failOver_ij(dbName, host, testClientHost);
        }

    }
    private void failOver_ij(
            String dbName,
            String host,  // Where the db is run.
            String testClientHost)
            throws Exception
    {
        
        String URL = masterURL(dbName)
                +";failover=true";
        String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                + PS + derbyVersion +FS+ "derbytools.jar";
        if ( host.equals("localhost") )
        { // Use full classpath when running locally. Can not vary server versions!
            ijClassPath = classPath;
        }
        
        String clientJvm = ReplicationRun.getClientJavaExecutableName();
        
        String command = clientJvm
                + " -Dij.driver=" + DRIVER_CLASS_NAME
                + " -Dij.connection.failover=\"" + URL + "\""
                + " -classpath " + ijClassPath + " org.apache.derby.tools.ij"
                + " " + userHome + FS + "ij_dummy_script.sql"
                ;
        
        // Execute the ij command on the testClientHost as testUser
        String results =
                runUserCommandRemotely(command,
                testClientHost,
                testUser,
                "failOver_ij ");
        util.DEBUG(results);
    }
    private void failOver_direct(String dbName)
            throws Exception
    {
        String URL = masterURL(dbName)
                +";failover=true";
               
            util.DEBUG("failOver_direct getConnection("+URL+")");

            try
            {
                Class.forName(DRIVER_CLASS_NAME); // Needed when running from classes!
                DriverManager.getConnection(URL);
            }
            catch (SQLException se)
            {
                int errCode = se.getErrorCode();
                String msg = se.getMessage();
                String sState = se.getSQLState();
                String expectedState = "XRE20";
                msg = "failOver_direct Got SQLException: " 
                        + errCode + " " + sState + " " + msg 
                        + ". Expected: " + expectedState;
                util.DEBUG(msg);
                BaseJDBCTestCase.assertSQLState(expectedState, se);
            }
   }
    
    int xFindServerPID(String serverHost, int serverPort)
    throws InterruptedException
    {
        if ( serverHost.equalsIgnoreCase("localhost") ) 
        { // Assuming we do not need the PID.
            return 0;
        }
        int pid = -1;

        String p1 = "ps auxwww"; // "/bin/ps auxwww";
        String p2 = " | grep " + serverPort; // /bin/grep
        String p3 = " | grep '.NetworkServerControl start -h '"; // /bin/grep
        String p4 = ""; // | /bin/grep '/trunk_slave/jars/'"; // Also used for master...
        String p5 = " | grep -v grep"; // /bin/grep
        String p6 = " | grep -v ssh"; // /bin/grep
        String p7 = " | grep -v bash";  // /bin/grep// Assuming always doing remote command (ssh)
        String p8 = " | gawk '{ print $2 }'"; // /bin/gawk
        String p9 = " | head -1"; // For cases where we also get some error...
        
        String command = p1 + p2 + p3 + p4 + p5 + p6 + p7 /* + p8 */ + ";";
        String result = runUserCommandRemotely(
                command, serverHost, testUser, "ps");
        util.DEBUG("xFindServerPID: '" + result + "'");
        // result = result.split(" ")[1]; // Without ' + p8 ' should show full line. But fails with PID less than 10000!
        command = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9 + ";";
        result = runUserCommandRemotely(command, serverHost, testUser, "ps");
        if ( result == null )
        {util.DEBUG("xFindServerPID: Server process not found");return -1;} // Avoid error on parseInt below
        util.DEBUG("xFindServerPID: '" + result + "'");
        pid = Integer.parseInt(result.trim());
        util.DEBUG("xFindServerPID: " + pid);
        return pid;
    }
    void xStopServer(String serverHost, int serverPID)
    throws InterruptedException
    {
        if ( serverPID == -1 || serverPID == 0 )
        {util.DEBUG("Illegal PID");return;}
        String command = "kill " + serverPID;
        runUserCommandRemotely(command,
                serverHost,
                testUser,
                "xStopServer");
    }
    
    void verifySlave()
    throws Exception
    {
        util.DEBUG("BEGIN verifySlave "+slaveServerHost+":"
                   +slaveServerPort+"/"+slaveDbPath( replicatedDb ) );
        
        if ( (replicationTest != null) // If 'replicationTest==null' no table was created/filled
                && simpleLoad )
        {
            _verifyDatabase(slaveServerHost, slaveServerPort, 
                    slaveDbPath( replicatedDb ),
                    simpleLoadTuples);
            // return;
        }

        ClientDataSourceInterface ds = configureDataSource
            ( slaveDbPath( replicatedDb ), slaveServerHost, slaveServerPort, useEncryption(false) );
        Connection conn = ds.getConnection();
            
        simpleVerify(conn);
        conn.close();
        /* BEGIN Distributed repl. tests only */
        if ( !slaveServerHost.equalsIgnoreCase("localhost") ){
        runSlaveVerificationCLient(jvmVersion,
                testClientHost,
                replicatedDb,
                slaveServerHost, slaveServerPort);}
        /* END Distributed repl. tests only */
        util.DEBUG("END   verifySlave");
    }
    void verifyMaster()
    throws Exception
    {
        util.DEBUG("BEGIN verifyMaster " + masterServerHost + ":"
                   +masterServerPort+"/"+masterDbPath( replicatedDb ) );
        
        if ( (replicationTest != null)  // If 'replicationTest==null' no table was created/filled
                && simpleLoad )
        {
            _verifyDatabase(masterServerHost, masterServerPort, 
                    masterDbPath( replicatedDb ),
                    simpleLoadTuples);
            // return;
        }

        ClientDataSourceInterface ds = configureDataSource
            ( masterDbPath( replicatedDb ), masterServerHost, masterServerPort, useEncryption(false) );
        Connection conn = ds.getConnection();
            
        simpleVerify(conn);
        conn.close();
        /* BEGIN Distributed repl. tests only */
        if ( !masterServerHost.equalsIgnoreCase("localhost") ){
        runMasterVerificationCLient(jvmVersion,
                testClientHost,
                replicatedDb,
                masterServerHost, masterServerPort);}
        /* END Distributed repl. tests only */
        util.DEBUG("END   verifyMaster");
    }
    private void simpleVerify(Connection conn) // Verification code..
    throws SQLException
    {
        Statement s = conn.createStatement();
        ResultSet rs = s.executeQuery("select SCHEMAID, TABLENAME from sys.systables");
        while (rs.next())
        {
            util.DEBUG(rs.getString(1) + " " + rs.getString(2));
        }
    }
    
    private void runSlaveVerificationCLient(String jvmVersion,
            String testClientHost,
            String dbName,
            String serverHost,
            int serverPort)
            throws Exception
    {
        util.DEBUG("runSlaveVerificationCLient");
        if ( replicationVerify != null){
        runTestOnSlave(replicationVerify,
                jvmVersion,
                testClientHost,
                serverHost,serverPort,
                dbName);
        }
    }
    
    private void runMasterVerificationCLient(String jvmVersion,
            String testClientHost,
            String dbName,
            String serverHost,
            int serverPort)
            throws Exception
    {
        util.DEBUG("runMasterVerificationCLient");
        if ( replicationVerify != null ){
        runTest(replicationVerify,
                jvmVersion,
                testClientHost,
                serverHost,serverPort,
                dbName);
        }
    }

    /**
     * Run a Java command locally on the test host. The spawned process
     * inherits the class path from the main test process.
     *
     * @param command the arguments to pass to the Java executable
     * @param ID an identifier used to prefix debug output
     * @param workingDir the directory in which the sub-process should run, or
     *    {@code null} to run in the same directory as the parent process
     */
    private void runUserCommandLocally(
            String[] command, String ID, File workingDir) {
        util.DEBUG("");
        final String debugId = "runUserCommandLocally " + ID + " ";
        util.DEBUG(debugId+command);
        
        {
            util.DEBUG(debugId + "localCommand: " + Arrays.asList(command));
            
            try
            {
                Process proc = execJavaCmd(null, null, command, workingDir);
                processDEBUGOutput(debugId+"pDo ", proc);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
        
        util.DEBUG(debugId+"--- runUserCommandLocally ");
        util.DEBUG("");
        
    }

    private String runUserCommandRemotely(String command,
            String host,
            String testUser,
            String id)
    {
        final String ID= "runUserCommandRemotely "+id+" ";
        util.DEBUG(ID+"Execute '"+ command +"' on '"+ host +"'" + " as " + testUser);
        
        String localCommand = remoteShell + " "
                + "-l " + testUser + " " + host + " "
                + command
                ;

        String output = "";
        try {
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec(localCommand);
            output = processOutput(ID, proc);
            int exitVal = proc.waitFor();
            util.DEBUG("ExitValue: " + exitVal);
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return output;
    }

    /**
     * Run a Java command locally on the test host in a separate thread. The
     * spawned process inherits the class path from the main test process.
     *
     * @param command the arguments to pass to the Java executable
     * @param dbDir the name of the sub-directory in which the sub-process
     *   should run, or {@code null} to run in the same directory as the
     *   parent process
     * @param id an identifier used to prefix debug output
     */
    private void runUserCommandInThreadLocally(final String[] command,
            String dbDir,
            String id)
    {
        util.DEBUG("");
        final String ID = "runUserCommandInThread "+id+" ";
        util.DEBUG(ID + "Execute '"+ command +"'");
        util.DEBUG("+++ "+ID);
     
        util.DEBUG("runUserCommand: " + command );

        String workingDirName = System.getProperty("user.dir");
        util.DEBUG("user.dir: " + workingDirName);

        // If dbDir is specified, start the process in that directory;
        // otherwise, inherit working dir from the main test process.
        final File workingDir = dbDir == null ?
                null : new File(workingDirName, dbDir);
        util.DEBUG(ID + "workingDir: " + workingDir);

        {
            Thread cmdThread = new Thread(
                    new Runnable()
            {
                public void run()
                {
                    util.DEBUG(ID+"************** In run().");
                    runUserCommandLocally(command, ID, workingDir);
                    util.DEBUG(ID+"************** Done run().");
                }
            }
            );
            util.DEBUG(ID+"************** Do .start().");
            cmdThread.start();
            registerThread(cmdThread);
        }
     
        util.DEBUG(ID+"--- ");
        util.DEBUG("");
    }
    
    // FIXME: NB NB Currently only invoked from startSlave_ij (others unused!)
    private void runUserCommandInThreadRemotely(String command,
            final String host,
            final String testUser,
            String id)
    {
        util.DEBUG("");
        final String ID=id+" runUserCommandInThreadRemotely ";
        util.DEBUG(ID+"+++ ");
        util.DEBUG(ID+"Execute '"+ command +"' on '"+ host +"'");
        
        util.DEBUG(ID + command
                + " @ " + host
                + " as " + testUser);
        
        final String[] envElements = {"CLASS_PATH="+""
                , "PATH="+FS+"home"+FS+testUser+FS+"bin:$PATH" // "/../bin" FIXME!!! All such!
                };
        
        String workingDirName = System.getProperty("user.home");
        util.DEBUG(ID+"user.home: " + workingDirName);
        util.DEBUG(ID+"envElements: " + util.splice(envElements, ' '));
        util.DEBUG(ID+"workingDir: " + workingDirName);
        
        {
            util.DEBUG(ID+"Running command on non-local host "+ host);
            
            String[] shEnvElements = {"CLASS_PATH="+""
                    , "PATH="+FS+"home"+FS+testUser+FS+"bin:${PATH}"
            };
            String shellEnv = util.splice(shEnvElements, ';');
            util.DEBUG(ID+"shellEnv: " + shellEnv);

            // user.home aka workingDirName must be accessible from master,
            // slave and client.
            final String shellCmd = "cd " + workingDirName + ";pwd;"
                    + shellEnv + ";"
                    + command;
            
            util.DEBUG(ID+"shellCmd: " + shellCmd);
            
            Thread serverThread = new Thread(
                    new Runnable()
            {
                public void run()
                {
                    util.DEBUG(ID+"************** In run().");
                    runUserCommandRemotely(shellCmd, host, testUser, ID);
                    util.DEBUG(ID+"************** Done exec().");
                }
            }
            );
            util.DEBUG(ID+"************** Do .start(). ");
            serverThread.start();
            registerThread(serverThread);
            
        }
        
        util.DEBUG(ID+"--- ");
        util.DEBUG("");
    }
        
    void initEnvironment()
    throws IOException
    {
        util.printDebug = System.getProperty("derby.tests.repltrace", "false")
                                                     .equalsIgnoreCase("true");
        util.DEBUG("printDebug: " + util.printDebug);
        
        util.DEBUG("*** ReplicationRun.initEnvironment -----------------------------------------");
        util.DEBUG("*** Properties -----------------------------------------");
        userDir = System.getProperty("user.dir");
        util.DEBUG("user.dir:          " + userDir);
        
        util.DEBUG("derby.system.home: " + System.getProperty("derby.system.home"));
        
        showSysinfo = true;
        util.DEBUG("showSysinfo: " + showSysinfo);
        
        testUser = null;
        util.DEBUG("testUser: " + testUser);
        
        masterServerHost = "localhost";
        util.DEBUG("masterServerHost: " + masterServerHost);
        
        util.DEBUG("masterServerPort: " + masterServerPort);
        
        slaveServerHost = "localhost";
        util.DEBUG("slaveServerHost: " + slaveServerHost);
        
        util.DEBUG("slaveServerPort: " + slaveServerPort);
        
        util.DEBUG("slaveReplPort: " + slaveReplPort);
        
        testClientHost = "localhost";
        util.DEBUG("testClientHost: " + testClientHost);
        
        masterDatabasePath = userDir;
        util.DEBUG("masterDatabasePath: " + masterDatabasePath);
        
        slaveDatabasePath = userDir;
        util.DEBUG("slaveDatabasePath: " + slaveDatabasePath);
        
        replicatedDb = "wombat";
        util.DEBUG("replicatedDb: " + replicatedDb);
        
        bootLoad = null;
        util.DEBUG("bootLoad: " + bootLoad);
        
        freezeDB = null;
        util.DEBUG("freezeDB: " + freezeDB);
        
        unFreezeDB = null;
        util.DEBUG("unFreezeDB: " + unFreezeDB);
        
        simpleLoad = System.getProperty("derby.tests.replSimpleLoad", "true")
                                                     .equalsIgnoreCase("true");
        util.DEBUG("simpleLoad: " + simpleLoad);
        
        /* Done in subclasses
        replicationTest = "org.apache.derbyTesting.functionTests.tests.replicationTests.ReplicationTestRun";
        util.DEBUG("replicationTest: " + replicationTest);
        replicationVerify = "org.apache.derbyTesting.functionTests.tests.replicationTests.ReplicationTestRunVerify";
        util.DEBUG("replicationVerify: " + replicationVerify);
         */
        
        sqlLoadInit = null;
        util.DEBUG("sqlLoadInit: " + sqlLoadInit);

        
        specialTestingJar = null;
        util.DEBUG("specialTestingJar: " + specialTestingJar);
        
        jvmVersion = System.getProperty("java.home") +FS+"lib";
        util.DEBUG("jvmVersion: " + jvmVersion);
        
        masterJvmVersion = null;
        if ( masterJvmVersion == null )
        {masterJvmVersion = jvmVersion;}
        util.DEBUG("masterJvmVersion: " + masterJvmVersion);
        
        slaveJvmVersion = null;
        if ( slaveJvmVersion == null )
        {slaveJvmVersion = jvmVersion;}
        util.DEBUG("slaveJvmVersion: " + slaveJvmVersion);
        
        classPath = System.getProperty("java.class.path"); util.DEBUG("classPath: " + classPath);
        
        util.DEBUG("derbyVersion: " + derbyVersion);
        
        derbyMasterVersion = null;
        if ( derbyMasterVersion == null )
        {derbyMasterVersion = derbyVersion;}
        util.DEBUG("derbyMasterVersion: " + derbyMasterVersion);
        
        derbySlaveVersion = null;
        if ( derbySlaveVersion == null )
        {derbySlaveVersion = derbyVersion;}
        util.DEBUG("derbySlaveVersion: " + derbySlaveVersion);
        
        String derbyTestingJar = derbyVersion + FS+"derbyTesting.jar";
        if ( specialTestingJar != null )  derbyTestingJar = specialTestingJar;
        util.DEBUG("derbyTestingJar: " + derbyTestingJar);
        
        junit_jar = derbyVersion + FS+"junit.jar";
        util.DEBUG("junit_jar: " + junit_jar);
        
        test_jars = derbyTestingJar
                + PS + junit_jar;
        util.DEBUG("test_jars: " + test_jars);
        
        sleepTime = 15000;
        util.DEBUG("sleepTime: " + sleepTime);
        
        runUnReplicated = false;
        util.DEBUG("runUnReplicated: " + runUnReplicated);
        
        localEnv = false;
        util.DEBUG("localEnv: " + localEnv);
        
        derbyProperties = 
                 "derby.infolog.append=true"+LF
                +"derby.drda.logConnections=true"+LF
                +"derby.drda.traceAll=true"+LF;

        
        util.DEBUG("--------------------------------------------------------");
        
        masterPreRepl = null; // FIXME!
        masterPostRepl = null; // FIXME!
        slavePreSlave = null; // FIXME!
        masterPostSlave = null; // FIXME!
        slavePostSlave = null; // FIXME!
        
        util.DEBUG("--------------------------------------------------------");
        // for SimplePerfTest
        tuplesToInsertPerf = 10000;
        commitFreq = 1000; // "0" is autocommit
        
        util.DEBUG("--------------------------------------------------------");
        
            // FIXME! state.initEnvironment(cp);
        
        util.DEBUG("--------------------------------------------------------");

    }
    
    void initMaster(String host, String dbName)
    throws Exception
    {
        
        File masterHome = new File(masterDatabasePath, masterDbSubPath);
        File slaveHome = new File(slaveDatabasePath, slaveDbSubPath);
        util.DEBUG("initMaster");
        
        /* bootMasterDataBase now does "connect ...;create=true" */
        
        String results = null;
        if ( host.equalsIgnoreCase("localhost") || localEnv )
        {
            if (PrivilegedFileOpsForTests.exists(masterHome)) {
                BaseTestCase.assertDirectoryDeleted(masterHome);
            }
            util.mkDirs(masterHome.getPath()); // Create the directory
            
            // Ditto for slave:
            if (PrivilegedFileOpsForTests.exists(slaveHome)) {
                BaseTestCase.assertDirectoryDeleted(slaveHome);
            }
            util.mkDirs(slaveHome.getPath()); // Create the directory
            
            // util.writeToFile(derbyProperties, dir+FS+"derby.properties");
        }
        else
        {            
            String command = "mkdir -p "+masterDatabasePath+FS+masterDbSubPath+";"
                + " cd "+masterDatabasePath+FS+masterDbSubPath+";"
                + " rm -rf " + dbName + " derby.log;"
                + " rm -f Server*.trace;"
                + " ls -al;"
                ;
            results = 
                runUserCommandRemotely(command,
                host,
                testUser,
                "initMaster ");
        }
        util.DEBUG(results);
        
        
    }
    private void removeSlaveDBfiles(String host, String dbName)
    throws InterruptedException
    {
        /*
         * PoC:
         * cd /home/user/Replication/testing/db_slave
         * rm -f test/seg0/*
         */
        
        String command = "cd " + slaveDatabasePath+FS+slaveDbSubPath+";"
                + " rm -f " + dbName + FS + "seg0" + FS + "* ;"
                + " ls -al test test/seg0" // DEBUG
                ;
        
        String results =
                runUserCommandRemotely(command,
                host,
                testUser,
                // dbName, // unneccessary?
                "removeSlaveDBfiles ");
        util.DEBUG(results);
        
    }
    void initSlave(String host, String clientVM, String dbName)
    throws Exception
    {
        
        util.DEBUG("initSlave");
        File slaveHome = new File(slaveDatabasePath, slaveDbSubPath);
        File masterHome = new File(masterDatabasePath, masterDbSubPath);
        File masterDb = new File(masterHome, dbName);
        
        String results = null;
        if ( host.equalsIgnoreCase("localhost") || localEnv )
        {
            // The slaveDb dir is cleaned by initMaster! NB NB SHOULD THIS BE SO?
            // util.cleanDir(slaveDb, true); // true: do delete the db directory itself.
                                          // derby.log etc will be kept.
            // Copy (.../master/test) into (.../slave/).
            File slaveDb = new File(slaveHome, dbName);
            PrivilegedFileOpsForTests.copy(masterDb, slaveDb);

            // util.writeToFile(derbyProperties, slaveDir+FS+"derby.properties");
        }
        else
        {
            String command = "mkdir -p " + slaveHome.getPath() + ";"
                + " cd " + slaveHome.getPath() +";"
                + " rm -rf " + dbName + " derby.log;"
                + " rm -f Server*.trace;"
                + " scp -r " + masterServerHost + ":" + masterDb.getPath() +"/ .;" // Copying the master DB.
                + " ls -al" // DEBUG
                ;
        
            results =
                runUserCommandRemotely(command,
                host,
                testUser,
                "initSlave ");
        }
        util.DEBUG(results);
        
    }
    // ?? The following should be moved to a separate class, subclass this and
    // ?? CompatibilityCombinations
    void restartServer(String serverVM, String serverVersion,
            String serverHost,
            String interfacesToListenOn,
            int serverPort,
            String dbSubDirPath)
            throws Exception
    {
            stopServer(serverVM, serverVersion,
                    serverHost, serverPort);
            
            startServer(serverVM, serverVersion,
                    serverHost,
                    interfacesToListenOn, 
                    serverPort,
                    dbSubDirPath); // Distinguishing master/slave
    }
    void startServer(String serverVM, String serverVersion,
            String serverHost,
            String interfacesToListenOn,
            int serverPort,
            String dbSubDirPath)
            throws Exception
    {
        util.DEBUG("");
         
        final String debugId = "startServer@" + serverHost + ":" + serverPort + " ";
        util.DEBUG(debugId+"+++ StartServer " + serverVM + " / " + serverVersion);
                
        String serverClassPath = serverVersion + FS+"derby.jar"
                + PS + serverVersion + FS+"derbynet.jar"
                + PS + test_jars; // Required if the test (run on the client) 
                                  // defines and uses functions on test classes.
                                  // Example: PADSTRING in StressMultiTest.

        final boolean isRemote = !serverHost.equals("localhost");
        
        String workingDirName = masterDatabasePath +FS+ dbSubDirPath;
        
        ArrayList<String> ceArray = new ArrayList<String>();

        // For remote tests, we need to specify the Java VM to use and the
        // classpath. For local tests, we'll just use the JVM and the classpath
        // BaseTestCase.execJavaCmd() gives us. Note that this means we cannot
        // vary server versions when running locally.
        if (isRemote) {
            ceArray.add( ReplicationRun.getMasterJavaExecutableName() );
            ceArray.add( "-cp" );
            ceArray.add( serverClassPath );
        }
        ceArray.add( "-Dderby.system.home=" + workingDirName );
        ceArray.add( "-Dderby.infolog.append=true" );
        //ceArray.add( " -Dderby.language.logStatementText=true" ); // Goes into derby.log: Gets HUGE );
        if ( db_uid != null )
        {
            ceArray.add( "-Dderby.authentication.provider=NATIVE:" + replicatedDb + ":LOCAL" );
        }
        ceArray.add( networkServerControl );
        ceArray.add( "start" );
        ceArray.add( "-h" );
        ceArray.add( interfacesToListenOn ); // allowedClient
        ceArray.add( "-p" );
        ceArray.add( String.valueOf( serverPort ) );
        ceArray.add( "-noSecurityManager" );

        final String[] commandElements = util.toStringArray(ceArray);

        if (!isRemote)
        {
            util.DEBUG(debugId+"Starting server on localhost "+ serverHost);
            runUserCommandInThreadLocally(commandElements, null, debugId);
        }
        else
        {
            util.DEBUG(debugId+"Starting server on non-local host "+ serverHost);
            String fullCmd = util.splice(commandElements, ' ');
            runUserCommandInThreadRemotely(
                    fullCmd, serverHost, testUser, debugId);
        }

        // Wait for the server to come up in a reasonable time.
        pingServer(serverHost, serverPort);

        util.DEBUG(debugId+"--- StartServer ");
        util.DEBUG("");
    }
    /* 
    private NetworkServerControl startServer_direct(String serverHost, 
            String interfacesToListenOn, 
            int serverPort, 
            String fullDbDirPath,
            String securityOption) // FIXME? true/false?
    throws Exception
    { // Wotk in progress. Not currently used! Only partly tested!
        util.DEBUG("startServer_direct " + serverHost 
                + " " + interfacesToListenOn +  " " + serverPort
                + " " + fullDbDirPath);
        assertTrue("Attempt to start server on non-localhost: " + serverHost, 
                serverHost.equalsIgnoreCase("localhost"));
        
        System.setProperty("derby.system.home", fullDbDirPath);
        System.setProperty("user.dir", fullDbDirPath);
        
        NetworkServerControl server = new NetworkServerControl(
                InetAddress.getByName(interfacesToListenOn), serverPort);
        
        server.start(null); 
        pingServer(serverHost, serverPort, 150);
        
        Properties sp = server.getCurrentProperties();
        sp.setProperty("noSecurityManager", 
                securityOption.equalsIgnoreCase("-noSecurityManager")?"true":"false");
        // derby.log for both master and slave ends up in masters system!
        // Both are run in the same VM! Not a good idea?
        return server;
    }
     */


    void killMaster(String masterServerHost, int masterServerPort)
    throws InterruptedException
    {
        util.DEBUG("killMaster: " + masterServerHost +":" + masterServerPort);
        if ( masterServerHost.equals("localhost") )
        {
            stopServer(masterJvmVersion, derbyMasterVersion,
                    masterServerHost, masterServerPort);
        }
        else
        {
            int pid = xFindServerPID(masterServerHost,masterServerPort);
            xStopServer(masterServerHost, pid);
        }
    }
    void killSlave(String slaveServerHost, int slaveServerPort)
    throws InterruptedException
    {
        util.DEBUG("killSlave: " + slaveServerHost +":" + slaveServerPort);
        if ( slaveServerHost.equals("localhost") )
        {
            stopServer(slaveJvmVersion, derbySlaveVersion,
                    slaveServerHost, slaveServerPort);
        }
        else
        {
            int pid = xFindServerPID(slaveServerHost, slaveServerPort);
            xStopServer(slaveServerHost, pid);
        }
    }
    void destroySlaveDB(String slaveServerHost)
    throws InterruptedException
    {
        removeSlaveDBfiles(slaveServerHost, replicatedDb);
    }
    void stopServer(String serverVM, String serverVersion,
            String serverHost, int serverPort)
    {
        util.DEBUG("");
        final String debugId = "stopServer@" + serverHost + ":" + serverPort + " ";
        util.DEBUG("+++ stopServer " + serverVM + " / " + serverVersion
                + " " + debugId);
        
        String serverJvm = ReplicationRun.getServerJavaExecutableName(serverHost,serverVM);
        String serverClassPath = serverVersion + FS+"derby.jar"
                + PS + serverVersion + FS+"derbynet.jar";

        final boolean isRemote = !serverHost.equals("localhost");

        ArrayList<String> ceArray = new ArrayList<String>();

        // For remote tests, we need to specify the Java VM to use and the
        // classpath. For local tests, we'll just use the JVM and the classpath
        // BaseTestCase.execJavaCmd() gives us. Note that this means we cannot
        // vary server versions when running locally.
        if (isRemote) {
            ceArray.add( serverJvm );
            ceArray.add( "-cp" );
            ceArray.add( serverClassPath );
        }
        ceArray.add( "-Dderby.infolog.append=true" );
        ceArray.add( networkServerControl );
        ceArray.add( "shutdown" );
        ceArray.add( "-h" );
        ceArray.add( serverHost ); // FIXME! interfacesToListenOn
        ceArray.add( "-p" );
        ceArray.add( String.valueOf(serverPort ) );
        if ( db_uid != null )
        {
            ceArray.add( "-user" );
            ceArray.add( db_uid );
            ceArray.add( "-password" );
            ceArray.add( db_passwd );
        }

        final String[] commandElements = util.toStringArray(ceArray);
        
        final String fullCmd = util.splice(commandElements, ' ');
        util.DEBUG(debugId+"commandElements: " + fullCmd);

        final boolean serverOnLocalhost =
                serverHost.equalsIgnoreCase("localhost");
        if (serverOnLocalhost)
        {
            util.DEBUG(debugId+"Stopping server on localhost "+ serverHost);
            runUserCommandLocally(commandElements, debugId, null);
        }
        else
        {
            util.DEBUG(debugId+"Stopping server on non-local host "+ serverHost);
            runUserCommandRemotely(fullCmd, serverHost, testUser, debugId);
        }
        
        util.DEBUG(debugId+"--- stopServer ");
        util.DEBUG("");
        
    }

    private String processOutput(String id, Process proc)
    throws Exception
    {
        InputStream serveInputStream = proc.getInputStream();
        InputStream serveErrorStream = proc.getErrorStream();
        
        InputStreamReader isr = new InputStreamReader(serveInputStream);
        InputStreamReader esr = new InputStreamReader(serveErrorStream);
        BufferedReader bir = new BufferedReader(isr);
        BufferedReader ber = new BufferedReader(esr);
        String line=null;
        String result = null;
        util.DEBUG(id+"---- out:");
        // if ( bir.readLine() != null ) {result = id+" ---- out:";}
        while ( (line = bir.readLine()) != null)
        {
            util.DEBUG(id+line);
            result = result + LF + line;
        }
        util.DEBUG(id+"---- err:");
        // if ( ber.readLine() != null ) {result = result + LF + id+" ---- err:";}
        while ( (line = ber.readLine()) != null)
        {
            util.DEBUG(id+line);
            result = result + LF + line;
        }
        util.DEBUG(id+"----     ");
        // result = result + LF + id+" ----";
        
        return result;
    }
    
    private void processDEBUGOutput(String id, Process proc)
    throws Exception
    {
        InputStream serveInputStream = proc.getInputStream();
        InputStream serveErrorStream = proc.getErrorStream();
        
        InputStreamReader isr = new InputStreamReader(serveInputStream);
        InputStreamReader esr = new InputStreamReader(serveErrorStream);
        BufferedReader bir = new BufferedReader(isr);
        BufferedReader ber = new BufferedReader(esr);
        String line=null;
        util.DEBUG(id+"---- out:");
        while ( (line = bir.readLine()) != null)
        {
            util.DEBUG(id+line);
        }
        util.DEBUG(id+"---- err:");
        while ( (line = ber.readLine()) != null)
        {
            util.DEBUG(id+line);
        }
        util.DEBUG(id+"----     ");

        int exitCode = proc.waitFor();
        util.DEBUG(id + "process exit status: " + exitCode);
    }

    /**
     * Register that a thread has been started so that we can wait for it to
     * complete in {@link #tearDown()}.
     *
     * @param thread a thread that has been started
     */
    private void registerThread(Thread thread) {
        helperThreads.add(thread);
    }

    private void pingServer( String hostName, int port)
    throws Exception
    {
        util.DEBUG("+++ pingServer: " + hostName +":" + port);
        NetworkServerControl controller =
            new NetworkServerControl(InetAddress.getByName(hostName), port);
        assertTrue("Server did not start in time",
                   NetworkServerTestSetup.pingForServerStart(controller));
        util.DEBUG("--- pingServer: " + hostName +":" + port);
    }

    void startOptionalLoad(Load load,
            String dbSubPath,
            String serverHost,
            int serverPort)
            throws Exception
    {
        String loadString = load.load;
        String database = load.database;
        boolean existingDB = load.existingDB;
        String testClientHost = load.clientHost;
        util.DEBUG("run load " + loadString
                + " on client " + testClientHost
                + " against server " + serverHost + ":" + serverPort
                + " using DB  " + database + "["+existingDB+"]"
                );
        if ( loadString == null )
        {
            util.DEBUG("No load supplied!");
            return;
        }
        if ( !existingDB )
        {
            // Create it!
            String URL = masterURL(database)
                    +";create=true"; // Creating! No need for encryption here?
            String ijClassPath = derbyVersion +FS+ "derbyclient.jar"
                    + PS + derbyVersion +FS+ "derbyTesting.jar"
                    + PS + derbyVersion +FS+ "derbytools.jar";
        if ( serverHost.equals("localhost") )
        { // Use full classpath when running locally. Can not vary server versions!
            ijClassPath = classPath;
        }
            String clientJvm = ReplicationRun.getClientJavaExecutableName();
            String command = "rm -rf /"+masterDbPath( database )+";" // FIXME! for slave load!
                    + clientJvm // "java"
                    + " -Dij.driver=" + DRIVER_CLASS_NAME
                    + " -Dij.connection.create"+database+"=\"" + URL + "\""
                    + " -classpath " + ijClassPath + " org.apache.derby.tools.ij"
                    + " " + sqlLoadInit // FIXME! Should be load specific!
                    ;
            String results =
                    runUserCommandRemotely(command,
                    testClientHost,
                    testUser,
                    "Create_"+database);
            
        }
        
        // Must run in separate thread!:
        runLoad(loadString,
                jvmVersion,
                testClientHost,
                serverHost, serverPort,
                dbSubPath+FS+database);
        
        // FIXME! How to join and cleanup....
        
    }

    void makeReadyForReplication()
        throws Exception
    {   // Replace the following code in all tests with a call to makeReadyForReplication()!
        cleanAllTestHosts();
        
        initEnvironment();
        
        initMaster(masterServerHost,
                replicatedDb);
        
        startServer(masterJvmVersion, derbyMasterVersion,
                masterServerHost,
                ALL_INTERFACES,
                masterServerPort,
                masterDbSubPath);
        
        startServer(slaveJvmVersion, derbySlaveVersion,
                slaveServerHost,
                ALL_INTERFACES,
                slaveServerPort,
                slaveDbSubPath);
        
        startServerMonitor(slaveServerHost);
        
        bootMasterDatabase(jvmVersion,
                masterDatabasePath +FS+ masterDbSubPath,
                replicatedDb,
                masterServerHost,
                masterServerPort,
                null // bootLoad, // The "test" to start when booting db.
                );
        
        initSlave(slaveServerHost,
                jvmVersion,
                replicatedDb);
        
        startSlave(jvmVersion, replicatedDb,
                slaveServerHost,
                slaveServerPort,
                slaveServerHost,
                slaveReplPort,
                testClientHost);
        
        startMaster(jvmVersion, replicatedDb,
                masterServerHost,
                masterServerPort,
                masterServerHost,
                slaveServerPort,
                slaveServerHost,
                slaveReplPort);
        
    }
    
    ///////////////////////////////////////////////////////////////////////////
    /* Remove any servers or tests still running
     */
    void cleanAllTestHosts()
    {
        util.DEBUG("************************** cleanAllTestHosts() Not yet implemented");
    }

    ///////////////////////////////////////////////////////////////////////////
    /* The following is used to run tests in the various states of replication
     */
    class State
    {
        String testPreStartedMasterServer = null;
        boolean testPreStartedMasterServerReturn = false;
        String testPreStartedSlaveServer = null;
        boolean testPreStartedSlaveServerReturn = false;
        String testPreStartedMaster = null;
        boolean testPreStartedMasterReturn = false;
        String testPreInitSlave = null;
        boolean testPreInitSlaveReturn = false;
        String testPreStartedSlave = null;
        boolean testPreStartedSlaveReturn = false;
        String testPostStartedMasterAndSlave = null;
        boolean testPostStartedMasterAndSlaveReturn = false;
        String testPreStoppedMaster = null;
        boolean testPreStoppedMasterReturn = false;
        String testPreStoppedMasterServer = null;
        boolean testPreStoppedMasterServerReturn = false;
        String testPreStoppedSlave = null;
        boolean testPreStoppedSlaveReturn = false;
        String testPreStoppedSlaveServer = null;
        boolean testPreStoppedSlaveServerReturn = false;
        String testPostStoppedSlave = null;
        boolean testPostStoppedSlaveReturn = false;
        String testPostStoppedSlaveServer = null;
        boolean testPostStoppedSlaveServerReturn = false;
                
        void initEnvironment(Properties cp)
        {
            testPreStartedMasterServer = cp.getProperty("test.PreStartedMasterServer", null);
            testPreStartedMasterServerReturn = cp.getProperty("test.PreStartedMasterServer.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreStartedMasterServer:" 
                    + testPreStartedMasterServer + FS 
                    + testPreStartedMasterServerReturn);
            
            testPreStartedSlaveServer = cp.getProperty("test.PreStartedSlaveServer", null);
            testPreStartedSlaveServerReturn = cp.getProperty("test.PreStartedSlaveServer.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreStartedSlaveServer:" 
                    + testPreStartedSlaveServer + FS 
                    + testPreStartedSlaveServerReturn);
            
            testPreInitSlave = cp.getProperty("test.PreInitSlave", null);
            testPreInitSlaveReturn = cp.getProperty("test.PreInitSlave.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreInitSlave:" 
                    + testPreInitSlave + FS 
                    + testPreInitSlaveReturn);
            
            testPreStartedMaster = cp.getProperty("test.PreStartedMaster", null);
            testPreStartedMasterReturn = cp.getProperty("test.PreStartedMaster.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreStartedMaster:" 
                    + testPreStartedMaster + FS 
                    + testPreStartedMasterReturn);
            
            testPreStartedSlave = cp.getProperty("test.PreStartedSlave", null);
            testPreStartedSlaveReturn = cp.getProperty("test.PreStartedSlave.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreStartedSlave:" 
                    + testPreStartedSlave + FS 
                    + testPreStartedSlaveReturn);
            
            testPostStartedMasterAndSlave = cp.getProperty("test.PostStartedMasterAndSlave", null);
            testPostStartedMasterAndSlaveReturn = cp.getProperty("test.PostStartedMasterAndSlave.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPostStartedMasterAndSlave:" 
                    + testPostStartedMasterAndSlave + FS 
                    + testPostStartedMasterAndSlaveReturn);
            
            testPreStoppedMaster = cp.getProperty("test.PreStoppedMaster", null);
            testPreStoppedMasterReturn = cp.getProperty("test.PreStoppedMaster.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreStoppedMaster:" 
                    + testPreStoppedMaster + FS 
                    + testPreStoppedMasterReturn);
            
            testPreStoppedMasterServer = cp.getProperty("test.PreStoppedMasterServer", null);
            testPreStoppedMasterServerReturn = cp.getProperty("test.PreStoppedMasterServer.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreStoppedMasterServer:" 
                    + testPreStoppedMasterServer + FS 
                    + testPreStoppedMasterServerReturn);
            
            testPreStoppedSlave = cp.getProperty("test.PreStoppedSlave", null);
            testPreStoppedSlaveReturn = cp.getProperty("test.PreStoppedSlave.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPreStoppedSlave:" 
                    + testPreStoppedSlave + FS 
                    + testPreStoppedSlaveReturn);
            
            testPostStoppedSlave = cp.getProperty("test.PostStoppedSlave", null);
            testPostStoppedSlaveReturn = cp.getProperty("test.PostStoppedSlave.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPostStoppedSlave:" 
                    + testPostStoppedSlave + FS 
                    + testPostStoppedSlaveReturn);
            
            testPostStoppedSlaveServer = cp.getProperty("test.PostStoppedSlaveServer", null);
            testPostStoppedSlaveServerReturn = cp.getProperty("test.PostStoppedSlaveServer.return", "false")
                                                             .equalsIgnoreCase("true");
            util.DEBUG("testPostStoppedSlaveServer:" 
                    + testPostStoppedSlaveServer + FS 
                    + testPostStoppedSlaveServerReturn);
        }
        
    boolean testPreStartedMasterServer()
        throws Exception
    {
        /*
# Superflueous? Set .test=null for false? test.preStartedMasterServer=true
# Test to run:
test.preStartedMasterServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartMasterCmdTooEarly
# Return from test framework immediatly:
test.preStartedMasterServer.return=true
         */
        util.DEBUG("****** BEGIN testPreStartedMasterServer");
        if ( testPreStartedMasterServer != null )
        {
            runStateTest(testPreStartedMasterServer, // E.g. org.apache.derbyTesting.functionTests.tests.replicationTests.TestPreStartedMasterServer
                    jvmVersion,
                    testClientHost, // using connect. On masterServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStartedMasterServerReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStartedMasterServer");
        return testPreStartedMasterServerReturn;
    }

    boolean testPreStartedSlaveServer()
        throws Exception
    {
        /*
# test.preStartedSlaveServer=true
test.preStartedSlaveServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartSlaveCmdTooEarly
test.preStartedSlaveServer.return=true
         */
        util.DEBUG("****** BEGIN testPreStartedSlaveServer");
        if ( testPreStartedSlaveServer != null )
        {
            runStateTest(testPreStartedSlaveServer, // E.g. org.apache.derbyTesting.functionTests.tests.replicationTests.TestPreStartedSlaveServer
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStartedSlaveServerReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStartedSlaveServer");
        return testPreStartedSlaveServerReturn;
    }

    boolean testPreStartedMaster()
        throws Exception
    {
        /*
# test.preStartedMaster=true
test.preStartedMaster.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartMasterCmd_OK
# test.preStartedMaster.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartMasterCmd_ERR
test.preStartedMaster.return=true
         */
        util.DEBUG("****** BEGIN testPreStartedMaster");
        if ( testPreStartedMaster != null )
        {
            runStateTest(testPreStartedMaster, // E.g. org.apache.derbyTesting.functionTests.tests.replicationTests.TestPreStartedMaster
                    jvmVersion,
                    testClientHost, // using connect. On masterServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStartedMasterReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStartedMaster");
        return testPreStartedMasterReturn;
    }

    boolean testPreInitSlave()
        throws Exception
    {
        /*
# test.preInitSlave=true
test.preInitSlave.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartSlaveCmd_OK
# test.preInitSlave.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartSlaveCmd_ERR
test.preInitSlave.return=true
         */
        util.DEBUG("****** BEGIN testPreInitSlave");
        if ( testPreInitSlave != null )
        {
            runStateTest(testPreInitSlave,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreInitSlaveReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreInitSlave");
        return testPreInitSlaveReturn;
    }

    boolean testPreStartedSlave()
        throws Exception
    {
        /*
# test.preStartedSlave=true
test.preStartedSlave.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartSlaveCmd_OK
# test.preStartedMaster.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StartSlaveCmd_ERR
test.preStartedSlave.return=true
         */
        util.DEBUG("****** BEGIN testPreStartedSlave");
        if ( testPreStartedSlave != null )
        {
            runStateTest(testPreStartedSlave,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStartedSlaveReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStartedSlave");
        return testPreStartedSlaveReturn;
    }

    boolean testPostStartedMasterAndSlave()
        throws Exception
    {
        /*
# test.postStartedMasterAndSlave=true
test.postStartedMasterAndSlave.test=org.apache.derbyTesting.functionTests.tests.replicationTests.XXXX_OK
# test.postStartedMasterAndSlave.test=org.apache.derbyTesting.functionTests.tests.replicationTests.XXXX_ERR
test.postStartedMasterAndSlave.return=true
         */
        util.DEBUG("****** BEGIN testPostStartedMasterAndSlave");
        if ( testPostStartedMasterAndSlave != null )
        {
            // run testPostStartedMasterAndSlave test
            runStateTest(testPostStartedMasterAndSlave,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPostStartedMasterAndSlaveReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPostStartedMasterAndSlave");
        return testPostStartedMasterAndSlaveReturn;
    }

    boolean testPreStoppedMaster()
        throws Exception
    {
        /*
# test.preStoppedMaster=true
test.preStoppedMaster.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StopMasterCmd_OK
# test.preStoppedMaster.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StopMasterCmd_ERR
test.preStoppedMaster.return=true
         */
        util.DEBUG("****** BEGIN testPreStoppedMaster");
        if ( testPreStoppedMaster != null )
        {
            runStateTest(testPreStoppedMaster,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStoppedMasterReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStoppedMaster");
        return testPreStoppedMasterReturn;
    }

    boolean testPreStoppedMasterServer()
        throws Exception
    {
        /*
# test.preStoppedMasterServer=true
test.preStoppedMasterServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.YYYYY_OK
# test.preStoppedMasterServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.YYYYY_ERR
test.preStoppedMasterServer.return=true
         */
        util.DEBUG("****** BEGIN testPreStoppedMasterServer");
        if ( testPreStoppedMasterServer != null )
        {
            runStateTest(testPreStoppedMasterServer,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStoppedMasterServerReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStoppedMasterServer");
        return testPreStoppedMasterServerReturn;
    }

    boolean testPreStoppedSlave()
        throws Exception
    {
        /*
# test.preStoppedSlave=true
test.preStoppedSlave.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StopSlaveCmd_OK
# test.preStoppedSlave.test=org.apache.derbyTesting.functionTests.tests.replicationTests.StopSlaveCmd_ERR
test.preStoppedSlave.return=true
         */
        util.DEBUG("****** BEGIN testPreStoppedSlave");
        if ( testPreStoppedSlave != null )
        {
            runStateTest(testPreStoppedSlave,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStoppedSlaveReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStoppedSlave");
        return testPreStoppedSlaveReturn;
    }

    boolean testPreStoppedSlaveServer()
        throws Exception
    {
        /*
# test.preStoppedSlaveServer=true
test.preStoppedSlaveServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.ZZZZZZ_OK
# test.preStoppedSlaveServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.ZZZZZZ_ERR
test.preStoppedSlaveServer.return=true
         */
        util.DEBUG("****** BEGIN testPreStoppedSlaveServer");
        if ( testPreStoppedSlaveServer != null )
        {
            runStateTest(testPreStoppedSlaveServer,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPreStoppedSlaveServerReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPreStoppedSlaveServer");
        return testPreStoppedSlaveServerReturn;
    }

    boolean testPostStoppedSlaveServer()
        throws Exception
    {
        /*
# test.postStoppedSlaveServer=true
test.postStoppedSlaveServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.ZZZXXX_OK
# test.postStoppedSlaveServer.test=org.apache.derbyTesting.functionTests.tests.replicationTests.ZZZXXX_ERR
test.postStoppedSlaveServer.return=true
         */
        util.DEBUG("****** BEGIN testPostStoppedSlaveServer");
        if ( testPostStoppedSlaveServer != null )
        {
            runStateTest(testPostStoppedSlaveServer,
                    jvmVersion,
                    testClientHost, // using connect. On slaveServerHost using CLI
                    masterServerHost, masterServerPort,
                    replicatedDb);
        }
        if ( testPostStoppedSlaveServerReturn ) cleanupAndShutdown();
        util.DEBUG("****** END   testPostStoppedSlaveServer");
        return testPostStoppedSlaveServerReturn;
    }

        private void cleanupAndShutdown()
        {
            stopServer(jvmVersion, derbyVersion,
                masterServerHost, masterServerPort);

            stopServer(jvmVersion, derbyVersion,
                slaveServerHost, slaveServerPort);
        }
    }
    ///////////////////////////////////////////////////////////////////////////
    
    ///////////////////////////////////////////////////////////////////////////
    /* Load started in different states of replication. */
    class Load
    {
        Load(String id, Properties testRunProperties)
        {
            util.DEBUG("Load(): " + id);
            
            String pid = "test." + id;
            if ( testRunProperties.getProperty(pid,"false").equalsIgnoreCase("false") )
            {
                util.DEBUG(pid + " Not defined or set to false!");
            }
            else
            {
                
                pid = "test." + id + ".load";
                load = testRunProperties.getProperty(pid,
                        "org.apache.derbyTesting.functionTests.tests.replicationTests.DefaultLoad");
                util.DEBUG(pid+": " + load);
                
                pid = "test." + id + ".database";
                database = testRunProperties.getProperty(pid, id);
                util.DEBUG(pid+": " + database);
                
                pid = "test." + id + ".existingDB";
                existingDB = testRunProperties.getProperty(pid,"false").equalsIgnoreCase("true");
                util.DEBUG(pid+": " + existingDB);
                
                pid = "test." + id + ".clientHost";
                clientHost = testRunProperties.getProperty(pid, testClientHost);
                util.DEBUG(pid+": " + clientHost);
                
            }
            
        }
        
        String load = null; // .sql file or junit class
        String database = null; // Database name used by load
        boolean existingDB = false; // Database already exists.
        String clientHost = null; // Host running load client.
    }
    static Load masterPreRepl;
    static Load masterPostRepl;
    static Load slavePreSlave;
    static Load masterPostSlave;
    static Load slavePostSlave;
    ///////////////////////////////////////////////////////////////////////////

    /**
     * Assert that the latest startSlave connection attempt got the expected
     * SQLState. The method will wait for upto 5 seconds for the startSlave
     * connection attemt to complete. If the connection attempt has not
     * completed after 5 seconds it is assumed to have failed.
     * @param expected the expected SQLState
     * @throws java.lang.Exception the Exception to check the SQLState of
     */
    protected void assertSqlStateSlaveConn(String expected) throws Exception {
        boolean verified = false;
        for (int i = 0; i < 10; i++) {
            if (startSlaveException != null) {
                if (startSlaveException instanceof SQLException) {
                    BaseJDBCTestCase.
                        assertSQLState("Unexpexted SQL State",
                                       expected,
                                       (SQLException)startSlaveException);
                    verified = true;
                    break;
                } else {
                    throw startSlaveException;
                }
            } else {
                Thread.sleep(500);
            }
        }
        if (!verified) {
            fail("Attempt to start slave hangs. Expected SQL state " +
                 expected);
        }
    }
    
    void assertException(SQLException se, String expectedSqlState)
    {
        if (se == null ) // Did not get an exception
        {
            util.DEBUG("Got 'null' exception, expected '" + expectedSqlState + "'");
            assertTrue("Expected exception: " + expectedSqlState + " got: 'null' exception", 
                    expectedSqlState == null);
            return;
        }
        int ec = se.getErrorCode();
        String ss = se.getSQLState();
        String msg = "Got " + ec + " " + ss + " " + se.getMessage()
        + ". Expected " + expectedSqlState;
        util.DEBUG(msg);
        
        if ( expectedSqlState != null ) // We expect an exception
        {
            assertTrue(msg, ss.equals(expectedSqlState));
        }
        else // We do not expect an exception, but got one.
        {
            assertTrue(msg, false);
        }
    }
    
    
    void _testInsertUpdateDeleteOnMaster(String serverHost, 
            int serverPort,
            String dbPath,
            int _noTuplesToInsert)
        throws SQLException, ClassNotFoundException, IllegalAccessException,
               InstantiationException, NoSuchMethodException, InvocationTargetException
    {
        util.DEBUG("_testInsertUpdateDeleteOnMaster: " + serverHost + ":" +
                   serverPort + "/" + dbPath + " " + _noTuplesToInsert);
        ClientDataSourceInterface ds = configureDataSource(
                dbPath, serverHost, serverPort, useEncryption(false) );
        Connection conn = ds.getConnection();
        
        PreparedStatement ps = conn.prepareStatement("create table t(i integer primary key, s varchar(64))");
        
        ps.execute();
        
        ps = conn.prepareStatement("insert into t values (?,?)");
        for (int i = 0; i< _noTuplesToInsert; i++)
        {
            ps.setInt(1,i);
            ps.setString(2,"dilldall"+i);
            ps.execute();
            if ( (i % 10000) == 0 ) conn.commit();
        }
        
        _verify(conn, _noTuplesToInsert);
        
        conn.close();
    }
    void _verifyDatabase(String serverHost, 
            int serverPort,
            String dbPath,
            int _noTuplesInserted)
        throws SQLException, ClassNotFoundException, IllegalAccessException,
               InstantiationException, NoSuchMethodException, InvocationTargetException
    {
        util.DEBUG("_verifyDatabase: "+serverHost+":"+serverPort+"/"+dbPath);
        ClientDataSourceInterface ds = configureDataSource(
                dbPath, serverHost, serverPort, useEncryption(false) );
        Connection conn = ds.getConnection();
        
        _verify(conn,_noTuplesInserted);
        
        conn.close();
    }
    void _verify(Connection conn, int _noTuplesInserted)
        throws SQLException
    {
        Statement s = conn.createStatement();
        ResultSet rs = s.executeQuery("select count(*) from t");
        rs.next();
        int count = rs.getInt(1);
        rs = s.executeQuery("select max(i) from t");
        rs.next();
        int max = rs.getInt(1);
        util.DEBUG("_verify: " + count + "/" + _noTuplesInserted + " " + max +
                   "/" + (_noTuplesInserted - 1));
        assertEquals("Expected "+ _noTuplesInserted +" tuples, got "+ count +".",
                     _noTuplesInserted, count);
        assertEquals("Expected " +(_noTuplesInserted-1) +" max, got " + max +".",
                     _noTuplesInserted - 1, max);
    }
    
    Connection getConnection(String serverHost, int serverPort,
            String databasePath, String dbSubPath, String replicatedDb)
        throws SQLException
    {
        String db = databasePath +FS+dbSubPath +FS+ replicatedDb;
        String connectionURL = serverURL( db, serverHost, serverPort );
        //String connectionURL = "jdbc:derby:"  
        //       + "//" + serverHost + ":" + serverPort + "/"
        //        + db;
        util.DEBUG(connectionURL);
        return DriverManager.getConnection(connectionURL);
    }
    
    String masterDbPath(String dbName)
    {
        return masterDatabasePath+FS+masterDbSubPath+FS+dbName;
    }
    String slaveDbPath(String dbName)
    {
        return slaveDatabasePath+FS+slaveDbSubPath+FS+dbName;
    }
    String masterURL(String dbName)
    {
        return serverURL( masterDbPath( dbName ), masterServerHost, masterServerPort );
    }
    String masterLoadURL(String dbSubPath)
    {
        return serverURL( masterDatabasePath+FS+dbSubPath, masterServerHost, masterServerPort );
    }
    String slaveURL(String dbName)
    {
        return serverURL( slaveDbPath( dbName ), slaveServerHost, slaveServerPort );
    }
    String  serverURL( String fullDbPath, String serverHost, int serverPort )
    {
        return DB_PROTOCOL
                +"://"+serverHost
                +":"+serverPort+"/"
                +fullDbPath
                +useEncryption(false)
                +credentials();
    }
    String  credentials()
    {
        if ( db_uid == null ) { return ""; }
        else { return ";user=" + db_uid + ";password=" + db_passwd; }
    }


    SQLException stopSlave(
        String slaveServerHost,
        int slaveServerPort,
        String slaveDatabasePath,
        String replicatedDb,
        boolean masterServerAlive)
        throws Exception
    {
        return stopSlave(slaveServerHost,
                         slaveServerPort,
                         slaveDatabasePath,
                         ReplicationRun.slaveDbSubPath,
                         replicatedDb,
                         masterServerAlive);
    }


    SQLException stopSlave(
        String slaveServerHost,
        int slaveServerPort,
        String slaveDatabasePath,
        String subPath,
        String replicatedDb,
        boolean masterServerAlive)
        throws Exception
    {
        util.DEBUG("stopSlave");
        String dbPath = slaveDatabasePath + FS + subPath + FS + replicatedDb;

        String connectionURL = serverURL( dbPath, slaveServerHost, slaveServerPort ) + ";stopSlave=true";
        //String connectionURL = "jdbc:derby:"
        //    + "//" + slaveServerHost + ":" + slaveServerPort + "/"
        //    + dbPath
        //    + ";stopSlave=true"
        //    + useEncryption(false);

        if (masterServerAlive) {
            try {
                Connection conn = DriverManager.getConnection(connectionURL);
                conn.close();
                return null; // If successful.
            } catch (SQLException se) {
                return se;
            }
        } else {
            // We use a loop below, to allow for intermediate states before the
            // expected final state REPLICATION_DB_NOT_BOOTED.
            //
            // If we get here quick enough we see these error states (in order):
            //     a) SLAVE_OPERATION_DENIED_WHILE_CONNECTED
            //     b) REPLICATION_SLAVE_SHUTDOWN_OK
            //
            SQLException gotEx = null;
            int tries = 20;

            while (tries-- > 0) {
                gotEx = null;

                try {
                    DriverManager.getConnection(connectionURL);
                    fail("Unexpectedly connected");
                } catch (SQLException se) {
                    if (se.getSQLState().
                            equals(SLAVE_OPERATION_DENIED_WHILE_CONNECTED)) {
                        // Try again, shutdown did not complete yet..
                        gotEx = se;
                        util.DEBUG
                            ("got SLAVE_OPERATION_DENIED_WHILE_CONNECTED, " +
                             "sleep");
                        Thread.sleep(1000L);
                        continue;

                    } else if (se.getSQLState().
                                   equals(REPLICATION_SLAVE_SHUTDOWN_OK)) {
                        // Try again, shutdown started but did not complete yet.
                        gotEx = se;
                        util.DEBUG("got REPLICATION_SLAVE_SHUTDOWN_OK, " +
                                   "sleep..");
                        Thread.sleep(1000L);
                        continue;

                    } else if (se.getSQLState().
                                   equals(REPLICATION_DB_NOT_BOOTED)) {
                        // All is fine, so proceed
                        util.DEBUG("Got REPLICATION_DB_NOT_BOOTED as expected");
                        break;

                    } else {
                        // Something else, so report.
                        gotEx = se;
                        break;
                    }
                }
            }

            if (gotEx != null) {
                // We did not get what we expected as the final state
                // (REPLICATION_DB_NOT_BOOTED) in reasonable time, or we saw
                // something that is not a legal intermediate state, so we fail
                // now:
                throw gotEx;
            }

            return null;
        }
    }
    private static String getMasterJavaExecutableName()
    {
        if ( masterServerHost.matches("localhost") )
        {
            return BaseTestCase.getJavaExecutableName();
        }
        return masterJvmVersion+FS+".."+FS+"bin"+FS+"java";
    }
    private static String getSlaveJavaExecutableName()
    {
        if ( slaveServerHost.matches("localhost") )
        {
            return BaseTestCase.getJavaExecutableName();
        }
        return slaveJvmVersion+FS+".."+FS+"bin"+FS+"java";
    }
    private static String getClientJavaExecutableName()
    {
        if ( testClientHost.matches("localhost") )
        {
            return BaseTestCase.getJavaExecutableName();
        }
        return jvmVersion+FS+".."+FS+"bin"+FS+"java";
    }
    private static String getServerJavaExecutableName(String serverHost,String serverVM)
    {
        if ( serverHost.matches("localhost") )
        {
            return BaseTestCase.getJavaExecutableName();
        }
        return serverVM+FS+".."+FS+"bin"+FS+"java";        
    }

    /**
     * <p>
     * Set up a data source.
     * </p>
     */
    ClientDataSourceInterface    configureDataSource
        (
         String dbName,
         String serverHost,
         int        serverPort,
         String     connectionAttributes
         ) throws ClassNotFoundException, IllegalAccessException,
                  InstantiationException, NoSuchMethodException, InvocationTargetException
    {
        ClientDataSourceInterface ds;

        Class<?> clazz;
        if (JDBC.vmSupportsJNDI()) {
            clazz = Class.forName("org.apache.derby.jdbc.ClientDataSource");
            ds = (ClientDataSourceInterface) clazz.getConstructor().newInstance();
        } else {
            clazz = Class.forName("org.apache.derby.jdbc.BasicClientDataSource40");
            ds = (ClientDataSourceInterface) clazz.getConstructor().newInstance();
        }

        ds.setDatabaseName( dbName );
        ds.setServerName( serverHost );
        ds.setPortNumber(serverPort);
        ds.setConnectionAttributes( connectionAttributes );

        if ( db_uid != null )
        {
            ds.setUser( db_uid );
            ds.setPassword( db_passwd );
        }
        
        return ds;
    }
    
}
