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

net.saliman.gradle.plugin.cobertura.CoberturaPlugin.groovy Maven / Gradle / Ivy

There is a newer version: 4.0.0
Show newest version
package net.saliman.gradle.plugin.cobertura

import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.UnknownConfigurationException
import org.gradle.api.execution.TaskExecutionGraph
import org.gradle.api.initialization.Settings
import org.gradle.api.invocation.Gradle
import org.gradle.api.plugins.PluginAware
import org.gradle.api.plugins.ReportingBasePlugin
import org.gradle.api.tasks.testing.Test

/**
 * Provides Cobertura coverage for Test tasks.
 *
 * This plugin will create 4 tasks.
 *
 * The first is the "cobertura" task that users may call to generate coverage
 * reports.  This task will run the all tasks named "test" from the directory
 * where gradle was invoked as well as "test" tasks in any sub projects.  In
 * addition, it will run all tasks of type {@code Test} in the project applying
 * the plugin.  It is designed to do the same thing as "gradle test", but with
 * all testing tasks running in the applying project and coverage reports
 * generated at the end of the build.  This task doesn't actually do any work,
 * it is used by the plugin to determine user intent. This task will do the
 * right thing for most users.
 * 

* The second task is called "coberturaReport". Users may call this task to * tell Gradle that coverage reports should be generated after tests are run, * but without causing any tests to actually run. This allows users to have * more fine-grained control over the tasks that get run by requiring them to * specify the desired tasks on the command line. *

* The third task is the "instrument" task, that will instrument the source * code. Users won't call it directly, but the plugin will make all test * tasks depend on it so that instrumentation only happens once, and only if * the task graph has the "cobertura" or "coberturaReport" task in the execution * graph. *

* The fourth task is the "copyCoberturaDatafile" task, which copies the input * datafile, generated by the instrument task, to the output datafile, which is * is modified by tests and used to generate the coverage report. This task * must run whenever we want a coverage report to make sure the data in it is * for the current run only. *

* The fifth task is the "generateCoberturaReport" task, which does the actual * work of generating the coverage reports if the "cobertura" or * "coberturaReport" tasks are in the execution graph. *

* This plugin also defines a "cobertura" extension with properties that are * used to configure the operation of the plugin and its tasks. * * The plugin runs cobertura coverage reports for sourceSets.main. A project * might have have multiple artifacts that use different parts of the code, and * there may be different test tasks that test different parts of the source, * but there is almost always only one main source set. * * Most of the magic of this plugin happens not at apply time, but when tasks * are added and when the task graph is ready. If the graph contains the * "cobertura" or "coberturaReport" task, it will make sure the instrument and * generateCoberturaReport" tasks are configured to do actual work, and that * task dependencies are what they need to be for the users to get what they * want. */ class CoberturaPlugin implements Plugin { // Constants for the tasks created by this plugin that don't have their own // classes. static final String COBERTURA_TASK_NAME = 'cobertura' static final String COBERTURA_REPORT_TASK_NAME = 'coberturaReport' static final String COBERTURA_CHECK_TASK_NAME = 'coberturaCheck' def void apply(PluginAware pluginAware) { if ( pluginAware instanceof Project ) { doApply(pluginAware) } else if ( pluginAware instanceof Settings ) { pluginAware.gradle.allprojects { p -> p.plugins.apply(CoberturaPlugin) } } else if ( pluginAware instanceof Gradle ) { pluginAware.allprojects { p -> p.plugins.apply(CoberturaPlugin) } } else { throw new IllegalArgumentException("${pluginAware.getClass()} is currently not supported as an apply target, please report if you need it") } } def void doApply(Project project) { if ( project.plugins.hasPlugin(CoberturaPlugin) ) { project.logger.info("Project ${project.name} already has the cobertura plugin") return } project.logger.info("Applying cobertura plugin to $project.name") project.plugins.apply(ReportingBasePlugin) def extension = project.extensions.create('cobertura', CoberturaExtension, project) if (!project.configurations.asMap['cobertura']) { project.configurations.create('cobertura') project.afterEvaluate { // When we're done evaluating, bring in the appropriate version of // Cobertura. Exclude logging dependencies because it causes conflicts // with classes Gradle has already loaded. project.dependencies { cobertura("net.sourceforge.cobertura:cobertura:${project.extensions.cobertura.coberturaVersion}") { exclude group: 'log4j', module: 'log4j' exclude group: 'org.slf4j', module: 'slf4j-api' } } } } createTasks(project, extension) // We need to wait until after the extension is fully configured to fix // task dependencies. project.afterEvaluate { fixTaskDependencies(project, extension) } // This marks the slf4j dependency as 'provided' for android projects. // The actual dependency will be satisfied by the gradle distribution. // This will not conflict with any dependencies in compile/test configurations. if (isAndroidProject(project)) { try { project.dependencies { provided 'org.slf4j:slf4j-api:1.7.5' } } catch (Exception ignored) { } } registerTaskFixupListener(project) } /** * Create the tasks that the Cobertura plugin will use. This method will * create the following tasks: *

    *
  • instrument - This is an internal task that does the actual work of * instrumenting the source code.
  • *
  • generateCoberturaReport - This is an internal task that does the * actual work of generating the Cobertura reports.
  • *
  • copyCoberturaDatafile - This is an internal task that makes a copy of * the instrumentation data file (the .ser file) for tests to use so that * the original remains untouched. This is needed to make sure * instrumentation only happens when source code changes.
  • *
  • coberturaReport - Users will use this task to tell the plugin that * they want coverage reports generated after all the tests run. The * {@code coberturaReport} task doesn't actually cause any tests to run; * users will need to specify one or more test tasks on the command line * as well.
  • *
  • cobertura
  • - Users will use this task to run tests and generate * a coverage report. This task is meant to be a convenience task that is * simpler than (but not quite the same as) {@code test coberturaReport} *
* * @param project the project being configured */ private void createTasks(Project project, CoberturaExtension extension) { CoberturaRunner runner = new CoberturaRunner() // Create the coberturaReport task. This task, when invoked, indicates // that users want a coverage report. project.tasks.create(name: COBERTURA_REPORT_TASK_NAME, group: 'Cobertura', type: DefaultTask) Task reportTask = project.tasks.getByName(COBERTURA_REPORT_TASK_NAME) reportTask.setDescription("Generate Cobertura reports after tests finish.") // Create the cobertura task. This task, when invoked, indicates that // users want a coverage report, and they want all tests to run. project.tasks.create(name: COBERTURA_TASK_NAME, group: 'Cobertura', type: DefaultTask) Task coberturaTask = project.tasks.getByName(COBERTURA_TASK_NAME) coberturaTask.setDescription("Run tests and generate Cobertura coverage reports.") // If we make cobertura depend on reportTask, it is easier later on to // determine of we need reports or not. coberturaTask.dependsOn reportTask // Create the checkCoverage task. This task, when invoked, indicates that // users want to check coverage levels, but it does not run any tests.. project.tasks.create(name: COBERTURA_CHECK_TASK_NAME, group: 'Cobertura', type: DefaultTask) Task checkCoverageTask = project.tasks.getByName(COBERTURA_CHECK_TASK_NAME) checkCoverageTask.setDescription("Check test coverage.") // It doesn't make much sense to check coverage without generating a // report checkCoverageTask.dependsOn reportTask // Create the instrument task that will instrument code. At the moment, it // is disabled. project.tasks.create(name: InstrumentTask.NAME, type: InstrumentTask, { configuration = project.extensions.cobertura classpath = project.configurations.cobertura }) Task instrumentTask = project.tasks.getByName(InstrumentTask.NAME) instrumentTask.setDescription("Instrument code for Cobertura coverage reports") instrumentTask.enabled = false instrumentTask.runner = runner // Create the copyCoberturaDatafile task that will copy the .ser file. It // is also disabled to start. project.tasks.create(name: CopyDatafileTask.NAME, type: CopyDatafileTask, { configuration = project.extensions.cobertura }) Task copyDatafileTask = project.tasks.getByName(CopyDatafileTask.NAME) copyDatafileTask.setDescription("Helper task that makes a fresh copy of the Cobertura data file for tests") copyDatafileTask.enabled = false copyDatafileTask.dependsOn instrumentTask // Create the generateCoberturaReport task that will generate the reports. // Like the others, it starts out disabled. project.tasks.create(name: GenerateReportTask.NAME, type: GenerateReportTask, { configuration = project.extensions.cobertura classpath = project.configurations.cobertura }) Task generateReportTask = project.tasks.getByName(GenerateReportTask.NAME) generateReportTask.setDescription("Generate a Cobertura report after tests finish.") generateReportTask.enabled = false generateReportTask.runner = runner generateReportTask.reports.all { report -> report.conventionMapping.with { enabled = true destination = { new File(extension.coverageReportDir, "index.html") } } } // Create the performCoverageCheck task that will do the work of checking // the coverage levels, and you guessed it, it is disabled. project.tasks.create(name: PerformCoverageCheckTask.NAME, type: PerformCoverageCheckTask, { configuration = project.extensions.cobertura classpath = project.configurations.cobertura }) Task performCoverageCheckTask = project.tasks.getByName(PerformCoverageCheckTask.NAME) performCoverageCheckTask.setDescription("Perform a Cobertura test coverage check.") performCoverageCheckTask.enabled = false performCoverageCheckTask.runner = runner performCoverageCheckTask.dependsOn generateReportTask } /** * We need to make several changes to the tasks in the task graph for the * cobertura plugin to work correctly. The changes need to be made to all * tasks currently in the project, as well as any new tasks that get added * later. * @param project the project applying the plugin. Used for logging. * @param extension the CoberturaExtension which has the various options for * the plugin, but also the closures that return classes and test * tasks so we can set up dependencies properly. */ private void fixTaskDependencies(Project project, CoberturaExtension extension) { Task instrumentTask = project.tasks.getByName(InstrumentTask.NAME) Task coberturaTask = project.tasks.getByName(COBERTURA_TASK_NAME) Task performCoverageCheckTask = project.tasks.getByName(PerformCoverageCheckTask.NAME) Task copyDatafileTask = project.tasks.getByName(CopyDatafileTask.NAME) // Add a whenTaskAdded listener for all projects from the base down. extension.coverageClassesTasks.all { task -> project.logger.info("Making :${project.name}:instrument depend on :${task.project.name}:${task.name}") instrumentTask.dependsOn task } // Tests need to depend on copying the data file and be finalized by the // perform coverage check task (which in turn depends on generate reports). // We'll figure out later what is enabled or not. extension.coverageTestTasks.all { task -> project.logger.info("Making :${project.name}:cobertura depend on :${task.project.name}:${task.name}") task.dependsOn copyDatafileTask task.finalizedBy performCoverageCheckTask coberturaTask.dependsOn task } } /** * Register a listener with Gradle. When gradle is ready to run tasks, it * will call our listener. If the coberturaReport task is in our graph, the * listener will fix the classpaths of all the test tasks that we are actually * running. * * @param project the project applying the plugin. */ private void registerTaskFixupListener(Project project) { // If the user wants cobertura reports, fix test classpaths, and set them // to generate reports on failure. If not, disable the 3 tasks that // instrument code and run reports. // "whenReady()" is a global event, so closure should be registered exactly // once for single and multi-project builds. Gradle gradle = project.gradle CoberturaExtension extension = project.extensions.findByType(CoberturaExtension) gradle.taskGraph.whenReady { TaskExecutionGraph graph -> // See if the user wants a coberturaReport. If so, fix the classpath of // any test task we are actually running. (we don't need to look for the // cobertura task because it depends on coberturaReport). if (graph.hasTask(project.tasks.findByName(COBERTURA_REPORT_TASK_NAME))) { project.tasks.withType(Test).all { Test test -> // Only apply classpath changes on Android to the variant we are covering. if (isAndroidProject(project) && test.name != "test${extension.androidVariant.capitalize()}UnitTest") { return } try { Configuration config = test.project.configurations['cobertura'] test.systemProperties.put('net.sourceforge.cobertura.datafile', test.project.extensions.cobertura.coverageOutputDatafile) test.classpath += config test.outputs.upToDateWhen { false } test.classpath = project.files("${project.buildDir}/instrumented_classes") + test.classpath } catch (UnknownConfigurationException e) { // Eat this. It just means we have a multi-project build, and // there is test in a project that doesn't have cobertura applied. } } // If we are not doing a dry-run We also need to enable // instrumentation, file copying and report generation, but not // coverage checking if ( !project.gradle.startParameter.dryRun ) { project.tasks.withType(InstrumentTask).all { it.enabled = true } project.tasks.withType(CopyDatafileTask).each { it.enabled = true } project.tasks.withType(GenerateReportTask).each { it.enabled = true } } } // If the user wants to check coverage levels, and we're not doing a // dry-run we need to enable all 4 of the tasks that do actual work. if (graph.hasTask(project.tasks.findByName(COBERTURA_CHECK_TASK_NAME))) { if ( !project.gradle.startParameter.dryRun ) { project.tasks.withType(InstrumentTask).all { it.enabled = true } project.tasks.withType(CopyDatafileTask).each { it.enabled = true } project.tasks.withType(GenerateReportTask).each { it.enabled = true } project.tasks.withType(PerformCoverageCheckTask).each { it.enabled = true } } } } } static boolean isAndroidProject(Project project) { return project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library") } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy