diff --git a/README.md b/README.md
index d1352b5..2aaff40 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ Note that the plugin version 1.13+ requires Hudson 1.320 to work and the followi
- Warnings 3.0
- Violations 0.5.4
- analysis-core 1.0
+ - Jacoco 1.0.18
The plugin is not activated for all jobs at start, each separate job has to activate the game. The game can also be de-activated in one job if some large merge activity is going to take place. To activate the game for a job, go to the job configuration page, click "Add post-build action" and select "Continuous integration game" from the list of available post-build actions.
@@ -69,6 +70,8 @@ Rules that depend on other plugins:
#####Checkstyle Plugin. [link](https://wiki.jenkins-ci.org/display/JENKINS/Checkstyle+Plugin)
- Adding/removing a checkstyle warning = -1/+1.
+#####Jacoco Plugin. [link](https://wiki.jenkins-ci.org/display/JENKINS/Jacoco+Plugin)
+ - Reducing/increasing coverage = -10/+10 for each percentage of line coverage, with a minimum of -1/+1.
###Adding rules to the game
diff --git a/pom.xml b/pom.xml
index 5134a05..20b633c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -141,7 +141,19 @@
org.jvnet.hudson.plugins
analysis-core
- 1.58
+ 1.58
+
+
+ org.jenkins-ci.plugins
+ jacoco
+ 1.0.18
+ true
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
org.mockito
@@ -154,12 +166,6 @@
hamcrest-core
-
-
- org.hamcrest
- hamcrest-core
- 1.2.1
- test
org.jenkins-ci.plugins
diff --git a/src/main/java/hudson/plugins/cigame/GameDescriptor.java b/src/main/java/hudson/plugins/cigame/GameDescriptor.java
index db8a5f6..5ecbe8c 100644
--- a/src/main/java/hudson/plugins/cigame/GameDescriptor.java
+++ b/src/main/java/hudson/plugins/cigame/GameDescriptor.java
@@ -11,6 +11,7 @@
import hudson.plugins.cigame.rules.build.BuildRuleSet;
import hudson.plugins.cigame.rules.plugins.checkstyle.CheckstyleRuleSet;
import hudson.plugins.cigame.rules.plugins.findbugs.FindBugsRuleSet;
+import hudson.plugins.cigame.rules.plugins.jacoco.JacocoRuleSet;
import hudson.plugins.cigame.rules.plugins.opentasks.OpenTasksRuleSet;
import hudson.plugins.cigame.rules.plugins.pmd.PmdRuleSet;
import hudson.plugins.cigame.rules.plugins.violation.ViolationsRuleSet;
@@ -58,6 +59,7 @@ public RuleBook getRuleBook() {
addRuleSetIfAvailable(rulebook, new FindBugsRuleSet());
addRuleSetIfAvailable(rulebook, new WarningsRuleSet());
addRuleSetIfAvailable(rulebook, new CheckstyleRuleSet());
+ addRuleSetIfAvailable(rulebook, new JacocoRuleSet());
}
return rulebook;
}
diff --git a/src/main/java/hudson/plugins/cigame/rules/plugins/jacoco/DefaultJacocoRule.java b/src/main/java/hudson/plugins/cigame/rules/plugins/jacoco/DefaultJacocoRule.java
new file mode 100644
index 0000000..625b3b6
--- /dev/null
+++ b/src/main/java/hudson/plugins/cigame/rules/plugins/jacoco/DefaultJacocoRule.java
@@ -0,0 +1,131 @@
+package hudson.plugins.cigame.rules.plugins.jacoco;
+
+import static java.lang.Math.ceil;
+import static java.lang.Math.floor;
+import hudson.model.Result;
+import hudson.model.AbstractBuild;
+import hudson.plugins.cigame.model.AggregatableRule;
+import hudson.plugins.cigame.model.RuleResult;
+import hudson.plugins.cigame.util.ActionRetriever;
+import hudson.plugins.jacoco.JacocoBuildAction;
+import hudson.plugins.jacoco.model.Coverage;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Default rule for the Jacoco plugin.
+ *
+ * @author Philip Aston
+ */
+public class DefaultJacocoRule implements AggregatableRule {
+
+ private static final RuleResult EMPTY_RESULT =
+ new RuleResult(0.0, "", 0d);
+
+ private final int pointsForIncreasingCoverage;
+ private final int pointsForReducingCoverage;
+
+ public DefaultJacocoRule(
+ int pointsForReducingCoveragePerCent,
+ int pointsForIncreasingCoveragePerCent) {
+ this.pointsForReducingCoverage = pointsForReducingCoveragePerCent;
+ this.pointsForIncreasingCoverage = pointsForIncreasingCoveragePerCent;
+ }
+
+ @Override
+ public RuleResult> aggregate(Collection> results) {
+ double score = 0.0;
+ double sumOfPercentageChanges = 0d;
+
+ for (RuleResult result : results) {
+ score += result.getPoints();
+ sumOfPercentageChanges += result.getAdditionalData();
+ }
+
+ if (sumOfPercentageChanges >= 0) {
+ return new RuleResult(
+ score,
+ Messages.JacocoRuleSet_DefaultRule_IncreasedCoverage(sumOfPercentageChanges * 0.01));
+ }
+ else {
+ return new RuleResult(
+ score,
+ Messages.JacocoRuleSet_DefaultRule_ReducedCoverage(sumOfPercentageChanges * -0.01));
+ }
+ }
+
+ @Override
+ public RuleResult evaluate(AbstractBuild, ?> previousBuild,
+ AbstractBuild, ?> build) {
+
+ if (build == null ||
+ build.getResult() == null ||
+ build.getResult().isWorseOrEqualTo(Result.FAILURE)) {
+ return EMPTY_RESULT;
+ }
+
+ if (previousBuild == null ||
+ previousBuild.getResult().isWorseOrEqualTo(Result.FAILURE)) {
+ return EMPTY_RESULT;
+ }
+
+ final List currentActions =
+ ActionRetriever.getResult(build, Result.UNSTABLE, JacocoBuildAction.class);
+
+ final Coverage currentCoverage = getLineCoverage(currentActions);
+
+ if (!currentCoverage.isInitialized()) {
+ return EMPTY_RESULT;
+ }
+
+ final List previousActions =
+ ActionRetriever.getResult(previousBuild, Result.UNSTABLE, JacocoBuildAction.class);
+
+ final Coverage previousCoverage = getLineCoverage(previousActions);
+
+ if (!previousCoverage.isInitialized()) {
+ return EMPTY_RESULT;
+ }
+
+ if (previousCoverage.equals(currentCoverage)) {
+ return EMPTY_RESULT;
+ }
+
+ final double percentage = currentCoverage.getPercentageFloat() - previousCoverage.getPercentageFloat();
+
+ if (percentage >= 0) {
+ return new RuleResult(
+ ceil(percentage * pointsForIncreasingCoverage),
+ Messages.JacocoRuleSet_DefaultRule_IncreasedCoverage(percentage * 0.01),
+ percentage);
+ }
+ else {
+ return new RuleResult(
+ floor(percentage * -1 * pointsForReducingCoverage),
+ Messages.JacocoRuleSet_DefaultRule_ReducedCoverage(percentage * -0.01),
+ percentage);
+ }
+ }
+
+ @Override
+ public RuleResult evaluate(AbstractBuild, ?> build) {
+ throw new UnsupportedOperationException();
+ }
+
+ private static Coverage getLineCoverage(List extends JacocoBuildAction> actions) {
+ final Coverage totalCoverage = new Coverage();
+
+ for (JacocoBuildAction action : actions) {
+ final Coverage lineCoverage = action.getLineCoverage();
+ totalCoverage.accumulatePP(lineCoverage.getMissed(), lineCoverage.getCovered());
+ }
+
+ return totalCoverage;
+ }
+
+ @Override
+ public String getName() {
+ return Messages.JacocoRuleSet_DefaultRule_Name();
+ }
+}
diff --git a/src/main/java/hudson/plugins/cigame/rules/plugins/jacoco/JacocoRuleSet.java b/src/main/java/hudson/plugins/cigame/rules/plugins/jacoco/JacocoRuleSet.java
new file mode 100644
index 0000000..d6c1707
--- /dev/null
+++ b/src/main/java/hudson/plugins/cigame/rules/plugins/jacoco/JacocoRuleSet.java
@@ -0,0 +1,15 @@
+package hudson.plugins.cigame.rules.plugins.jacoco;
+
+import hudson.plugins.cigame.rules.plugins.PluginRuleSet;
+
+public class JacocoRuleSet extends PluginRuleSet {
+
+ public JacocoRuleSet() {
+ super("jacoco", Messages.JacocoRuleSet_Title()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @Override
+ protected void loadRules() {
+ add(new DefaultJacocoRule(-10, +10));
+ }
+}
diff --git a/src/main/resources/hudson/plugins/cigame/rules/plugins/jacoco/Messages.properties b/src/main/resources/hudson/plugins/cigame/rules/plugins/jacoco/Messages.properties
new file mode 100644
index 0000000..f12a5c4
--- /dev/null
+++ b/src/main/resources/hudson/plugins/cigame/rules/plugins/jacoco/Messages.properties
@@ -0,0 +1,4 @@
+JacocoRuleSet.DefaultRule.Name=Jacoco coverage
+JacocoRuleSet.DefaultRule.IncreasedCoverage=Coverage was increased by {0,number,##0.0#%}
+JacocoRuleSet.DefaultRule.ReducedCoverage=Coverage was reduced by {0,number,##0.0#%}
+JacocoRuleSet.Title=Jacoco coverage
diff --git a/src/test/java/hudson/plugins/cigame/rules/plugins/jacoco/DefaultJacocoRuleTest.java b/src/test/java/hudson/plugins/cigame/rules/plugins/jacoco/DefaultJacocoRuleTest.java
new file mode 100644
index 0000000..bbed6d3
--- /dev/null
+++ b/src/test/java/hudson/plugins/cigame/rules/plugins/jacoco/DefaultJacocoRuleTest.java
@@ -0,0 +1,279 @@
+package hudson.plugins.cigame.rules.plugins.jacoco;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+import hudson.model.BuildListener;
+import hudson.model.Result;
+import hudson.model.AbstractBuild;
+import hudson.plugins.cigame.model.RuleResult;
+import hudson.plugins.jacoco.JacocoBuildAction;
+import hudson.plugins.jacoco.model.Coverage;
+import hudson.plugins.jacoco.model.CoverageElement.Type;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link DefaultJacocoRule}.
+ *
+ * @author Philip Aston
+ */
+public class DefaultJacocoRuleTest {
+
+ @Mock
+ private AbstractBuild, ?> build;
+ @Mock
+ private AbstractBuild, ?> previous;
+
+ private final DefaultJacocoRule rule = new DefaultJacocoRule(-2, 3);
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ when(build.getResult()).thenReturn(Result.SUCCESS);
+ when(previous.getResult()).thenReturn(Result.SUCCESS);
+ }
+
+ @Test
+ public void reducedCoverageGiveNegativePoints() {
+ addCoverage(previous, 5, 5);
+ addCoverage(build, 6, 4);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(-20f, ruleResult);
+
+ assertThat(ruleResult.getDescription(),
+ allOf(
+ containsString("reduced"),
+ containsString("10.0%")));
+ }
+
+ @Test
+ public void reducedCoveragePointsRoundedDown() {
+ addCoverage(previous, 100, 990);
+ addCoverage(build, 151, 949);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(-10f, ruleResult);
+
+ assertThat(ruleResult.getDescription(),
+ allOf(
+ containsString("reduced"),
+ containsString("4.55%")));
+ }
+
+ @Test
+ public void reducedCoverageCostsAtLeastOnePoint() {
+ addCoverage(previous, 10, 990);
+ addCoverage(build, 11, 989);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(-1f, ruleResult);
+
+ assertThat(ruleResult.getDescription(),
+ allOf(
+ containsString("reduced"),
+ containsString("0.1%")));
+ }
+
+ @Test
+ public void increasedCoverageGivePositivePoints() {
+ addCoverage(previous, 6, 4);
+ addCoverage(build, 5, 5);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(30f, ruleResult);
+
+ assertThat(ruleResult.getDescription(),
+ allOf(
+ containsString("increased"),
+ containsString("10.0%")));
+ }
+
+ @Test
+ public void increasedCoveragePointsRoundedUp() {
+ addCoverage(previous, 151, 949);
+ addCoverage(build, 100, 990);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(14f, ruleResult);
+
+ assertThat(ruleResult.getDescription(),
+ allOf(
+ containsString("increased"),
+ containsString("4.55%")));
+ }
+
+ @Test
+ public void increasedCoverageWinsAtLeastOnePoint() {
+ addCoverage(previous, 10, 990);
+ addCoverage(build, 9, 991);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(1f, ruleResult);
+ }
+
+ @Test
+ public void noChangeGivesNoPoints() {
+ addCoverage(previous, 6, 4);
+ addCoverage(build, 6, 4);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void sameCoverageGivesNoPoints() {
+ addCoverage(previous, 5, 10);
+ addCoverage(build, 4, 8);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void previousBuildHasNoCoverage() {
+ addCoverage(build, 5, 5);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void currentBuildHasNoCoverage() {
+ addCoverage(previous, 5, 5);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void failedBuildIsWorthZeroPoints() {
+ when(build.getResult()).thenReturn(Result.FAILURE);
+ addCoverage(build, 5, 5);
+ addCoverage(previous, 5, 7);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void noPreviousBuildIsWorthZeroPoints() {
+ addCoverage(build, 5, 5);
+
+ final RuleResult> ruleResult = rule.evaluate(null, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void noCurrentBuildIsWorthZeroPoints() {
+ addCoverage(previous, 5, 5);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, null);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void currentBuildNotCompleteIsWorthZeroPoints() {
+ addCoverage(previous, 2, 5);
+ addCoverage(build, 5, 5);
+
+ when(build.getResult()).thenReturn(null);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @Test
+ public void previousBuildFailedResultIsWorthZeroPoints() {
+ when(previous.getResult()).thenReturn(Result.FAILURE);
+ addCoverage(build, 5, 5);
+ addCoverage(previous, 5, 7);
+
+ final RuleResult> ruleResult = rule.evaluate(previous, build);
+
+ assertPoints(0f, ruleResult);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void aggregateIncreasingScore() {
+ final RuleResult> aggregation = rule.aggregate(
+ asList(
+ new RuleResult(-2, null, -0.05),
+ new RuleResult(+5, null, +0.06)
+ ));
+
+ assertEquals(3, aggregation.getPoints(), 0.1f);
+
+ assertThat(aggregation.getDescription(),
+ allOf(
+ containsString("increased"),
+ containsString("0.01%")));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void aggregateDecreasingScore() {
+ final RuleResult> aggregation = rule.aggregate(
+ asList(
+ new RuleResult(+2, null, +0.05),
+ new RuleResult(-5, null, -0.06)
+ ));
+
+ assertEquals(-3, aggregation.getPoints(), 0.1f);
+
+ assertThat(aggregation.getDescription(),
+ allOf(
+ containsString("reduced"),
+ containsString("0.01%")));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testDeprecatedEvaluateUnsupported() {
+ rule.evaluate(build);
+ }
+
+ @Test
+ public void testGetName() {
+ assertThat(rule.getName(), containsString("coverage"));
+ }
+
+ private static void assertPoints(double expected, RuleResult> ruleResult) {
+ assertEquals(expected, ruleResult.getPoints(), 0.1d);
+ }
+
+ private static void addCoverage(AbstractBuild, ?> build, int missed, int covered) {
+ final Map ratios = Collections.emptyMap();
+ final JacocoBuildAction action =
+ new JacocoBuildAction(build, null, ratios, null, mock(BuildListener.class), null, null);
+
+ action.getLineCoverage().accumulate(missed, covered);
+
+ when(build.getActions(JacocoBuildAction.class)).thenReturn(asList(action));
+ }
+}
diff --git a/src/test/java/hudson/plugins/cigame/rules/plugins/jacoco/JacocoRuleSetTest.java b/src/test/java/hudson/plugins/cigame/rules/plugins/jacoco/JacocoRuleSetTest.java
new file mode 100644
index 0000000..74c20c1
--- /dev/null
+++ b/src/test/java/hudson/plugins/cigame/rules/plugins/jacoco/JacocoRuleSetTest.java
@@ -0,0 +1,25 @@
+package hudson.plugins.cigame.rules.plugins.jacoco;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+import hudson.plugins.cigame.model.Rule;
+
+import java.util.Collection;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link JacocoRuleSet}.
+ *
+ * @author Philip Aston
+ */
+public class JacocoRuleSetTest {
+
+ @Test
+ public void getRules() {
+ final Collection rules = new JacocoRuleSet().getRules();
+
+ assertThat(rules, contains(instanceOf(DefaultJacocoRule.class)));
+ }
+}