From 0b2a624ab694ec9330486aa626e4636cd58a65a1 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Wed, 15 Oct 2014 10:10:40 +0200 Subject: [PATCH] SONAR-5389 Add replacement for CoverageMeasureBuilder --- .../sonar/batch/scan2/BaseSensorContext.java | 7 + .../sonar/api/batch/sensor/SensorContext.java | 8 + .../sensor/internal/DefaultStorable.java | 2 +- .../sonar/api/batch/sensor/test/Coverage.java | 103 ++++++++++++ .../sensor/test/internal/DefaultCoverage.java | 147 ++++++++++++++++++ .../test/internal/DefaultCoverageTest.java | 94 +++++++++++ .../internal/DefaultTestCaseCoverageTest.java | 8 +- 7 files changed, 364 insertions(+), 5 deletions(-) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/Coverage.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverage.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverageTest.java diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java index c925f400100..09b9e5b5f3e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java @@ -38,8 +38,10 @@ import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; +import org.sonar.api.batch.sensor.test.Coverage; import org.sonar.api.batch.sensor.test.TestCaseCoverage; import org.sonar.api.batch.sensor.test.TestCaseExecution; +import org.sonar.api.batch.sensor.test.internal.DefaultCoverage; import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseCoverage; import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution; import org.sonar.api.config.Settings; @@ -154,6 +156,11 @@ public abstract class BaseSensorContext implements SensorContext, SensorStorage } } + @Override + public Coverage newCoverage() { + return new DefaultCoverage(this); + } + @Override public TestCaseExecution newTestCaseExecution() { return new DefaultTestCaseExecution(this); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index 865fff64c9e..805d9db2417 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -30,6 +30,7 @@ import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; +import org.sonar.api.batch.sensor.test.Coverage; import org.sonar.api.batch.sensor.test.TestCaseCoverage; import org.sonar.api.batch.sensor.test.TestCaseExecution; import org.sonar.api.config.Settings; @@ -112,6 +113,13 @@ public interface SensorContext { // ------------ TESTS ------------ + /** + * Create a new coverage report. + * Don't forget to call {@link Coverage#save()} once all parameters are provided. + * @since 5.0 + */ + Coverage newCoverage(); + /** * Create a new test case execution report. * Don't forget to call {@link TestCaseExecution#save()} once all parameters are provided. diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/DefaultStorable.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/DefaultStorable.java index 9b1c9fa113c..587ee6cd3d6 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/DefaultStorable.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/DefaultStorable.java @@ -41,7 +41,7 @@ public abstract class DefaultStorable { public final void save() { Preconditions.checkNotNull(this.storage, "No persister on this object"); - Preconditions.checkState(!saved, "This measure was already saved"); + Preconditions.checkState(!saved, "This object was already saved"); doSave(); this.saved = true; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/Coverage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/Coverage.java new file mode 100644 index 00000000000..91817547ccd --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/Coverage.java @@ -0,0 +1,103 @@ +/* + * 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.api.batch.sensor.test; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.measures.CoreMetrics; + +/** + * @since 5.0 + */ +public interface Coverage { + + public 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), + INTEGRATION(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 Metric linesToCover; + private Metric uncoveredLines; + private Metric lineHitsData; + private Metric conditionsToCover; + private Metric uncoveredConditions; + private Metric conditionsByLine; + private Metric coveredConditionsByLine; + + private CoverageType(Metric linesToCover, Metric uncoveredLines, Metric lineHitsData, Metric conditionsToCover, + Metric uncoveredConditions, Metric conditionsByLine, Metric coveredConditionsByLine) { + this.linesToCover = linesToCover; + this.uncoveredLines = uncoveredLines; + this.lineHitsData = lineHitsData; + this.conditionsToCover = conditionsToCover; + this.uncoveredConditions = uncoveredConditions; + this.conditionsByLine = conditionsByLine; + this.coveredConditionsByLine = coveredConditionsByLine; + } + + public Metric linesToCover() { + return linesToCover; + } + + public Metric uncoveredLines() { + return uncoveredLines; + } + + public Metric lineHitsData() { + return lineHitsData; + } + + public Metric conditionsToCover() { + return conditionsToCover; + } + + public Metric uncoveredConditions() { + return uncoveredConditions; + } + + public Metric conditionsByLine() { + return conditionsByLine; + } + + public Metric coveredConditionsByLine() { + return coveredConditionsByLine; + } + } + + /** + * The file you are storing coverage on. + */ + Coverage onFile(InputFile inputFile); + + Coverage ofType(CoverageType type); + + Coverage lineHits(int line, int hits); + + Coverage conditions(int line, int conditions, int coveredConditions); + + /** + * Call this method only once when your are done with defining the test case coverage. + */ + void save(); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverage.java new file mode 100644 index 00000000000..4671dab8620 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverage.java @@ -0,0 +1,147 @@ +/* + * 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.api.batch.sensor.test.internal; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.sensor.SensorStorage; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.batch.sensor.test.Coverage; +import org.sonar.api.utils.KeyValueFormat; + +import java.util.SortedMap; + +public final class DefaultCoverage implements Coverage { + + private static final String INPUT_FILE_SHOULD_BE_NON_NULL = "InputFile should be non null"; + + private InputFile file; + private CoverageType type; + private int totalCoveredLines = 0, totalConditions = 0, totalCoveredConditions = 0; + private SortedMap hitsByLine = Maps.newTreeMap(); + private SortedMap conditionsByLine = Maps.newTreeMap(); + private SortedMap coveredConditionsByLine = Maps.newTreeMap(); + + protected transient final SensorStorage storage; + private transient boolean saved = false; + + public DefaultCoverage() { + this.storage = null; + } + + public DefaultCoverage(SensorStorage storage) { + this.storage = storage; + } + + @Override + public DefaultCoverage lineHits(int lineId, int hits) { + if (!hitsByLine.containsKey(lineId)) { + hitsByLine.put(lineId, hits); + if (hits > 0) { + totalCoveredLines += 1; + } + } + return this; + } + + @Override + public DefaultCoverage conditions(int lineId, int conditions, int coveredConditions) { + if (conditions > 0 && !conditionsByLine.containsKey(lineId)) { + totalConditions += conditions; + totalCoveredConditions += coveredConditions; + conditionsByLine.put(lineId, conditions); + coveredConditionsByLine.put(lineId, coveredConditions); + } + return this; + } + + public InputFile file() { + return file; + } + + @Override + public DefaultCoverage onFile(InputFile inputFile) { + Preconditions.checkNotNull(inputFile, INPUT_FILE_SHOULD_BE_NON_NULL); + Preconditions.checkArgument(inputFile.type() == Type.MAIN, "Coverage is only supported on main files"); + this.file = inputFile; + return this; + } + + public CoverageType type() { + return type; + } + + @Override + public DefaultCoverage ofType(CoverageType type) { + Preconditions.checkNotNull(type); + this.type = type; + return this; + } + + public void save() { + Preconditions.checkNotNull(this.storage, "No persister on this object"); + Preconditions.checkState(!saved, "This object was already saved"); + Preconditions.checkNotNull(this.file, "File is mandatory on Coverage"); + Preconditions.checkNotNull(this.type, "Type is mandatory on Coverage"); + + if (hitsByLine.size() > 0) { + new DefaultMeasure(storage) + .onFile(file) + .forMetric(type.linesToCover()) + .withValue(hitsByLine.size()) + .save(); + new DefaultMeasure(storage) + .onFile(file) + .forMetric(type.uncoveredLines()) + .withValue(hitsByLine.size() - totalCoveredLines) + .save(); + new DefaultMeasure(storage) + .onFile(file) + .forMetric(type.lineHitsData()) + .withValue(KeyValueFormat.format(hitsByLine)) + .save(); + } + if (totalConditions > 0) { + new DefaultMeasure(storage) + .onFile(file) + .forMetric(type.conditionsToCover()) + .withValue(totalConditions) + .save(); + new DefaultMeasure(storage) + .onFile(file) + .forMetric(type.uncoveredConditions()) + .withValue(totalConditions - totalCoveredConditions) + .save(); + new DefaultMeasure(storage) + .onFile(file) + .forMetric(type.coveredConditionsByLine()) + .withValue(KeyValueFormat.format(coveredConditionsByLine)) + .save(); + new DefaultMeasure(storage) + .onFile(file) + .forMetric(type.conditionsByLine()) + .withValue(KeyValueFormat.format(conditionsByLine)) + .save(); + } + } + +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverageTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverageTest.java new file mode 100644 index 00000000000..d89707b3aab --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultCoverageTest.java @@ -0,0 +1,94 @@ +/* + * 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.api.batch.sensor.test.internal; + +import org.junit.Test; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.SensorStorage; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.batch.sensor.test.Coverage.CoverageType; +import org.sonar.api.measures.CoreMetrics; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DefaultCoverageTest { + + private InputFile main = new DefaultInputFile("foo", "src/Foo.php").setType(InputFile.Type.MAIN); + + @Test + public void testCreation() { + DefaultCoverage coverage = new DefaultCoverage() + .onFile(main) + .ofType(CoverageType.UNIT) + .lineHits(1, 2) + .lineHits(2, 5) + .conditions(1, 2, 1); + + assertThat(coverage.file()).isEqualTo(main); + assertThat(coverage.type()).isEqualTo(CoverageType.UNIT); + } + + @Test + public void testSaveUnitTests() { + SensorStorage storage = mock(SensorStorage.class); + new DefaultCoverage(storage) + .onFile(main) + .ofType(CoverageType.UNIT) + .lineHits(1, 2) + .lineHits(2, 5) + .lineHits(3, 0) + .lineHits(4, 0) + .conditions(1, 2, 1) + .save(); + + verify(storage).store(new DefaultMeasure() + .onFile(main) + .forMetric(CoreMetrics.LINES_TO_COVER) + .withValue(4)); + verify(storage).store(new DefaultMeasure() + .onFile(main) + .forMetric(CoreMetrics.UNCOVERED_LINES) + .withValue(2)); + verify(storage).store(new DefaultMeasure() + .onFile(main) + .forMetric(CoreMetrics.COVERAGE_LINE_HITS_DATA) + .withValue("1=2;2=5;3=0;4=0")); + verify(storage).store(new DefaultMeasure() + .onFile(main) + .forMetric(CoreMetrics.CONDITIONS_TO_COVER) + .withValue(2)); + verify(storage).store(new DefaultMeasure() + .onFile(main) + .forMetric(CoreMetrics.UNCOVERED_CONDITIONS) + .withValue(1)); + verify(storage).store(new DefaultMeasure() + .onFile(main) + .forMetric(CoreMetrics.COVERED_CONDITIONS_BY_LINE) + .withValue("1=1")); + verify(storage).store(new DefaultMeasure() + .onFile(main) + .forMetric(CoreMetrics.CONDITIONS_BY_LINE) + .withValue("1=2")); + } + +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseCoverageTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseCoverageTest.java index 2daa6e5d34e..7f4d8001df7 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseCoverageTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseCoverageTest.java @@ -39,7 +39,7 @@ public class DefaultTestCaseCoverageTest { @Test public void testCreation() throws Exception { - DefaultTestCaseCoverage testCaseCoverage = new DefaultTestCaseCoverage(null) + DefaultTestCaseCoverage testCaseCoverage = new DefaultTestCaseCoverage() .testFile(testFile) .testName("myTest") .cover(mainFile) @@ -53,17 +53,17 @@ public class DefaultTestCaseCoverageTest { @Test public void testEqualsHashCodeToString() { - DefaultTestCaseCoverage testCaseCoverage1 = new DefaultTestCaseCoverage(null) + DefaultTestCaseCoverage testCaseCoverage1 = new DefaultTestCaseCoverage() .testFile(testFile) .testName("myTest") .cover(mainFile) .onLines(Arrays.asList(1, 2, 3)); - DefaultTestCaseCoverage testCaseCoverage1a = new DefaultTestCaseCoverage(null) + DefaultTestCaseCoverage testCaseCoverage1a = new DefaultTestCaseCoverage() .testFile(testFile) .testName("myTest") .cover(mainFile) .onLines(Arrays.asList(1, 2, 3)); - DefaultTestCaseCoverage testCaseCoverage2 = new DefaultTestCaseCoverage(null) + DefaultTestCaseCoverage testCaseCoverage2 = new DefaultTestCaseCoverage() .testFile(testFile) .testName("myTest2") .cover(mainFile) -- 2.39.5