com.splout.db.common.TestUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.splout.db.common.TestUtils.java

Source

package com.splout.db.common;

/*
 * #%L
 * Splout SQL Server
 * %%
 * Copyright (C) 2012 Datasalt Systems S.L.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.splout.db.common.JSONSerDe.JSONSerDeException;
import com.splout.db.dnode.DNode;
import com.splout.db.dnode.DNodeProperties;
import com.splout.db.dnode.FetcherProperties;
import com.splout.db.dnode.IDNodeHandler;
import com.splout.db.engine.EngineManager.EngineException;
import com.splout.db.engine.SQLite4JavaManager;
import com.splout.db.hazelcast.CoordinationStructures;
import com.splout.db.hazelcast.HazelcastConfigBuilder;
import com.splout.db.hazelcast.HazelcastConfigBuilder.HazelcastConfigBuilderException;
import com.splout.db.qnode.IQNodeHandler;
import com.splout.db.qnode.QNode;
import com.splout.db.qnode.QNodeProperties;
import org.apache.commons.io.FileUtils;
import org.apache.thrift.transport.TTransportException;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Things that are used extensively in unit / integration tests.
 */
public class TestUtils {

    /**
     * Creates a simple database with two columns: one integer (a) and one string (b). It also insertes one default row
     * from parameters a, b.
     *
     * @throws EngineException
     */
    public static void createFooDatabase(String where, int a, String b)
            throws SQLException, JSONSerDeException, ClassNotFoundException, EngineException {
        File dbFolder = new File(where);
        dbFolder.mkdir();
        final SQLite4JavaManager manager = new SQLite4JavaManager();
        SploutConfiguration testConfig = SploutConfiguration.getTestConfig();
        manager.init(new File(where + "/" + "foo.db"), testConfig, null);
        manager.query("DROP TABLE IF EXISTS t;", 100);
        manager.query("CREATE TABLE t (a INT, b TEXT);", 100);
        manager.query("INSERT INTO t (a, b) VALUES (" + a + ", \"" + b + "\")", 100);
        manager.close();
    }

    /**
     * Use this method to get a high-level client for Hazelcast in unit tests.
     *
     * @throws HazelcastConfigBuilderException
     */
    public static CoordinationStructures getCoordinationStructures(SploutConfiguration testConfig)
            throws HazelcastConfigBuilderException {
        HazelcastInstance hz = Hazelcast.newHazelcastInstance(HazelcastConfigBuilder.build(testConfig));
        CoordinationStructures coord = new CoordinationStructures(hz);
        return coord;
    }

    /**
     * Use this class for waiting on a certain condition up to some time.
     */
    public static abstract class NotWaitingForeverCondition {

        public abstract boolean endCondition() throws Exception;

        public void waitAtMost(long patience) throws Exception {
            long waitedSoFar = 0;
            while (!endCondition()) {
                Thread.sleep(200);
                waitedSoFar += 200;
                if (waitedSoFar > patience) {
                    throw new AssertionError("Waited more than " + patience + " on a test condition.");
                }
            }
        }
    }

    /**
     * Utility class that can be used for implementing things that can be retried up to a maximum number of times.
     */
    public static abstract class CatchAndRetry {

        private int maxRetrials;
        private Class<? extends Throwable> exception;

        public CatchAndRetry(Class<? extends Throwable> exception, int maxRetrials) {
            this.exception = exception;
            this.maxRetrials = maxRetrials;
        }

        public abstract void businessLogic() throws Throwable;

        public abstract void retryLogic();

        public void catchAndRetry() throws Throwable {
            int trial = 0;
            boolean succeeded = false;
            do {
                try {
                    businessLogic();
                    succeeded = true;
                } catch (Throwable t) {
                    if (exception.isAssignableFrom(t.getClass())) {
                        trial++;
                        retryLogic();
                    } else {
                        throw t;
                    }
                }
            } while (!succeeded && trial < maxRetrials);
        }
    }

    /**
     * Returns a QNode instance if, after a maximum of X trials, we can find a port to bind it to.
     * The configuration passed by instance might have been modified accordingly.
     */
    public static QNode getTestQNode(final SploutConfiguration testConfig, final IQNodeHandler handler)
            throws Throwable {
        final AtomicReference<QNode> reference = new AtomicReference<QNode>();
        CatchAndRetry qNodeInit = new CatchAndRetry(java.net.BindException.class, 50) {

            @Override
            public void businessLogic() throws Throwable {
                QNode qNode = new QNode();
                qNode.start(testConfig, handler);
                reference.set(qNode);
            }

            @Override
            public void retryLogic() {
                testConfig.setProperty(QNodeProperties.PORT, testConfig.getInt(QNodeProperties.PORT) + 1);
            }
        };
        qNodeInit.catchAndRetry();
        return reference.get();
    }

    public static DNode getTestDNode(final SploutConfiguration testConfig, final IDNodeHandler handler,
            final String dataFolder) throws Throwable {
        return getTestDNode(testConfig, handler, dataFolder, true);
    }

    /**
     * Returns a DNode instance if, after a maximum of X trials, we can find a port to bind it to.
     * The configuration passed by instance might have been modified accordingly.
     */
    public static DNode getTestDNode(final SploutConfiguration testConfig, final IDNodeHandler handler,
            final String dataFolder, boolean deleteDataFolder) throws Throwable {
        final AtomicReference<DNode> reference = new AtomicReference<DNode>();
        testConfig.setProperty(DNodeProperties.DATA_FOLDER, dataFolder);
        if (deleteDataFolder) {
            File file = new File(dataFolder);
            if (file.exists()) {
                FileUtils.deleteDirectory(file);
            }
            file.mkdir();
        }
        testConfig.setProperty(FetcherProperties.TEMP_DIR, "fetcher-" + dataFolder);
        File fetcherTmp = new File("fetcher-" + dataFolder);
        if (fetcherTmp.exists()) {
            FileUtils.deleteDirectory(fetcherTmp);
        }
        fetcherTmp.mkdir();
        CatchAndRetry dNodeInit = new CatchAndRetry(TTransportException.class, 50) {

            @Override
            public void businessLogic() throws Throwable {
                DNode dNode = new DNode(testConfig, handler);
                dNode.init();
                reference.set(dNode);
            }

            @Override
            public void retryLogic() {
                testConfig.setProperty(DNodeProperties.PORT, testConfig.getInt(DNodeProperties.PORT) + 1);
            }
        };
        dNodeInit.catchAndRetry();
        return reference.get();
    }

    /**
     * Delete folders that *might* have been created by DNodes & ZooKeepers from a certain test class.
     * The number of instances is the number of different methods (and therefore unique namespaces) in the test class.
     * The convention is: dnode-ClassName-i for Dnode data folder of test class "className" and method "i".
     * Same follows for ZooKeeper data folders "zk-" and Fetcher TMP folders "fetcher-dnode-" .
     */
    public static void cleanUpTmpFolders(String className, int nInstances) throws IOException {
        File file = new File("dnode-" + className);
        if (file.exists()) {
            FileUtils.deleteDirectory(file);
        }
        file = new File("zk-" + className);
        if (file.exists()) {
            FileUtils.deleteDirectory(file);
        }
        file = new File("fetcher-dnode-" + className);
        if (file.exists()) {
            FileUtils.deleteDirectory(file);
        }
        for (int i = 0; i <= nInstances; i++) {
            file = new File("dnode-" + className + "-" + i);
            if (file.exists()) {
                FileUtils.deleteDirectory(file);
            }
            file = new File("zk-" + className + "-" + i);
            if (file.exists()) {
                FileUtils.deleteDirectory(file);
            }
            file = new File("fetcher-dnode-" + className + "-" + i);
            if (file.exists()) {
                FileUtils.deleteDirectory(file);
            }
        }
    }

}