Skip to content

Configurable checkScoverage task #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 5, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,27 @@ Available tasks
CheckScoverage
---------

By default, when you launch `gradle checkScoverage` build fail if only 75% of project is covered by tests.
By default, when you launch `gradle checkScoverage` build fail if only 75% of statements in project is covered by tests.

To configure it as you want, add this configuration :
```
checkScoverage {
minimumLineRate = 0.5
minimumRate = 0.5
}
```

You can also modify type of value to check from `Statement`s to `Line`s or `Branch`es:

```
checkScoverage {
coverageType = 'Line'
minimumRate = 0.5
}
```

```
checkScoverage {
coverageType = 'Branch'
minimumRate = 0.5
}
```
74 changes: 64 additions & 10 deletions src/main/groovy/org/scoverage/OverallCheckTask.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,86 @@ import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.TaskAction

import java.text.DecimalFormat

/**
* Throws a GradleException if overall line coverage dips below the configured percentage.
* Handles different types of coverage Scoverage can measure.
*/
enum CoverageType {
Line('cobertura.xml', 'line-rate', 1.0),
Statement('scoverage.xml', 'statement-rate', 100.0),
Branch('scoverage.xml', 'branch-rate', 100.0)

/** Name of file with coverage data */
String fileName
/** Name of param in XML file with coverage value */
String paramName
/** Used to normalize coverage value */
private double factor

private CoverageType(String fileName, String paramName, double factor) {
this.fileName = fileName
this.paramName = paramName
this.factor = factor
}

/** Normalize coverage value to [0, 1] */
Double normalize(Double value) {
return value / factor
}
}

/**
* Throws a GradleException if overall coverage dips below the configured percentage.
*/
class OverallCheckTask extends DefaultTask {
File cobertura
double minimumLineRate = 0.75

protected XmlParser parser;
/** Type of coverage to check. Available options: Line, Statement and Branch */
CoverageType coverageType = CoverageType.Statement
double minimumRate = 0.75

/** Set if want to change default from 'reportDir' in scoverage extension. */
File reportDir

protected XmlParser parser
protected DecimalFormat df = new DecimalFormat("#.##")

OverallCheckTask() {
parser = new XmlParser()
parser.setFeature('http://apache.org/xml/features/disallow-doctype-decl', false)
parser.setFeature('http://apache.org/xml/features/nonvalidating/load-external-dtd', false)
}

/** Extracted to method for testing purposes */
static String errorMsg(String actual, String expected, CoverageType type) {
return "Only $actual% of project is covered by tests instead of $expected% (coverageType: $type)"
}

/** Extracted to method for testing purposes */
static String fileNotFoundErrorMsg(CoverageType coverageType) {
return "Coverage file (type: $coverageType) not found, check your configuration."
}

@TaskAction
void requireLineCoverage() {
def extension = ScoveragePlugin.extensionIn(project)

if (cobertura == null) cobertura = new File(extension.reportDir, 'cobertura.xml')
File reportFile = new File(reportDir ? reportDir : extension.reportDir, coverageType.fileName)

try {
def xml = parser.parse(reportFile)
Double overallRate = coverageType.normalize(xml.attribute(coverageType.paramName).toDouble())
println("$minimumRate - $overallRate")
def difference = (minimumRate - overallRate)

def xml = parser.parse(cobertura)
def overallLineRate = xml.attribute('line-rate').toDouble()
def difference = (minimumLineRate - overallLineRate)
if (difference > 1e-7) {
String is = df.format(overallRate * 100)
String needed = df.format(minimumRate * 100)
throw new GradleException(errorMsg(is, needed, coverageType))
}
} catch (FileNotFoundException fnfe) {
throw new GradleException(fileNotFoundErrorMsg(coverageType), fnfe)
}

if (difference > 1e-7)
throw new GradleException("Only ${overallLineRate * 100}% of project is covered by tests instead of ${(minimumLineRate * 100).toInteger()}%!")
}
}
117 changes: 108 additions & 9 deletions src/test/groovy/org/scoverage/OverallCheckTaskTest.groovy
Original file line number Diff line number Diff line change
@@ -1,45 +1,144 @@
package org.scoverage

import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.tasks.TaskExecutionException
import org.gradle.testfixtures.ProjectBuilder
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException

/**
* Copied from the Internet, just to check if we have correct exception thrown.
*/
class CauseMatcher extends TypeSafeMatcher<Throwable> {

private final Class<? extends Throwable> type;
private final String expectedMessage;

public CauseMatcher(Class<? extends Throwable> type, String expectedMessage) {
this.type = type;
this.expectedMessage = expectedMessage;
}

@Override
protected boolean matchesSafely(Throwable item) {
return item.getClass().isAssignableFrom(type) && item.getMessage().contains(expectedMessage);
}

@Override
public void describeTo(Description description) {
description.appendText("expects type ")
.appendValue(type)
.appendText(" and a message ")
.appendValue(expectedMessage);
}
}

class OverallCheckTaskTest {

@Rule
public ExpectedException expectedException = ExpectedException.none()

private Project projectForLineRate(Number lineRate) {
private Project projectForRate(Number coverageRate, CoverageType type) {
Project project = ProjectBuilder.builder().build()
project.plugins.apply(ScoveragePlugin)
project.tasks.create('bob', OverallCheckTask) {
minimumLineRate = lineRate
cobertura = new File('src/test/resources/cobertura.xml')
minimumRate = coverageRate
reportDir = new File('src/test/resources')
coverageType = type
}
project
}

// error when report file is not there

@Test
void failsWhenLineRateIsBelowTarget(){
Project project = projectForLineRate(1)
expectedException.expect(TaskExecutionException)
void failsWhenReportFileIsNotFound() {
Project project = ProjectBuilder.builder().build()
project.plugins.apply(ScoveragePlugin)
project.tasks.create('bob', OverallCheckTask) {
minimumRate = 1.0
reportDir = new File('src/test/nothingthere')
coverageType = CoverageType.Line
}
expectedException.expectCause(new CauseMatcher(
GradleException.class,
OverallCheckTask.fileNotFoundErrorMsg(CoverageType.Line)
))
project.tasks.bob.execute()
}

// line coverage

@Test
void failsWhenLineRateIsBelowTarget() {
Project project = projectForRate(1, CoverageType.Line)
expectedException.expectCause(new CauseMatcher(
GradleException.class,
OverallCheckTask.errorMsg("66", "100", CoverageType.Line)
))
project.tasks.bob.execute()
}

@Test
void doesNotFailWhenLineRateIsAtTarget() throws Exception {
Project project = projectForLineRate(0.66)
Project project = projectForRate(0.66, CoverageType.Line)
project.tasks.bob.execute()
}

@Test
void doesNotFailWhenLineRateIsAboveTarget() throws Exception {
Project project = projectForLineRate(0.6)
Project project = projectForRate(0.6, CoverageType.Line)
project.tasks.bob.execute()
}

// Statement coverage

@Test
void failsWhenStatementRateIsBelowTarget() {
Project project = projectForRate(1, CoverageType.Statement)
expectedException.expectCause(new CauseMatcher(
GradleException.class,
OverallCheckTask.errorMsg("33.33", "100", CoverageType.Statement)
))
project.tasks.bob.execute()
}

@Test
void doesNotFailWhenStatementRateIsAtTarget() throws Exception {
Project project = projectForRate(0.33, CoverageType.Statement)
project.tasks.bob.execute()
}

@Test
void doesNotFailWhenStatementRateIsAboveTarget() throws Exception {
Project project = projectForRate(0.3, CoverageType.Statement)
project.tasks.bob.execute()
}

// Branch coverage

@Test
void failsWhenBranchRateIsBelowTarget() {
Project project = projectForRate(1, CoverageType.Branch)
expectedException.expectCause(new CauseMatcher(
GradleException.class,
OverallCheckTask.errorMsg("50", "100", CoverageType.Branch)
))
project.tasks.bob.execute()
}

@Test
void doesNotFailWhenBranchRateIsAtTarget() throws Exception {
Project project = projectForRate(0.50, CoverageType.Branch)
project.tasks.bob.execute()
}

@Test
void doesNotFailWhenBranchRateIsAboveTarget() throws Exception {
Project project = projectForRate(0.45, CoverageType.Branch)
project.tasks.bob.execute()
}

Expand Down
3 changes: 2 additions & 1 deletion src/test/happy day/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ dependencies {
}

checkScoverage {
minimumLineRate = 1.0
minimumRate = 1.0
coverageType = 'Line'
}

tasks.withType(ScalaCompile) {
Expand Down
51 changes: 51 additions & 0 deletions src/test/resources/scoverage.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<scoverage
statement-count="6" statements-invoked="2" statement-rate="33.33" branch-rate="50.00" version="1.0" timestamp="1423567091046">
<packages>
<package name="&lt;empty&gt;" statement-count="6" statements-invoked="2" statement-rate="33.33">
<classes>
<class
name="D" filename="\src\main\scala\A.scala" statement-count="2" statements-invoked="0" statement-rate="0.00" branch-rate="0.00">
<methods>
<method
name="&lt;empty&gt;/D/apply" statement-count="1" statements-invoked="0" statement-rate="0.00" branch-rate="0.00">
<statements>
<statement
package="&lt;empty&gt;" class="D" class-type="Object" top-level-class="D" source="C:\Users\Jerzy\Documents\GitHub\gradle-scoverage-sample\multi-module\a\src\main\scala\A.scala" method="apply" start="189" end="196" line="14" branch="false" invocation-count="0">
</statement>
</statements>
</method>
<method name="&lt;empty&gt;/D/bob" statement-count="1" statements-invoked="0" statement-rate="0.00" branch-rate="0.00">
<statements>
<statement
package="&lt;empty&gt;" class="D" class-type="Class" top-level-class="D" source="C:\Users\Jerzy\Documents\GitHub\gradle-scoverage-sample\multi-module\a\src\main\scala\A.scala" method="bob" start="130" end="143" line="9" branch="false" invocation-count="0">
</statement>
</statements>
</method>
</methods>
</class>
<class
name="A" filename="\src\main\scala\A.scala" statement-count="4" statements-invoked="2" statement-rate="50.00" branch-rate="50.00">
<methods>
<method
name="&lt;empty&gt;/A/foo" statement-count="4" statements-invoked="2" statement-rate="50.00" branch-rate="50.00">
<statements>
<statement
package="&lt;empty&gt;" class="A" class-type="Class" top-level-class="A" source="C:\Users\Jerzy\Documents\GitHub\gradle-scoverage-sample\multi-module\a\src\main\scala\A.scala" method="foo" start="65" end="66" line="3" branch="true" invocation-count="1">
</statement>
<statement
package="&lt;empty&gt;" class="A" class-type="Class" top-level-class="A" source="C:\Users\Jerzy\Documents\GitHub\gradle-scoverage-sample\multi-module\a\src\main\scala\A.scala" method="foo" start="72" end="73" line="3" branch="true" invocation-count="0">
</statement>
<statement
package="&lt;empty&gt;" class="A" class-type="Class" top-level-class="A" source="C:\Users\Jerzy\Documents\GitHub\gradle-scoverage-sample\multi-module\a\src\main\scala\A.scala" method="foo" start="65" end="66" line="3" branch="false" invocation-count="1">
</statement>
<statement
package="&lt;empty&gt;" class="A" class-type="Class" top-level-class="A" source="C:\Users\Jerzy\Documents\GitHub\gradle-scoverage-sample\multi-module\a\src\main\scala\A.scala" method="foo" start="72" end="73" line="3" branch="false" invocation-count="0">
</statement>
</statements>
</method>
</methods>
</class>
</classes>
</package>
</packages>
</scoverage>