All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atlassian.maven.plugin.clover.internal.instrumentation.AbstractInstrumenter Maven / Gradle / Ivy

package com.atlassian.maven.plugin.clover.internal.instrumentation;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import clover.org.apache.commons.lang3.StringUtils;
import com.atlassian.clover.CloverInstr;
import com.atlassian.clover.Logger;
import com.atlassian.clover.cfg.instr.java.SourceLevel;
import com.atlassian.clover.spi.lang.Language;
import com.atlassian.maven.plugin.clover.MethodWithMetricsContext;
import com.atlassian.maven.plugin.clover.MvnLogger;
import com.atlassian.maven.plugin.clover.TestClass;
import com.atlassian.maven.plugin.clover.TestMethod;
import com.atlassian.maven.plugin.clover.TestSources;
import com.atlassian.maven.plugin.clover.internal.CompilerConfiguration;
import com.atlassian.maven.plugin.clover.internal.scanner.CloverSourceScanner;
import com.atlassian.maven.plugin.clover.internal.scanner.LanguageFileExtensionFilter;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.FileUtils;
import org.jetbrains.annotations.TestOnly;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static clover.org.apache.commons.lang3.StringUtils.defaultString;

/**
 * Code common for instrumentation of various source roots (main sources, test sources).
 */
public abstract class AbstractInstrumenter {
    private CompilerConfiguration configuration;

    String outputSourceDirectory;
    private static final String PROP_PROJECT_BUILD_SOURCEENCODING = "project.build.sourceEncoding";

    public AbstractInstrumenter(final CompilerConfiguration configuration, final String outputSourceDirectory) {
        this.configuration = configuration;
        this.outputSourceDirectory = outputSourceDirectory;
    }

    protected CompilerConfiguration getConfiguration() {
        return this.configuration;
    }

    /**
     *
     * @throws MojoExecutionException when instrumentation fails
     * @see com.atlassian.maven.plugin.clover.CloverInstrumentInternalMojo#calcIncludedFilesForGroovy()
     * @see com.atlassian.maven.plugin.clover.CloverInstrumentInternalMojo#redirectOutputDirectories()
     */
    public void instrument() throws MojoExecutionException {
        final CloverSourceScanner scanner = getSourceScanner();
        // get source files to be instrumented, but only for Java as they will be instrumented by CloverInstr
        final Map javaFilesToInstrument = scanner.getSourceFilesToInstrument(LanguageFileExtensionFilter.JAVA_LANGUAGE, true);
        if (javaFilesToInstrument.isEmpty()) {
            getConfiguration().getLog().info("No Clover instrumentation done on source files in: "
                    + getCompileSourceRoots() + " as no matching sources files found (JAVA_LANGUAGE)");
        } else {
            instrumentSources(javaFilesToInstrument, outputSourceDirectory);
        }

        // find groovy files in all compilation roots and copy them
        //
        // 1) in case when 'src/main/java' (or 'src/test/java') contains *.groovy source files (this is a trick possible
        // with a groovy-eclipse-plugin, see http://groovy.codehaus.org/Groovy-Eclipse+compiler+plugin+for+Maven
        // "Setting up source folders / Do nothing") we must copy *.groovy files as well
        // reason: 'src/main/java' (or 'src/test/java') will be redirected to 'target/clover/src-instrumented'
        // (or 'target/clover/src-test-instrumented') and Groovy compiler must be able to find these groovy sources
        //
        // 2) however we shall not copy groovy files from 'src/(main|test)/groovy' because these source roots are not
        // being redirected to 'target/clover/src-(test-)instrumented'; furthermore groovy-eclipse-plugin has
        // 'src/(main|test)/groovy' location hardcoded, so copying files would end up with 'duplicate class' build error
        final Map groovyFilesToInstrument = scanner.getSourceFilesToInstrument(LanguageFileExtensionFilter.GROOVY_LANGUAGE, true);

        // copy groovy files
        if (!groovyFilesToInstrument.isEmpty()) {
            copyExcludedFiles(groovyFilesToInstrument, outputSourceDirectory);
        }

        // We need to copy excluded files too as otherwise they won't be in the new Clover source directory and
        // thus won't be compiled by the compile plugin. This will lead to compilation errors if any other
        // file depends on any of these excluded files.
        if (configuration.isCopyExcludedFiles()) {
            final Map explicitlyExcludedFiles = scanner.getExcludedFiles();
            // 'src/(main|test)/groovy' is already filtered-out in getExcludedFiles()
            copyExcludedFiles(explicitlyExcludedFiles, outputSourceDirectory);
        }
    }

    public String redirectSourceDirectories() {
        return redirectSourceDirectories(outputSourceDirectory);
    }

    protected abstract CloverSourceScanner getSourceScanner();

    protected abstract String getSourceDirectory();

    protected abstract void setSourceDirectory(final String targetDirectory);

    protected abstract List getCompileSourceRoots();

    protected abstract void addCompileSourceRoot(final String sourceRoot);

    protected abstract boolean isGeneratedSourcesDirectory(final String sourceRoot);

    private String redirectSourceDirectories(final String targetDirectory) {
        final String oldSourceDirectory = getSourceDirectory();
        if (new File(oldSourceDirectory).exists()) {
            setSourceDirectory(targetDirectory);
        }

        getConfiguration().getLog().debug("Clover " + getSourceType() + " source directories before change:");
        logSourceDirectories();

        // Maven2 limitation: changing the source directory doesn't change the compile source roots
        // See http://jira.codehaus.org/browse/MNG-1945
        final List sourceRoots = new ArrayList(getCompileSourceRoots());

        // Clean all source roots to add them again in order to keep the same original order of source roots.
        getCompileSourceRoots().removeAll(sourceRoots);

        final CloverSourceScanner scanner = getSourceScanner();
        for (final String sourceRoot : sourceRoots) {
            // if includeAllSourceRoots=true then all source roots will be redirected to the location of instrumented sources
            // if includeAllSourceRoots=false then we don't redirect generated source roots
            boolean needsRedirection = getConfiguration().isIncludesAllSourceRoots() ||
                    !isGeneratedSourcesDirectory(sourceRoot);

            // a) if it's a Java directory then use location of instrumented sources instead of the original source
            // root (e.g. 'src/main/java' -> 'target/clover/src-instrumented')
            // b) if it's a Groovy directory then don't change the location because we don't instrument Groovy on
            // a source level, so the Clover's instrumented folder is empty; Groovy files will be instrumented
            // during compilation on the AST level (e.g. 'src/main/groovy' -> 'src/main/groovy')
            if (scanner.isSourceRootForLanguage(sourceRoot, Language.Builtin.GROOVY))  {
                addCompileSourceRoot(sourceRoot);
            } else {
                addCompileSourceRoot(needsRedirection ? getSourceDirectory() : sourceRoot);
            }
        }

        getConfiguration().getLog().debug("Clover " + getSourceType() + " source directories after change:");
        logSourceDirectories();
        return oldSourceDirectory;
    }

    private void logSourceDirectories() {
        if (getConfiguration().getLog().isDebugEnabled()) {
            for (String sourceRoot : getCompileSourceRoots()) {
                getConfiguration().getLog().debug("[Clover]  source root [" + sourceRoot + "]");
            }
        }
    }

    /**
     * Copy all files that have been excluded by the user (using the excludes configuration property). This is required
     * as otherwise the excluded files won't be in the new Clover source directory and thus won't be compiled by the
     * compile plugin. This will lead to compilation errors if any other Java file depends on any of them.
     *
     * @throws MojoExecutionException if a failure happens during the copy
     */
    private void copyExcludedFiles(final Map excludedFiles, final String targetDirectory) throws MojoExecutionException {
        for (String sourceRoot : excludedFiles.keySet()) {
            final String[] filesInSourceRoot = excludedFiles.get(sourceRoot);

            for (String fileName : filesInSourceRoot) {
                final File srcFile = new File(sourceRoot, fileName);
                try {
                    configuration.getLog().debug("Copying excluded file: " + srcFile.getAbsolutePath() + " to " + targetDirectory);
                    FileUtils.copyFile(srcFile, new File(targetDirectory,
                            srcFile.getPath().substring(sourceRoot.length())));
                } catch (IOException e) {
                    throw new MojoExecutionException("Failed to copy excluded file [" + srcFile + "] to ["
                            + targetDirectory + "]", e);
                }
            }
        }
    }

    private void instrumentSources(final Map filesToInstrument, final String outputDir) throws MojoExecutionException {

        Logger.setInstance(new MvnLogger(configuration.getLog()));
        // only make dirs when there is src to instrument. see CLMVN-118
        new File(outputDir).mkdirs();
        int result = CloverInstr.mainImpl(createCliArgs(filesToInstrument, outputDir));
        if (result != 0) {
            throw new MojoExecutionException("Clover has failed to instrument the source files "
                    + "in the [" + outputDir + "] directory");
        }
    }

    /**
     * @return the CLI args to be passed to CloverInstr
     */
    private String[] createCliArgs(final Map filesToInstrument, final String outputDir) throws MojoExecutionException {
        final List parameters = new ArrayList();

        parameters.add("-p");
        parameters.add(getConfiguration().getFlushPolicy());
        parameters.add("-f");
        parameters.add("" + getConfiguration().getFlushInterval());

        parameters.add("-i");
        parameters.add(getConfiguration().resolveCloverDatabase());

        parameters.add("-d");
        parameters.add(outputDir);

        if (getConfiguration().getLog().isDebugEnabled()) {
            parameters.add("-v");
        }

        if (getConfiguration().getDistributedCoverage() != null && getConfiguration().getDistributedCoverage().isEnabled()) {
            parameters.add("--distributedCoverage");
            parameters.add(getConfiguration().getDistributedCoverage().toString());
        }

        final String javaLevel = getConfiguration().getJdk();
        if (javaLevel != null) {
            // warn about old source levels
            if (SourceLevel.isUnsupported(javaLevel)) {
                getConfiguration().getLog().warn(SourceLevel.getUnsupportedMessage(javaLevel));
            }

            parameters.add("--source");
            parameters.add(javaLevel);
        }

        if (!getConfiguration().isUseFullyQualifiedJavaLang()) {
            parameters.add("--dontFullyQualifyJavaLang");
        }

        if (getConfiguration().getEncoding() != null) {
            parameters.add("--encoding");
            parameters.add(getConfiguration().getEncoding());
        } else if (getConfiguration().getProject().getProperties().get(PROP_PROJECT_BUILD_SOURCEENCODING) != null) {
            parameters.add("--encoding");
            parameters.add(getConfiguration().getProject().getProperties().get(PROP_PROJECT_BUILD_SOURCEENCODING).toString());
        }

        if (getConfiguration().getInstrumentation() != null) {
            parameters.add("--instrlevel");
            parameters.add(getConfiguration().getInstrumentation());
        }

        if (getConfiguration().getInstrumentLambda() != null) {
            parameters.add("--instrlambda");
            parameters.add(getConfiguration().getInstrumentLambda());
        }

        for (final String srcDir : filesToInstrument.keySet()) {
            final String[] filesInSourceRoot = filesToInstrument.get(srcDir);
            for (String s : filesInSourceRoot) {
                File file = new File(srcDir, s);
                parameters.add(file.getPath());
            }
        }

        if (getConfiguration().isRecordTestResults()) {
            parameters.add("--recordTestResults");
            parameters.add(Boolean.toString(getConfiguration().isRecordTestResults()));
        }

        // custom contexts
        addCustomContexts(parameters, getConfiguration().getMethodContexts().entrySet(), "-mc");
        addCustomContexts(parameters, getConfiguration().getStatementContexts().entrySet(), "-sc");
        addMethodWithMetricsContexts(parameters, getConfiguration().getMethodWithMetricsContexts());

        // custom test detector
        addTestSources(parameters, getConfiguration().getTestSources(), getSourceDirectory());

        // Log parameters
        if (getConfiguration().getLog().isDebugEnabled()) {
            getConfiguration().getLog().debug("Parameter list being passed to Clover CLI:");
            for (String param : parameters) {
                getConfiguration().getLog().debug("  parameter = [" + param + "]");
            }
        }

        return parameters.toArray(new String[0]);
    }

    private void addCustomContexts(final List parameters, final Set> contexts, final String flag) {
        for (final Map.Entry entry : contexts) {
            parameters.add(flag);
            parameters.add(entry.getKey() + "=" + entry.getValue());
        }
    }

    /**
     * See com.atlassian.clover.cmdline.CloverInstrArgProcessors#MethodWithMetricsContext in clover-core
     *
     * @param parameters commandline parameters to be modified
     * @param contexts set of method contexts
     */
    @TestOnly
    static void addMethodWithMetricsContexts(final List parameters, final Set contexts) {
        for (final MethodWithMetricsContext context : contexts) {
            parameters.add("-mmc");
            parameters.add(String.format("%s;%s;%d;%d;%d;%d",
                    context.getName(),
                    context.getRegexp(),
                    context.getMaxStatements(),
                    context.getMaxComplexity(),
                    context.getMaxAggregatedStatements(),
                    context.getMaxAggregatedComplexity()));
        }
    }

    /**
     * See in clover-core:
     * 
    *
  • com.atlassian.clover.cmdline.CloverInstrArgProcessors#TestSourceRoot
  • *
  • com.atlassian.clover.cmdline.CloverInstrArgProcessors#TestSourceIncludes
  • *
  • com.atlassian.clover.cmdline.CloverInstrArgProcessors#TestSourceExcludes
  • *
  • com.atlassian.clover.cmdline.CloverInstrArgProcessors#TestSourceClass
  • *
  • com.atlassian.clover.cmdline.CloverInstrArgProcessors#TestSourceMethod
  • *
* * @param parameters commandline parameters to be modified * @param testSources set of test sources/classes/methods for the test detector */ @TestOnly static void addTestSources(List parameters, TestSources testSources, String sourceDirectory) { if (testSources != null) { // root parameters.add("-tsr"); parameters.add(sourceDirectory); // includes if (!testSources.getIncludes().isEmpty()) { String allIncludes = StringUtils.join(testSources.getIncludes().iterator(), ","); parameters.add("-tsi"); parameters.add(allIncludes); } // excludes if (!testSources.getExcludes().isEmpty()) { String allExcludes = StringUtils.join(testSources.getExcludes().iterator(), ","); parameters.add("-tse"); parameters.add(allExcludes); } // classes if (!testSources.getTestClasses().isEmpty()) { for (TestClass testClass : testSources.getTestClasses()) { parameters.add("-tsc"); // ;;;; parameters.add(String.format("%s;%s;%s;%s;%s", defaultString(testClass.getName()), defaultString(testClass.getPackage()), defaultString(testClass.getAnnotation()), defaultString(testClass.getSuper()), defaultString(testClass.getTag()))); // methods for (TestMethod testMethod : testClass.getTestMethods()) { parameters.add("-tsm"); // ;;; parameters.add(String.format("%s;%s;%s;%s", defaultString(testMethod.getName()), defaultString(testMethod.getAnnotation()), defaultString(testMethod.getReturnType()), defaultString(testMethod.getTag()))); } } } } } protected abstract String getSourceType(); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy