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 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))); + } +}