fr.ens.biologie.genomique.eoulsan.it.ITFactory.java Source code

Java tutorial

Introduction

Here is the source code for fr.ens.biologie.genomique.eoulsan.it.ITFactory.java

Source

/*
 *                  Eoulsan development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public License version 2.1 or
 * later and CeCILL-C. This should be distributed with the code.
 * If you do not have a copy, see:
 *
 *      http://www.gnu.org/licenses/lgpl-2.1.txt
 *      http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt
 *
 * Copyright for this code is held jointly by the Genomic platform
 * of the Institut de Biologie de l'cole normale suprieure and
 * the individual authors. These should be listed in @author doc
 * comments.
 *
 * For more information on the Eoulsan project and its aims,
 * or to join the Eoulsan Google group, visit the home page
 * at:
 *
 *      http://outils.genomique.biologie.ens.fr/eoulsan
 *
 */
package fr.ens.biologie.genomique.eoulsan.it;

import static com.google.common.io.Files.newReader;
import static fr.ens.biologie.genomique.eoulsan.util.FileUtils.checkExistingDirectoryFile;
import static fr.ens.biologie.genomique.eoulsan.util.FileUtils.checkExistingStandardFile;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.compress.utils.Charsets;
import org.testng.annotations.Factory;

import fr.ens.biologie.genomique.eoulsan.EoulsanException;
import fr.ens.biologie.genomique.eoulsan.Globals;
import fr.ens.biologie.genomique.eoulsan.util.ProcessUtils;

/**
 * This class launch integration test with Testng.
 * @since 2.0
 * @author Laurent Jourdren
 * @author Sandrine Perrin
 */
public class ITFactory {

    // Java system properties keys used for integration tests
    public static final String IT_CONF_PATH_SYSTEM_KEY = "it.conf.path";
    public static final String IT_TEST_LIST_PATH_SYSTEM_KEY = "it.test.list.path";
    public static final String IT_TEST_SYSTEM_KEY = "it.test.name";
    public static final String IT_GENERATE_ALL_EXPECTED_DATA_SYSTEM_KEY = "it.generate.all.expected.data";
    public static final String IT_GENERATE_NEW_EXPECTED_DATA_SYSTEM_KEY = "it.generate.new.expected.data";
    public static final String IT_APPLICATION_PATH_KEY_SYSTEM_KEY = "it.application.path";
    public static final String IT_DEBUG_ENABLE_SYSTEM_KEY = "it.debug.enable";

    /** Set test output directory to replace value in configuration file. */
    public static final String IT_OUTPUT_DIR_SYSTEM_KEY = "it.output.dir";

    // Configuration properties keys
    static final String TESTS_DIRECTORY_CONF_KEY = "tests.directory";
    static final String OUTPUT_ANALYSIS_DIRECTORY_CONF_KEY = "output.analysis.directory";
    static final String LOG_DIRECTORY_CONF_KEY = "log.directory";
    static final String PRE_TEST_SCRIPT_CONF_KEY = "pre.test.script";
    static final String POST_TEST_SCRIPT_CONF_KEY = "post.test.script";
    static final String GENERATE_ALL_EXPECTED_DATA_CONF_KEY = "generate.all.expected.data";
    static final String GENERATE_NEW_EXPECTED_DATA_CONF_KEY = "generate.new.expected.data";
    static final String DESCRIPTION_CONF_KEY = "description";
    static final String COMMAND_TO_LAUNCH_APPLICATION_CONF_KEY = "command.to.launch.application";
    static final String COMMAND_TO_GENERATE_MANUALLY_CONF_KEY = "command.to.generate.manually";
    static final String COMMAND_TO_GET_APPLICATION_VERSION_CONF_KEY = "command.to.get.application.version";
    static final String INCLUDE_CONF_KEY = "include";

    // Configure to delete file matching with patterns if IT succeeded
    static final String SUCCESS_IT_DELETE_FILE_CONF_KEY = "success.it.delete.file";

    // Default value for key configuration success.it.delete.file
    private static final String SUCCESS_IT_DELETE_FILE_DEFAULT_VALUE = "false";

    /** Patterns */
    static final String FILE_TO_COMPARE_PATTERNS_CONF_KEY = "files.to.compare";
    static final String EXCLUDE_TO_COMPARE_PATTERNS_CONF_KEY = "excluded.files.to.compare";
    static final String CHECK_LENGTH_FILE_PATTERNS_CONF_KEY = "files.to.check.length";
    static final String CHECK_EXISTENCE_FILE_PATTERNS_CONF_KEY = "files.to.check.existences";
    static final String CHECK_ABSENCE_FILE_PATTERNS_CONF_KEY = "files.to.check.absence";
    static final String FILE_TO_REMOVE_CONF_KEY = "files.to.remove";

    static final String MANUAL_GENERATION_EXPECTED_DATA_CONF_KEY = "manual.generation.expected.data";

    static final String RUNTIME_IT_MAXIMUM_KEY = "runtime.test.maximum";

    static final String PRETREATMENT_GLOBAL_SCRIPT_KEY = "pre.global.script";
    static final String POSTTREATMENT_GLOBAL_SCRIPT_KEY = "post.global.script";

    static final String APPLICATION_PATH_VARIABLE = "application.path";

    static final String TEST_CONFIGURATION_FILENAME = "test.conf";

    // Runtime maximum for a test beyond stop execution, in minutes
    static final int RUNTIME_IT_MAXIMUM_DEFAULT = 1;

    private static final Properties CONSTANTS = initConstants();

    private final Properties globalsConf;
    private final File applicationPath;

    // File with tests name to execute
    private final File selectedTestsFile;
    private final String selectedTest;
    private final File testsDataDirectory;
    private final Map<String, File> testsDirectoryFoundToExecute;

    /**
     * Create all instance for integrated tests.
     * @return array object from integrated tests
     */
    @Factory
    public final Object[] createInstances() {

        // If no test configuration path defined, do nothing
        if (this.applicationPath == null) {
            return new Object[0];
        }

        // Set the default local for all the application
        Globals.setDefaultLocale();
        try {

            final int testsCount = ITSuite.getInstance().getCountTest();

            if (testsCount == 0) {
                return new Object[0];
            }

            // Return all tests
            return ITSuite.getInstance().getTestsInstanceToArray();

        } catch (final Throwable e) {
            System.err.println(e.getMessage());

        }

        // Return none test
        return new Object[0];
    }

    //
    // Methods to collect tests
    //

    /**
     * Collect all tests to launch from parameter command : in one case all tests
     * present in output test directory, in other case from a list with all name
     * test directory. For each, it checks the file configuration 'test.txt'.
     * @return collection of test directories
     * @throws EoulsanException if an error occurs while create instance for each
     *           test.
     * @throws IOException if the source file doesn't exist
     */
    private Map<String, File> collectTestsDirectoryToExecute() throws EoulsanException, IOException {

        // final List<IT> tests = new ArrayList<>();
        final Map<String, File> result = new HashMap<>();
        final List<File> testsToExecuteDirectories = new ArrayList<>();

        // Collect tests from a file with names tests
        testsToExecuteDirectories.addAll(readTestListFile());

        // Add the selected test if set
        if (this.selectedTest != null) {

            testsToExecuteDirectories.add(new File(this.testsDataDirectory, this.selectedTest));
        }

        // If no test was defined by user use all the existing tests
        final File[] files = this.testsDataDirectory.listFiles();
        if (files != null && testsToExecuteDirectories.isEmpty()) {
            testsToExecuteDirectories.addAll(Arrays.asList(files));
        }

        if (testsToExecuteDirectories.size() == 0) {
            throw new EoulsanException("None test directory found in " + this.testsDataDirectory.getAbsolutePath());
        }

        // Build map
        for (final File testDirectory : testsToExecuteDirectories) {

            // Ignore file
            if (testDirectory.isFile()) {
                continue;
            }

            checkExistingDirectoryFile(testDirectory, "test directory");

            if (!new File(testDirectory, TEST_CONFIGURATION_FILENAME).exists()) {
                continue;
            }

            result.put(testDirectory.getName(), testDirectory);
        }

        return Collections.unmodifiableMap(result);
    }

    /**
     * Collect tests to launch from text files with name tests.
     * @return list all directories test found
     * @throws IOException if an error occurs while read file
     */
    private List<File> readTestListFile() throws IOException {

        final List<File> result = new ArrayList<>();

        if (this.selectedTestsFile == null) {
            return Collections.emptyList();
        }

        checkExistingStandardFile(this.selectedTestsFile, "selected tests file");

        final BufferedReader br = new BufferedReader(
                newReader(this.selectedTestsFile, Charsets.toCharset(Globals.DEFAULT_FILE_ENCODING)));

        String nameTest;
        while ((nameTest = br.readLine()) != null) {
            // Skip commentary
            if (nameTest.startsWith("#") || nameTest.isEmpty()) {
                continue;
            }

            result.add(new File(this.testsDataDirectory, nameTest.trim()));
        }

        // Close buffer
        br.close();

        return result;
    }

    //
    // Methods to load and read configuration and properties
    //

    /**
     * Initialize the constants values.
     * @return a map with the constants
     */
    private static Properties initConstants() {

        final Properties constants = new Properties();

        // Add java properties
        for (final Map.Entry<Object, Object> e : System.getProperties().entrySet()) {
            constants.put(e.getKey(), e.getValue());
        }

        // Add environment properties
        for (final Map.Entry<String, String> e : System.getenv().entrySet()) {
            constants.put(e.getKey(), e.getValue());
        }

        return constants;
    }

    /**
     * Load configuration file in properties object.
     * @param configurationFile configuration file
     * @return properties
     * @throws IOException if an error occurs when reading file.
     * @throws EoulsanException if an error occurs evaluate value property.
     */
    private static Properties loadProperties(final File configurationFile) throws IOException, EoulsanException {

        // TODO Replace properties by treeMap to use constant in set environment
        // variables
        final Properties rawProps = new Properties();
        final Properties props;

        checkExistingStandardFile(configurationFile, "test configuration file");

        // Load configuration file
        rawProps.load(newReader(configurationFile, Charsets.toCharset(Globals.DEFAULT_FILE_ENCODING)));

        props = evaluateProperties(rawProps);

        // Check include
        final String includeOption = props.getProperty(INCLUDE_CONF_KEY);

        if (includeOption != null) {
            // Check configuration file
            final File otherConfigurationFile = new File(includeOption);

            checkExistingStandardFile(otherConfigurationFile, "configuration file doesn't exist");

            // Load configuration in global configuration
            final Properties rawPropsIncludedConfigurationFile = new Properties();
            rawPropsIncludedConfigurationFile
                    .load(newReader(otherConfigurationFile, Charsets.toCharset(Globals.DEFAULT_FILE_ENCODING)));

            final Properties newProps = evaluateProperties(rawPropsIncludedConfigurationFile);

            for (final String propertyName : newProps.stringPropertyNames()) {

                // No overwrite property from includes file configuration
                if (props.containsKey(propertyName)) {
                    continue;
                }

                props.put(propertyName, newProps.getProperty(propertyName));
            }
        }

        // Add default value
        addDefaultProperties(props);

        return props;
    }

    /**
     * Adds the default properties if does not exist in the configuration file.
     * @param props the props
     */
    private static void addDefaultProperties(Properties props) {
        // Particular case delete files
        if (props.getProperty(SUCCESS_IT_DELETE_FILE_CONF_KEY) == null) {
            // Set default value
            props.put(SUCCESS_IT_DELETE_FILE_CONF_KEY, SUCCESS_IT_DELETE_FILE_DEFAULT_VALUE);
        }

        // Add default runtime test duration
        if (props.getProperty(RUNTIME_IT_MAXIMUM_KEY) == null) {
            props.put(RUNTIME_IT_MAXIMUM_KEY, "" + RUNTIME_IT_MAXIMUM_DEFAULT);
        }
    }

    /**
     * Evaluate properties.
     * @param rawProps the raw props
     * @return the properties
     * @throws EoulsanException if the evaluation expression from value failed.
     */
    private static Properties evaluateProperties(final Properties rawProps) throws EoulsanException {
        final Properties props = new Properties();
        final int pos = IT.PREFIX_ENV_VAR.length();

        // Extract environment variable
        for (final String propertyName : rawProps.stringPropertyNames()) {
            if (propertyName.startsWith(IT.PREFIX_ENV_VAR)) {

                // Evaluate property
                final String evalPropValue = evaluateExpressions(rawProps.getProperty(propertyName), true);

                // Put in constants map
                CONSTANTS.put(propertyName.substring(pos), evalPropValue);

            }
        }

        // Evaluate property
        for (final String propertyName : rawProps.stringPropertyNames()) {

            final String propertyValue = evaluateExpressions(rawProps.getProperty(propertyName), true);

            // Set property
            props.setProperty(propertyName, propertyValue);
        }

        return props;
    }

    /**
     * Evaluate expression in a string.
     * @param s string in witch expression must be replaced
     * @param allowExec allow execution of code
     * @return a string with expression evaluated
     * @throws EoulsanException if an error occurs while parsing the string or
     *           executing an expression
     */
    static String evaluateExpressions(final String s, final boolean allowExec) throws EoulsanException {

        if (s == null) {
            return null;
        }

        final StringBuilder result = new StringBuilder();

        final int len = s.length();

        for (int i = 0; i < len; i++) {

            final int c0 = s.codePointAt(i);

            // Variable substitution
            if (c0 == '$' && i + 1 < len) {

                final int c1 = s.codePointAt(i + 1);
                if (c1 == '{') {

                    final String expr = subStr(s, i + 2, '}');

                    final String trimmedExpr = expr.trim();
                    if (CONSTANTS.containsKey(trimmedExpr)) {
                        result.append(CONSTANTS.get(trimmedExpr));
                    }

                    i += expr.length() + 2;
                    continue;
                }
            }

            // Command substitution
            if (c0 == '`' && allowExec) {
                final String expr = subStr(s, i + 1, '`');
                try {
                    final String r = ProcessUtils.execToString(evaluateExpressions(expr, false));

                    // remove last '\n' in the result
                    if (!r.isEmpty() && r.charAt(r.length() - 1) == '\n') {
                        result.append(r.substring(0, r.length() - 1));
                    } else {
                        result.append(r);
                    }

                } catch (final IOException e) {
                    throw new EoulsanException("Error while evaluating expression \"" + expr + "\"", e);
                }
                i += expr.length() + 1;
                continue;
            }

            result.appendCodePoint(c0);
        }

        return result.toString();
    }

    private static String subStr(final String s, final int beginIndex, final int charPoint)
            throws EoulsanException {

        final int endIndex = s.indexOf(charPoint, beginIndex);

        if (endIndex == -1) {
            throw new EoulsanException("Unexpected end of expression in \"" + s + "\"");
        }

        return s.substring(beginIndex, endIndex);
    }

    //
    // Other methods
    //

    /**
     * Adds the parameter command line in configuration.
     * @throws EoulsanException occurs if the it output directory path is invalid
     *           (to a file or parent directory not exists).
     * @throws IOException occurs if it can not be create the directory.
     */
    private void addParameterCommandLineInConfiguration() throws EoulsanException, IOException {

        if (this.globalsConf == null) {
            throw new EoulsanException("Configuration file from integrated test has not be loaded. "
                    + "Can not add parameter command line.");
        }

        // Load command line properties
        // Command generate all expected directories test
        this.globalsConf.setProperty(GENERATE_ALL_EXPECTED_DATA_CONF_KEY,
                getBooleanFromSystemProperty(IT_GENERATE_ALL_EXPECTED_DATA_SYSTEM_KEY).toString());

        // Command generate new expected directories test
        this.globalsConf.setProperty(GENERATE_NEW_EXPECTED_DATA_CONF_KEY,
                getBooleanFromSystemProperty(IT_GENERATE_NEW_EXPECTED_DATA_SYSTEM_KEY).toString());

        // If exist in command line, replace output directory from configuration
        final File itOutputDirectoryFromCommandLine = getFileFromSystemProperty(IT_OUTPUT_DIR_SYSTEM_KEY);

        if (itOutputDirectoryFromCommandLine != null) {
            checkDirectoryFileAndCreateIfNotExist(itOutputDirectoryFromCommandLine);

            // Replace value from configuration file
            this.globalsConf.put(OUTPUT_ANALYSIS_DIRECTORY_CONF_KEY,
                    itOutputDirectoryFromCommandLine.getAbsolutePath());

        }
    }

    /**
     * Check directory file and create if not exist.
     * @param dir the dir
     * @throws EoulsanException occurs if the it output directory path is invalid
     *           (to a file or parent directory not exists).
     * @throws IOException occurs if it can not be create the directory.
     */
    private void checkDirectoryFileAndCreateIfNotExist(File dir) throws EoulsanException, IOException {

        // Check path exist
        if (dir.isDirectory()) {
            return;
        }

        if (dir.exists() && dir.isFile()) {
            throw new EoulsanException(
                    "The it output directory is a file not a directory " + dir.getAbsolutePath());
        }

        // Check parent directory exist
        final File parentDir = dir.getParentFile();

        if (!parentDir.isDirectory()) {
            throw new EoulsanException("Can not create it output directory (" + dir.getName()
                    + "), parent directory doesn't exist " + parentDir.getAbsolutePath());
        }

        // Check create directory
        if (!dir.mkdir()) {
            throw new IOException(
                    "Fail to create it output directory set in command line " + dir.getAbsolutePath());
        }

    }

    /**
     * Get a File object from a Java System property.
     * @param property the key of the property to get
     * @return a File object or null if the property does not exists
     */
    private static File getFileFromSystemProperty(final String property) {

        if (property == null) {
            return null;
        }

        final String value = System.getProperty(property);
        if (value == null) {
            return null;
        }

        return new File(value);
    }

    /**
     * Get a Boolean object from a Java System property.
     * @param property the key of the property to get
     * @return a Boolean object or false if the property does not exists
     */
    private static Boolean getBooleanFromSystemProperty(final String property) {

        return (property != null) && Boolean.getBoolean(property);
    }

    /**
     * Get the application path as a File object. If the "it.application.path"
     * system property is set, return a File object pointing to the file, else try
     * to find the application in <tt>./target/dist</tt> directory.
     * @return a File object or null if no application path is found
     */
    private static File getApplicationPath() {

        final File dir = getFileFromSystemProperty(IT_APPLICATION_PATH_KEY_SYSTEM_KEY);

        if (dir != null) {
            return dir;
        }

        // Get user dir
        final File distDir = new File(
                System.getProperty("user.dir") + File.separator + "target" + File.separator + "dist");

        // The dist directory does not exists ?
        if (!distDir.isDirectory()) {
            return null;
        }

        // Set Java property for TestNG
        System.setProperty("maven.testng.output.dir", "");

        // Search if the dist directory only contains an unique directory
        File subDir = null;
        int dirCount = 0;
        int fileCount = 0;

        final File[] files = distDir.listFiles();
        if (files != null) {
            for (final File f : files) {

                if (f.getName().startsWith(".")) {
                    continue;
                }

                if (f.isDirectory()) {
                    dirCount++;
                    subDir = f;
                } else if (f.isFile()) {
                    fileCount++;
                }
            }
        }

        // There only on directory in dist directory
        if (fileCount == 0 && dirCount == 1) {
            return subDir;
        }

        // Other cases
        return distDir;
    }

    //
    // Constructor
    //

    /**
     * Public constructor.
     * @throws EoulsanException if an error occurs when reading configuration
     *           file.
     * @throws IOException
     */
    public ITFactory() throws EoulsanException, IOException {

        // Get configuration file path
        final File configurationFile = getFileFromSystemProperty(IT_CONF_PATH_SYSTEM_KEY);

        if (configurationFile != null) {

            // Get application path
            this.applicationPath = getApplicationPath();
            CONSTANTS.setProperty(APPLICATION_PATH_VARIABLE, this.applicationPath.getAbsolutePath());

            checkExistingDirectoryFile(this.applicationPath, "application path");

            // Get the file with the list of tests to run
            this.selectedTestsFile = getFileFromSystemProperty(IT_TEST_LIST_PATH_SYSTEM_KEY);

            // Get the test to execute
            this.selectedTest = System.getProperty(IT_TEST_SYSTEM_KEY);

            // Load configuration file
            this.globalsConf = loadProperties(configurationFile);

            addParameterCommandLineInConfiguration();

            // Set test data source directory
            this.testsDataDirectory = new File(this.globalsConf.getProperty(TESTS_DIRECTORY_CONF_KEY));

            this.testsDirectoryFoundToExecute = collectTestsDirectoryToExecute();

            // Init it suite with all potential tests found in test data direction
            ITSuite.getInstance(this.testsDirectoryFoundToExecute, this.globalsConf, this.applicationPath);

        } else {
            // Case no testng must be create when compile project with maven
            this.applicationPath = null;
            this.testsDataDirectory = null;
            this.selectedTestsFile = null;
            this.selectedTest = null;
            this.globalsConf = null;
            this.testsDirectoryFoundToExecute = null;
        }
    }

}