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

org.gradle.api.plugins.clover.CloverPlugin.groovy Maven / Gradle / Ivy

/*
 * Copyright 2011 the original author or authors.
 *
 * 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.gradle.api.plugins.clover

import groovy.util.logging.Slf4j
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.AsmBackedClassGenerator
import org.gradle.api.plugins.GroovyPlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.clover.internal.LicenseResolverFactory
import org.gradle.api.tasks.testing.Test

import java.lang.reflect.Constructor

/**
 * 

A {@link org.gradle.api.Plugin} that provides a task for creating a code coverage report using Clover.

* * @author Benjamin Muschko */ @Slf4j class CloverPlugin implements Plugin { static final String CONFIGURATION_NAME = 'clover' static final String GENERATE_REPORT_TASK_NAME = 'cloverGenerateReport' static final String AGGREGATE_REPORTS_TASK_NAME = 'cloverAggregateReports' static final String AGGREGATE_DATABASES_TASK_NAME = 'cloverAggregateDatabases' static final String REPORT_GROUP = 'report' static final String CLOVER_GROUP = 'clover' static final String DEFAULT_JAVA_INCLUDES = '**/*.java' static final String DEFAULT_GROOVY_INCLUDES = '**/*.groovy' static final String DEFAULT_JAVA_TEST_INCLUDES = '**/*Test.java' static final String DEFAULT_GROOVY_TEST_INCLUDES = '**/*Test.groovy' static final String DEFAULT_CLOVER_DATABASE = '.clover/clover.db' static final String DEFAULT_CLOVER_SNAPSHOT = '.clover/coverage.db.snapshot' @Override void apply(Project project) { project.configurations.add(CONFIGURATION_NAME).setVisible(false).setTransitive(true) .setDescription('The Clover library to be used for this project.') CloverPluginConvention cloverPluginConvention = new CloverPluginConvention() project.convention.plugins.clover = cloverPluginConvention AggregateDatabasesTask aggregateDatabasesTask = configureAggregateDatabasesTask(project, cloverPluginConvention) configureActions(project, cloverPluginConvention, aggregateDatabasesTask) configureGenerateCoverageReportTask(project, cloverPluginConvention, aggregateDatabasesTask) configureAggregateReportsTask(project, cloverPluginConvention) } private AggregateDatabasesTask configureAggregateDatabasesTask(Project project, CloverPluginConvention cloverPluginConvention) { project.tasks.withType(AggregateDatabasesTask).whenTaskAdded { AggregateDatabasesTask aggregateDatabasesTask -> aggregateDatabasesTask.conventionMapping.with { map('initString') { getInitString(cloverPluginConvention) } map('cloverClasspath') { project.configurations.getByName(CONFIGURATION_NAME).asFileTree } map('licenseFile') { getLicenseFile(project, cloverPluginConvention) } } } AggregateDatabasesTask aggregateDatabasesTask = project.tasks.add(AGGREGATE_DATABASES_TASK_NAME, AggregateDatabasesTask) aggregateDatabasesTask.description = 'Aggregates Clover code coverage databases for the project.' aggregateDatabasesTask.group = CLOVER_GROUP aggregateDatabasesTask } private void configureActions(Project project, CloverPluginConvention cloverPluginConvention, AggregateDatabasesTask aggregateDatabasesTask) { project.tasks.withType(Test) { Test test -> project.afterEvaluate { if (cloverPluginConvention.includeTasks) { if (test.name in cloverPluginConvention.includeTasks) { configureActionsForTask(test, project, cloverPluginConvention, aggregateDatabasesTask) } } else if (!(test.name in cloverPluginConvention.excludeTasks)) { configureActionsForTask(test, project, cloverPluginConvention, aggregateDatabasesTask) } } } } private void configureActionsForTask(Test test, Project project, CloverPluginConvention cloverPluginConvention, AggregateDatabasesTask aggregateDatabasesTask) { test.classpath += project.configurations.getByName(CONFIGURATION_NAME).asFileTree OptimizeTestSetAction optimizeTestSetAction = createOptimizeTestSetAction(cloverPluginConvention, project, test) test.doFirst optimizeTestSetAction // add first, gets executed second test.doFirst createInstrumentCodeAction(cloverPluginConvention, project, test) // add second, gets executed first test.include optimizeTestSetAction // action is also a file inclusion spec test.doLast createCreateSnapshotAction(cloverPluginConvention, project, test) test.doLast createRestoreOriginalClassesAction(cloverPluginConvention, project, test) aggregateDatabasesTask.aggregate(test) } private RestoreOriginalClassesAction createRestoreOriginalClassesAction(CloverPluginConvention cloverPluginConvention, Project project, Test testTask) { RestoreOriginalClassesAction restoreOriginalClassesAction = createInstance(RestoreOriginalClassesAction) restoreOriginalClassesAction.conventionMapping.map('classesBackupDir') { getClassesBackupDirectory(project, cloverPluginConvention) } restoreOriginalClassesAction.conventionMapping.map('testClassesBackupDir') { getTestClassesBackupDirectory(project, cloverPluginConvention) } restoreOriginalClassesAction.conventionMapping.map('classesDir') { project.sourceSets.main.output.classesDir } restoreOriginalClassesAction.conventionMapping.map('testClassesDir') { testTask.testClassesDir } restoreOriginalClassesAction } private CreateSnapshotAction createCreateSnapshotAction(CloverPluginConvention cloverPluginConvention, Project project, Test testTask) { CreateSnapshotAction createSnapshotAction = createInstance(CreateSnapshotAction) createSnapshotAction.conventionMapping.map('initString') { getInitString(cloverPluginConvention, testTask) } createSnapshotAction.conventionMapping.map('optimizeTests') { cloverPluginConvention.optimizeTests } createSnapshotAction.conventionMapping.map('snapshotFile') { getSnapshotFile(project, cloverPluginConvention, true, testTask) } createSnapshotAction.conventionMapping.map('cloverClasspath') { project.configurations.getByName(CONFIGURATION_NAME).asFileTree } createSnapshotAction.conventionMapping.map('licenseFile') { getLicenseFile(project, cloverPluginConvention) } createSnapshotAction.conventionMapping.map('buildDir') { project.buildDir } createSnapshotAction } private OptimizeTestSetAction createOptimizeTestSetAction(CloverPluginConvention cloverPluginConvention, Project project, Test testTask) { OptimizeTestSetAction optimizeTestSetAction = createInstance(OptimizeTestSetAction) optimizeTestSetAction.conventionMapping.map('initString') { getInitString(cloverPluginConvention, testTask) } optimizeTestSetAction.conventionMapping.map('optimizeTests') { cloverPluginConvention.optimizeTests } optimizeTestSetAction.conventionMapping.map('snapshotFile') { getSnapshotFile(project, cloverPluginConvention, false, testTask) } optimizeTestSetAction.conventionMapping.map('licenseFile') { getLicenseFile(project, cloverPluginConvention) } optimizeTestSetAction.conventionMapping.map('cloverClasspath') { project.configurations.getByName(CONFIGURATION_NAME).asFileTree } optimizeTestSetAction.conventionMapping.map('testSrcDirs') { getTestSourceDirectories(project, cloverPluginConvention, testTask) } optimizeTestSetAction.conventionMapping.map('buildDir') { project.buildDir } optimizeTestSetAction } private InstrumentCodeAction createInstrumentCodeAction(CloverPluginConvention cloverPluginConvention, Project project, Test testTask) { InstrumentCodeAction instrumentCodeAction = createInstance(InstrumentCodeAction) instrumentCodeAction.conventionMapping.map('initString') { getInitString(cloverPluginConvention, testTask) } instrumentCodeAction.conventionMapping.map('compileGroovy') { hasGroovyPlugin(project) } instrumentCodeAction.conventionMapping.map('cloverClasspath') { project.configurations.getByName(CONFIGURATION_NAME).asFileTree } instrumentCodeAction.conventionMapping.map('testRuntimeClasspath') { getTestRuntimeClasspath(project, testTask).asFileTree } instrumentCodeAction.conventionMapping.map('groovyClasspath') { project.configurations.groovy.asFileTree } instrumentCodeAction.conventionMapping.map('classesBackupDir') { getClassesBackupDirectory(project, cloverPluginConvention) } instrumentCodeAction.conventionMapping.map('testClassesBackupDir') { getTestClassesBackupDirectory(project, cloverPluginConvention) } instrumentCodeAction.conventionMapping.map('licenseFile') { getLicenseFile(project, cloverPluginConvention) } instrumentCodeAction.conventionMapping.map('buildDir') { project.buildDir } instrumentCodeAction.conventionMapping.map('classesDir') { project.sourceSets.main.output.classesDir } instrumentCodeAction.conventionMapping.map('testClassesDir') { testTask.testClassesDir } instrumentCodeAction.conventionMapping.map('srcDirs') { getSourceDirectories(project, cloverPluginConvention) } instrumentCodeAction.conventionMapping.map('testSrcDirs') { getTestSourceDirectories(project, cloverPluginConvention, testTask) } instrumentCodeAction.conventionMapping.map('sourceCompatibility') { project.sourceCompatibility?.toString() } instrumentCodeAction.conventionMapping.map('targetCompatibility') { project.targetCompatibility?.toString() } instrumentCodeAction.conventionMapping.map('includes') { getIncludes(project, cloverPluginConvention) } instrumentCodeAction.conventionMapping.map('excludes') { cloverPluginConvention.excludes } instrumentCodeAction.conventionMapping.map('testIncludes') { getTestIncludes(project, cloverPluginConvention) } instrumentCodeAction.conventionMapping.map('statementContexts') { cloverPluginConvention.contexts.statements } instrumentCodeAction.conventionMapping.map('methodContexts') { cloverPluginConvention.contexts.methods } instrumentCodeAction } private void configureGenerateCoverageReportTask(Project project, CloverPluginConvention cloverPluginConvention, AggregateDatabasesTask aggregateDatabasesTask) { project.tasks.withType(GenerateCoverageReportTask).whenTaskAdded { GenerateCoverageReportTask generateCoverageReportTask -> generateCoverageReportTask.dependsOn aggregateDatabasesTask generateCoverageReportTask.conventionMapping.map('initString') { getInitString(cloverPluginConvention) } generateCoverageReportTask.conventionMapping.map('buildDir') { project.buildDir } generateCoverageReportTask.conventionMapping.map('cloverClasspath') { project.configurations.getByName(CONFIGURATION_NAME).asFileTree } generateCoverageReportTask.conventionMapping.map('licenseFile') { getLicenseFile(project, cloverPluginConvention) } generateCoverageReportTask.conventionMapping.map('targetPercentage') { cloverPluginConvention.targetPercentage } generateCoverageReportTask.conventionMapping.map('filter') { cloverPluginConvention.report.filter } setCloverReportConventionMappings(project, cloverPluginConvention, generateCoverageReportTask) } GenerateCoverageReportTask generateCoverageReportTask = project.tasks.add(GENERATE_REPORT_TASK_NAME, GenerateCoverageReportTask) generateCoverageReportTask.description = 'Generates Clover code coverage report.' generateCoverageReportTask.group = REPORT_GROUP } private void configureAggregateReportsTask(Project project, CloverPluginConvention cloverPluginConvention) { project.tasks.withType(AggregateReportsTask).whenTaskAdded { AggregateReportsTask aggregateReportsTask -> aggregateReportsTask.conventionMapping.map('initString') { getInitString(cloverPluginConvention) } aggregateReportsTask.conventionMapping.map('cloverClasspath') { project.configurations.getByName(CONFIGURATION_NAME).asFileTree } aggregateReportsTask.conventionMapping.map('licenseFile') { getLicenseFile(project, cloverPluginConvention) } aggregateReportsTask.conventionMapping.map('buildDir') { project.buildDir } aggregateReportsTask.conventionMapping.map('subprojectBuildDirs') { project.subprojects.collect { it.buildDir } } setCloverReportConventionMappings(project, cloverPluginConvention, aggregateReportsTask) } // Only add task to root project if(project == project.rootProject && project.subprojects.size() > 0) { AggregateReportsTask aggregateReportsTask = project.rootProject.tasks.add(AGGREGATE_REPORTS_TASK_NAME, AggregateReportsTask) aggregateReportsTask.description = 'Aggregates Clover code coverage reports.' aggregateReportsTask.group = REPORT_GROUP project.allprojects*.tasks*.withType(GenerateCoverageReportTask) { aggregateReportsTask.dependsOn it } } } /** * Sets Clover report convention mappings. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @param task Task */ private void setCloverReportConventionMappings(Project project, CloverPluginConvention cloverPluginConvention, Task task) { task.conventionMapping.map('reportsDir') { new File(project.buildDir, 'reports') } task.conventionMapping.map('xml') { cloverPluginConvention.report.xml } task.conventionMapping.map('json') { cloverPluginConvention.report.json } task.conventionMapping.map('html') { cloverPluginConvention.report.html } task.conventionMapping.map('pdf') { cloverPluginConvention.report.pdf } task.conventionMapping.map('projectName') { project.name } } /** * Creates an instance of the specified class, using an ASM-backed class generator. * * @param clazz the type of object to create * @return an instance of the specified type */ private createInstance(Class clazz) { AsmBackedClassGenerator generator = new AsmBackedClassGenerator() Class instrumentClass = generator.generate(clazz) Constructor constructor = instrumentClass.getConstructor() return constructor.newInstance() } /** * Gets init String that determines location of Clover database. * * @param cloverPluginConvention Clover plugin convention * @return Init String */ private String getInitString(CloverPluginConvention cloverPluginConvention) { cloverPluginConvention.initString ?: DEFAULT_CLOVER_DATABASE } private String getInitString(CloverPluginConvention cloverPluginConvention, Test testTask) { "${getInitString(cloverPluginConvention)}-${testTask.name}" } /** * Gets classes backup directory. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @return Classes backup directory */ private File getClassesBackupDirectory(Project project, CloverPluginConvention cloverPluginConvention) { cloverPluginConvention.classesBackupDir ?: new File("${project.sourceSets.main.output.classesDir}-bak") } /** * Gets test classes backup directory. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @return Classes backup directory */ private File getTestClassesBackupDirectory(Project project, CloverPluginConvention cloverPluginConvention) { cloverPluginConvention.testClassesBackupDir ?: new File("${project.sourceSets.test.output.classesDir}-bak") } /** * Gets Clover license file. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @return License file */ private File getLicenseFile(Project project, CloverPluginConvention cloverPluginConvention) { LicenseResolverFactory.instance.getResolver(cloverPluginConvention.licenseLocation).resolve(project.rootDir, cloverPluginConvention.licenseLocation) } /** * Gets the Clover snapshot file location. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @param force if true, return the snapshot file even if it doesn't exist; if false, don't return the snapshot file if it doesn't exist * @return the Clover snapshot file location */ private File getSnapshotFile(Project project, CloverPluginConvention cloverPluginConvention, boolean force, Test testTask) { File file = cloverPluginConvention.snapshotFile != null && cloverPluginConvention.snapshotFile != '' ? project.file("${cloverPluginConvention.snapshotFile}-${testTask.name}") : project.file("${DEFAULT_CLOVER_SNAPSHOT}-${testTask.name}") return file.exists() || force ? file : null } /** * Gets source directories. If the Groovy plugin was applied we only its source directories in addition to the * Java plugin source directories. We only add directories that actually exist. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @return Source directories */ private Set getSourceDirectories(Project project, CloverPluginConvention cloverPluginConvention) { def srcDirs = [] as Set if(hasGroovyPlugin(project)) { addExistingSourceDirectories(srcDirs, project.sourceSets.main.java.srcDirs) addExistingSourceDirectories(srcDirs, project.sourceSets.main.groovy.srcDirs) } else { addExistingSourceDirectories(srcDirs, project.sourceSets.main.java.srcDirs) } if(cloverPluginConvention.additionalSourceDirs) { addExistingSourceDirectories(srcDirs, cloverPluginConvention.additionalSourceDirs) } srcDirs } /** * Gets test source directories. If the Groovy plugin was applied we only its test source directories in addition to the * Java plugin source directories. We only add directories that actually exist. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @return Test source directories */ private Set getTestSourceDirectories(Project project, CloverPluginConvention cloverPluginConvention, Test testTask) { def testSrcDirs = [] as Set //default test task if (testTask.testSrcDirs as Set == project.sourceSets.test.java.srcDirs) { if(hasGroovyPlugin(project)) { addExistingSourceDirectories(testSrcDirs, project.sourceSets.test.java.srcDirs) addExistingSourceDirectories(testSrcDirs, project.sourceSets.test.groovy.srcDirs) } else if(hasJavaPlugin(project)) { addExistingSourceDirectories(testSrcDirs, project.sourceSets.test.java.srcDirs) } } else { addExistingSourceDirectories(testSrcDirs, testTask.testSrcDirs as Set) } if(cloverPluginConvention.additionalTestDirs) { addExistingSourceDirectories(testSrcDirs, cloverPluginConvention.additionalTestDirs) } testSrcDirs } /** * Adds source directories to target Set only if they actually exist. * * @param target Target * @param source Source */ private void addExistingSourceDirectories(Set target, Set source) { source.each { if(it.exists()) { target << it } else { log.warn "The specified source directory '$it.canonicalPath' does not exist. It won't be included in Clover instrumentation." } } } /** * Gets includes for compilation. Uses includes if set as convention property. Otherwise, use default includes. The * default includes are determined by the fact if Groovy plugin was applied to project or not. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @return Includes */ private List getIncludes(Project project, CloverPluginConvention cloverPluginConvention) { if(cloverPluginConvention.includes) { return cloverPluginConvention.includes } if(hasGroovyPlugin(project)) { return [DEFAULT_JAVA_INCLUDES, DEFAULT_GROOVY_INCLUDES] } [DEFAULT_JAVA_INCLUDES] } /** * Gets test includes for compilation. Uses includes if set as convention property. Otherwise, use default includes. The * default includes are determined by the fact if Groovy plugin was applied to project or not. * * @param project Project * @param cloverPluginConvention Clover plugin convention * @return Test includes */ private List getTestIncludes(Project project, CloverPluginConvention cloverPluginConvention) { if(cloverPluginConvention.testIncludes) { return cloverPluginConvention.testIncludes } if(hasGroovyPlugin(project)) { return [DEFAULT_JAVA_TEST_INCLUDES, DEFAULT_GROOVY_TEST_INCLUDES] } [DEFAULT_JAVA_TEST_INCLUDES] } /** * Checks to see if Java plugin got applied to project. * * @param project Project * @return Flag */ private boolean hasJavaPlugin(Project project) { project.plugins.hasPlugin(JavaPlugin) } /** * Checks to see if Groovy plugin got applied to project. * * @param project Project * @return Flag */ private boolean hasGroovyPlugin(Project project) { project.plugins.hasPlugin(GroovyPlugin) } private FileCollection getTestRuntimeClasspath(Project project, Test testTask) { testTask.classpath.filter { !it.directory } + project.configurations.getByName(CONFIGURATION_NAME) } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy