diff --git a/README.md b/README.md index 2f438c8..a0c31f6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A plugin to enable the use of Scoverage in a gradle Scala project. This has now been deployed to maven central. +Using gradle-scoverage +====================== + Getting started --------------- ```groovy @@ -34,29 +37,47 @@ This creates an additional task testCoverage which will run tests against instru Then launch command : `gradle testScoverage` or `gradle checkScoverage` +Available tasks +--------------- + +* testScoverage - Executes all tests and creates Scoverage XML report with information about code coverage +* reportScoverage - Generates reports (see below). +* aggregateScoverage - Aggregates reports from multiple sub-projects (see below). +* checkScoverage - See below. +* compileScoverageScala - Instruments code without running tests. + +ReportScoverage +--------------- + +You can configure output generated by `gradle reportScoverage` using flags: + +| Flag name | Default value | Description | +| ------------------------|---------------|-------------------------------------------------| +| coverageOutputCobertura | true | Enables/disables cobertura.xml file generation. | +| coverageOutputXML | true | Enables/disables scoverage XML output. | +| coverageOutputHTML | true | Enables/disables scoverage HTML output. | +| coverageDebug | false | Enables/disables scoverage debug output. | + Aggregating Reports ------------------- -There is now experimental support for aggregating coverage statistics across subprojects. +There is now experimental support for aggregating coverage statistics across sub-projects. The project hosting the aggregation task **must** be configured as the sub-projects are; i.e. with the scoverage plugin applied and the scoverage dependencies configured. +You also have to declare this task: + ```groovy task aggregateScoverage(type: org.scoverage.ScoverageAggregate) ``` -This will produce a report into _build_ / scoverage-aggregate +This will produce a report into `build/scoverage-aggregate` directory. -Available tasks ---------- -* testScoverage - Executes all tests and creates Scoverage XML report with information about code coverage -* reportScoverage - Generates HTML report. -* checkScoverage - See below. -* compileScoverageScala - Instruments code without running tests. +Aggregation uses same flags as reporting for enabling/disabling different output types. CheckScoverage ---------- +-------------- By default, when you launch `gradle checkScoverage` build fail if only 75% of project is covered by tests. diff --git a/src/main/groovy/org/scoverage/AggregateReportApp.java b/src/main/groovy/org/scoverage/AggregateReportApp.java index fdb28e1..914bc89 100644 --- a/src/main/groovy/org/scoverage/AggregateReportApp.java +++ b/src/main/groovy/org/scoverage/AggregateReportApp.java @@ -13,10 +13,23 @@ public static void main(String... args) { File rootDir = new File(args[0]); File reportDir = new File(args[1]); Boolean clean = Boolean.parseBoolean(args[2]); - reportDir.mkdirs(); + + Boolean coverageOutputCobertura = java.lang.Boolean.valueOf(args[3]); + Boolean coverageOutputXML = java.lang.Boolean.valueOf(args[4]); + Boolean coverageOutputHTML = java.lang.Boolean.valueOf(args[5]); + Boolean coverageDebug = java.lang.Boolean.valueOf(args[6]); + Coverage coverage = CoverageAggregator.aggregate(rootDir, clean).get(); - new ScoverageHtmlWriter(rootDir, reportDir).write(coverage); - new CoberturaXmlWriter(rootDir, reportDir).write(coverage); + + ScoverageWriter.write( + rootDir, + reportDir, + coverage, + coverageOutputCobertura, + coverageOutputXML, + coverageOutputHTML, + coverageDebug + ); } } \ No newline at end of file diff --git a/src/main/groovy/org/scoverage/ScoverageAggregate.groovy b/src/main/groovy/org/scoverage/ScoverageAggregate.groovy index 5d9d3a2..8703d09 100644 --- a/src/main/groovy/org/scoverage/ScoverageAggregate.groovy +++ b/src/main/groovy/org/scoverage/ScoverageAggregate.groovy @@ -9,13 +9,21 @@ class ScoverageAggregate extends JavaExec { @Override void exec() { + def extension = ScoveragePlugin.extensionIn(project) setClasspath(ScoveragePlugin.extensionIn(project).pluginClasspath) setMain('org.scoverage.AggregateReportApp') def reportPath = reportDirOrDefault() - setArgs([project.projectDir, reportPath.absolutePath, clean]) + setArgs([ + project.projectDir, + reportPath.absolutePath, + clean, + // TODO - consider separate options for `report` and `aggregate` tasks + extension.coverageOutputCobertura, + extension.coverageOutputXML, + extension.coverageOutputHTML, + extension.coverageDebug + ]) super.exec() - def reportEntryPoint = new File(reportPath, 'index.html').absolutePath - project.logger.lifecycle("Wrote aggregated scoverage report to ${reportEntryPoint}") } def reportDirOrDefault() { diff --git a/src/main/groovy/org/scoverage/ScoverageExtension.groovy b/src/main/groovy/org/scoverage/ScoverageExtension.groovy index 240ee3c..4d4c943 100644 --- a/src/main/groovy/org/scoverage/ScoverageExtension.groovy +++ b/src/main/groovy/org/scoverage/ScoverageExtension.groovy @@ -48,6 +48,12 @@ class ScoverageExtension { FileCollection pluginClasspath + /** Options for enabling and disabling output */ + boolean coverageOutputCobertura = true + boolean coverageOutputXML = true + boolean coverageOutputHTML = true + boolean coverageDebug = false + ScoverageExtension(Project project) { project.plugins.apply(JavaPlugin.class); diff --git a/src/main/groovy/org/scoverage/ScoverageReport.groovy b/src/main/groovy/org/scoverage/ScoverageReport.groovy index 58061a1..ab1f1b7 100644 --- a/src/main/groovy/org/scoverage/ScoverageReport.groovy +++ b/src/main/groovy/org/scoverage/ScoverageReport.groovy @@ -10,7 +10,15 @@ class ScoverageReport extends JavaExec { extension.reportDir.mkdirs() setClasspath(extension.pluginClasspath) setMain('org.scoverage.SingleReportApp') - setArgs([extension.sources.absolutePath, extension.dataDir.absolutePath, extension.reportDir.absolutePath]) + setArgs([ + /* sourceDir = */ extension.sources.absolutePath, + /* dataDir = */ extension.dataDir.absolutePath, + /* reportDir = */ extension.reportDir.absolutePath, + extension.coverageOutputCobertura, + extension.coverageOutputXML, + extension.coverageOutputHTML, + extension.coverageDebug + ]) super.exec() } } diff --git a/src/main/groovy/org/scoverage/ScoverageWriter.java b/src/main/groovy/org/scoverage/ScoverageWriter.java new file mode 100644 index 0000000..0d8ab2f --- /dev/null +++ b/src/main/groovy/org/scoverage/ScoverageWriter.java @@ -0,0 +1,74 @@ +package org.scoverage; + +import scoverage.Constants; +import scoverage.Coverage; +import scoverage.report.CoberturaXmlWriter; +import scoverage.report.ScoverageHtmlWriter; +import scoverage.report.ScoverageXmlWriter; + +import java.io.File; + +/** + * Util for generating and saving coverage files. + *

+ * Copied from sbt-scoverage and converted to Java to avoid dependency to Scala. + */ +public class ScoverageWriter { + + /** + * Generates all reports from given data. + * + * @param sourceDir directory with project sources + * @param reportDir directory for generate reports + * @param coverage coverage data + * @param coverageOutputCobertura switch for Cobertura output + * @param coverageOutputXML switch for Scoverage XML output + * @param coverageOutputHTML switch for Scoverage HTML output + * @param coverageDebug switch for Scoverage Debug output + */ + public static void write(File sourceDir, + File reportDir, + Coverage coverage, + Boolean coverageOutputCobertura, + Boolean coverageOutputXML, + Boolean coverageOutputHTML, + Boolean coverageDebug) { + + System.out.println("[scoverage] Generating scoverage reports..."); + + reportDir.mkdirs(); + + if (coverageOutputCobertura) { + new CoberturaXmlWriter(sourceDir, reportDir).write(coverage); + System.out.println("[scoverage] Written Cobertura XML report to " + + reportDir.getAbsolutePath() + + File.separator + + "cobertura.xml"); + } + + if (coverageOutputXML) { + new ScoverageXmlWriter(sourceDir, reportDir, /* debug = */ false).write(coverage); + System.out.println("[scoverage] Written XML report to " + + reportDir.getAbsolutePath() + + File.separator + + Constants.XMLReportFilename()); + if (coverageDebug) { + new ScoverageXmlWriter(sourceDir, reportDir, /* debug = */ true).write(coverage); + System.out.println("[scoverage] Written XML report with debug information to " + + reportDir.getAbsolutePath() + + File.separator + + Constants.XMLReportFilenameWithDebug()); + } + } + + if (coverageOutputHTML) { + new ScoverageHtmlWriter(sourceDir, reportDir).write(coverage); + System.out.println("[scoverage] Written HTML report to " + + reportDir.getAbsolutePath() + + File.separator + + "index.html"); + } + + System.out.println("[scoverage] Coverage reports completed"); + } +} diff --git a/src/main/groovy/org/scoverage/SingleReportApp.java b/src/main/groovy/org/scoverage/SingleReportApp.java index bf3a4c5..998cb83 100644 --- a/src/main/groovy/org/scoverage/SingleReportApp.java +++ b/src/main/groovy/org/scoverage/SingleReportApp.java @@ -5,9 +5,6 @@ import scoverage.Coverage; import scoverage.IOUtils; import scoverage.Serializer; -import scoverage.report.CoberturaXmlWriter; -import scoverage.report.ScoverageHtmlWriter; -import scoverage.report.ScoverageXmlWriter; import java.io.File; import java.util.Arrays; @@ -21,20 +18,36 @@ public static void main(String... args) { File sourceDir = new File(args[0]); File dataDir = new File(args[1]); File reportDir = new File(args[2]); - reportDir.mkdirs(); + + Boolean coverageOutputCobertura = java.lang.Boolean.valueOf(args[3]); + Boolean coverageOutputXML = java.lang.Boolean.valueOf(args[4]); + Boolean coverageOutputHTML = java.lang.Boolean.valueOf(args[5]); + Boolean coverageDebug = java.lang.Boolean.valueOf(args[6]); File coverageFile = Serializer.coverageFile(dataDir); - File[] array = IOUtils.findMeasurementFiles(dataDir); - // TODO: patch scoverage core to use a consistent collection type? - Seq measurementFiles = scala.collection.JavaConversions.asScalaBuffer(Arrays.asList(array)); - Coverage coverage = Serializer.deserialize(coverageFile); + if (!coverageFile.exists()) { + System.out.println("[scoverage] Could not find coverage file, skipping..."); + } else { + File[] array = IOUtils.findMeasurementFiles(dataDir); + // TODO: patch scoverage core to use a consistent collection type? + Seq measurementFiles = scala.collection.JavaConversions.asScalaBuffer(Arrays.asList(array)); + + Coverage coverage = Serializer.deserialize(coverageFile); + + Set measurements = IOUtils.invoked(measurementFiles); + coverage.apply(measurements); + + ScoverageWriter.write( + sourceDir, + reportDir, + coverage, + coverageOutputCobertura, + coverageOutputXML, + coverageOutputHTML, + coverageDebug); + } + } - Set measurements = IOUtils.invoked(measurementFiles); - coverage.apply(measurements); - new ScoverageXmlWriter(sourceDir, reportDir, false).write(coverage); - new ScoverageHtmlWriter(sourceDir, reportDir).write(coverage); - new CoberturaXmlWriter(sourceDir, reportDir).write(coverage); - } } \ No newline at end of file diff --git a/src/test/groovy/org/scoverage/AcceptanceTestUtils.groovy b/src/test/groovy/org/scoverage/AcceptanceTestUtils.groovy new file mode 100644 index 0000000..306da17 --- /dev/null +++ b/src/test/groovy/org/scoverage/AcceptanceTestUtils.groovy @@ -0,0 +1,31 @@ +package org.scoverage + +import org.gradle.tooling.BuildLauncher +import org.gradle.tooling.GradleConnector +import org.hamcrest.core.Is +import org.junit.Assert + +/** + * Some utils for easy acceptance testing. + */ +class AcceptanceTestUtils { + + + protected BuildLauncher setupBuild(File projectRoot, boolean useAnt) { + return GradleConnector. + newConnector(). + forProjectDirectory(projectRoot). + connect(). + newBuild(). + withArguments("-PuseAnt=$useAnt") + } + + protected void checkFile(String description, File file, boolean shouldExist) throws Exception { + Assert.assertThat(description + ' should be created at ' + file.absolutePath, file.exists(), Is.is(shouldExist)) + } + + protected File reportDir(File baseDir) { + return new File(baseDir, 'build/reports/scoverage') + } + +} diff --git a/src/test/groovy/org/scoverage/AggregationAcceptanceTest.groovy b/src/test/groovy/org/scoverage/AggregationAcceptanceTest.groovy new file mode 100644 index 0000000..a0ac493 --- /dev/null +++ b/src/test/groovy/org/scoverage/AggregationAcceptanceTest.groovy @@ -0,0 +1,42 @@ +package org.scoverage + +import org.junit.Test + +class AggregationAcceptanceTest extends AcceptanceTestUtils { + + private static File aggregateReportDir(File baseDir) { + return new File(baseDir, 'build/scoverage-aggregate') + } + + private testWater(boolean useAnt) throws Exception { + File projectDir = new File('src/test/water') + + def build = setupBuild(projectDir, useAnt) + build.forTasks('clean', 'reportScoverage', 'aggregateScoverage').run() + + def indexHtml = new File(aggregateReportDir(projectDir), 'index.html') + checkFile('an aggregated index HTML file', indexHtml, true) + + def cobertura = new File(aggregateReportDir(projectDir), 'cobertura.xml') + checkFile('an aggregated cobertura XML file', cobertura, true) + + def scoverageXml = new File(aggregateReportDir(projectDir), 'scoverage.xml') + checkFile('an aggregated scoverage XML file', scoverageXml, true) + + def krillsHtml = new File(aggregateReportDir(projectDir), 'krills.html') + checkFile('a HTML file for \'krills\' sub-project', krillsHtml, true) + + def whalesHtml = new File(aggregateReportDir(projectDir), 'whales.html') + checkFile('a HTML file for \'whales\' sub-project', whalesHtml, true) + } + + @Test + public void testMultiProjectAggregationWithAnt() throws Exception { + testWater(true) + } + + @Test + public void testMultiProjectAggregationWithZinc() throws Exception { + testWater(false) + } +} diff --git a/src/test/groovy/org/scoverage/PluginAcceptanceTest.groovy b/src/test/groovy/org/scoverage/PluginAcceptanceTest.groovy deleted file mode 100644 index 21cade1..0000000 --- a/src/test/groovy/org/scoverage/PluginAcceptanceTest.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package org.scoverage - -import org.gradle.tooling.GradleConnector -import org.junit.Test - -import static org.junit.Assert.assertThat -import static org.hamcrest.core.Is.is - -class PluginAcceptanceTest { - - static def checkHappyDay(boolean useAnt) { - def projectRoot = "src/test/happy day" - def build = GradleConnector. - newConnector(). - forProjectDirectory(new File(projectRoot)). - connect().newBuild(). - withArguments("-PuseAnt=$useAnt") - build.forTasks('clean', 'checkScoverage').run() - - def html = new File("$projectRoot/build/reports/scoverage/index.html") - assertThat('an HTML file should be created at ' + html.absolutePath, html.exists(), is(true)) - } - - @Test - public void testAntProjectWithCompleteCoverage() throws Exception { - checkHappyDay(true) - } - - @Test - public void testZincProjectWithCompleteCoverage() throws Exception { - checkHappyDay(false) - } -} diff --git a/src/test/groovy/org/scoverage/SimpleReportAcceptanceTest.groovy b/src/test/groovy/org/scoverage/SimpleReportAcceptanceTest.groovy new file mode 100644 index 0000000..e5a9522 --- /dev/null +++ b/src/test/groovy/org/scoverage/SimpleReportAcceptanceTest.groovy @@ -0,0 +1,31 @@ +package org.scoverage + +import org.junit.Test + +class SimpleReportAcceptanceTest extends AcceptanceTestUtils { + + private testHappyDay(boolean useAnt) throws Exception { + File projectRoot = new File('src/test/happy day') + def build = setupBuild(projectRoot, useAnt) + + build.forTasks('clean', 'checkScoverage').run() + + def html = new File(reportDir(projectRoot), 'index.html') + checkFile('an index HTML file', html, true) + def cobertura = new File(reportDir(projectRoot), 'cobertura.xml') + checkFile('a cobertura XML file', cobertura, true) + def scoverageXml = new File(reportDir(projectRoot), 'scoverage.xml') + checkFile('a scoverage XML file', scoverageXml, true) + } + + @Test + public void testAntProjectWithCompleteCoverage() throws Exception { + testHappyDay(true) + } + + @Test + public void testZincProjectWithCompleteCoverage() throws Exception { + testHappyDay(false) + } + +} diff --git a/src/test/water/bluewhale/src/main/scala/whales/BlueWhale.scala b/src/test/water/bluewhale/src/main/scala/whales/BlueWhale.scala new file mode 100644 index 0000000..2953e26 --- /dev/null +++ b/src/test/water/bluewhale/src/main/scala/whales/BlueWhale.scala @@ -0,0 +1,5 @@ +package whales + +class BlueWhale { + def swim(): String = "I'm swimming!" +} \ No newline at end of file diff --git a/src/test/water/bluewhale/src/test/scala/whales/BlueWhaleTest.scala b/src/test/water/bluewhale/src/test/scala/whales/BlueWhaleTest.scala new file mode 100644 index 0000000..2e3d0be --- /dev/null +++ b/src/test/water/bluewhale/src/test/scala/whales/BlueWhaleTest.scala @@ -0,0 +1,11 @@ +package whales + +import org.junit.Test +import org.junit.Assert + +class BlueWhaleTest { + + @Test def bob(): Unit = { + Assert.assertEquals("Whale cannot swim :(", new BlueWhale().swim(), "I'm swimming!") + } +} \ No newline at end of file diff --git a/src/test/water/build.gradle b/src/test/water/build.gradle new file mode 100644 index 0000000..dd11037 --- /dev/null +++ b/src/test/water/build.gradle @@ -0,0 +1,29 @@ +description = 'a multi-project setup for gradle-scoverage' + +buildscript { + repositories { + // need to get up to the working directory of gradle-plugins build + flatDir dir: "${project.projectDir}/../../../build/libs" + } + dependencies { + classpath name: 'gradle-scoverage', version: '+' + } +} + +task aggregateScoverage(type: org.scoverage.ScoverageAggregate) + +allprojects { + repositories { + mavenCentral() + } + + apply plugin: 'scoverage' + + dependencies { + scoverage 'org.scoverage:scalac-scoverage-plugin_2.11:1.0.4', + 'org.scoverage:scalac-scoverage-runtime_2.11:1.0.4' + compile 'org.scala-lang:scala-library:2.11.5' + + testCompile 'junit:junit:4.11' + } +} diff --git a/src/test/water/krill/src/main/scala/krills/NorthernKrill.scala b/src/test/water/krill/src/main/scala/krills/NorthernKrill.scala new file mode 100644 index 0000000..16fdf22 --- /dev/null +++ b/src/test/water/krill/src/main/scala/krills/NorthernKrill.scala @@ -0,0 +1,5 @@ +package krills + +class NorthernKrill { + def swim(): String = "I can only float :(" +} \ No newline at end of file diff --git a/src/test/water/krill/src/test/scala/krills/NorthernKrillTest.scala b/src/test/water/krill/src/test/scala/krills/NorthernKrillTest.scala new file mode 100644 index 0000000..bcb4dd7 --- /dev/null +++ b/src/test/water/krill/src/test/scala/krills/NorthernKrillTest.scala @@ -0,0 +1,11 @@ +package krills + +import org.junit.Test +import org.junit.Assert + +class NorthernKrillTest { + + @Test def bob(): Unit = { + Assert.assertEquals("Krill can swim", new NorthernKrill().swim(), "I can only float :(") + } +} \ No newline at end of file diff --git a/src/test/water/settings.gradle b/src/test/water/settings.gradle new file mode 100644 index 0000000..a978eb9 --- /dev/null +++ b/src/test/water/settings.gradle @@ -0,0 +1 @@ +include 'bluewhale', 'krill' \ No newline at end of file