From 55d74d47d9b39317083f36352fdc8df3b0151ab6 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Fri, 13 Nov 2015 11:54:47 +0100 Subject: [PATCH] SONAR-6183 Validate that coverage measures are correct regarding line number and resource --- .../src/test/java/it/test/CoverageTest.java | 3 +- .../xoo/coverage/ItCoverageSensorTest.java | 4 +- .../coverage/OverallCoverageSensorTest.java | 4 +- .../xoo/coverage/UtCoverageSensorTest.java | 4 +- .../deprecated/DeprecatedSensorContext.java | 11 ++- .../sensor/coverage/CoverageExclusions.java | 82 ++++++++++++++- .../coverage/CoverageMediumTest.java | 1 - .../tests/CoveragePerTestMediumTest.java | 99 ++++++++++++++----- .../coverage/CoverageExclusionsTest.java | 69 ++++++++++++- .../coverage/internal/DefaultCoverage.java | 34 ++++++- .../internal/SensorContextTesterTest.java | 42 +++++++- 11 files changed, 305 insertions(+), 48 deletions(-) diff --git a/it/it-tests/src/test/java/it/test/CoverageTest.java b/it/it-tests/src/test/java/it/test/CoverageTest.java index 1ca241841cb..70d04b20f3d 100644 --- a/it/it-tests/src/test/java/it/test/CoverageTest.java +++ b/it/it-tests/src/test/java/it/test/CoverageTest.java @@ -8,7 +8,9 @@ package it.test; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.SonarRunner; import it.Category2Suite; + import java.io.File; + import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.TrueFileFilter; @@ -18,7 +20,6 @@ import org.junit.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.sonar.wsclient.services.Resource; import org.sonar.wsclient.services.ResourceQuery; - import static org.assertj.core.api.Assertions.assertThat; import static util.ItUtils.projectDir; diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java index 1fb232fa3bb..405f132d0d4 100644 --- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java @@ -71,7 +71,7 @@ public class ItCoverageSensorTest { public void testLineHitNoConditions() throws IOException { File coverage = new File(baseDir, "src/foo.xoo.itcoverage"); FileUtils.write(coverage, "1:3\n\n#comment"); - DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo"); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo").setLines(10); context.fileSystem().add(inputFile); sensor.execute(context); @@ -83,7 +83,7 @@ public class ItCoverageSensorTest { public void testLineHitAndConditions() throws IOException { File coverage = new File(baseDir, "src/foo.xoo.itcoverage"); FileUtils.write(coverage, "1:3:4:2"); - DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo"); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo").setLines(10); context.fileSystem().add(inputFile); sensor.execute(context); diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java index 3a626f91bed..0707923d2bb 100644 --- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java @@ -71,7 +71,7 @@ public class OverallCoverageSensorTest { public void testLineHitNoConditions() throws IOException { File coverage = new File(baseDir, "src/foo.xoo.overallcoverage"); FileUtils.write(coverage, "1:3\n\n#comment"); - DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo"); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo").setLines(10); context.fileSystem().add(inputFile); sensor.execute(context); @@ -83,7 +83,7 @@ public class OverallCoverageSensorTest { public void testLineHitAndConditions() throws IOException { File coverage = new File(baseDir, "src/foo.xoo.overallcoverage"); FileUtils.write(coverage, "1:3:4:2"); - DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo"); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo").setLines(10); context.fileSystem().add(inputFile); sensor.execute(context); diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java index a8f14d7dfe2..b3fd1e6e86d 100644 --- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java @@ -71,7 +71,7 @@ public class UtCoverageSensorTest { public void testLineHitNoConditions() throws IOException { File coverage = new File(baseDir, "src/foo.xoo.coverage"); FileUtils.write(coverage, "1:3\n\n#comment"); - DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo"); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo").setLines(10); context.fileSystem().add(inputFile); sensor.execute(context); @@ -85,7 +85,7 @@ public class UtCoverageSensorTest { public void testLineHitAndConditions() throws IOException { File coverage = new File(baseDir, "src/foo.xoo.coverage"); FileUtils.write(coverage, "1:3:4:2"); - DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo"); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo").setLines(10); context.fileSystem().add(inputFile); sensor.execute(context); diff --git a/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java index 32837480dbe..669a467dc52 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java @@ -60,6 +60,7 @@ public class DeprecatedSensorContext extends DefaultSensorContext implements Sen this.index = index; this.project = project; this.coverageFilter = coverageFilter; + } public Project getProject() { @@ -157,12 +158,15 @@ public class DeprecatedSensorContext extends DefaultSensorContext implements Sen @Override public Measure saveMeasure(Resource resource, Metric metric, Double value) { - return saveMeasure(resource, new Measure(metric, value)); + Measure measure = new Measure(metric, value); + coverageFilter.validate(measure, resource.getPath()); + return saveMeasure(resource, measure); } @Override public Measure saveMeasure(Resource resource, Measure measure) { Resource resourceOrProject = resourceOrProject(resource); + if (coverageFilter.accept(resourceOrProject, measure)) { return index.addMeasure(resourceOrProject, measure); } else { @@ -190,11 +194,14 @@ public class DeprecatedSensorContext extends DefaultSensorContext implements Sen @Override public Measure saveMeasure(InputFile inputFile, Metric metric, Double value) { - return saveMeasure(getResource(inputFile), metric, value); + Measure measure = new Measure(metric, value); + coverageFilter.validate(measure, inputFile); + return saveMeasure(getResource(inputFile), measure); } @Override public Measure saveMeasure(InputFile inputFile, Measure measure) { + coverageFilter.validate(measure, inputFile); return saveMeasure(getResource(inputFile), measure); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java b/sonar-batch/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java index 4439043e370..f3ec090b8ff 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java +++ b/sonar-batch/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java @@ -19,13 +19,22 @@ */ package org.sonar.batch.sensor.coverage; +import org.sonar.api.batch.fs.FileSystem; + +import javax.annotation.CheckForNull; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.utils.KeyValueFormat; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; + import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; @@ -42,11 +51,16 @@ public class CoverageExclusions { private final Settings settings; private final Set coverageMetrics; + private final Set byLineMetrics; private Collection resourcePatterns; - public CoverageExclusions(Settings settings) { + private final FileSystem fs; + + public CoverageExclusions(Settings settings, FileSystem fs) { this.settings = settings; + this.fs = fs; this.coverageMetrics = new HashSet<>(); + this.byLineMetrics = new HashSet<>(); // UT coverageMetrics.add(CoreMetrics.COVERAGE); coverageMetrics.add(CoreMetrics.LINE_COVERAGE); @@ -90,10 +104,72 @@ public class CoverageExclusions { coverageMetrics.add(CoreMetrics.NEW_OVERALL_UNCOVERED_LINES); coverageMetrics.add(CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS); + byLineMetrics.add(CoreMetrics.OVERALL_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.COVERED_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.IT_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE); + initPatterns(); } - public boolean accept(Resource resource, Measure measure) { + private boolean isLineMetrics(Metric metric) { + return this.byLineMetrics.contains(metric); + } + + public void validate(Measure measure, InputFile inputFile) { + Metric metric = measure.getMetric(); + + if (!isLineMetrics(metric)) { + return; + } + + Map m = KeyValueFormat.parseIntInt(measure.getData()); + validatePositiveLine(m, inputFile.absolutePath()); + validateMaxLine(m, inputFile); + } + + @CheckForNull + private InputFile getInputFile(String filePath) { + return fs.inputFile(fs.predicates().hasRelativePath(filePath)); + } + + public void validate(Measure measure, String filePath) { + Metric metric = measure.getMetric(); + + if (!isLineMetrics(metric)) { + return; + } + + InputFile inputFile = getInputFile(filePath); + + if (inputFile == null) { + throw new IllegalStateException(String.format("Can't create measure for resource '%s': resource is not indexed as a file", filePath)); + } + + validate(measure, inputFile); + } + + private static void validateMaxLine(Map m, InputFile inputFile) { + int maxLine = inputFile.lines(); + + for (int l : m.keySet()) { + if (l > maxLine) { + throw new IllegalStateException(String.format("Can't create measure for line %d for file '%s' with %d lines", l, inputFile.absolutePath(), maxLine)); + } + } + } + + private static void validatePositiveLine(Map m, String filePath) { + for (int l : m.keySet()) { + if (l <= 0) { + throw new IllegalStateException(String.format("Measure with line %d for file '%s' must be > 0", l, filePath)); + } + } + } + + public boolean accept(Resource resource, Measure measure) { if (isCoverageMetric(measure.getMetric())) { return !hasMatchingPattern(resource); } else { @@ -101,7 +177,7 @@ public class CoverageExclusions { } } - private boolean isCoverageMetric(Metric metric) { + private boolean isCoverageMetric(Metric metric) { return this.coverageMetrics.contains(metric); } 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 12483ea36d0..e8c49dbd0aa 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 @@ -130,7 +130,6 @@ public class CoverageMediumTest { assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey") .doesNotContain(CoreMetrics.LINES_TO_COVER_KEY, CoreMetrics.UNCOVERED_LINES_KEY, CoreMetrics.CONDITIONS_TO_COVER_KEY, CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY); - } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java index 19ecaeb8df2..b978e7e66bb 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java @@ -19,6 +19,11 @@ */ package org.sonar.batch.mediumtest.tests; +import org.hamcrest.Description; + +import org.hamcrest.TypeSafeMatcher; +import org.junit.Rule; +import org.junit.rules.ExpectedException; import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; import org.junit.After; @@ -37,9 +42,12 @@ import static org.assertj.core.api.Assertions.assertThat; public class CoveragePerTestMediumTest { - @org.junit.Rule + @Rule public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException exception = ExpectedException.none(); + public BatchMediumTester tester = BatchMediumTester.builder() .registerPlugin("xoo", new XooPlugin()) .addDefaultQProfile("xoo", "Sonar Way") @@ -56,25 +64,36 @@ public class CoveragePerTestMediumTest { } @Test - public void coveragePerTestInReport() throws IOException { - - File baseDir = temp.getRoot(); + // SONAR-6183 + public void invalidCoverage() throws IOException { + File baseDir = createTestFiles(); File srcDir = new File(baseDir, "src"); - srcDir.mkdir(); - File testDir = new File(baseDir, "test"); - testDir.mkdir(); - File xooFile = new File(srcDir, "sample.xoo"); - FileUtils.write(xooFile, "foo"); + File coverageFile = new File(srcDir, "sample.xoo.coverage"); + FileUtils.write(coverageFile, "0:2\n"); - File xooFile2 = new File(srcDir, "sample2.xoo"); - FileUtils.write(xooFile2, "foo"); + exception.expect(IllegalStateException.class); + exception.expectMessage("Error processing line 1 of file"); + exception.expectCause(new TypeSafeMatcher() { - File xooTestFile = new File(testDir, "sampleTest.xoo"); - FileUtils.write(xooTestFile, "failure\nerror\nok\nskipped"); + @Override + public void describeTo(Description description) { + // nothing to do + } - File xooTestFile2 = new File(testDir, "sample2Test.xoo"); - FileUtils.write(xooTestFile2, "test file tests"); + @Override + protected boolean matchesSafely(Throwable item) { + return item.getMessage().contains("Line number must be strictly positive"); + } + }); + runTask(baseDir); + + } + + @Test + public void coveragePerTestInReport() throws IOException { + File baseDir = createTestFiles(); + File testDir = new File(baseDir, "test"); File xooTestExecutionFile = new File(testDir, "sampleTest.xoo.test"); FileUtils.write(xooTestExecutionFile, "some test:4:::OK:UNIT\n" + @@ -85,18 +104,7 @@ public class CoveragePerTestMediumTest { FileUtils.write(xooCoveragePerTestFile, "some test;src/sample.xoo,10,11;src/sample2.xoo,1,2\n" + "another test;src/sample.xoo,10,20\n"); - TaskResult result = tester.newTask() - .properties(ImmutableMap.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") - .put("sonar.tests", "test") - .build()) - .start(); + TaskResult result = runTask(baseDir); InputFile file = result.inputFile("test/sampleTest.xoo"); org.sonar.batch.protocol.output.BatchReport.CoverageDetail someTest = result.coveragePerTestFor(file, "some test"); @@ -112,4 +120,41 @@ public class CoveragePerTestMediumTest { assertThat(anotherTest.getCoveredFile(0).getCoveredLineList()).containsExactly(10, 20); } + private TaskResult runTask(File baseDir) { + return tester.newTask() + .properties(ImmutableMap.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") + .put("sonar.tests", "test") + .build()) + .start(); + } + + private File createTestFiles() throws IOException { + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "foo"); + + File xooFile2 = new File(srcDir, "sample2.xoo"); + FileUtils.write(xooFile2, "foo"); + + File xooTestFile = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile, "failure\nerror\nok\nskipped"); + + File xooTestFile2 = new File(testDir, "sample2Test.xoo"); + FileUtils.write(xooTestFile2, "test file tests"); + + return baseDir; + } + } diff --git a/sonar-batch/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java b/sonar-batch/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java index 0e2efc2782a..66e508986df 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java @@ -19,30 +19,97 @@ */ package org.sonar.batch.sensor.coverage; +import org.junit.rules.TemporaryFolder; + +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import com.google.common.collect.ImmutableMap; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.resources.File; import org.sonar.api.resources.Resource; +import org.sonar.api.utils.KeyValueFormat; import org.sonar.core.config.ExclusionProperties; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class CoverageExclusionsTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + private Settings settings; + private DefaultFileSystem fs; private CoverageExclusions filter; @Before public void createFilter() { settings = new Settings(new PropertyDefinitions(ExclusionProperties.all())); - filter = new CoverageExclusions(settings); + fs = new DefaultFileSystem(temp.getRoot()); + filter = new CoverageExclusions(settings, fs); + } + + @Test + public void shouldValidateStrictlyPositiveLine() { + DefaultInputFile file = new DefaultInputFile("module", "testfile"); + Measure measure = mock(Measure.class); + Map map = ImmutableMap.of(0, 3); + + String data = KeyValueFormat.format(map); + when(measure.getMetric()).thenReturn(CoreMetrics.IT_CONDITIONS_BY_LINE); + when(measure.getData()).thenReturn(data); + + fs.add(file); + + exception.expect(IllegalStateException.class); + exception.expectMessage("must be > 0"); + filter.validate(measure, "testfile"); + } + + @Test + public void shouldValidateFileExists() { + DefaultInputFile file = new DefaultInputFile("module", "testfile"); + Measure measure = mock(Measure.class); + Map map = ImmutableMap.of(0, 3); + + String data = KeyValueFormat.format(map); + when(measure.getMetric()).thenReturn(CoreMetrics.IT_CONDITIONS_BY_LINE); + when(measure.getData()).thenReturn(data); + + fs.add(file); + + exception.expect(IllegalStateException.class); + exception.expectMessage("resource is not indexed as a file"); + filter.validate(measure, "dummy"); + } + + @Test + public void shouldValidateMaxLine() { + DefaultInputFile file = new DefaultInputFile("module", "testfile"); + file.setLines(10); + Measure measure = mock(Measure.class); + Map map = ImmutableMap.of(11, 3); + + String data = KeyValueFormat.format(map); + when(measure.getMetric()).thenReturn(CoreMetrics.COVERED_CONDITIONS_BY_LINE); + when(measure.getData()).thenReturn(data); + + exception.expect(IllegalStateException.class); + filter.validate(measure, file); } @Test 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 8aa9d38ac47..d11b73583c2 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 @@ -54,7 +54,6 @@ public class DefaultCoverage extends DefaultStorable implements NewCoverage { @Override public DefaultCoverage onFile(InputFile inputFile) { - Preconditions.checkNotNull(inputFile, "file can't be null"); this.inputFile = (DefaultInputFile) inputFile; return this; } @@ -65,6 +64,7 @@ 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; @@ -76,6 +76,9 @@ public class DefaultCoverage extends DefaultStorable implements NewCoverage { @Override public NewCoverage lineHits(int line, int hits) { + validateFile(); + validateLine(line); + if (!hitsByLine.containsKey(line)) { hitsByLine.put(line, hits); if (hits > 0) { @@ -85,8 +88,34 @@ public class DefaultCoverage extends DefaultStorable implements NewCoverage { return this; } + private void validateLine(int line) { + Preconditions.checkState(line <= inputFile.lines(), String.format("Line %d is out of range in the file %s (lines: %d)", line, inputFile.relativePath(), inputFile.lines())); + 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"); + } + @Override public NewCoverage conditions(int line, int conditions, int coveredConditions) { + validateFile(); + validateLine(line); + if (conditions > 0 && !conditionsByLine.containsKey(line)) { totalConditions += conditions; totalCoveredConditions += coveredConditions; @@ -126,8 +155,9 @@ public class DefaultCoverage extends DefaultStorable implements NewCoverage { @Override public void doSave() { - Preconditions.checkNotNull(inputFile, "Call onFile() first"); + validateFile(); Preconditions.checkNotNull(type, "Call ofType() first"); + validateLines(); storage.store(this); } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java index 493547b6698..7895ab3f3ee 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java @@ -19,8 +19,13 @@ */ package org.sonar.api.batch.sensor.internal; +import org.sonar.api.batch.sensor.coverage.NewCoverage; + +import org.junit.rules.ExpectedException; + import java.io.File; import java.io.StringReader; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -37,13 +42,16 @@ import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.config.Settings; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.rule.RuleKey; - import static org.assertj.core.api.Assertions.assertThat; public class SensorContextTesterTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + private SensorContextTester tester; private File baseDir; @@ -150,19 +158,43 @@ public class SensorContextTesterTest { assertThat(tester.duplications()).hasSize(1); } + @Test + public void testCoverageAtLineZero() { + assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 1)).isNull(); + assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 4)).isNull(); + + exception.expect(IllegalStateException.class); + NewCoverage coverage = tester.newCoverage() + .onFile(new DefaultInputFile("foo", "src/Foo.java").initMetadata(new FileMetadata().readMetadata(new StringReader("annot dsf fds foo bar")))) + .ofType(CoverageType.UNIT) + .lineHits(0, 3); + } + + @Test + public void testCoverageAtLineOutOfRange() { + assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 1)).isNull(); + assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 4)).isNull(); + exception.expect(IllegalStateException.class); + + NewCoverage coverage = tester.newCoverage() + .onFile(new DefaultInputFile("foo", "src/Foo.java").initMetadata(new FileMetadata().readMetadata(new StringReader("annot dsf fds foo bar")))) + .ofType(CoverageType.UNIT) + .lineHits(4, 3); + } + @Test public void testLineHits() { assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 1)).isNull(); assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 4)).isNull(); tester.newCoverage() - .onFile(new DefaultInputFile("foo", "src/Foo.java").initMetadata(new FileMetadata().readMetadata(new StringReader("annot dsf fds foo bar")))) + .onFile(new DefaultInputFile("foo", "src/Foo.java").initMetadata(new FileMetadata().readMetadata(new StringReader("annot dsf fds foo bar\nasdas")))) .ofType(CoverageType.UNIT) .lineHits(1, 2) - .lineHits(4, 3) + .lineHits(2, 3) .save(); assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 1)).isEqualTo(2); assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.IT, 1)).isNull(); - assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 4)).isEqualTo(3); + assertThat(tester.lineHits("foo:src/Foo.java", CoverageType.UNIT, 2)).isEqualTo(3); } @Test @@ -170,7 +202,7 @@ public class SensorContextTesterTest { assertThat(tester.conditions("foo:src/Foo.java", CoverageType.UNIT, 1)).isNull(); assertThat(tester.coveredConditions("foo:src/Foo.java", CoverageType.UNIT, 1)).isNull(); tester.newCoverage() - .onFile(new DefaultInputFile("foo", "src/Foo.java").initMetadata(new FileMetadata().readMetadata(new StringReader("annot dsf fds foo bar")))) + .onFile(new DefaultInputFile("foo", "src/Foo.java").initMetadata(new FileMetadata().readMetadata(new StringReader("annot dsf fds foo bar\nasd\nasdas\nasdfas")))) .ofType(CoverageType.UNIT) .conditions(1, 4, 2) .save(); -- 2.39.5