hu.bme.mit.sette.common.tasks.RunnerProjectRunner.java Source code

Java tutorial

Introduction

Here is the source code for hu.bme.mit.sette.common.tasks.RunnerProjectRunner.java

Source

/*
 * SETTE - Symbolic Execution based Test Tool Evaluator
 *
 * SETTE is a tool to help the evaluation and comparison of symbolic execution
 * based test input generator tools.
 *
 * Budapest University of Technology and Economics (BME)
 *
 * Authors: Lajos Cseppent <lajos.cseppento@inf.mit.bme.hu>, Zoltn Micskei
 * <micskeiz@mit.bme.hu>
 *
 * Copyright 2014
 *
 * 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 hu.bme.mit.sette.common.tasks;

import hu.bme.mit.sette.common.Tool;
import hu.bme.mit.sette.common.exceptions.RunnerProjectRunnerException;
import hu.bme.mit.sette.common.exceptions.SetteConfigurationException;
import hu.bme.mit.sette.common.exceptions.SetteException;
import hu.bme.mit.sette.common.model.runner.RunnerProjectUtils;
import hu.bme.mit.sette.common.model.snippet.Snippet;
import hu.bme.mit.sette.common.model.snippet.SnippetContainer;
import hu.bme.mit.sette.common.model.snippet.SnippetProject;
import hu.bme.mit.sette.common.util.JavaFileUtils;
import hu.bme.mit.sette.common.util.process.ProcessRunner;
import hu.bme.mit.sette.common.util.process.ProcessRunnerListener;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang3.Validate;

/**
 * A SETTE task which provides base for runner project running. The phases are
 * the following: validation, preparation, running.
 *
 * @param <T>
 *            the type of the tool
 */
public abstract class RunnerProjectRunner<T extends Tool> extends SetteTask<T> {
    /** The poll interval for {@link ProcessRunner} objects. */
    public static final int POLL_INTERVAL = 100;

    /** The default timeout for called processes. */
    public static final int DEFAULT_TIMEOUT = 30000;

    /** The timeout in ms for the called processes. */
    private int timeoutInMs;

    /**
     * Instantiates a new runner project runner.
     *
     * @param snippetProject
     *            the snippet project
     * @param outputDirectory
     *            the output directory
     * @param tool
     *            the tool
     */
    public RunnerProjectRunner(final SnippetProject snippetProject, final File outputDirectory, final T tool) {
        super(snippetProject, outputDirectory, tool);
        this.timeoutInMs = RunnerProjectRunner.DEFAULT_TIMEOUT;
    }

    /**
     * Gets the timeout for the called processes.
     *
     * @return the timeout for the called processes
     */
    public final int getTimeoutInMs() {
        return this.timeoutInMs;
    }

    /**
     * Sets the timeout for the called processes.
     *
     * @param pTimeoutInMs
     *            the new timeout for the called processes
     */
    public final void setTimeoutInMs(final int pTimeoutInMs) {
        this.timeoutInMs = pTimeoutInMs;
    }

    /**
     * Runs the runner project.
     *
     * @param pLogger
     *            the logger stream
     * @throws RunnerProjectRunnerException
     *             if running has failed
     */
    public final void run(final PrintStream pLogger) throws RunnerProjectRunnerException {
        String phase = null;
        PrintStream logger = null;

        try {
            this.cleanUp();

            // validate preconditions
            phase = "validate (do)";
            validate();
            phase = "validate (after)";
            afterValidate();

            // prepare
            phase = "prepare (do)";
            prepare();
            phase = "prepare (after)";
            afterPrepare();

            // create logger
            File logFile = RunnerProjectUtils.getRunnerLogFile(getRunnerProjectSettings());

            if (pLogger != null) {
                pLogger.println("Log file: " + logFile.getCanonicalPath());
                logger = new PrintStream(new TeeOutputStream(new FileOutputStream(logFile), pLogger), true);
            } else {
                logger = new PrintStream(new FileOutputStream(logFile), true);
            }

            // run all
            phase = "run all (do)";
            runAll(logger);
            phase = "run all (after)";
            afterRunAll();

            cleanUp();
            phase = "complete";
        } catch (Exception e) {
            String message = String.format("The runner project run has failed\n" + "(phase: [%s])\n(tool: [%s])",
                    phase, getTool().getFullName());
            throw new RunnerProjectRunnerException(message, this, e);
        } finally {
            IOUtils.closeQuietly(logger);
        }
    }

    /**
     * Validates both the snippet and runner project settings.
     *
     * @throws SetteConfigurationException
     *             if a SETTE configuration problem occurred
     */
    private void validate() throws SetteConfigurationException {
        Validate.isTrue(getSnippetProject().getState().equals(SnippetProject.State.PARSED),
                "The snippet project must be parsed (state: [%s]) ", getSnippetProject().getState().name());

        // TODO currently snippet project validation can fail even if it is
        // valid
        // getSnippetProjectSettings().validateExists();
        getRunnerProjectSettings().validateExists();
    }

    /**
     * Prepares the running of the runner project, i.e. make everything ready
     * for the execution.
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void prepare() throws IOException {
        // delete previous outputs
        if (getRunnerProjectSettings().getRunnerOutputDirectory().exists()) {
            FileUtils.forceDelete(getRunnerProjectSettings().getRunnerOutputDirectory());
        }

        // create output directory
        FileUtils.forceMkdir(getRunnerProjectSettings().getRunnerOutputDirectory());
    }

    /**
     * Runs the tool on all the snippets.
     *
     * @param loggerOut
     *            the {@link PrintStream} of the logger
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void runAll(final PrintStream loggerOut) throws IOException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // foreach containers
        for (SnippetContainer container : getSnippetProject().getModel().getContainers()) {
            // skip container with higher java version than supported
            if (container.getRequiredJavaVersion().compareTo(getTool().getSupportedJavaVersion()) > 0) {
                // TODO error/warning handling
                loggerOut.println("Skipping container: " + container.getJavaClass().getName()
                        + " (required Java version: " + container.getRequiredJavaVersion() + ")");
                System.err.println("Skipping container: " + container.getJavaClass().getName()
                        + " (required Java version: " + container.getRequiredJavaVersion() + ")");
                continue;
            }

            // foreach snippets
            for (Snippet snippet : container.getSnippets().values()) {
                String filenameBase = getFilenameBase(snippet);

                File infoFile = RunnerProjectUtils.getSnippetInfoFile(getRunnerProjectSettings(), snippet);
                File outputFile = RunnerProjectUtils.getSnippetOutputFile(getRunnerProjectSettings(), snippet);
                File errorFile = RunnerProjectUtils.getSnippetErrorFile(getRunnerProjectSettings(), snippet);

                try {
                    String timestamp = dateFormat.format(new Date());
                    loggerOut.println("[" + timestamp + "] Running for snippet: " + filenameBase);
                    this.runOne(snippet, infoFile, outputFile, errorFile);
                    this.cleanUp();
                } catch (Exception e) {
                    loggerOut.println("Exception: " + e.getMessage());
                    loggerOut.println("==========");
                    e.printStackTrace(loggerOut);
                    loggerOut.println("==========");
                }
            }
        }
    }

    /**
     * This method is called after validation but before preparation.
     *
     * @throws SetteConfigurationException
     *             if a SETTE configuration problem occurred
     */
    protected void afterValidate() throws SetteConfigurationException {
    }

    /**
     * This method is called after preparation but before writing.
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    protected void afterPrepare() throws IOException {
    }

    /**
     * This method is called after running.
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws SetteException
     *             if a SETTE problem occurred
     */
    protected void afterRunAll() throws IOException, SetteException {
    }

    /**
     * Runs the tool on one snippet.
     *
     * @param snippet
     *            the snippet
     * @param infoFile
     *            the info file for the snippet
     * @param outputFile
     *            the output file for the snippet
     * @param errorFile
     *            the error file for the snippet
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws SetteException
     *             if a SETTE problem occurred
     */
    protected abstract void runOne(Snippet snippet, File infoFile, File outputFile, File errorFile)
            throws IOException, SetteException;

    /**
     * Cleans up the processes, i.e. kills undesired and stuck processes.
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws SetteException
     *             if a SETTE problem occurred
     */
    public abstract void cleanUp() throws IOException, SetteException;

    protected static final String getFilenameBase(final Snippet snippet) {
        return JavaFileUtils.packageNameToFilename(snippet.getContainer().getJavaClass().getName()) + "_"
                + snippet.getMethod().getName();
    }

    protected static final class OutputWriter implements ProcessRunnerListener {
        private long startTime = -1;
        private long finishTime = -1;
        private final String command;
        private final File infoFile;
        private final File outputFile;
        private final File errorFile;

        public OutputWriter(final String pCommand, final File pInfoFile, final File pOutputFile,
                final File pErrorFile) {
            this.command = pCommand;
            this.infoFile = pInfoFile;
            this.outputFile = pOutputFile;
            this.errorFile = pErrorFile;
        }

        public long getElapsedTimeInMs() {
            if (this.startTime > 0 && this.finishTime > 0) {
                return this.finishTime - this.startTime;
            } else {
                return -1;
            }
        }

        /*
         * (non-Javadoc)
         *
         * @see hu.bme.mit.sette.common.util.process.ProcessRunnerListener
         * #onStdoutRead(hu.bme.mit.sette.common.util.process.ProcessRunner,
         * int)
         */
        @Override
        public void onStdoutRead(final ProcessRunner processRunner, final int charactersRead) {
        }

        /*
         * (non-Javadoc)
         *
         * @see hu.bme.mit.sette.common.util.process.ProcessRunnerListener
         * #onStderrRead(hu.bme.mit.sette.common.util.process.ProcessRunner,
         * int)
         */
        @Override
        public void onStderrRead(final ProcessRunner processRunner, final int charactersRead) {
        }

        @Override
        public void onTick(final ProcessRunner processRunner, final long elapsedTimeInMs) {
            if (this.startTime < 0) {
                this.startTime = System.currentTimeMillis();
            }
        }

        @Override
        public void onIOException(final ProcessRunner processRunner, final IOException e) {
            System.err.println("  IOException occured");
            System.err.println(e);
        }

        @Override
        public void onComplete(final ProcessRunner processRunner) {
            this.finishTime = System.currentTimeMillis();

            // save info
            StringBuffer infoData = new StringBuffer();

            infoData.append("Command: ").append(this.command).append('\n');
            infoData.append("Exit value: ").append(processRunner.getExitValue()).append('\n');

            infoData.append("Destroyed: ");
            if (processRunner.wasDestroyed()) {
                infoData.append("yes");
            } else {
                infoData.append("no");
            }
            infoData.append('\n');

            infoData.append("Elapsed time: ").append(this.getElapsedTimeInMs()).append(" ms\n");
            this.saveToFile(this.infoFile, infoData);

            // save stdout
            this.saveToFile(this.outputFile, processRunner.getStdout());

            // save stderr
            this.saveToFile(this.errorFile, processRunner.getStderr());
        }

        private void saveToFile(final File file, final StringBuffer data) {
            if (data.length() <= 0) {
                return;
            }

            // TODO handle errors and exceptions
            file.getParentFile().mkdirs();

            FileWriter fw = null;
            try {
                fw = new FileWriter(file);

                // TODO enhance comment
                // don't use toString() because if data is big, you will be out
                // of memory!
                // this is bad: pw.print(data.toString())
                // https://forums.oracle.com/message/9015943 -> doit5 (this is
                // the best way regarding both speed and memory)

                // TODO externize buffer size
                // TODO other tasks here
                int bufferSize = 8192;
                for (int start = 0; start < data.length(); start += bufferSize) {
                    int end = start + bufferSize;

                    if (end > data.length()) {
                        fw.write(data.substring(start)); // to end of stream
                    } else {
                        fw.write(data.substring(start, end));
                    }
                }

                fw.flush();
            } catch (Exception ex) {
                // TODO syserr is kinda antipattern
                System.err.println("  Exception when saving to file (" + file + ")");
                ex.printStackTrace();
            } finally {
                IOUtils.closeQuietly(fw);
            }
        }
    }
}