From eac465bea9f5bf89a7beb8036e4d8eea4723f421 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Thu, 19 Feb 2015 21:56:36 +0100 Subject: [PATCH] SONAR-6202 Compute overall coverage on all files --- .../org/sonar/plugins/core/CorePlugin.java | 4 +- .../MissingOverallCoverageDecorator.java | 82 +++++++++++++ .../MissingOverallCoverageDecoratorTest.java | 114 ++++++++++++++++++ .../api/measures/CoverageMeasuresBuilder.java | 69 ++++++++--- 4 files changed, 253 insertions(+), 16 deletions(-) create mode 100644 plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecorator.java create mode 100644 plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecoratorTest.java diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index cc03c972881..602948249a4 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -47,8 +47,9 @@ import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator; import org.sonar.plugins.core.sensors.ItCoverageDecorator; import org.sonar.plugins.core.sensors.ItLineCoverageDecorator; import org.sonar.plugins.core.sensors.LineCoverageDecorator; -import org.sonar.plugins.core.sensors.MissingCoverageDecorator; import org.sonar.plugins.core.sensors.ManualMeasureDecorator; +import org.sonar.plugins.core.sensors.MissingCoverageDecorator; +import org.sonar.plugins.core.sensors.MissingOverallCoverageDecorator; import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator; import org.sonar.plugins.core.sensors.OverallCoverageDecorator; import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator; @@ -337,6 +338,7 @@ public final class CorePlugin extends SonarPlugin { FilesDecorator.class, ManualMeasureDecorator.class, MissingCoverageDecorator.class, + MissingOverallCoverageDecorator.class, // time machine TendencyDecorator.class, diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecorator.java new file mode 100644 index 00000000000..bdae925a865 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecorator.java @@ -0,0 +1,82 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.core.sensors; + +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.CoverageMeasuresBuilder; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasureUtils; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Resource; + +import java.util.List; + +/** + * Compute overall coverage when it was not already saved by language plugin. + */ +public final class MissingOverallCoverageDecorator implements Decorator { + + @DependsUpon + public Metric dependsUpon() { + return CoreMetrics.LINES_TO_COVER; + } + + @DependedUpon + public List provides() { + return CoverageMeasuresBuilder.CoverageType.OVERALL.all(); + } + + @Override + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + @Override + public void decorate(Resource resource, DecoratorContext context) { + if (Qualifiers.isFile(resource) && !MeasureUtils.hasValue(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER))) { + copyMeasure(context, CoreMetrics.LINES_TO_COVER, CoreMetrics.OVERALL_LINES_TO_COVER); + copyMeasure(context, CoreMetrics.UNCOVERED_LINES, CoreMetrics.OVERALL_UNCOVERED_LINES); + copyMeasure(context, CoreMetrics.COVERAGE_LINE_HITS_DATA, CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA); + copyMeasure(context, CoreMetrics.CONDITIONS_TO_COVER, CoreMetrics.OVERALL_CONDITIONS_TO_COVER); + copyMeasure(context, CoreMetrics.UNCOVERED_CONDITIONS, CoreMetrics.OVERALL_UNCOVERED_CONDITIONS); + copyMeasure(context, CoreMetrics.CONDITIONS_BY_LINE, CoreMetrics.OVERALL_CONDITIONS_BY_LINE); + copyMeasure(context, CoreMetrics.COVERED_CONDITIONS_BY_LINE, CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE); + } + } + + private void copyMeasure(DecoratorContext context, Metric from, Metric to) { + Measure sourceMeasure = context.getMeasure(from); + if (sourceMeasure != null) { + Double value = sourceMeasure.getValue(); + if (value != null) { + context.saveMeasure(to, value); + } else if (sourceMeasure.hasData()) { + context.saveMeasure(new Measure(to, sourceMeasure.getData())); + } + } + + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecoratorTest.java new file mode 100644 index 00000000000..c94eb3bce52 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/MissingOverallCoverageDecoratorTest.java @@ -0,0 +1,114 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.core.sensors; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Project; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyDouble; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class MissingOverallCoverageDecoratorTest { + + private MissingOverallCoverageDecorator decorator; + + @Before + public void prepare() { + decorator = new MissingOverallCoverageDecorator(); + } + + @Test + public void increaseCoverage() { + assertThat(decorator.provides()).isNotEmpty(); + assertThat(decorator.dependsUpon()).isEqualTo(CoreMetrics.LINES_TO_COVER); + assertThat(decorator.shouldExecuteOnProject(new Project("foo"))).isTrue(); + } + + @Test + public void testExecuteOnlyOnMainFile() { + DecoratorContext context = mock(DecoratorContext.class); + decorator.decorate(File.create("test/FooTest.java", Java.INSTANCE, true), context); + decorator.decorate(Directory.create("src"), context); + decorator.decorate(new Project("foo"), context); + verifyNoMoreInteractions(context); + } + + @Test + public void dontDoAnythingIfOverallCoverageAlreadyDefined() { + DecoratorContext context = mock(DecoratorContext.class); + File file = File.create("src/Foo.java"); + + when(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER)).thenReturn(new Measure<>(CoreMetrics.OVERALL_LINES_TO_COVER, 0.0)); + + decorator.decorate(file, context); + + verify(context, never()).saveMeasure(eq(CoreMetrics.OVERALL_LINES_TO_COVER), anyDouble()); + } + + @Test + public void testCopyUnitTestMeasures() { + DecoratorContext context = mock(DecoratorContext.class); + File file = File.create("src/Foo.java"); + + when(context.getMeasure(CoreMetrics.LINES_TO_COVER)).thenReturn(new Measure<>(CoreMetrics.LINES_TO_COVER, 10.0)); + when(context.getMeasure(CoreMetrics.UNCOVERED_LINES)).thenReturn(new Measure<>(CoreMetrics.UNCOVERED_LINES, 5.0)); + when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn(new Measure<>(CoreMetrics.COVERAGE_LINE_HITS_DATA, "1=1;2=2;")); + when(context.getMeasure(CoreMetrics.CONDITIONS_TO_COVER)).thenReturn(new Measure<>(CoreMetrics.CONDITIONS_TO_COVER, 2.0)); + when(context.getMeasure(CoreMetrics.UNCOVERED_CONDITIONS)).thenReturn(new Measure<>(CoreMetrics.UNCOVERED_CONDITIONS, 1.0)); + when(context.getMeasure(CoreMetrics.CONDITIONS_BY_LINE)).thenReturn(new Measure<>(CoreMetrics.CONDITIONS_BY_LINE, "1=4")); + when(context.getMeasure(CoreMetrics.COVERED_CONDITIONS_BY_LINE)).thenReturn(new Measure<>(CoreMetrics.COVERED_CONDITIONS_BY_LINE, "1=2")); + + decorator.decorate(file, context); + + verify(context).saveMeasure(CoreMetrics.OVERALL_LINES_TO_COVER, 10.0); + verify(context).saveMeasure(CoreMetrics.OVERALL_UNCOVERED_LINES, 5.0); + verify(context).saveMeasure(new Measure(CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA, "1=1;2=2;")); + verify(context).saveMeasure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER, 2.0); + verify(context).saveMeasure(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS, 1.0); + verify(context).saveMeasure(new Measure(CoreMetrics.OVERALL_CONDITIONS_BY_LINE, "1=4")); + verify(context).saveMeasure(new Measure(CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE, "1=2")); + } + + @Test + public void dontFailOnBrokenValues() { + DecoratorContext context = mock(DecoratorContext.class); + File file = File.create("src/Foo.java"); + + when(context.getMeasure(CoreMetrics.LINES_TO_COVER)).thenReturn(new Measure<>(CoreMetrics.LINES_TO_COVER, 10.0)); + when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn(new Measure<>(CoreMetrics.COVERAGE_LINE_HITS_DATA)); + + decorator.decorate(file, context); + + verify(context).saveMeasure(CoreMetrics.OVERALL_LINES_TO_COVER, 10.0); + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java index 823155b2d0a..7648dac9e8e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java @@ -34,13 +34,48 @@ import java.util.SortedMap; */ public final class CoverageMeasuresBuilder { + /** + * @since 5.1 + */ + public static enum CoverageType { + UNIT(CoreMetrics.LINES_TO_COVER, CoreMetrics.UNCOVERED_LINES, CoreMetrics.COVERAGE_LINE_HITS_DATA, + CoreMetrics.CONDITIONS_TO_COVER, CoreMetrics.UNCOVERED_CONDITIONS, CoreMetrics.CONDITIONS_BY_LINE, + CoreMetrics.COVERED_CONDITIONS_BY_LINE), + IT(CoreMetrics.IT_LINES_TO_COVER, CoreMetrics.IT_UNCOVERED_LINES, CoreMetrics.IT_COVERAGE_LINE_HITS_DATA, + CoreMetrics.IT_CONDITIONS_TO_COVER, CoreMetrics.IT_UNCOVERED_CONDITIONS, CoreMetrics.IT_CONDITIONS_BY_LINE, + CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE), + OVERALL(CoreMetrics.OVERALL_LINES_TO_COVER, CoreMetrics.OVERALL_UNCOVERED_LINES, CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA, + CoreMetrics.OVERALL_CONDITIONS_TO_COVER, CoreMetrics.OVERALL_UNCOVERED_CONDITIONS, CoreMetrics.OVERALL_CONDITIONS_BY_LINE, + CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE); + + private final Metric linesToCover; + private final Metric uncoveredLines; + private final Metric lineHits; + private final Metric conditionsToCover; + private final Metric uncoveredConditions; + private final Metric conditionsByLine; + private final Metric coveredConditionsByLine; + + private CoverageType(Metric linesToCover, Metric uncoveredLines, Metric lineHits, Metric conditionsToCover, + Metric uncoveredConditions, Metric conditionsByLine, Metric coveredConditionsByLine) { + this.linesToCover = linesToCover; + this.uncoveredLines = uncoveredLines; + this.lineHits = lineHits; + this.conditionsToCover = conditionsToCover; + this.uncoveredConditions = uncoveredConditions; + this.conditionsByLine = conditionsByLine; + this.coveredConditionsByLine = coveredConditionsByLine; + } + + public List all() { + return Arrays.asList(linesToCover, uncoveredLines, lineHits, conditionsToCover, uncoveredConditions, conditionsByLine, coveredConditionsByLine); + } + } + /** * Metrics of generated measures */ - public static final List METRICS = Arrays.asList( - CoreMetrics.LINES_TO_COVER, CoreMetrics.UNCOVERED_LINES, CoreMetrics.COVERAGE_LINE_HITS_DATA, - CoreMetrics.CONDITIONS_TO_COVER, CoreMetrics.UNCOVERED_CONDITIONS, CoreMetrics.CONDITIONS_BY_LINE, - CoreMetrics.COVERED_CONDITIONS_BY_LINE); + public static final List METRICS = CoverageType.UNIT.all(); private int totalCoveredLines = 0, totalConditions = 0, totalCoveredConditions = 0; private SortedMap hitsByLine = Maps.newTreeMap(); @@ -110,29 +145,33 @@ public final class CoverageMeasuresBuilder { } public Collection createMeasures() { + return createMeasures(CoverageType.UNIT); + } + + public Collection createMeasures(CoverageType type) { Collection measures = Lists.newArrayList(); if (getLinesToCover() > 0) { - measures.add(new Measure(CoreMetrics.LINES_TO_COVER, (double) getLinesToCover())); - measures.add(new Measure(CoreMetrics.UNCOVERED_LINES, (double) (getLinesToCover() - getCoveredLines()))); - measures.add(new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA).setData(KeyValueFormat.format(hitsByLine)).setPersistenceMode(PersistenceMode.DATABASE)); + measures.add(new Measure(type.linesToCover, (double) getLinesToCover())); + measures.add(new Measure(type.uncoveredLines, (double) (getLinesToCover() - getCoveredLines()))); + measures.add(new Measure(type.lineHits).setData(KeyValueFormat.format(hitsByLine)).setPersistenceMode(PersistenceMode.DATABASE)); } if (getConditions() > 0) { - measures.add(new Measure(CoreMetrics.CONDITIONS_TO_COVER, (double) getConditions())); - measures.add(new Measure(CoreMetrics.UNCOVERED_CONDITIONS, (double) (getConditions() - getCoveredConditions()))); - measures.add(createConditionsByLine()); - measures.add(createCoveredConditionsByLine()); + measures.add(new Measure(type.conditionsToCover, (double) getConditions())); + measures.add(new Measure(type.uncoveredConditions, (double) (getConditions() - getCoveredConditions()))); + measures.add(createConditionsByLine(type)); + measures.add(createCoveredConditionsByLine(type)); } return measures; } - private Measure createCoveredConditionsByLine() { - return new Measure(CoreMetrics.COVERED_CONDITIONS_BY_LINE) + private Measure createCoveredConditionsByLine(CoverageType type) { + return new Measure(type.coveredConditionsByLine) .setData(KeyValueFormat.format(coveredConditionsByLine)) .setPersistenceMode(PersistenceMode.DATABASE); } - private Measure createConditionsByLine() { - return new Measure(CoreMetrics.CONDITIONS_BY_LINE) + private Measure createConditionsByLine(CoverageType type) { + return new Measure(type.conditionsByLine) .setData(KeyValueFormat.format(conditionsByLine)) .setPersistenceMode(PersistenceMode.DATABASE); } -- 2.39.5