]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6183 Validate that coverage measures are correct regarding line number and...
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 13 Nov 2015 10:54:47 +0000 (11:54 +0100)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 16 Nov 2015 14:21:43 +0000 (15:21 +0100)
it/it-tests/src/test/java/it/test/CoverageTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java
sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java
sonar-batch/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java
sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java
sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java
sonar-batch/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java

index 1ca241841cb1eb933c5de24df741719e6ca33898..70d04b20f3d5f8940ce6748ef6e3413a3425bdcd 100644 (file)
@@ -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;
 
index 1fb232fa3bbb339112c8507ef8bc3358279f2f2d..405f132d0d446480ed0ff8bd4305aa6ea62a9572 100644 (file)
@@ -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);
index 3a626f91bed81159f751c9f6d8c97b36f9b4df7d..0707923d2bb539ff81e56fb984bac35c43a0a386 100644 (file)
@@ -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);
index a8f14d7dfe273cf67f19e3df3b3f2bc8219b9252..b3fd1e6e86d4404ef0e9ddbd438055516fddad0e 100644 (file)
@@ -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);
index 32837480dbef38df4e72f9e0b0fc44c3d7f5c724..669a467dc5221a72ae739e49033fd0f35ce0eae7 100644 (file)
@@ -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);
   }
 
index 4439043e370b75ef5288ee68b89e188690ef7f9a..f3ec090b8ffed411e551809579e7b4ac57f24dd5 100644 (file)
  */
 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<Metric> coverageMetrics;
+  private final Set<Metric> byLineMetrics;
   private Collection<WildcardPattern> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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);
   }
 
index 12483ea36d0abec83601ff298b47c519c8d743f0..e8c49dbd0aa6a6b2247046f623d243f6f5e5af3c 100644 (file)
@@ -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);
-
   }
 
 }
index 19ecaeb8df246cc88c62eb3529c1149049c716cd..b978e7e66bbc75f4481ff34b26b5ae65909efc9f 100644 (file)
  */
 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<Throwable>() {
 
-    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.<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")
-        .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.<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")
+        .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;
+  }
+
 }
index 0e2efc2782af00842afed8023b63d43b4895ce4f..66e508986df555902442d3c521557c1d81dd1bbd 100644 (file)
  */
 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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
index 8aa9d38ac478b7924ceda3c24d493fc57b24d894..d11b73583c26d06ff097581cfb8d9e56af8eb0c5 100644 (file)
@@ -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);
   }
 
index 493547b669839a803044aaa98432e991fa31735b..7895ab3f3eee44287437e5dae895806824123094 100644 (file)
  */
 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();