diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2016-02-19 10:39:17 +0100 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2016-02-22 10:57:26 +0100 |
commit | accc8fc25dbdddb7fc6fbc5fa171ce29929d01d9 (patch) | |
tree | 0b4daa853beea7ec734f8b52df0cf126f4e7efe8 | |
parent | cef567021858b0e7239ac80d4512172ef7b70dbd (diff) | |
download | sonarqube-accc8fc25dbdddb7fc6fbc5fa171ce29929d01d9.tar.gz sonarqube-accc8fc25dbdddb7fc6fbc5fa171ce29929d01d9.zip |
SONAR-5772 New executable_lines_data metric
17 files changed, 368 insertions, 175 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java index 319b8292ba3..4b7c4b61f57 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java @@ -35,6 +35,7 @@ import org.sonar.batch.scm.ScmConfiguration; import org.sonar.batch.scm.ScmSensor; import org.sonar.batch.source.CodeColorizerSensor; import org.sonar.batch.source.LinesSensor; +import org.sonar.batch.source.ZeroCoverageSensor; import org.sonar.batch.task.ListTask; import org.sonar.batch.task.ScanTask; import org.sonar.batch.task.Tasks; @@ -55,6 +56,7 @@ public class BatchComponents { ScmSensor.class, LinesSensor.class, + ZeroCoverageSensor.class, CodeColorizerSensor.class, // Issues tracking diff --git a/sonar-batch/src/main/java/org/sonar/batch/report/CoveragePublisher.java b/sonar-batch/src/main/java/org/sonar/batch/report/CoveragePublisher.java index dbfe3b4d03b..921c37f5a83 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/report/CoveragePublisher.java +++ b/sonar-batch/src/main/java/org/sonar/batch/report/CoveragePublisher.java @@ -54,49 +54,49 @@ public class CoveragePublisher implements ReportPublisherStep { } Map<Integer, Coverage.Builder> coveragePerLine = new LinkedHashMap<>(); - applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, + int lineCount = ((InputFile) resource.inputComponent()).lines(); + applyLineMeasure(resource.key(), lineCount, CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, new MeasureOperation() { @Override public void apply(String value, Coverage.Builder builder) { builder.setUtHits(Integer.parseInt(value) > 0); } }); - applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.CONDITIONS_BY_LINE_KEY, coveragePerLine, + applyLineMeasure(resource.key(), lineCount, CoreMetrics.CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() { @Override public void apply(String value, Coverage.Builder builder) { builder.setConditions(Integer.parseInt(value)); } }); - applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, + applyLineMeasure(resource.key(), lineCount, CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() { @Override public void apply(String value, Coverage.Builder builder) { builder.setUtCoveredConditions(Integer.parseInt(value)); } }); - applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.IT_COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, + applyLineMeasure(resource.key(), lineCount, CoreMetrics.IT_COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, new MeasureOperation() { @Override public void apply(String value, Coverage.Builder builder) { builder.setItHits(Integer.parseInt(value) > 0); } }); - applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, + applyLineMeasure(resource.key(), lineCount, CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() { @Override public void apply(String value, Coverage.Builder builder) { builder.setItCoveredConditions(Integer.parseInt(value)); } }); - applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, + applyLineMeasure(resource.key(), lineCount, CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() { @Override public void apply(String value, Coverage.Builder builder) { builder.setOverallCoveredConditions(Integer.parseInt(value)); } }); - writer.writeComponentCoverage(resource.batchId(), Iterables.transform(coveragePerLine.values(), BuildCoverage.INSTANCE)); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/report/MeasuresPublisher.java b/sonar-batch/src/main/java/org/sonar/batch/report/MeasuresPublisher.java index 9f7ed5657af..5561f456043 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/report/MeasuresPublisher.java +++ b/sonar-batch/src/main/java/org/sonar/batch/report/MeasuresPublisher.java @@ -20,9 +20,11 @@ package org.sonar.batch.report; import com.google.common.base.Function; -import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; import java.io.Serializable; +import java.util.Set; import javax.annotation.Nonnull; +import org.sonar.api.batch.measure.Metric; import org.sonar.api.measures.Measure; import org.sonar.api.measures.Metric.ValueType; import org.sonar.batch.index.BatchComponent; @@ -31,97 +33,135 @@ import org.sonar.batch.protocol.Constants.MeasureValueType; import org.sonar.batch.protocol.output.BatchReport; import org.sonar.batch.protocol.output.BatchReportWriter; import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.core.metric.BatchMetrics; + +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Sets.newHashSet; public class MeasuresPublisher implements ReportPublisherStep { - private final BatchComponentCache resourceCache; - private final MeasureCache measureCache; + private static final class MeasureToReportMeasure implements Function<Measure, BatchReport.Measure> { + private final BatchComponent resource; + private final BatchReport.Measure.Builder builder = BatchReport.Measure.newBuilder(); - public MeasuresPublisher(BatchComponentCache resourceCache, MeasureCache measureCache) { - this.resourceCache = resourceCache; - this.measureCache = measureCache; - } + private MeasureToReportMeasure(BatchComponent resource) { + this.resource = resource; + } - @Override - public void publish(BatchReportWriter writer) { - for (final BatchComponent resource : resourceCache.all()) { - Iterable<Measure> batchMeasures = measureCache.byResource(resource.resource()); - Iterable<org.sonar.batch.protocol.output.BatchReport.Measure> reportMeasures = Iterables.transform(batchMeasures, new Function<Measure, BatchReport.Measure>() { - private final BatchReport.Measure.Builder builder = BatchReport.Measure.newBuilder(); - - @Override - public BatchReport.Measure apply(@Nonnull Measure input) { - validateMeasure(input, resource.key()); - return toReportMeasure(builder, input); - } - }); - writer.writeComponentMeasures(resource.batchId(), reportMeasures); + @Override + public BatchReport.Measure apply(@Nonnull Measure input) { + validateMeasure(input, resource.key()); + return toReportMeasure(builder, input); + } + + private static void validateMeasure(Measure measure, String componentKey) { + if (measure.getValue() == null && measure.getData() == null) { + throw new IllegalArgumentException(String.format("Measure on metric '%s' and component '%s' has no value, but it's not allowed", measure.getMetricKey(), componentKey)); + } + } + + private BatchReport.Measure toReportMeasure(BatchReport.Measure.Builder builder, Measure measure) { + builder.clear(); + + builder.setValueType(getMeasureValueType(measure.getMetric().getType())); + setValueAccordingToType(builder, measure); + // Because some numeric measures also have a data (like Sqale rating) + String data = measure.getData(); + if (data != null) { + builder.setStringValue(data); + } + builder.setMetricKey(measure.getMetricKey()); + return builder.build(); } - } - private static void validateMeasure(Measure measure, String componentKey) { - if (measure.getValue() == null && measure.getData() == null) { - throw new IllegalArgumentException(String.format("Measure on metric '%s' and component '%s' has no value, but it's not allowed", measure.getMetricKey(), componentKey)); + private void setValueAccordingToType(BatchReport.Measure.Builder builder, Measure measure) { + Serializable value = measure.value(); + switch (builder.getValueType()) { + case BOOLEAN: + builder.setBooleanValue((Boolean) value); + break; + case DOUBLE: + builder.setDoubleValue(((Number) value).doubleValue()); + break; + case INT: + builder.setIntValue(((Number) value).intValue()); + break; + case LONG: + builder.setLongValue(((Number) value).longValue()); + break; + case STRING: + builder.setStringValue((String) value); + break; + default: + throw new IllegalStateException("Unknown value type: " + builder.getValueType()); + } } + + private MeasureValueType getMeasureValueType(ValueType type) { + switch (type) { + case INT: + case RATING: + return MeasureValueType.INT; + case FLOAT: + case PERCENT: + return MeasureValueType.DOUBLE; + case BOOL: + return MeasureValueType.BOOLEAN; + case STRING: + case DATA: + case LEVEL: + case DISTRIB: + return MeasureValueType.STRING; + case WORK_DUR: + case MILLISEC: + return MeasureValueType.LONG; + default: + throw new IllegalStateException("Unknown value type: " + type); + } + } + } - private BatchReport.Measure toReportMeasure(BatchReport.Measure.Builder builder, Measure measure) { - builder.clear(); + private static final class IsMetricAllowed implements Predicate<Measure> { + private final Set<String> allowedMetricKeys; + + private IsMetricAllowed(Set<String> allowedMetricKeys) { + this.allowedMetricKeys = allowedMetricKeys; + } - builder.setValueType(getMeasureValueType(measure.getMetric().getType())); - setValueAccordingToType(builder, measure); - // Because some numeric measures also have a data (like Sqale rating) - String data = measure.getData(); - if (data != null) { - builder.setStringValue(data); + @Override + public boolean apply(Measure input) { + return allowedMetricKeys.contains(input.getMetricKey()); } - builder.setMetricKey(measure.getMetricKey()); - return builder.build(); } - private void setValueAccordingToType(BatchReport.Measure.Builder builder, Measure measure) { - Serializable value = measure.value(); - switch (builder.getValueType()) { - case BOOLEAN: - builder.setBooleanValue((Boolean) value); - break; - case DOUBLE: - builder.setDoubleValue(((Number) value).doubleValue()); - break; - case INT: - builder.setIntValue(((Number) value).intValue()); - break; - case LONG: - builder.setLongValue(((Number) value).longValue()); - break; - case STRING: - builder.setStringValue((String) value); - break; - default: - throw new IllegalStateException("Unknown value type: " + builder.getValueType()); + private static final class MetricToKey implements Function<Metric, String> { + @Override + public String apply(Metric input) { + return input.key(); } } - private MeasureValueType getMeasureValueType(ValueType type) { - switch (type) { - case INT: - case RATING: - return MeasureValueType.INT; - case FLOAT: - case PERCENT: - return MeasureValueType.DOUBLE; - case BOOL: - return MeasureValueType.BOOLEAN; - case STRING: - case DATA: - case LEVEL: - case DISTRIB: - return MeasureValueType.STRING; - case WORK_DUR: - case MILLISEC: - return MeasureValueType.LONG; - default: - throw new IllegalStateException("Unknown value type: " + type); + private final BatchComponentCache resourceCache; + private final MeasureCache measureCache; + private final BatchMetrics batchMetrics; + + public MeasuresPublisher(BatchComponentCache resourceCache, MeasureCache measureCache, BatchMetrics batchMetrics) { + this.resourceCache = resourceCache; + this.measureCache = measureCache; + this.batchMetrics = batchMetrics; + } + + @Override + public void publish(BatchReportWriter writer) { + final Set<String> allowedMetricKeys = newHashSet(transform(batchMetrics.getMetrics(), new MetricToKey())); + for (final BatchComponent resource : resourceCache.all()) { + Iterable<Measure> batchMeasures = measureCache.byResource(resource.resource()); + Iterable<org.sonar.batch.protocol.output.BatchReport.Measure> reportMeasures = transform( + filter(batchMeasures, new IsMetricAllowed(allowedMetricKeys)), + new MeasureToReportMeasure(resource)); + writer.writeComponentMeasures(resource.batchId(), reportMeasures); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index a567cd2e48b..2e265e2640d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -92,6 +92,7 @@ import org.sonar.batch.source.CodeColorizers; import org.sonar.batch.test.TestPlanBuilder; import org.sonar.batch.test.TestableBuilder; import org.sonar.core.issue.workflow.FunctionExecutor; +import org.sonar.core.metric.BatchMetrics; import org.sonar.core.platform.ComponentContainer; public class ProjectScanContainer extends ComponentContainer { @@ -197,6 +198,7 @@ public class ProjectScanContainer extends ComponentContainer { ProjectSettings.class, // Report + BatchMetrics.class, ReportPublisher.class, AnalysisContextReportPublisher.class, MetadataPublisher.class, @@ -206,7 +208,7 @@ public class ProjectScanContainer extends ComponentContainer { CoveragePublisher.class, SourcePublisher.class, TestExecutionAndCoveragePublisher.class, - + // Cpd CpdExecutor.class, SonarDuplicationsIndex.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java index c0adb26f4b6..963f7e14b91 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java @@ -51,7 +51,11 @@ public class MeasureCache { } public Iterable<Measure> byResource(Resource r) { - return cache.values(r.getEffectiveKey()); + return byComponentKey(r.getEffectiveKey()); + } + + public Iterable<Measure> byComponentKey(String effectiveKey) { + return cache.values(effectiveKey); } @CheckForNull diff --git a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java index 1d7221b8d96..09604877532 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java +++ b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java @@ -67,10 +67,6 @@ public class DefaultSensorStorage implements SensorStorage { private static final Logger LOG = LoggerFactory.getLogger(DefaultSensorStorage.class); private static final List<Metric> INTERNAL_METRICS = Arrays.<Metric>asList( - // Computed by CpdSensor - CoreMetrics.DUPLICATED_FILES, - CoreMetrics.DUPLICATED_LINES, - CoreMetrics.DUPLICATED_BLOCKS, // Computed by LinesSensor CoreMetrics.LINES); diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/ZeroCoverageSensor.java b/sonar-batch/src/main/java/org/sonar/batch/source/ZeroCoverageSensor.java new file mode 100644 index 00000000000..f211050dabc --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/source/ZeroCoverageSensor.java @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.batch.source; + +import com.google.common.base.Function; +import com.google.common.collect.Sets; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.coverage.CoverageType; +import org.sonar.api.batch.sensor.coverage.NewCoverage; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.batch.scan.measure.MeasureCache; + +import static com.google.common.collect.Iterables.concat; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Sets.newHashSet; + +@Phase(name = Phase.Name.POST) +public final class ZeroCoverageSensor implements Sensor { + + private static final class MeasureToMetricKey implements Function<Measure, String> { + @Override + public String apply(Measure input) { + return input.getMetricKey(); + } + } + + private static final class MetricToKey implements Function<Metric, String> { + @Override + public String apply(Metric input) { + return input.key(); + } + } + + private final MeasureCache measureCache; + + public ZeroCoverageSensor(MeasureCache measureCache) { + this.measureCache = measureCache; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("Zero Coverage Sensor"); + } + + @Override + public void execute(final SensorContext context) { + FileSystem fs = context.fileSystem(); + for (InputFile f : fs.inputFiles(fs.predicates().hasType(Type.MAIN))) { + if (!isCoverageMeasuresAlreadyDefined(f)) { + Measure execLines = measureCache.byMetric(f.key(), CoreMetrics.EXECUTABLE_LINES_DATA_KEY); + if (execLines != null) { + storeZeroCoverageForEachExecutableLine(context, f, execLines); + } + + } + } + } + + private static void storeZeroCoverageForEachExecutableLine(final SensorContext context, InputFile f, Measure execLines) { + NewCoverage newCoverage = context.newCoverage().ofType(CoverageType.UNIT).onFile(f); + Map<Integer, String> lineMeasures = KeyValueFormat.parseIntString((String) execLines.value()); + for (Map.Entry<Integer, String> lineMeasure : lineMeasures.entrySet()) { + int lineIdx = lineMeasure.getKey(); + if (lineIdx <= f.lines()) { + String value = lineMeasure.getValue(); + if (StringUtils.isNotEmpty(value) && Integer.parseInt(value) > 0) { + newCoverage.lineHits(lineIdx, 0); + } + } + } + newCoverage.save(); + } + + private boolean isCoverageMeasuresAlreadyDefined(InputFile f) { + Set<String> metricKeys = newHashSet(transform(measureCache.byComponentKey(f.key()), new MeasureToMetricKey())); + Function<Metric, String> metricToKey = new MetricToKey(); + Set<String> allCoverageMetricKeys = newHashSet(concat(transform(CoverageType.UNIT.allMetrics(), metricToKey), + transform(CoverageType.IT.allMetrics(), metricToKey), + transform(CoverageType.OVERALL.allMetrics(), metricToKey))); + return !Sets.intersection(metricKeys, allCoverageMetricKeys).isEmpty(); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java index 9c9fa85b624..d693527df47 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java @@ -91,11 +91,11 @@ public class CoverageMediumTest { assertThat(result.coverageFor(file, 2).getOverallCoveredConditions()).isEqualTo(0); Map<String, List<org.sonar.batch.protocol.output.BatchReport.Measure>> allMeasures = result.allMeasures(); - assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue", "stringValue") - .contains(tuple(CoreMetrics.LINES_TO_COVER_KEY, 2, ""), - tuple(CoreMetrics.UNCOVERED_LINES_KEY, 0, ""), - tuple(CoreMetrics.CONDITIONS_TO_COVER_KEY, 2, ""), - tuple(CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, 0, "2=1")); + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue") + .contains(tuple(CoreMetrics.LINES_TO_COVER_KEY, 2), + tuple(CoreMetrics.UNCOVERED_LINES_KEY, 0), + tuple(CoreMetrics.CONDITIONS_TO_COVER_KEY, 2), + tuple(CoreMetrics.UNCOVERED_CONDITIONS_KEY, 1)); } @Test @@ -132,4 +132,49 @@ public class CoverageMediumTest { CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY); } + @Test + public void fallbackOnExecutableLines() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File measuresFile = new File(srcDir, "sample.xoo.measures"); + FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}"); + FileUtils.write(measuresFile, "executable_lines_data:2=1;3=1;4=0"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFile("src/sample.xoo"); + assertThat(result.coverageFor(file, 1)).isNull(); + + assertThat(result.coverageFor(file, 2).getUtHits()).isFalse(); + assertThat(result.coverageFor(file, 2).getItHits()).isFalse(); + assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(0); + assertThat(result.coverageFor(file, 2).getUtCoveredConditions()).isEqualTo(0); + assertThat(result.coverageFor(file, 2).getItCoveredConditions()).isEqualTo(0); + assertThat(result.coverageFor(file, 2).getOverallCoveredConditions()).isEqualTo(0); + + assertThat(result.coverageFor(file, 3).getUtHits()).isFalse(); + assertThat(result.coverageFor(file, 4)).isNull(); + + Map<String, List<org.sonar.batch.protocol.output.BatchReport.Measure>> allMeasures = result.allMeasures(); + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue") + .contains(tuple(CoreMetrics.LINES_TO_COVER_KEY, 2), + tuple(CoreMetrics.UNCOVERED_LINES_KEY, 2)); + + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey").doesNotContain(CoreMetrics.CONDITIONS_TO_COVER_KEY, CoreMetrics.UNCOVERED_CONDITIONS_KEY); + } + } diff --git a/sonar-batch/src/test/java/org/sonar/batch/report/MeasuresPublisherTest.java b/sonar-batch/src/test/java/org/sonar/batch/report/MeasuresPublisherTest.java index 3399ab52604..51830c30d1e 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/report/MeasuresPublisherTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/report/MeasuresPublisherTest.java @@ -30,8 +30,6 @@ import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; -import org.sonar.api.measures.Metric; -import org.sonar.api.measures.Metric.ValueType; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.batch.index.BatchComponentCache; @@ -39,6 +37,7 @@ import org.sonar.batch.protocol.output.BatchReport; import org.sonar.batch.protocol.output.BatchReportReader; import org.sonar.batch.protocol.output.BatchReportWriter; import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.core.metric.BatchMetrics; import org.sonar.core.util.CloseableIterator; import static java.util.Arrays.asList; @@ -70,28 +69,17 @@ public class MeasuresPublisherTest { resourceCache.add(sampleFile, null); measureCache = mock(MeasureCache.class); when(measureCache.byResource(any(Resource.class))).thenReturn(Collections.<Measure>emptyList()); - publisher = new MeasuresPublisher(resourceCache, measureCache); + publisher = new MeasuresPublisher(resourceCache, measureCache, new BatchMetrics()); } @Test public void publishMeasures() throws Exception { - Measure measure = new Measure<>(CoreMetrics.COVERAGE) - .setValue(2.0) - .setPersonId(2); - // Manual measure - Measure manual = new Measure<>(new Metric<>("manual_metric", ValueType.BOOL)) - .setValue(1.0); - // Sqale rating have both a value and a data - Measure rating = new Measure<>(CoreMetrics.SQALE_RATING) - .setValue(2.0) - .setData("A"); - // Long measure - Measure longMeasure = new Measure<>(CoreMetrics.TECHNICAL_DEBT) - .setValue(1.0); + Measure measure = new Measure<>(CoreMetrics.LINES_TO_COVER) + .setValue(2.0); // String value Measure stringMeasure = new Measure<>(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION) .setData("foo bar"); - when(measureCache.byResource(sampleFile)).thenReturn(asList(measure, manual, rating, longMeasure, stringMeasure)); + when(measureCache.byResource(sampleFile)).thenReturn(asList(measure, stringMeasure)); File outputDir = temp.newFolder(); BatchReportWriter writer = new BatchReportWriter(outputDir); @@ -102,13 +90,13 @@ public class MeasuresPublisherTest { assertThat(reader.readComponentMeasures(1)).hasSize(0); try (CloseableIterator<BatchReport.Measure> componentMeasures = reader.readComponentMeasures(2)) { - assertThat(componentMeasures).hasSize(5); + assertThat(componentMeasures).hasSize(2); } } @Test public void fail_with_IAE_when_measure_has_no_value() throws Exception { - Measure measure = new Measure<>(CoreMetrics.COVERAGE); + Measure measure = new Measure<>(CoreMetrics.LINES_TO_COVER); when(measureCache.byResource(sampleFile)).thenReturn(Collections.singletonList(measure)); File outputDir = temp.newFolder(); @@ -118,7 +106,7 @@ public class MeasuresPublisherTest { publisher.publish(writer); fail(); } catch (RuntimeException e) { - assertThat(ExceptionUtils.getFullStackTrace(e)).contains("Measure on metric 'coverage' and component 'foo:src/Foo.php' has no value, but it's not allowed"); + assertThat(ExceptionUtils.getFullStackTrace(e)).contains("Measure on metric 'lines_to_cover' and component 'foo:src/Foo.php' has no value, but it's not allowed"); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java index 043ae5eb107..61f21529547 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java @@ -166,22 +166,6 @@ public class MeasureCacheTest extends AbstractCachesTest { } @Test - public void should_add_measure_with_same_metric() { - Project p = new Project("struts"); - - assertThat(measureCache.entries()).hasSize(0); - assertThat(measureCache.byResource(p)).hasSize(0); - - Measure m1 = new Measure(CoreMetrics.NCLOC, 1.0); - Measure m2 = new Measure(CoreMetrics.NCLOC, 1.0).setPersonId(2); - measureCache.put(p, m1); - measureCache.put(p, m2); - - assertThat(measureCache.entries()).hasSize(2); - assertThat(measureCache.byResource(p)).hasSize(2); - } - - @Test public void should_get_measures() { Project p = new Project("struts"); Resource dir = Directory.create("foo/bar").setEffectiveKey("struts:foo/bar"); diff --git a/sonar-core/src/main/java/org/sonar/core/metric/BatchMetrics.java b/sonar-core/src/main/java/org/sonar/core/metric/BatchMetrics.java index 0fcaf3bee79..f93d5faa92d 100644 --- a/sonar-core/src/main/java/org/sonar/core/metric/BatchMetrics.java +++ b/sonar-core/src/main/java/org/sonar/core/metric/BatchMetrics.java @@ -20,10 +20,8 @@ package org.sonar.core.metric; import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import java.util.Arrays; import java.util.List; import java.util.Set; import javax.annotation.Nullable; @@ -32,6 +30,8 @@ import org.sonar.api.measures.Metric; import org.sonar.api.measures.Metrics; import org.sonar.api.server.ServerSide; +import static com.google.common.collect.FluentIterable.from; +import static java.util.Arrays.asList; import static org.sonar.api.measures.CoreMetrics.ACCESSORS; import static org.sonar.api.measures.CoreMetrics.CLASSES; import static org.sonar.api.measures.CoreMetrics.COMMENTED_OUT_CODE_LINES; @@ -45,9 +45,6 @@ import static org.sonar.api.measures.CoreMetrics.CONDITIONS_TO_COVER; import static org.sonar.api.measures.CoreMetrics.COVERAGE_LINE_HITS_DATA; import static org.sonar.api.measures.CoreMetrics.COVERED_CONDITIONS_BY_LINE; import static org.sonar.api.measures.CoreMetrics.DIRECTORIES; -import static org.sonar.api.measures.CoreMetrics.DUPLICATED_BLOCKS; -import static org.sonar.api.measures.CoreMetrics.DUPLICATED_FILES; -import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES; import static org.sonar.api.measures.CoreMetrics.FILES; import static org.sonar.api.measures.CoreMetrics.FILE_COMPLEXITY_DISTRIBUTION; import static org.sonar.api.measures.CoreMetrics.FUNCTIONS; @@ -115,10 +112,6 @@ public class BatchMetrics { STATEMENTS, ACCESSORS, - DUPLICATED_LINES, - DUPLICATED_BLOCKS, - DUPLICATED_FILES, - COMPLEXITY, COMPLEXITY_IN_CLASSES, COMPLEXITY_IN_FUNCTIONS, @@ -155,11 +148,14 @@ public class BatchMetrics { OVERALL_COVERED_CONDITIONS_BY_LINE, OVERALL_CONDITIONS_BY_LINE, - QUALITY_PROFILES - ); + QUALITY_PROFILES); private final Set<Metric> metrics; + public BatchMetrics() { + this.metrics = ALLOWED_CORE_METRICS; + } + public BatchMetrics(Metrics[] metricsRepositories) { this.metrics = ImmutableSet.copyOf(Iterables.concat(getPluginMetrics(metricsRepositories), ALLOWED_CORE_METRICS)); } @@ -169,8 +165,7 @@ public class BatchMetrics { } private static Iterable<Metric> getPluginMetrics(Metrics[] metricsRepositories) { - return FluentIterable.from(Arrays.asList(metricsRepositories)) - .transformAndConcat(FlattenMetrics.INSTANCE); + return from(asList(metricsRepositories)).transformAndConcat(FlattenMetrics.INSTANCE); } private enum FlattenMetrics implements Function<Metrics, List<Metric>> { diff --git a/sonar-core/src/test/java/org/sonar/core/metric/BatchMetricsTest.java b/sonar-core/src/test/java/org/sonar/core/metric/BatchMetricsTest.java index 1af8d06e155..c1e2e5e2fb3 100644 --- a/sonar-core/src/test/java/org/sonar/core/metric/BatchMetricsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/metric/BatchMetricsTest.java @@ -31,12 +31,12 @@ import static org.assertj.core.api.Assertions.assertThat; public class BatchMetricsTest { - static final BatchMetrics SENSOR_METRICS_WITHOUT_METRIC_PLUGIN = new BatchMetrics(new Metrics[]{}); - static final BatchMetrics SENSOR_METRICS_WITH_PLUGIN = new BatchMetrics(new Metrics[]{new FakeMetrics()}); + static final BatchMetrics SENSOR_METRICS_WITHOUT_METRIC_PLUGIN = new BatchMetrics(new Metrics[] {}); + static final BatchMetrics SENSOR_METRICS_WITH_PLUGIN = new BatchMetrics(new Metrics[] {new FakeMetrics()}); @Test public void check_number_of_allowed_core_metrics() throws Exception { - assertThat(SENSOR_METRICS_WITHOUT_METRIC_PLUGIN.getMetrics()).hasSize(52); + assertThat(SENSOR_METRICS_WITHOUT_METRIC_PLUGIN.getMetrics()).hasSize(49); } @Test @@ -52,8 +52,7 @@ public class BatchMetricsTest { public List<Metric> getMetrics() { return ImmutableList.<Metric>of( new Metric.Builder("key1", "name1", Metric.ValueType.INT).create(), - new Metric.Builder("key2", "name2", Metric.ValueType.FLOAT).create() - ); + new Metric.Builder("key2", "name2", Metric.ValueType.FLOAT).create()); } } } diff --git a/sonar-db/pom.xml b/sonar-db/pom.xml index 86339eed76d..9cb8dc2b64c 100644 --- a/sonar-db/pom.xml +++ b/sonar-db/pom.xml @@ -13,6 +13,10 @@ <name>SonarQube :: Database</name> <description>Create and request SonarQube schema</description> + + <properties> + <sonar.exclusions>target/generated-sources/**</sonar.exclusions> + </properties> <dependencies> <dependency> 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 66734f4026f..228dab0a51c 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 @@ -96,7 +96,7 @@ public interface SensorContext { /** * Builder to define coverage in a file. - * Don't forget to call {@link NewDuplication#save()}. + * Don't forget to call {@link NewCoverage#save()}. */ NewCoverage newCoverage(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java index ceef90f9788..cdab75f44c4 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java @@ -20,9 +20,31 @@ package org.sonar.api.batch.sensor.coverage; import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableSet; +import java.util.Set; import org.sonar.api.measures.Metric; -import static org.sonar.api.measures.CoreMetrics.*; +import static org.sonar.api.measures.CoreMetrics.CONDITIONS_BY_LINE; +import static org.sonar.api.measures.CoreMetrics.CONDITIONS_TO_COVER; +import static org.sonar.api.measures.CoreMetrics.COVERAGE_LINE_HITS_DATA; +import static org.sonar.api.measures.CoreMetrics.COVERED_CONDITIONS_BY_LINE; +import static org.sonar.api.measures.CoreMetrics.IT_CONDITIONS_BY_LINE; +import static org.sonar.api.measures.CoreMetrics.IT_CONDITIONS_TO_COVER; +import static org.sonar.api.measures.CoreMetrics.IT_COVERAGE_LINE_HITS_DATA; +import static org.sonar.api.measures.CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE; +import static org.sonar.api.measures.CoreMetrics.IT_LINES_TO_COVER; +import static org.sonar.api.measures.CoreMetrics.IT_UNCOVERED_CONDITIONS; +import static org.sonar.api.measures.CoreMetrics.IT_UNCOVERED_LINES; +import static org.sonar.api.measures.CoreMetrics.LINES_TO_COVER; +import static org.sonar.api.measures.CoreMetrics.OVERALL_CONDITIONS_BY_LINE; +import static org.sonar.api.measures.CoreMetrics.OVERALL_CONDITIONS_TO_COVER; +import static org.sonar.api.measures.CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA; +import static org.sonar.api.measures.CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE; +import static org.sonar.api.measures.CoreMetrics.OVERALL_LINES_TO_COVER; +import static org.sonar.api.measures.CoreMetrics.OVERALL_UNCOVERED_CONDITIONS; +import static org.sonar.api.measures.CoreMetrics.OVERALL_UNCOVERED_LINES; +import static org.sonar.api.measures.CoreMetrics.UNCOVERED_CONDITIONS; +import static org.sonar.api.measures.CoreMetrics.UNCOVERED_LINES; /** * Different coverage categories. @@ -43,6 +65,7 @@ public enum CoverageType { private final Metric uncoveredConditions; private final Metric coveredConditionsByLine; private final Metric conditionsByLine; + private final Set<Metric> all; private CoverageType(Metric linesToCover, Metric uncoveredLines, Metric lineHitsData, Metric conditionsToCover, Metric uncoveredConditions, Metric coveredConditionsByLine, Metric conditionsByLine) { @@ -53,6 +76,11 @@ public enum CoverageType { this.uncoveredConditions = uncoveredConditions; this.coveredConditionsByLine = coveredConditionsByLine; this.conditionsByLine = conditionsByLine; + this.all = ImmutableSet.of(linesToCover, uncoveredLines, lineHitsData, conditionsToCover, uncoveredConditions, coveredConditionsByLine, conditionsByLine); + } + + public Set<Metric> allMetrics() { + return all; } public Metric linesToCover() { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java index 7f4ca3ec676..96e5f9737ba 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java @@ -21,6 +21,9 @@ package org.sonar.api.batch.sensor.coverage.internal; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import java.util.Collections; +import java.util.SortedMap; +import javax.annotation.Nullable; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.coverage.CoverageType; @@ -28,11 +31,6 @@ import org.sonar.api.batch.sensor.coverage.NewCoverage; import org.sonar.api.batch.sensor.internal.DefaultStorable; import org.sonar.api.batch.sensor.internal.SensorStorage; -import javax.annotation.Nullable; - -import java.util.Collections; -import java.util.SortedMap; - public class DefaultCoverage extends DefaultStorable implements NewCoverage { private DefaultInputFile inputFile; @@ -64,7 +62,6 @@ public class DefaultCoverage extends DefaultStorable implements NewCoverage { @Override public NewCoverage ofType(CoverageType type) { - validateFile(); Preconditions.checkNotNull(type, "type can't be null"); this.type = type; return this; @@ -93,20 +90,6 @@ public class DefaultCoverage extends DefaultStorable implements NewCoverage { Preconditions.checkState(line > 0, "Line number must be strictly positive: " + line); } - private void validateLines() { - for (int l : hitsByLine.keySet()) { - validateLine(l); - } - - for (int l : conditionsByLine.keySet()) { - validateLine(l); - } - - for (int l : coveredConditionsByLine.keySet()) { - validateLine(l); - } - } - private void validateFile() { Preconditions.checkNotNull(inputFile, "Call onFile() first"); } @@ -157,7 +140,6 @@ public class DefaultCoverage extends DefaultStorable implements NewCoverage { public void doSave() { validateFile(); Preconditions.checkNotNull(type, "Call ofType() first"); - validateLines(); storage.store(this); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index da186c87254..796c66d1a15 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -19,7 +19,6 @@ */ package org.sonar.api.measures; -import com.google.common.annotations.Beta; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import java.lang.reflect.Field; @@ -2123,7 +2122,6 @@ public final class CoreMetrics { /** * @since 2.14 */ - @Beta public static final String NCLOC_DATA_KEY = "ncloc_data"; /** @@ -2133,7 +2131,6 @@ public final class CoreMetrics { * @see org.sonar.api.measures.FileLinesContext * @since 2.14 */ - @Beta public static final Metric<String> NCLOC_DATA = new Metric.Builder(NCLOC_DATA_KEY, "ncloc_data", Metric.ValueType.DATA) .setHidden(true) .setDomain(DOMAIN_SIZE) @@ -2142,7 +2139,6 @@ public final class CoreMetrics { /** * @since 2.14 */ - @Beta public static final String COMMENT_LINES_DATA_KEY = "comment_lines_data"; /** @@ -2152,12 +2148,28 @@ public final class CoreMetrics { * @see org.sonar.api.measures.FileLinesContext * @since 2.14 */ - @Beta public static final Metric<String> COMMENT_LINES_DATA = new Metric.Builder(COMMENT_LINES_DATA_KEY, "comment_lines_data", Metric.ValueType.DATA) .setHidden(true) .setDomain(DOMAIN_DOCUMENTATION) .create(); + /** + * @since 5.5 + */ + public static final String EXECUTABLE_LINES_DATA_KEY = "executable_lines_data"; + + /** + * Information about executable lines of code in file. + * Key-value pairs, where key - is a number of line, and value - is an indicator of whether line contains executable code (1) or not (0). + * + * @see org.sonar.api.measures.FileLinesContext + * @since 5.5 + */ + public static final Metric<String> EXECUTABLE_LINES_DATA = new Metric.Builder(EXECUTABLE_LINES_DATA_KEY, "executable_lines_data", Metric.ValueType.DATA) + .setHidden(true) + .setDomain(DOMAIN_TESTS) + .create(); + // -------------------------------------------------------------------------------------------------------------------- // // OTHERS |