@@ -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); |
@@ -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. |
@@ -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; | |||
} |
@@ -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<Integer> linesToCover; | |||
private Metric<Integer> uncoveredLines; | |||
private Metric<String> lineHitsData; | |||
private Metric<Integer> conditionsToCover; | |||
private Metric<Integer> uncoveredConditions; | |||
private Metric<String> conditionsByLine; | |||
private Metric<String> coveredConditionsByLine; | |||
private CoverageType(Metric<Integer> linesToCover, Metric<Integer> uncoveredLines, Metric<String> lineHitsData, Metric<Integer> conditionsToCover, | |||
Metric<Integer> uncoveredConditions, Metric<String> conditionsByLine, Metric<String> coveredConditionsByLine) { | |||
this.linesToCover = linesToCover; | |||
this.uncoveredLines = uncoveredLines; | |||
this.lineHitsData = lineHitsData; | |||
this.conditionsToCover = conditionsToCover; | |||
this.uncoveredConditions = uncoveredConditions; | |||
this.conditionsByLine = conditionsByLine; | |||
this.coveredConditionsByLine = coveredConditionsByLine; | |||
} | |||
public Metric<Integer> linesToCover() { | |||
return linesToCover; | |||
} | |||
public Metric<Integer> uncoveredLines() { | |||
return uncoveredLines; | |||
} | |||
public Metric<String> lineHitsData() { | |||
return lineHitsData; | |||
} | |||
public Metric<Integer> conditionsToCover() { | |||
return conditionsToCover; | |||
} | |||
public Metric<Integer> uncoveredConditions() { | |||
return uncoveredConditions; | |||
} | |||
public Metric<String> conditionsByLine() { | |||
return conditionsByLine; | |||
} | |||
public Metric<String> 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(); | |||
} |
@@ -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<Integer, Integer> hitsByLine = Maps.newTreeMap(); | |||
private SortedMap<Integer, Integer> conditionsByLine = Maps.newTreeMap(); | |||
private SortedMap<Integer, Integer> 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<Integer>(storage) | |||
.onFile(file) | |||
.forMetric(type.linesToCover()) | |||
.withValue(hitsByLine.size()) | |||
.save(); | |||
new DefaultMeasure<Integer>(storage) | |||
.onFile(file) | |||
.forMetric(type.uncoveredLines()) | |||
.withValue(hitsByLine.size() - totalCoveredLines) | |||
.save(); | |||
new DefaultMeasure<String>(storage) | |||
.onFile(file) | |||
.forMetric(type.lineHitsData()) | |||
.withValue(KeyValueFormat.format(hitsByLine)) | |||
.save(); | |||
} | |||
if (totalConditions > 0) { | |||
new DefaultMeasure<Integer>(storage) | |||
.onFile(file) | |||
.forMetric(type.conditionsToCover()) | |||
.withValue(totalConditions) | |||
.save(); | |||
new DefaultMeasure<Integer>(storage) | |||
.onFile(file) | |||
.forMetric(type.uncoveredConditions()) | |||
.withValue(totalConditions - totalCoveredConditions) | |||
.save(); | |||
new DefaultMeasure<String>(storage) | |||
.onFile(file) | |||
.forMetric(type.coveredConditionsByLine()) | |||
.withValue(KeyValueFormat.format(coveredConditionsByLine)) | |||
.save(); | |||
new DefaultMeasure<String>(storage) | |||
.onFile(file) | |||
.forMetric(type.conditionsByLine()) | |||
.withValue(KeyValueFormat.format(conditionsByLine)) | |||
.save(); | |||
} | |||
} | |||
} |
@@ -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<Integer>() | |||
.onFile(main) | |||
.forMetric(CoreMetrics.LINES_TO_COVER) | |||
.withValue(4)); | |||
verify(storage).store(new DefaultMeasure<Integer>() | |||
.onFile(main) | |||
.forMetric(CoreMetrics.UNCOVERED_LINES) | |||
.withValue(2)); | |||
verify(storage).store(new DefaultMeasure<String>() | |||
.onFile(main) | |||
.forMetric(CoreMetrics.COVERAGE_LINE_HITS_DATA) | |||
.withValue("1=2;2=5;3=0;4=0")); | |||
verify(storage).store(new DefaultMeasure<Integer>() | |||
.onFile(main) | |||
.forMetric(CoreMetrics.CONDITIONS_TO_COVER) | |||
.withValue(2)); | |||
verify(storage).store(new DefaultMeasure<Integer>() | |||
.onFile(main) | |||
.forMetric(CoreMetrics.UNCOVERED_CONDITIONS) | |||
.withValue(1)); | |||
verify(storage).store(new DefaultMeasure<String>() | |||
.onFile(main) | |||
.forMetric(CoreMetrics.COVERED_CONDITIONS_BY_LINE) | |||
.withValue("1=1")); | |||
verify(storage).store(new DefaultMeasure<String>() | |||
.onFile(main) | |||
.forMetric(CoreMetrics.CONDITIONS_BY_LINE) | |||
.withValue("1=2")); | |||
} | |||
} |
@@ -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) |