Skip to content

Commit 75beb93

Browse files
committed
Merge pull request #41 from Kwestor/feature/configure-check-task
Configurable checkScoverage task
2 parents 1f23733 + a1acc5d commit 75beb93

File tree

5 files changed

+243
-22
lines changed

5 files changed

+243
-22
lines changed

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,27 @@ Aggregation uses same flags as reporting for enabling/disabling different output
7979
CheckScoverage
8080
--------------
8181

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

8484
To configure it as you want, add this configuration :
8585
```
8686
checkScoverage {
87-
minimumLineRate = 0.5
87+
minimumRate = 0.5
88+
}
89+
```
90+
91+
You can also modify type of value to check from `Statement`s to `Line`s or `Branch`es:
92+
93+
```
94+
checkScoverage {
95+
coverageType = 'Line'
96+
minimumRate = 0.5
97+
}
98+
```
99+
100+
```
101+
checkScoverage {
102+
coverageType = 'Branch'
103+
minimumRate = 0.5
88104
}
89105
```

src/main/groovy/org/scoverage/OverallCheckTask.groovy

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,86 @@ import org.gradle.api.DefaultTask
44
import org.gradle.api.GradleException
55
import org.gradle.api.tasks.TaskAction
66

7+
import java.text.DecimalFormat
8+
79
/**
8-
* Throws a GradleException if overall line coverage dips below the configured percentage.
10+
* Handles different types of coverage Scoverage can measure.
11+
*/
12+
enum CoverageType {
13+
Line('cobertura.xml', 'line-rate', 1.0),
14+
Statement('scoverage.xml', 'statement-rate', 100.0),
15+
Branch('scoverage.xml', 'branch-rate', 100.0)
16+
17+
/** Name of file with coverage data */
18+
String fileName
19+
/** Name of param in XML file with coverage value */
20+
String paramName
21+
/** Used to normalize coverage value */
22+
private double factor
23+
24+
private CoverageType(String fileName, String paramName, double factor) {
25+
this.fileName = fileName
26+
this.paramName = paramName
27+
this.factor = factor
28+
}
29+
30+
/** Normalize coverage value to [0, 1] */
31+
Double normalize(Double value) {
32+
return value / factor
33+
}
34+
}
35+
36+
/**
37+
* Throws a GradleException if overall coverage dips below the configured percentage.
938
*/
1039
class OverallCheckTask extends DefaultTask {
11-
File cobertura
12-
double minimumLineRate = 0.75
1340

14-
protected XmlParser parser;
41+
/** Type of coverage to check. Available options: Line, Statement and Branch */
42+
CoverageType coverageType = CoverageType.Statement
43+
double minimumRate = 0.75
44+
45+
/** Set if want to change default from 'reportDir' in scoverage extension. */
46+
File reportDir
47+
48+
protected XmlParser parser
49+
protected DecimalFormat df = new DecimalFormat("#.##")
1550

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

57+
/** Extracted to method for testing purposes */
58+
static String errorMsg(String actual, String expected, CoverageType type) {
59+
return "Only $actual% of project is covered by tests instead of $expected% (coverageType: $type)"
60+
}
61+
62+
/** Extracted to method for testing purposes */
63+
static String fileNotFoundErrorMsg(CoverageType coverageType) {
64+
return "Coverage file (type: $coverageType) not found, check your configuration."
65+
}
66+
2267
@TaskAction
2368
void requireLineCoverage() {
2469
def extension = ScoveragePlugin.extensionIn(project)
2570

26-
if (cobertura == null) cobertura = new File(extension.reportDir, 'cobertura.xml')
71+
File reportFile = new File(reportDir ? reportDir : extension.reportDir, coverageType.fileName)
72+
73+
try {
74+
def xml = parser.parse(reportFile)
75+
Double overallRate = coverageType.normalize(xml.attribute(coverageType.paramName).toDouble())
76+
println("$minimumRate - $overallRate")
77+
def difference = (minimumRate - overallRate)
2778

28-
def xml = parser.parse(cobertura)
29-
def overallLineRate = xml.attribute('line-rate').toDouble()
30-
def difference = (minimumLineRate - overallLineRate)
79+
if (difference > 1e-7) {
80+
String is = df.format(overallRate * 100)
81+
String needed = df.format(minimumRate * 100)
82+
throw new GradleException(errorMsg(is, needed, coverageType))
83+
}
84+
} catch (FileNotFoundException fnfe) {
85+
throw new GradleException(fileNotFoundErrorMsg(coverageType), fnfe)
86+
}
3187

32-
if (difference > 1e-7)
33-
throw new GradleException("Only ${overallLineRate * 100}% of project is covered by tests instead of ${(minimumLineRate * 100).toInteger()}%!")
3488
}
3589
}

src/test/groovy/org/scoverage/OverallCheckTaskTest.groovy

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,144 @@
11
package org.scoverage
22

3+
import org.gradle.api.GradleException
34
import org.gradle.api.Project
4-
import org.gradle.api.tasks.TaskExecutionException
55
import org.gradle.testfixtures.ProjectBuilder
66
import org.hamcrest.Description
77
import org.hamcrest.TypeSafeMatcher
88
import org.junit.Rule
99
import org.junit.Test
1010
import org.junit.rules.ExpectedException
1111

12+
/**
13+
* Copied from the Internet, just to check if we have correct exception thrown.
14+
*/
15+
class CauseMatcher extends TypeSafeMatcher<Throwable> {
16+
17+
private final Class<? extends Throwable> type;
18+
private final String expectedMessage;
19+
20+
public CauseMatcher(Class<? extends Throwable> type, String expectedMessage) {
21+
this.type = type;
22+
this.expectedMessage = expectedMessage;
23+
}
24+
25+
@Override
26+
protected boolean matchesSafely(Throwable item) {
27+
return item.getClass().isAssignableFrom(type) && item.getMessage().contains(expectedMessage);
28+
}
29+
30+
@Override
31+
public void describeTo(Description description) {
32+
description.appendText("expects type ")
33+
.appendValue(type)
34+
.appendText(" and a message ")
35+
.appendValue(expectedMessage);
36+
}
37+
}
38+
1239
class OverallCheckTaskTest {
1340

1441
@Rule
1542
public ExpectedException expectedException = ExpectedException.none()
1643

17-
private Project projectForLineRate(Number lineRate) {
44+
private Project projectForRate(Number coverageRate, CoverageType type) {
1845
Project project = ProjectBuilder.builder().build()
1946
project.plugins.apply(ScoveragePlugin)
2047
project.tasks.create('bob', OverallCheckTask) {
21-
minimumLineRate = lineRate
22-
cobertura = new File('src/test/resources/cobertura.xml')
48+
minimumRate = coverageRate
49+
reportDir = new File('src/test/resources')
50+
coverageType = type
2351
}
2452
project
2553
}
2654

55+
// error when report file is not there
56+
2757
@Test
28-
void failsWhenLineRateIsBelowTarget(){
29-
Project project = projectForLineRate(1)
30-
expectedException.expect(TaskExecutionException)
58+
void failsWhenReportFileIsNotFound() {
59+
Project project = ProjectBuilder.builder().build()
60+
project.plugins.apply(ScoveragePlugin)
61+
project.tasks.create('bob', OverallCheckTask) {
62+
minimumRate = 1.0
63+
reportDir = new File('src/test/nothingthere')
64+
coverageType = CoverageType.Line
65+
}
66+
expectedException.expectCause(new CauseMatcher(
67+
GradleException.class,
68+
OverallCheckTask.fileNotFoundErrorMsg(CoverageType.Line)
69+
))
70+
project.tasks.bob.execute()
71+
}
72+
73+
// line coverage
74+
75+
@Test
76+
void failsWhenLineRateIsBelowTarget() {
77+
Project project = projectForRate(1, CoverageType.Line)
78+
expectedException.expectCause(new CauseMatcher(
79+
GradleException.class,
80+
OverallCheckTask.errorMsg("66", "100", CoverageType.Line)
81+
))
3182
project.tasks.bob.execute()
3283
}
3384

3485
@Test
3586
void doesNotFailWhenLineRateIsAtTarget() throws Exception {
36-
Project project = projectForLineRate(0.66)
87+
Project project = projectForRate(0.66, CoverageType.Line)
3788
project.tasks.bob.execute()
3889
}
3990

4091
@Test
4192
void doesNotFailWhenLineRateIsAboveTarget() throws Exception {
42-
Project project = projectForLineRate(0.6)
93+
Project project = projectForRate(0.6, CoverageType.Line)
94+
project.tasks.bob.execute()
95+
}
96+
97+
// Statement coverage
98+
99+
@Test
100+
void failsWhenStatementRateIsBelowTarget() {
101+
Project project = projectForRate(1, CoverageType.Statement)
102+
expectedException.expectCause(new CauseMatcher(
103+
GradleException.class,
104+
OverallCheckTask.errorMsg("33.33", "100", CoverageType.Statement)
105+
))
106+
project.tasks.bob.execute()
107+
}
108+
109+
@Test
110+
void doesNotFailWhenStatementRateIsAtTarget() throws Exception {
111+
Project project = projectForRate(0.33, CoverageType.Statement)
112+
project.tasks.bob.execute()
113+
}
114+
115+
@Test
116+
void doesNotFailWhenStatementRateIsAboveTarget() throws Exception {
117+
Project project = projectForRate(0.3, CoverageType.Statement)
118+
project.tasks.bob.execute()
119+
}
120+
121+
// Branch coverage
122+
123+
@Test
124+
void failsWhenBranchRateIsBelowTarget() {
125+
Project project = projectForRate(1, CoverageType.Branch)
126+
expectedException.expectCause(new CauseMatcher(
127+
GradleException.class,
128+
OverallCheckTask.errorMsg("50", "100", CoverageType.Branch)
129+
))
130+
project.tasks.bob.execute()
131+
}
132+
133+
@Test
134+
void doesNotFailWhenBranchRateIsAtTarget() throws Exception {
135+
Project project = projectForRate(0.50, CoverageType.Branch)
136+
project.tasks.bob.execute()
137+
}
138+
139+
@Test
140+
void doesNotFailWhenBranchRateIsAboveTarget() throws Exception {
141+
Project project = projectForRate(0.45, CoverageType.Branch)
43142
project.tasks.bob.execute()
44143
}
45144

src/test/happy day/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ dependencies {
2424
}
2525

2626
checkScoverage {
27-
minimumLineRate = 1.0
27+
minimumRate = 1.0
28+
coverageType = 'Line'
2829
}
2930

3031
tasks.withType(ScalaCompile) {

src/test/resources/scoverage.xml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<scoverage
2+
statement-count="6" statements-invoked="2" statement-rate="33.33" branch-rate="50.00" version="1.0" timestamp="1423567091046">
3+
<packages>
4+
<package name="&lt;empty&gt;" statement-count="6" statements-invoked="2" statement-rate="33.33">
5+
<classes>
6+
<class
7+
name="D" filename="\src\main\scala\A.scala" statement-count="2" statements-invoked="0" statement-rate="0.00" branch-rate="0.00">
8+
<methods>
9+
<method
10+
name="&lt;empty&gt;/D/apply" statement-count="1" statements-invoked="0" statement-rate="0.00" branch-rate="0.00">
11+
<statements>
12+
<statement
13+
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">
14+
</statement>
15+
</statements>
16+
</method>
17+
<method name="&lt;empty&gt;/D/bob" statement-count="1" statements-invoked="0" statement-rate="0.00" branch-rate="0.00">
18+
<statements>
19+
<statement
20+
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">
21+
</statement>
22+
</statements>
23+
</method>
24+
</methods>
25+
</class>
26+
<class
27+
name="A" filename="\src\main\scala\A.scala" statement-count="4" statements-invoked="2" statement-rate="50.00" branch-rate="50.00">
28+
<methods>
29+
<method
30+
name="&lt;empty&gt;/A/foo" statement-count="4" statements-invoked="2" statement-rate="50.00" branch-rate="50.00">
31+
<statements>
32+
<statement
33+
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">
34+
</statement>
35+
<statement
36+
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">
37+
</statement>
38+
<statement
39+
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">
40+
</statement>
41+
<statement
42+
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">
43+
</statement>
44+
</statements>
45+
</method>
46+
</methods>
47+
</class>
48+
</classes>
49+
</package>
50+
</packages>
51+
</scoverage>

0 commit comments

Comments
 (0)