org.ensembl.healthcheck.StandaloneTestRunner.java Source code

Java tutorial

Introduction

Here is the source code for org.ensembl.healthcheck.StandaloneTestRunner.java

Source

/*
 * Copyright [1999-2018] EMBL-European Bioinformatics Institute
 * 
 * Licensed 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.ensembl.healthcheck;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.ensembl.healthcheck.StandaloneReporter.OutputFormat;
import org.ensembl.healthcheck.configuration.ConfigureTestGroups;
import org.ensembl.healthcheck.configurationmanager.ConfigurationException;
import org.ensembl.healthcheck.testcase.EnsTestCase;
import org.ensembl.healthcheck.testcase.SingleDatabaseTestCase;
import org.ensembl.healthcheck.testcase.funcgen.CompareFuncgenSchema;
import org.ensembl.healthcheck.testcase.generic.CompareSchema;
import org.ensembl.healthcheck.testcase.variation.CompareVariationSchema;
import org.ensembl.healthcheck.util.DBUtils;

import com.google.gson.Gson;
import com.mysql.jdbc.Driver;

import uk.co.flamingpenguin.jewel.cli.ArgumentValidationException;
import uk.co.flamingpenguin.jewel.cli.Cli;
import uk.co.flamingpenguin.jewel.cli.CliFactory;
import uk.co.flamingpenguin.jewel.cli.Option;

/**
 * Simple class to run one or more tests or groups on a single database
 * 
 * @author dstaines
 *
 */
public class StandaloneTestRunner {

    /**
     * 
     */
    private static final String WRITE_STDOUT = "-";

    /**
     * Options that specify how the tests run
     * 
     * @author dstaines
     *
     */
    public interface StandaloneTestOptions extends ConfigureTestGroups {

        @Option(helpRequest = true, description = "display help")
        boolean getHelp();

        @Option(shortName = "o", longName = "output_file", defaultValue = WRITE_STDOUT, description = "File to write any failures to (use '-' for standard out)")
        String getOutputFile();

        @Option(shortName = "f", longName = "output_format", defaultValue = "text", description = "Format for writing output")
        String getOutputFormat();

        @Option(shortName = "v", longName = "verbose", description = "Show detailed debugging output")
        boolean isVerbose();

        @Option(shortName = "d", longName = "dbname", description = "Database to test")
        String getDbname();

        boolean isDbname();

        @Option(shortName = "u", longName = "user", description = "Username for test database")
        String getUser();

        boolean isUser();

        @Option(shortName = "h", longName = "host", description = "Host for test database")
        String getHost();

        boolean isHost();

        @Option(shortName = "p", longName = "pass", description = "Password for test database")
        String getPassword();

        boolean isPassword();

        @Option(shortName = "P", longName = "port", description = "Port for test database")
        int getPort();

        boolean isPort();

        @Option(longName = "compara_dbname", defaultValue = "ensembl_compara_master", description = "Name of compara master database")
        String getComparaMasterDbname();

        boolean isComparaMasterDbname();

        @Option(longName = "compara_host", description = "Compara master database host")
        String getComparaHost();

        boolean isComparaHost();

        @Option(longName = "compara_port", description = "Compara master database port")
        int getComparaPort();

        boolean isComparaPort();

        @Option(longName = "compara_user", description = "Compara master database user")
        String getComparaUser();

        boolean isComparaUser();

        @Option(longName = "compara_pass", description = "Compara master database password")
        String getComparaPassword();

        boolean isComparaPassword();

        @Option(longName = "prod_dbname", defaultValue = "ensembl_production_93", description = "Name of production database")
        String getProductionDbname();

        boolean isProductionDbname();

        @Option(longName = "prod_host", description = "Production database host")
        String getProductionHost();

        boolean isProductionHost();

        @Option(longName = "prod_port", description = "Production database port")
        int getProductionPort();

        boolean isProductionPort();

        @Option(longName = "prod_user", description = "Production database user")
        String getProductionUser();

        boolean isProductionUser();

        @Option(longName = "prod_pass", description = "Production database password")
        String getProductionPassword();

        boolean isProductionPassword();

        @Option(longName = "secondary_host", description = "Secondary database host (ie. previous release)")
        String getSecondaryHost();

        boolean isSecondaryHost();

        @Option(longName = "secondary_port", description = "Secondary database port")
        int getSecondaryPort();

        boolean isSecondaryPort();

        @Option(longName = "secondary_user", description = "Secondary database user")
        String getSecondaryUser();

        boolean isSecondaryUser();

        @Option(longName = "secondary_pass", description = "Secondary database password")
        String getSecondaryPassword();

        boolean isSecondaryPassword();

        @Option(longName = "staging_host", description = "Staging database host (ie. current release)")
        String getStagingHost();

        boolean isStagingHost();

        @Option(longName = "staging_port", description = "Staging database port")
        int getStagingPort();

        boolean isStagingPort();

        @Option(longName = "staging_user", description = "Staging database user")
        String getStagingUser();

        boolean isStagingUser();

        @Option(longName = "staging_pass", description = "Staging database password")
        String getStagingPassword();

        boolean isStagingPassword();

        @Option(longName = "release", shortName = "r", description = "Current release")
        String getRelease();

        boolean isRelease();

        @Option(longName = "data_files_path", shortName = "D", description = "Path to data files directory")
        String getDataFilesPath();

        boolean isDataFilesPath();

        @Option(longName = "master_schema", shortName = "m", description = "Name of master schema for comparisons")
        String getMasterSchema();

        boolean isMasterSchema();

        @Option(longName = "list_tests", shortName = "l", description = "Show all the tests in the specified groups and tests")
        boolean isListTests();

    }

    /**
     * @param args
     */
    public static void main(String[] args) {

        StandaloneTestOptions options = null;
        Cli<StandaloneTestOptions> cli = CliFactory.createCli(StandaloneTestOptions.class);
        try {
            options = cli.parseArguments(args);
        } catch (ArgumentValidationException e) {
            System.err.println(e.getMessage());
            System.exit(2);
        }

        if (options.isListTests()) {
            System.exit(listTests(options));
        }

        if (!options.isDbname() || !options.isHost() || !options.isPort() || !options.isUser()) {
            System.err.println("--dbname, --host, --port and --user are required options");
            System.err.println(cli.getHelpMessage());
            System.exit(2);
        }

        StandaloneTestRunner runner = new StandaloneTestRunner(options);

        if (!StringUtils.isEmpty(options.getOutputFile()) && !options.getOutputFile().equals(WRITE_STDOUT)) {
            File outfile = new File(options.getOutputFile());
            if (outfile.exists()) {
                runner.getLogger().fine("Deleting existing output file " + options.getOutputFile());
                if (!outfile.delete()) {
                    runner.getLogger().fine("Could not delete existing output file " + options.getOutputFile());
                    System.exit(3);
                }
            }
        }

        StandaloneReporter reporter = new StandaloneReporter(runner.getLogger());
        ReportManager.setReporter(reporter);

        boolean result = runner.runAll();
        if (!result) {
            printFailures(options, runner, reporter);
        } else {
            runner.getLogger().info("Completed healthchecks with no failures");
        }
        System.exit(result ? 0 : 1);

    }

    private static int listTests(StandaloneTestOptions options) {
        Writer w = null;
        try {
            TestRegistry testRegistry = new ConfigurationBasedTestRegistry(options);
            Set<String> testCases = new HashSet<>();
            for (EnsTestCase test : testRegistry.getAll()) {
                testCases.add(test.getName());
            }
            if (options.getOutputFile().equals(WRITE_STDOUT)) {
                w = new PrintWriter(System.out);
            } else {
                w = new BufferedWriter(new FileWriter(new File(options.getOutputFile())));
            }
            switch (OutputFormat.valueOf(options.getOutputFormat().toUpperCase())) {
            case JSON:
                w.write(new Gson().toJson(testCases));
                break;
            case TEXT:
                for (String test : testCases) {
                    w.write(test);
                    w.write("\n");
                }
                break;
            default:
                throw new IllegalArgumentException("Don't know how to handle format " + options.getOutputFormat());
            }
            w.close();
            return 0;
        } catch (Exception e) {
            System.err.println(e.getMessage());
            return 1;
        } finally {
            IOUtils.closeQuietly(w);
        }
    }

    private static void printFailures(StandaloneTestOptions options, StandaloneTestRunner runner,
            StandaloneReporter reporter) {
        OutputFormat format = OutputFormat.valueOf(options.getOutputFormat().toUpperCase());
        if (options.getOutputFile().equals(WRITE_STDOUT)) {
            runner.getLogger().severe("Failures detected - writing details to screen");
            try {
                PrintWriter writer = new PrintWriter(System.out);
                reporter.writeFailures(writer, format);
                writer.close();
            } catch (IOException e) {
                System.err.println(e.getMessage());
                System.exit(4);
            }
        } else {
            runner.getLogger().severe("Failures detected - writing details to " + options.getOutputFile());
            reporter.writeFailureFile(options.getOutputFile(), format);
        }
    }

    private Logger logger;
    private final StandaloneTestOptions options;
    private DatabaseRegistryEntry productionDb;
    private DatabaseRegistryEntry comparaMasterDb;
    private DatabaseRegistryEntry testDb;
    private DatabaseServer primaryServer;
    private DatabaseServer secondaryServer;
    private DatabaseServer stagingServer;

    public StandaloneTestRunner(StandaloneTestOptions options) {
        this.options = options;
        getLogger().fine("Connecting to primary server " + options.getHost());
        DBUtils.overrideMainDatabaseServer(getPrimaryServer(), true);
        if (options.isSecondaryHost()) {
            getLogger().fine("Connecting to secondary server " + options.getSecondaryHost());
            DBUtils.overrideSecondaryDatabaseServer(getSecondaryServer());
        }
        String release = null;
        if (!options.isRelease()) {
            getLogger().fine("Release not specified, inferring from " + options.getDbname());
            release = DatabaseRegistryEntry.getInfoFromName(options.getDbname()).getSchemaVersion();
        } else {
            release = options.getRelease();
        }
        if (options.isStagingHost()) {
            getLogger().fine("Connecting to staging server " + options.getStagingHost());
            DBUtils.overrideMainDatabaseServer(getStagingServer(), false);
        }
        if (!StringUtils.isEmpty(release)) {
            getLogger().fine("Setting release " + release);
            DBUtils.setRelease(release);
        }
        if (options.isDataFilesPath()) {
            System.setProperty("dataFileBasePath", options.getDataFilesPath());
        }
        System.setProperty("compara_master.database", options.getComparaMasterDbname());

        String masterSchema = options.isMasterSchema() ? options.getMasterSchema() : null;
        if (options.getDbname().matches(".*_compara_.*")) {
            if (StringUtils.isEmpty(masterSchema)) {
                masterSchema = "master_schema_compara_" + release;
            }
            System.setProperty("master_compara.schema", masterSchema);
        } else if (options.getDbname().matches(".*_funcgen_.*")) {
            if (StringUtils.isEmpty(masterSchema)) {
                masterSchema = "master_schema_funcgen_" + release;
            }
            System.setProperty(CompareFuncgenSchema.MASTER_FUNCGEN_SCHEMA, masterSchema);
        } else if (options.getDbname().matches(".*_variation_.*")) {
            if (StringUtils.isEmpty(masterSchema)) {
                masterSchema = "master_schema_variation_" + release;
            }
            System.setProperty(CompareVariationSchema.MASTER_VARIATION_SCHEMA, masterSchema);
        } else {
            if (StringUtils.isEmpty(masterSchema)) {
                masterSchema = "master_schema_" + release;
            }
            System.setProperty(CompareSchema.MASTER_SCHEMA, masterSchema);
        }
    }

    public Logger getLogger() {
        if (logger == null) {
            logger = Logger.getLogger(StandaloneTestRunner.class.getCanonicalName());
            ConsoleHandler localConsoleHandler = new ConsoleHandler();
            localConsoleHandler.setFormatter(new Formatter() {
                DateFormat format = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");

                @Override
                public String format(LogRecord record) {
                    return String.format("%s %s %s : %s%n", format.format(new Date(record.getMillis())),
                            record.getSourceClassName(), record.getLevel().toString(), record.getMessage());
                }
            });
            if (options.isVerbose()) {
                localConsoleHandler.setLevel(Level.ALL);
                logger.setLevel(Level.ALL);
            } else {
                localConsoleHandler.setLevel(Level.INFO);
                logger.setLevel(Level.INFO);
            }
            logger.setUseParentHandlers(false);
            logger.addHandler(localConsoleHandler);
        }
        return logger;
    }

    public DatabaseRegistryEntry getProductionDb() {
        if (productionDb == null && options.isProductionHost()) {
            getLogger().info("Connecting to production database " + options.getProductionDbname());
            productionDb = new DatabaseRegistryEntry(new DatabaseServer(options.getProductionHost(),
                    String.valueOf(options.getProductionPort()), options.getProductionUser(),
                    options.isProductionPassword() ? options.getProductionPassword() : null,
                    Driver.class.getName()), options.getProductionDbname(), null, null);
        }
        return productionDb;
    }

    public DatabaseRegistryEntry getComparaMasterDb() {
        if (comparaMasterDb == null && options.isComparaHost()) {
            getLogger().info("Connecting to compara master database " + options.getComparaMasterDbname());
            comparaMasterDb = new DatabaseRegistryEntry(new DatabaseServer(options.getComparaHost(),
                    String.valueOf(options.getComparaPort()), options.getComparaUser(),
                    options.isComparaPassword() ? options.getComparaPassword() : null, Driver.class.getName()),
                    options.getComparaMasterDbname(), null, null);
        }
        return comparaMasterDb;
    }

    public DatabaseRegistryEntry getTestDb() {
        if (testDb == null) {
            getLogger().info("Connecting to test database " + options.getDbname());
            testDb = new DatabaseRegistryEntry(getPrimaryServer(), options.getDbname(), null, null);
            if (testDb.getConnection() == null) {
                throw new ConfigurationException("Test database " + options.getDbname() + " not found");
            }
        }
        return testDb;
    }

    public DatabaseServer getPrimaryServer() {
        if (primaryServer == null)
            primaryServer = new DatabaseServer(options.getHost(), String.valueOf(options.getPort()),
                    options.getUser(), options.isPassword() ? options.getPassword() : null, Driver.class.getName());
        return primaryServer;
    }

    public DatabaseServer getSecondaryServer() {
        if (secondaryServer == null) {
            secondaryServer = new DatabaseServer(options.getSecondaryHost(),
                    String.valueOf(options.getSecondaryPort()), options.getSecondaryUser(),
                    options.isSecondaryPassword() ? options.getSecondaryPassword() : null, Driver.class.getName());
        }
        return secondaryServer;
    }

    public DatabaseServer getStagingServer() {
        if (stagingServer == null) {
            stagingServer = new DatabaseServer(options.getStagingHost(), String.valueOf(options.getStagingPort()),
                    options.getStagingUser(), options.isStagingPassword() ? options.getStagingPassword() : null,
                    Driver.class.getName());
        }
        return stagingServer;
    }

    private TestRegistry testRegistry;

    private TestRegistry getTestRegistry() {
        if (testRegistry == null) {
            try {
                this.testRegistry = new ConfigurationBasedTestRegistry(options);
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            } catch (UnknownTestTypeException e) {
                throw new RuntimeException(e);
            }
        }
        return testRegistry;
    }

    public boolean runAll() {
        boolean success = true;
        for (EnsTestCase testCase : getTestRegistry().getAll()) {
            success &= runTestCase(testCase);
        }
        return success;
    }

    public boolean runTestCase(EnsTestCase test) {
        boolean success = true;
        if (SingleDatabaseTestCase.class.isAssignableFrom(test.getClass())) {
            getLogger().info("Executing testcase " + test.getName());
            test.setProductionDatabase(getProductionDb());
            test.setComparaMasterDatabase(getComparaMasterDb());
            ReportManager.startTestCase(test, getTestDb());
            if (test.appliesToType(getTestDb().getType())) {
                boolean result = ((SingleDatabaseTestCase) test).run(getTestDb());
                ReportManager.finishTestCase(test, result, getTestDb());
                getLogger().info(test.getName() + " " + (result ? "succeeded" : "failed"));
                success &= result;
            } else {
                getLogger().info("Skipping testcase " + test.getName() + " for database " + getTestDb().getName()
                        + " of type " + getTestDb().getType().getName());
            }
        } else {
            getLogger().fine("Skipping non-single testcase " + test.getName());
        }
        return success;
    }

}