org.sonar.dotnet.tools.gallio.GallioCommandBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.dotnet.tools.gallio.GallioCommandBuilder.java

Source

/*
 * .NET tools :: Gallio Runner
 * Copyright (C) 2010 Jose Chillan, Alexandre Victoor and SonarSource
 * dev@sonar.codehaus.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonar.dotnet.tools.gallio;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.utils.command.Command;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioProject;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioSolution;

import com.google.common.collect.Lists;

/**
 * Class used to build the command line to run Gallio.
 */
public final class GallioCommandBuilder {

    private static final Logger LOG = LoggerFactory.getLogger(GallioCommandBuilder.class);

    private static final String DEFAULT_GALLIO_RUNNER = "IsolatedProcess";
    private static final String PART_COVER_EXE = "PartCover.exe";

    private VisualStudioSolution solution;
    private String buildConfigurations = "Debug";
    // Information needed for simple Gallio execution
    private File gallioExecutable;
    private File gallioReportFile;
    private String filter;
    private File workDir;
    // Information needed for coverage execution
    private CoverageTool coverageTool;
    private File partCoverInstallDirectory;
    private String coverageExcludes;
    private File coverageReportFile;

    private GallioCommandBuilder() {
    }

    /**
     * Constructs a {@link GallioCommandBuilder} object for the given Visual Studio solution.
     * 
     * @param solution
     *          the solution to analyse
     * @return a Gallio builder for this solution
     */
    public static GallioCommandBuilder createBuilder(VisualStudioSolution solution) {
        GallioCommandBuilder builder = new GallioCommandBuilder();
        builder.solution = solution;
        return builder;
    }

    /**
     * Sets the install dir for Gallio
     * 
     * @param gallioExecutable
     *          the executable
     * @return the current builder
     */
    public GallioCommandBuilder setExecutable(File gallioExecutable) {
        this.gallioExecutable = gallioExecutable;
        return this;
    }

    /**
     * Sets the report file to generate
     * 
     * @param reportFile
     *          the report file
     * @return the current builder
     */
    public GallioCommandBuilder setReportFile(File reportFile) {
        this.gallioReportFile = reportFile;
        return this;
    }

    /**
     * Sets Gallio test filter. <br/>
     * This can be used to execute only a specific test category (i.e. CategotyName:unit to consider only tests from the 'unit' category)
     * 
     * @param gallioFilter
     *          the filter for Gallio
     * @return the current builder
     */
    public GallioCommandBuilder setFilter(String gallioFilter) {
        this.filter = gallioFilter;
        return this;
    }

    /**
     * Sets the build configurations. By default, it is "Debug".
     * 
     * @param buildConfigurations
     *          the build configurations
     * @return the current builder
     */
    public GallioCommandBuilder setBuildConfigurations(String buildConfigurations) {
        this.buildConfigurations = buildConfigurations;
        return this;
    }

    /**
     * Set the working directory
     * 
     * @param workDir
     *          the working directory
     */
    public GallioCommandBuilder setWorkDir(File workDir) {
        this.workDir = workDir;
        return this;
    }

    /**
     * Sets the coverage tool to use, given its name (insensitive, for instance "ncover" or "NCover"). If none corresponding to the given name
     * is found, or if an empty string is passed, then no coverage tool will be used and no coverage report will be generated. <br>
     * <br/>
     * To know which tools are currently supported, check {@link CoverageTool}
     * 
     * @see CoverageTool
     * @param coverageToolName
     *          the name of the tool
     */
    public GallioCommandBuilder setCoverageTool(String coverageToolName) {
        this.coverageTool = CoverageTool.findFromName(coverageToolName);
        return this;
    }

    /**
     * Sets PartCover installation directory.
     * 
     * @param partCoverInstallDirectory
     *          the install dir
     */
    public GallioCommandBuilder setPartCoverInstallDirectory(File partCoverInstallDirectory) {
        this.partCoverInstallDirectory = partCoverInstallDirectory;
        return this;
    }

    /**
     * Sets the namespaces and assemblies excluded from the code coverage, seperated by a comma. The format for an exclusion is the PartCover
     * format: "[assembly]namespace".
     * 
     * @param coverageExcludes
     *          the excludes
     */
    public GallioCommandBuilder setCoverageExcludes(String coverageExcludes) {
        this.coverageExcludes = coverageExcludes;
        return this;
    }

    /**
     * Sets the coverage report file to generate
     * 
     * @param coverageReportFile
     *          the report file
     * @return the current builder
     */
    public GallioCommandBuilder setCoverageReportFile(File coverageReportFile) {
        this.coverageReportFile = coverageReportFile;
        return this;
    }

    /**
     * Transforms this command object into a Command object that can be passed to the CommandExecutor.
     * 
     * @return the Command object that represents the command to launch.
     */
    public Command toCommand() throws GallioException {
        List<File> testAssemblies = findTestAssemblies();
        validateGallioInfo(testAssemblies);

        Command command = createCommand();
        List<String> gallioArguments = generateGallioArguments(testAssemblies);

        if (CoverageTool.PARTCOVER.equals(coverageTool)) {
            addPartCoverArguments(command, gallioArguments);
        } else if (CoverageTool.NCOVER.equals(coverageTool)) {
            addNCoverArguments(command, gallioArguments);
        } else {
            command.addArguments(gallioArguments);
        }

        return command;
    }

    protected Command createCommand() throws GallioException {
        Command command = null;
        LOG.debug("- Gallio executable   : " + gallioExecutable);

        if (CoverageTool.PARTCOVER.equals(coverageTool)) {
            // In case of PartCover, the executable is not Gallio but PartCover itself
            File partCoverExecutable = new File(partCoverInstallDirectory, PART_COVER_EXE);
            validatePartCoverInfo(partCoverExecutable);
            LOG.debug("- PartCover executable: " + partCoverExecutable);
            command = Command.create(partCoverExecutable.getAbsolutePath());
        } else {
            command = Command.create(gallioExecutable.getAbsolutePath());
        }

        command.setDirectory(workDir);

        return command;
    }

    protected List<String> generateGallioArguments(List<File> testAssemblies) {
        List<String> gallioArguments = Lists.newArrayList();

        String runner = DEFAULT_GALLIO_RUNNER;
        if (coverageTool != null) {
            LOG.debug("- Coverage tool       : {}", coverageTool.getName());
            runner = coverageTool.getGallioRunner();
        }
        LOG.debug("- Runner              : {}", runner);
        gallioArguments.add("/r:" + runner);

        File reportDirectory = gallioReportFile.getParentFile();
        LOG.debug("- Report directory    : {}", reportDirectory.getAbsolutePath());
        gallioArguments.add("/report-directory:" + reportDirectory.getAbsolutePath());

        String reportName = trimFileReportName();
        LOG.debug("- Report file         : {}", reportName);
        gallioArguments.add("/report-name-format:" + reportName);

        gallioArguments.add("/report-type:Xml");

        if (StringUtils.isNotEmpty(filter)) {
            LOG.debug("- Filter              : {}", filter);
            gallioArguments.add("/f:" + filter);
        }

        LOG.debug("- Test assemblies     :");
        for (File testAssembly : testAssemblies) {
            LOG.debug("   o {}", testAssembly);
            gallioArguments.add(testAssembly.getAbsolutePath());
        }
        return gallioArguments;
    }

    protected void addPartCoverArguments(Command command, List<String> gallioArguments) {
        // DEBUG info has already been printed out for "--target"
        command.addArgument("--target");
        command.addArgument(gallioExecutable.getAbsolutePath());

        LOG.debug("- Working directory   : {}", workDir.getAbsolutePath());
        command.addArgument("--target-work-dir");
        command.addArgument(workDir.getAbsolutePath());

        // DEBUG info has already been printed out for "--target-args"
        command.addArgument("--target-args");
        command.addArgument(escapeGallioArguments(gallioArguments));

        // We add all the covered assemblies
        for (String assemblyName : listCoveredAssemblies()) {
            LOG.debug("- Partcover include   : [{}]*", assemblyName);
            command.addArgument("--include");
            command.addArgument("[" + assemblyName + "]*");
        }

        // We add all the configured exclusions
        if (!StringUtils.isEmpty(coverageExcludes)) {
            for (String exclusion : StringUtils.split(coverageExcludes, ",")) {
                LOG.debug("- Partcover exclude   : {}", exclusion.trim());
                command.addArgument("--exclude");
                command.addArgument(exclusion.trim());
            }
        }

        LOG.debug("- Coverage report     : {}", coverageReportFile.getAbsolutePath());
        command.addArgument("--output");
        command.addArgument(coverageReportFile.getAbsolutePath());
    }

    private void addNCoverArguments(Command command, List<String> gallioArguments) {
        command.addArguments(gallioArguments);

        LOG.debug("- Coverage report     : {}", coverageReportFile.getAbsolutePath());
        command.addArgument("/runner-property:NCoverCoverageFile=" + coverageReportFile.getAbsolutePath());

        String coveredAssemblies = StringUtils.join(listCoveredAssemblies().toArray(), ";");
        LOG.debug("- NCover arguments    : {}", coveredAssemblies);
        command.addArgument("/runner-property:NCoverArguments=//ias " + coveredAssemblies);
    }

    protected List<String> listCoveredAssemblies() {
        List<String> coveredAssemblyNames = new ArrayList<String>();
        for (VisualStudioProject visualProject : solution.getProjects()) {
            if (!visualProject.isTest()) {
                coveredAssemblyNames.add(visualProject.getAssemblyName());
            }
        }
        return coveredAssemblyNames;
    }

    // TODO : try to refactor this
    protected String escapeGallioArguments(List<String> gallioArguments) {
        StringBuilder targetArgsBuilder = new StringBuilder();
        boolean isFirst = true;
        for (String currentArg : gallioArguments) {
            if (isFirst) {
                isFirst = false;
            } else {
                targetArgsBuilder.append(' ');
            }
            String escapedArg = escapeQuotes(currentArg);
            targetArgsBuilder.append(escapedArg);
        }
        return targetArgsBuilder.toString();
    }

    /*
     * Escapes the quotes of a string. TODO : try to refactor this
     */
    protected String escapeQuotes(String input) {
        StringBuilder result = new StringBuilder(input.length());
        for (int idxChar = 0, len = input.length(); idxChar < len; idxChar++) {
            char currentChar = input.charAt(idxChar);
            if (currentChar == '"') {
                result.append('\\');
            } else if (idxChar == 0) {
                result.append("\\\"");
            }

            result.append(currentChar);

            if (currentChar != '"' && idxChar == len - 1) {
                result.append("\\\"");
            }
        }
        return result.toString();
    }

    protected String trimFileReportName() {
        String reportName = gallioReportFile.getName();
        if (StringUtils.endsWithIgnoreCase(reportName, ".xml")) {
            // We remove the terminal .xml that will be added by the Gallio runner
            reportName = reportName.substring(0, reportName.length() - 4);
        }
        return reportName;
    }

    protected List<File> findTestAssemblies() throws GallioException {
        List<File> assemblyFileList = Lists.newArrayList();
        if (solution != null) {
            for (VisualStudioProject visualStudioProject : solution.getTestProjects()) {
                addAssembly(assemblyFileList, visualStudioProject);
            }
        } else {
            throw new GallioException("No .NET solution or project has been given to the Gallio command builder.");
        }
        return assemblyFileList;
    }

    protected void addAssembly(List<File> assemblyFileList, VisualStudioProject visualStudioProject) {
        File assembly = visualStudioProject.getArtifact(buildConfigurations);
        if (assembly != null && assembly.isFile()) {
            assemblyFileList.add(assembly);
        }
    }

    protected void validateGallioInfo(List<File> testAssemblies) throws GallioException {
        if (gallioExecutable == null || !gallioExecutable.isFile()) {
            throw new GallioException(
                    "Gallio executable cannot be found at the following location:" + gallioExecutable);
        }
        if (gallioReportFile == null) {
            throw new GallioException("Gallio report file has not been specified.");
        }
        if (workDir == null || !workDir.isDirectory()) {
            throw new GallioException("The working directory cannot be found at the following location:" + workDir);
        }
        if (testAssemblies.isEmpty()) {
            throw new GallioException(
                    "No test assembly was found. Please check your project's Gallio plugin configuration.");
        }
    }

    protected void validatePartCoverInfo(File partCoverExecutable) throws GallioException {
        if (partCoverExecutable == null || !partCoverExecutable.isFile()) {
            throw new GallioException(
                    "PartCover executable cannot be found at the following location:" + partCoverExecutable);
        }
        if (coverageReportFile == null) {
            throw new GallioException("Gallio coverage report file has not been specified.");
        }
    }
}