]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6200 New Sensor API to report UT/IT/Overall coverage
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 22 Apr 2015 20:07:54 +0000 (22:07 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Wed, 22 Apr 2015 20:18:07 +0000 (22:18 +0200)
27 files changed:
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/ItCoverageSensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/OverallCoverageSensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/UtCoverageSensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java
sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java
sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java
sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java
sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/NewCoverage.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java
sonar-plugin-api/src/main/java/org/sonar/api/test/MutableTestCase.java
sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/DefaultInputFileTest.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java

index a94fe8c055c57434b23f1900a04a12f62a2ea0ef..48756de367982bc695160e9be814bf089df84a40 100644 (file)
@@ -20,6 +20,9 @@
 package org.sonar.xoo;
 
 import org.sonar.api.SonarPlugin;
+import org.sonar.xoo.coverage.ItCoverageSensor;
+import org.sonar.xoo.coverage.OverallCoverageSensor;
+import org.sonar.xoo.coverage.UtCoverageSensor;
 import org.sonar.xoo.extensions.XooPostJob;
 import org.sonar.xoo.extensions.XooProjectBuilder;
 import org.sonar.xoo.lang.*;
@@ -70,6 +73,11 @@ public class XooPlugin extends SonarPlugin {
       OneIssueOnDirPerFileSensor.class,
       CreateIssueByInternalKeySensor.class,
 
+      // Coverage
+      UtCoverageSensor.class,
+      ItCoverageSensor.class,
+      OverallCoverageSensor.class,
+
       // Other
       XooProjectBuilder.class,
       XooPostJob.class);
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java
new file mode 100644 (file)
index 0000000..2930df6
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.coverage;
+
+import com.google.common.base.Splitter;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.fs.InputFile;
+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.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.xoo.Xoo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+public abstract class AbstractCoverageSensor implements Sensor {
+  private static final Logger LOG = Loggers.get(AbstractCoverageSensor.class);
+
+  private void processCoverage(InputFile inputFile, SensorContext context) {
+    File coverageFile = new File(inputFile.file().getParentFile(), inputFile.file().getName() + getCoverageExtension());
+    if (coverageFile.exists()) {
+      LOG.debug("Processing " + coverageFile.getAbsolutePath());
+      try {
+        List<String> lines = FileUtils.readLines(coverageFile, context.fileSystem().encoding().name());
+        NewCoverage coverageBuilder = context.newCoverage()
+          .onFile(inputFile)
+          .ofType(getCoverageType());
+        int lineNumber = 0;
+        for (String line : lines) {
+          lineNumber++;
+          if (StringUtils.isBlank(line)) {
+            continue;
+          }
+          if (line.startsWith("#")) {
+            continue;
+          }
+          try {
+            Iterator<String> split = Splitter.on(":").split(line).iterator();
+            int lineId = Integer.parseInt(split.next());
+            int lineHits = Integer.parseInt(split.next());
+            coverageBuilder.lineHits(lineId, lineHits);
+            if (split.hasNext()) {
+              int conditions = Integer.parseInt(split.next());
+              int coveredConditions = Integer.parseInt(split.next());
+              coverageBuilder.conditions(lineId, conditions, coveredConditions);
+            }
+          } catch (Exception e) {
+            throw new IllegalStateException("Error processing line " + lineNumber + " of file " + coverageFile.getAbsolutePath(), e);
+          }
+        }
+        coverageBuilder.save();
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  protected abstract String getCoverageExtension();
+
+  protected abstract CoverageType getCoverageType();
+
+  @Override
+  public void describe(SensorDescriptor descriptor) {
+    descriptor
+      .name(getSensorName())
+      .onlyOnLanguages(Xoo.KEY);
+  }
+
+  protected abstract String getSensorName();
+
+  @Override
+  public void execute(SensorContext context) {
+    for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+      processCoverage(file, context);
+    }
+  }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/ItCoverageSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/ItCoverageSensor.java
new file mode 100644 (file)
index 0000000..6a53ec4
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.coverage;
+
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+
+/**
+ * Parse files *.xoo.itcoverage
+ */
+public class ItCoverageSensor extends AbstractCoverageSensor {
+
+  @Override
+  protected String getCoverageExtension() {
+    return ".itcoverage";
+  }
+
+  @Override
+  protected CoverageType getCoverageType() {
+    return CoverageType.IT;
+  }
+
+  @Override
+  protected String getSensorName() {
+    return "Xoo IT Coverage Sensor";
+  }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/OverallCoverageSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/OverallCoverageSensor.java
new file mode 100644 (file)
index 0000000..3b321ca
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.coverage;
+
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+
+/**
+ * Parse files *.xoo.overallcoverage
+ */
+public class OverallCoverageSensor extends AbstractCoverageSensor {
+
+  @Override
+  protected String getCoverageExtension() {
+    return ".overallcoverage";
+  }
+
+  @Override
+  protected CoverageType getCoverageType() {
+    return CoverageType.OVERALL;
+  }
+
+  @Override
+  protected String getSensorName() {
+    return "Xoo Overall Coverage Sensor";
+  }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/UtCoverageSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/UtCoverageSensor.java
new file mode 100644 (file)
index 0000000..224df05
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.coverage;
+
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+
+/**
+ * Parse files *.xoo.coverage
+ */
+public class UtCoverageSensor extends AbstractCoverageSensor {
+
+  @Override
+  protected String getCoverageExtension() {
+    return ".coverage";
+  }
+
+  @Override
+  protected CoverageType getCoverageType() {
+    return CoverageType.UNIT;
+  }
+
+  @Override
+  protected String getSensorName() {
+    return "Xoo UT Coverage Sensor";
+  }
+
+}
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
new file mode 100644 (file)
index 0000000..1fb232f
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.coverage;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ItCoverageSensorTest {
+
+  private ItCoverageSensor sensor;
+  private SensorContextTester context;
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private File baseDir;
+
+  @Before
+  public void prepare() throws IOException {
+    baseDir = temp.newFolder();
+    sensor = new ItCoverageSensor();
+    context = SensorContextTester.create(baseDir);
+  }
+
+  @Test
+  public void testDescriptor() {
+    sensor.describe(new DefaultSensorDescriptor());
+  }
+
+  @Test
+  public void testNoExecutionIfNoCoverageFile() {
+    DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
+    context.fileSystem().add(inputFile);
+    sensor.execute(context);
+  }
+
+  @Test
+  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");
+    context.fileSystem().add(inputFile);
+
+    sensor.execute(context);
+
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.IT, 1)).isEqualTo(3);
+  }
+
+  @Test
+  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");
+    context.fileSystem().add(inputFile);
+
+    sensor.execute(context);
+
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.IT, 1)).isEqualTo(3);
+    assertThat(context.conditions("foo:src/foo.xoo", CoverageType.IT, 1)).isEqualTo(4);
+    assertThat(context.coveredConditions("foo:src/foo.xoo", CoverageType.IT, 1)).isEqualTo(2);
+  }
+}
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
new file mode 100644 (file)
index 0000000..3a626f9
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.coverage;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OverallCoverageSensorTest {
+
+  private OverallCoverageSensor sensor;
+  private SensorContextTester context;
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private File baseDir;
+
+  @Before
+  public void prepare() throws IOException {
+    baseDir = temp.newFolder();
+    sensor = new OverallCoverageSensor();
+    context = SensorContextTester.create(baseDir);
+  }
+
+  @Test
+  public void testDescriptor() {
+    sensor.describe(new DefaultSensorDescriptor());
+  }
+
+  @Test
+  public void testNoExecutionIfNoCoverageFile() {
+    DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
+    context.fileSystem().add(inputFile);
+    sensor.execute(context);
+  }
+
+  @Test
+  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");
+    context.fileSystem().add(inputFile);
+
+    sensor.execute(context);
+
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.OVERALL, 1)).isEqualTo(3);
+  }
+
+  @Test
+  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");
+    context.fileSystem().add(inputFile);
+
+    sensor.execute(context);
+
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.OVERALL, 1)).isEqualTo(3);
+    assertThat(context.conditions("foo:src/foo.xoo", CoverageType.OVERALL, 1)).isEqualTo(4);
+    assertThat(context.coveredConditions("foo:src/foo.xoo", CoverageType.OVERALL, 1)).isEqualTo(2);
+  }
+}
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
new file mode 100644 (file)
index 0000000..a8f14d7
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.coverage;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UtCoverageSensorTest {
+
+  private UtCoverageSensor sensor;
+  private SensorContextTester context;
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private File baseDir;
+
+  @Before
+  public void prepare() throws IOException {
+    baseDir = temp.newFolder();
+    sensor = new UtCoverageSensor();
+    context = SensorContextTester.create(baseDir);
+  }
+
+  @Test
+  public void testDescriptor() {
+    sensor.describe(new DefaultSensorDescriptor());
+  }
+
+  @Test
+  public void testNoExecutionIfNoCoverageFile() {
+    DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
+    context.fileSystem().add(inputFile);
+    sensor.execute(context);
+  }
+
+  @Test
+  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");
+    context.fileSystem().add(inputFile);
+
+    sensor.execute(context);
+
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.UNIT, 1)).isEqualTo(3);
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.IT, 1)).isNull();
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.OVERALL, 1)).isNull();
+  }
+
+  @Test
+  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");
+    context.fileSystem().add(inputFile);
+
+    sensor.execute(context);
+
+    assertThat(context.lineHits("foo:src/foo.xoo", CoverageType.UNIT, 1)).isEqualTo(3);
+    assertThat(context.conditions("foo:src/foo.xoo", CoverageType.UNIT, 1)).isEqualTo(4);
+    assertThat(context.conditions("foo:src/foo.xoo", CoverageType.IT, 1)).isNull();
+    assertThat(context.conditions("foo:src/foo.xoo", CoverageType.OVERALL, 1)).isNull();
+    assertThat(context.coveredConditions("foo:src/foo.xoo", CoverageType.UNIT, 1)).isEqualTo(2);
+    assertThat(context.coveredConditions("foo:src/foo.xoo", CoverageType.IT, 1)).isNull();
+    assertThat(context.coveredConditions("foo:src/foo.xoo", CoverageType.OVERALL, 1)).isNull();
+  }
+}
index 4a523b252e189064be24b54870011308b214fab7..f2f7b3797590f7674a80231255d0eb17501eb0e2 100644 (file)
@@ -42,12 +42,11 @@ import org.sonar.batch.dependency.DependencyCache;
 import org.sonar.batch.duplication.DuplicationCache;
 import org.sonar.batch.index.Cache.Entry;
 import org.sonar.batch.issue.IssueCache;
-import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.*;
 import org.sonar.batch.protocol.output.BatchReport.Component;
 import org.sonar.batch.protocol.output.BatchReport.Metadata;
 import org.sonar.batch.protocol.output.BatchReport.Range;
 import org.sonar.batch.protocol.output.BatchReport.Symbols.Symbol;
-import org.sonar.batch.protocol.output.BatchReportReader;
 import org.sonar.batch.report.BatchReportUtils;
 import org.sonar.batch.report.ReportPublisher;
 import org.sonar.batch.scan.ProjectScanContainer;
@@ -248,6 +247,23 @@ public class TaskResult implements org.sonar.batch.mediumtest.ScanTaskObserver {
     return Collections.emptyList();
   }
 
+  @CheckForNull
+  public BatchReport.Coverage coverageFor(InputFile file, int line) {
+    int ref = reportComponents.get(((DefaultInputFile) file).key()).getRef();
+    try (InputStream inputStream = FileUtils.openInputStream(getReportReader().readComponentCoverage(ref))) {
+      BatchReport.Coverage coverage = BatchReport.Coverage.PARSER.parseDelimitedFrom(inputStream);
+      while (coverage != null) {
+        if (coverage.getLine() == line) {
+          return coverage;
+        }
+        coverage = BatchReport.Coverage.PARSER.parseDelimitedFrom(inputStream);
+      }
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+    return null;
+  }
+
   /**
    * @return null if no dependency else return dependency weight.
    */
index f6fb5f2d42382d42df7d793b7c36c52d414f1870..9387f3c832ec5aad34f2b0cc3fc3fb2fd8556063 100644 (file)
@@ -23,6 +23,8 @@ import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.fs.FileSystem;
 import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.coverage.NewCoverage;
+import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
 import org.sonar.api.batch.sensor.dependency.NewDependency;
 import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency;
 import org.sonar.api.batch.sensor.duplication.NewDuplication;
@@ -94,6 +96,11 @@ public class DefaultSensorContext implements SensorContext {
     return new DefaultDuplication(sensorStorage);
   }
 
+  @Override
+  public NewCoverage newCoverage() {
+    return new DefaultCoverage(sensorStorage);
+  }
+
   @Override
   public NewDependency newDependency() {
     return new DefaultDependency(sensorStorage);
index 63109a53d853b588b084fd8cace01cea7e157d57..be0c84ee4cf703bda4f2d1c60e6ee7c7da09ed9d 100644 (file)
@@ -30,6 +30,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.measure.MetricFinder;
 import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
 import org.sonar.api.batch.sensor.duplication.Duplication;
 import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplication;
 import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
@@ -51,6 +53,7 @@ import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Scopes;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.source.Symbol;
+import org.sonar.api.utils.KeyValueFormat;
 import org.sonar.batch.duplication.DuplicationCache;
 import org.sonar.batch.index.BatchResource;
 import org.sonar.batch.index.DefaultIndex;
@@ -272,4 +275,22 @@ public class DefaultSensorStorage implements SensorStorage {
 
       }));
   }
+
+  @Override
+  public void store(DefaultCoverage defaultCoverage) {
+    File file = getFile(defaultCoverage.inputFile());
+    CoverageType type = defaultCoverage.type();
+    if (defaultCoverage.linesToCover() > 0) {
+      sonarIndex.addMeasure(file, new org.sonar.api.measures.Measure(type.linesToCover(), (double) defaultCoverage.linesToCover()));
+      sonarIndex.addMeasure(file, new org.sonar.api.measures.Measure(type.uncoveredLines(), (double) (defaultCoverage.linesToCover() - defaultCoverage.coveredLines())));
+      sonarIndex.addMeasure(file, new org.sonar.api.measures.Measure(type.lineHitsData()).setData(KeyValueFormat.format(defaultCoverage.hitsByLine())));
+    }
+    if (defaultCoverage.conditions() > 0) {
+      sonarIndex.addMeasure(file, new org.sonar.api.measures.Measure(type.conditionsToCover(), (double) defaultCoverage.conditions()));
+      sonarIndex.addMeasure(file, new org.sonar.api.measures.Measure(type.uncoveredConditions(), (double) (defaultCoverage.conditions() - defaultCoverage.coveredConditions())));
+      sonarIndex.addMeasure(file, new org.sonar.api.measures.Measure(type.coveredConditionsByLine()).setData(KeyValueFormat.format(defaultCoverage.coveredConditionsByLine())));
+      sonarIndex.addMeasure(file, new org.sonar.api.measures.Measure(type.conditionsByLine()).setData(KeyValueFormat.format(defaultCoverage.conditionsByLine())));
+    }
+
+  }
 }
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java
new file mode 100644 (file)
index 0000000..6a10da8
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.mediumtest.coverage;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.batch.mediumtest.BatchMediumTester;
+import org.sonar.batch.mediumtest.TaskResult;
+import org.sonar.xoo.XooPlugin;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CoverageTest {
+
+  @org.junit.Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  public BatchMediumTester tester = BatchMediumTester.builder()
+    .registerPlugin("xoo", new XooPlugin())
+    .addDefaultQProfile("xoo", "Sonar Way")
+    .build();
+
+  @Before
+  public void prepare() {
+    tester.start();
+  }
+
+  @After
+  public void stop() {
+    tester.stop();
+  }
+
+  @Test
+  public void unitTests() throws IOException {
+
+    File baseDir = temp.newFolder();
+    File srcDir = new File(baseDir, "src");
+    srcDir.mkdir();
+
+    File xooFile = new File(srcDir, "sample.xoo");
+    File xooUtCoverageFile = new File(srcDir, "sample.xoo.coverage");
+    FileUtils.write(xooFile, "function foo() {\n  if (a && b) {\nalert('hello');\n}\n}");
+    FileUtils.write(xooUtCoverageFile, "2:2:2:1\n3:1");
+
+    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, 2).getUtHits()).isTrue();
+    assertThat(result.coverageFor(file, 2).getItHits()).isFalse();
+    assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(2);
+    assertThat(result.coverageFor(file, 2).getUtCoveredConditions()).isEqualTo(1);
+    assertThat(result.coverageFor(file, 2).getItCoveredConditions()).isEqualTo(0);
+    assertThat(result.coverageFor(file, 2).getOverallCoveredConditions()).isEqualTo(0);
+
+    assertThat(result.measures()).contains(new DefaultMeasure<Integer>()
+      .forMetric(CoreMetrics.LINES_TO_COVER)
+      .onFile(new DefaultInputFile("com.foo.project", "src/sample.xoo"))
+      .withValue(2));
+
+    assertThat(result.measures()).contains(new DefaultMeasure<Integer>()
+      .forMetric(CoreMetrics.UNCOVERED_LINES)
+      .onFile(new DefaultInputFile("com.foo.project", "src/sample.xoo"))
+      .withValue(0));
+
+    assertThat(result.measures()).contains(new DefaultMeasure<Integer>()
+      .forMetric(CoreMetrics.CONDITIONS_TO_COVER)
+      .onFile(new DefaultInputFile("com.foo.project", "src/sample.xoo"))
+      .withValue(2));
+
+    assertThat(result.measures()).contains(new DefaultMeasure<String>()
+      .forMetric(CoreMetrics.COVERED_CONDITIONS_BY_LINE)
+      .onFile(new DefaultInputFile("com.foo.project", "src/sample.xoo"))
+      .withValue("2=1"));
+  }
+
+}
index 0455bf55dba638346e8cf4f40d8a852e601bc827..e2787402209ff2fafd87cff6c971b96c09b34f6d 100644 (file)
@@ -100,7 +100,7 @@ public class HighlightingMediumTest {
     File srcDir = new File(baseDir, "src");
     srcDir.mkdir();
 
-    int nbFiles = 10;
+    int nbFiles = 100;
     int ruleCount = 100000;
     int nblines = 1000;
     int linesize = ruleCount / nblines;
index b8ca587d1b96e465038b341d006d40a968d4912f..a9aae88e06c449895561417ba472cb2c1849fa67 100644 (file)
@@ -57,7 +57,7 @@ public class SymbolMediumTest {
   }
 
   @Test
-  public void computeSyntaxHighlightingOnTempProject() throws IOException {
+  public void computeSymbolReferencesOnTempProject() throws IOException {
 
     File baseDir = temp.newFolder();
     File srcDir = new File(baseDir, "src");
index 834325d779210145597ce5ae0856c8ce6fc6da10..187c784ff0066f5380578bed9928e7c400e9dec6 100644 (file)
@@ -24,6 +24,7 @@ import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.CpdMapping;
 import org.sonar.api.batch.fs.FileSystem;
 import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.sensor.coverage.NewCoverage;
 import org.sonar.api.batch.sensor.dependency.NewDependency;
 import org.sonar.api.batch.sensor.duplication.NewDuplication;
 import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
@@ -99,6 +100,12 @@ public interface SensorContext {
 
   // ------------ TESTS ------------
 
+  /**
+   * Builder to define coverage in a file.
+   * Don't forget to call {@link NewDuplication#save()}.
+   */
+  NewCoverage newCoverage();
+
   // TODO
 
   // ------------ DEPENDENCIES ------------
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
new file mode 100644 (file)
index 0000000..fa4fcf9
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.sensor.coverage;
+
+import com.google.common.annotations.Beta;
+import org.sonar.api.measures.Metric;
+
+import static org.sonar.api.measures.CoreMetrics.*;
+
+/**
+ * Different coverage categories.
+ * @since 5.2
+ */
+@Beta
+public enum CoverageType {
+
+  UNIT(LINES_TO_COVER, UNCOVERED_LINES, COVERAGE_LINE_HITS_DATA, CONDITIONS_TO_COVER, UNCOVERED_CONDITIONS, COVERED_CONDITIONS_BY_LINE, CONDITIONS_BY_LINE),
+  IT(IT_LINES_TO_COVER, IT_UNCOVERED_LINES, IT_COVERAGE_LINE_HITS_DATA, IT_CONDITIONS_TO_COVER, IT_UNCOVERED_CONDITIONS, IT_COVERED_CONDITIONS_BY_LINE, IT_CONDITIONS_BY_LINE),
+  OVERALL(OVERALL_LINES_TO_COVER, OVERALL_UNCOVERED_LINES, OVERALL_COVERAGE_LINE_HITS_DATA, OVERALL_CONDITIONS_TO_COVER, OVERALL_UNCOVERED_CONDITIONS,
+    OVERALL_COVERED_CONDITIONS_BY_LINE, OVERALL_CONDITIONS_BY_LINE);
+
+  private final Metric linesToCover;
+  private final Metric uncoveredLines;
+  private final Metric lineHitsData;
+  private final Metric conditionsToCover;
+  private final Metric uncoveredConditions;
+  private final Metric coveredConditionsByLine;
+  private final Metric conditionsByLine;
+
+  private CoverageType(Metric linesToCover, Metric uncoveredLines, Metric lineHitsData, Metric conditionsToCover, Metric uncoveredConditions, Metric coveredConditionsByLine,
+    Metric conditionsByLine) {
+    this.linesToCover = linesToCover;
+    this.uncoveredLines = uncoveredLines;
+    this.lineHitsData = lineHitsData;
+    this.conditionsToCover = conditionsToCover;
+    this.uncoveredConditions = uncoveredConditions;
+    this.coveredConditionsByLine = coveredConditionsByLine;
+    this.conditionsByLine = conditionsByLine;
+  }
+
+  public Metric linesToCover() {
+    return linesToCover;
+  }
+
+  public Metric uncoveredLines() {
+    return uncoveredLines;
+  }
+
+  public Metric lineHitsData() {
+    return lineHitsData;
+  }
+
+  public Metric conditionsToCover() {
+    return conditionsToCover;
+  }
+
+  public Metric uncoveredConditions() {
+    return uncoveredConditions;
+  }
+
+  public Metric coveredConditionsByLine() {
+    return coveredConditionsByLine;
+  }
+
+  public Metric conditionsByLine() {
+    return conditionsByLine;
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/NewCoverage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/NewCoverage.java
new file mode 100644 (file)
index 0000000..461a786
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.sensor.coverage;
+
+import com.google.common.annotations.Beta;
+import org.sonar.api.batch.fs.InputFile;
+
+/**
+ * This builder is used to define code coverage by tests of a given type (UT/IT/Overall) on files.
+ * @since 5.2
+ */
+@Beta
+public interface NewCoverage {
+
+  /**
+   * The covered file.
+   */
+  NewCoverage onFile(InputFile inputFile);
+
+  NewCoverage ofType(CoverageType type);
+
+  /**
+   * Call this method as many time as needed to report coverage hits per line. This method should only be called for executable lines.
+   * @param line Line number (starts at 1).
+   * @param hits Number of time the line was hit.
+   */
+  NewCoverage lineHits(int line, int hits);
+
+  /**
+   * Call this method as many time as needed to report coverage of conditions.
+   * @param line Line number (starts at 1).
+   * @param conditions Number of conditions on this line (should be greater than 1).
+   * @param coveredConditions Number of covered conditions.
+   */
+  NewCoverage conditions(int line, int conditions, int coveredConditions);
+
+  /**
+   * Call this method only once when your are done with defining coverage of the file.
+   */
+  void save();
+}
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
new file mode 100644 (file)
index 0000000..ff0e6aa
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.sensor.coverage.internal;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+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;
+  private CoverageType type;
+  private int totalCoveredLines = 0, totalConditions = 0, totalCoveredConditions = 0;
+  private SortedMap<Integer, Integer> hitsByLine = Maps.newTreeMap();
+  private SortedMap<Integer, Integer> conditionsByLine = Maps.newTreeMap();
+  private SortedMap<Integer, Integer> coveredConditionsByLine = Maps.newTreeMap();
+
+  public DefaultCoverage() {
+    super();
+  }
+
+  public DefaultCoverage(@Nullable SensorStorage storage) {
+    super(storage);
+  }
+
+  @Override
+  public DefaultCoverage onFile(InputFile inputFile) {
+    Preconditions.checkNotNull(inputFile, "file can't be null");
+    this.inputFile = (DefaultInputFile) inputFile;
+    return this;
+  }
+
+  public InputFile inputFile() {
+    return inputFile;
+  }
+
+  @Override
+  public NewCoverage ofType(CoverageType type) {
+    Preconditions.checkNotNull(type, "type can't be null");
+    this.type = type;
+    return this;
+  }
+
+  public CoverageType type() {
+    return type;
+  }
+
+  @Override
+  public NewCoverage lineHits(int line, int hits) {
+    if (!hitsByLine.containsKey(line)) {
+      hitsByLine.put(line, hits);
+      if (hits > 0) {
+        totalCoveredLines += 1;
+      }
+    }
+    return this;
+  }
+
+  @Override
+  public NewCoverage conditions(int line, int conditions, int coveredConditions) {
+    if (conditions > 0 && !conditionsByLine.containsKey(line)) {
+      totalConditions += conditions;
+      totalCoveredConditions += coveredConditions;
+      conditionsByLine.put(line, conditions);
+      coveredConditionsByLine.put(line, coveredConditions);
+    }
+    return this;
+  }
+
+  public int coveredLines() {
+    return totalCoveredLines;
+  }
+
+  public int linesToCover() {
+    return hitsByLine.size();
+  }
+
+  public int conditions() {
+    return totalConditions;
+  }
+
+  public int coveredConditions() {
+    return totalCoveredConditions;
+  }
+
+  public SortedMap<Integer, Integer> hitsByLine() {
+    return Collections.unmodifiableSortedMap(hitsByLine);
+  }
+
+  public SortedMap<Integer, Integer> conditionsByLine() {
+    return Collections.unmodifiableSortedMap(conditionsByLine);
+  }
+
+  public SortedMap<Integer, Integer> coveredConditionsByLine() {
+    return Collections.unmodifiableSortedMap(coveredConditionsByLine);
+  }
+
+  @Override
+  public void doSave() {
+    Preconditions.checkNotNull(inputFile, "Call onFile() first");
+    Preconditions.checkNotNull(type, "Call ofType() first");
+    storage.store(this);
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/package-info.java
new file mode 100644 (file)
index 0000000..4965b41
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.api.batch.sensor.coverage.internal;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/package-info.java
new file mode 100644 (file)
index 0000000..bbf90b9
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.api.batch.sensor.coverage;
index 278d9ad174167817ff3ae143d5a9408738cbb621..70f80865045a12543663e54acaaac3f00ac796d8 100644 (file)
@@ -32,6 +32,9 @@ import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
 import org.sonar.api.batch.sensor.Sensor;
 import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.coverage.CoverageType;
+import org.sonar.api.batch.sensor.coverage.NewCoverage;
+import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
 import org.sonar.api.batch.sensor.dependency.Dependency;
 import org.sonar.api.batch.sensor.dependency.NewDependency;
 import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency;
@@ -170,11 +173,52 @@ public class SensorContextTester implements SensorContext {
     }
   }
 
+  @CheckForNull
+  public Integer lineHits(String fileKey, CoverageType type, int line) {
+    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
+    if (defaultCoverageByType == null) {
+      return null;
+    }
+    if (defaultCoverageByType.containsKey(type)) {
+      return defaultCoverageByType.get(type).hitsByLine().get(line);
+    }
+    return null;
+  }
+
+  @CheckForNull
+  public Integer conditions(String fileKey, CoverageType type, int line) {
+    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
+    if (defaultCoverageByType == null) {
+      return null;
+    }
+    if (defaultCoverageByType.containsKey(type)) {
+      return defaultCoverageByType.get(type).conditionsByLine().get(line);
+    }
+    return null;
+  }
+
+  @CheckForNull
+  public Integer coveredConditions(String fileKey, CoverageType type, int line) {
+    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
+    if (defaultCoverageByType == null) {
+      return null;
+    }
+    if (defaultCoverageByType.containsKey(type)) {
+      return defaultCoverageByType.get(type).coveredConditionsByLine().get(line);
+    }
+    return null;
+  }
+
   @Override
   public NewHighlighting newHighlighting() {
     return new DefaultHighlighting(sensorStorage);
   }
 
+  @Override
+  public NewCoverage newCoverage() {
+    return new DefaultCoverage(sensorStorage);
+  }
+
   public List<TypeOfText> highlightingTypeAt(String componentKey, int line, int lineOffset) {
     DefaultHighlighting syntaxHighlightingData = sensorStorage.highlightingByComponent.get(componentKey);
     if (syntaxHighlightingData == null) {
@@ -240,6 +284,7 @@ public class SensorContextTester implements SensorContext {
     private Map<String, List<Issue>> issuesByComponent = new HashMap<>();
 
     private Map<String, DefaultHighlighting> highlightingByComponent = new HashMap<>();
+    private Map<String, Map<CoverageType, DefaultCoverage>> coverageByComponent = new HashMap<>();
 
     private List<Duplication> duplications = new ArrayList<>();
     private List<Dependency> dependencies = new ArrayList<>();
@@ -285,6 +330,15 @@ public class SensorContextTester implements SensorContext {
       highlightingByComponent.put(getKey(highlighting.inputFile()), highlighting);
     }
 
+    @Override
+    public void store(DefaultCoverage defaultCoverage) {
+      String key = getKey(defaultCoverage.inputFile());
+      if (!coverageByComponent.containsKey(key)) {
+        coverageByComponent.put(key, new HashMap<CoverageType, DefaultCoverage>());
+      }
+      coverageByComponent.get(key).put(defaultCoverage.type(), defaultCoverage);
+    }
+
     @CheckForNull
     private String getKey(@Nullable InputPath inputPath) {
       if (inputPath == null) {
index 121ba2a874c5c3ad2ede2cf175e6392d0b3d0efe..6f81ce61226cdfdb12a642eb203f2b82d00e2726 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.api.batch.sensor.internal;
 
 import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
 import org.sonar.api.batch.sensor.dependency.Dependency;
 import org.sonar.api.batch.sensor.duplication.Duplication;
 import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
@@ -42,4 +43,9 @@ public interface SensorStorage extends BatchComponent {
 
   void store(DefaultHighlighting highlighting);
 
+  /**
+   * @since 5.2
+   */
+  void store(DefaultCoverage defaultCoverage);
+
 }
index d4ddac918538f42ac1a2e1ab37204b9201790fa3..7f9a1b1baf9e5e350b65a83d91d819a4ca37ecc7 100644 (file)
 package org.sonar.api.measures;
 
 import com.google.common.collect.Maps;
+import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.utils.KeyValueFormat;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.SortedMap;
+import java.util.*;
 
 /**
  * @since 2.7
+ * @deprecated since 5.2 use {@link SensorContext#newCoverage()}
  */
+@Deprecated
 public final class CoverageMeasuresBuilder {
 
   /**
index a656a76bbb1256ebe3d48278bf309fbc36f5302d..50a5e0c21ff01dff53a02406aa289c5214e62d37 100644 (file)
@@ -32,6 +32,10 @@ public interface MutableTestCase extends TestCase {
 
   MutableTestCase setStackTrace(@Nullable String s);
 
+  /**
+   * @deprecated since 5.2 not used
+   */
+  @Deprecated
   MutableTestCase setType(@Nullable String s);
 
   MutableTestCase setCoverageBlock(Testable testable, List<Integer> lines);
index abe7025a02ac56f3135c9a734d1815c0c7c3f99c..a01fa7ae2deddbca2445bdbd63e5d60419912068 100644 (file)
@@ -30,7 +30,15 @@ public interface TestCase {
     }
   }
 
+  /**
+   * @deprecated since 5.2 not used
+   */
+  @Deprecated
   String TYPE_UNIT = "UNIT";
+  /**
+   * @deprecated since 5.2 not used
+   */
+  @Deprecated
   String TYPE_INTEGRATION = "INTEGRATION";
 
   /**
index 6b8b6af9fea8fc1631df9885cfed87ffca32d115..3e3339bf63d0c7e9a1c97084a3a10dc922081aa6 100644 (file)
@@ -166,6 +166,12 @@ public class DefaultInputFileTest {
     } catch (Exception e) {
       assertThat(e).hasMessage("Start pointer [line=1, lineOffset=0] should be before end pointer [line=1, lineOffset=0]");
     }
+    try {
+      file.newRange(file.newPointer(1, 0), file.newPointer(1, 10));
+      fail();
+    } catch (Exception e) {
+      assertThat(e).hasMessage("10 is not a valid line offset for pointer. File [moduleKey=ABCDE, relative=src/Foo.php, basedir=null] has 9 character(s) at line 1");
+    }
   }
 
   @Test
index c1784a24dfcc60e9ed94404ae2963c35dce336e8..8fcc6f3a6ce656e5888df88e5fb64d8f2e5da648 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.fs.internal.FileMetadata;
 import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
+import org.sonar.api.batch.sensor.coverage.CoverageType;
 import org.sonar.api.batch.sensor.highlighting.TypeOfText;
 import org.sonar.api.config.Settings;
 import org.sonar.api.measures.CoreMetrics;
@@ -176,4 +177,35 @@ public class SensorContextTesterTest {
       .save();
     assertThat(tester.dependencies()).hasSize(1);
   }
+
+  @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"))))
+      .ofType(CoverageType.UNIT)
+      .lineHits(1, 2)
+      .lineHits(4, 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);
+  }
+
+  @Test
+  public void testConditions() {
+    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"))))
+      .ofType(CoverageType.UNIT)
+      .conditions(1, 4, 2)
+      .save();
+    assertThat(tester.conditions("foo:src/Foo.java", CoverageType.UNIT, 1)).isEqualTo(4);
+    assertThat(tester.coveredConditions("foo:src/Foo.java", CoverageType.UNIT, 1)).isEqualTo(2);
+
+    assertThat(tester.conditions("foo:src/Foo.java", CoverageType.IT, 1)).isNull();
+    assertThat(tester.coveredConditions("foo:src/Foo.java", CoverageType.IT, 1)).isNull();
+  }
 }