aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java8
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java102
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/ItCoverageSensor.java44
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/OverallCoverageSensor.java44
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/UtCoverageSensor.java44
-rw-r--r--plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java95
-rw-r--r--plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java95
-rw-r--r--plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java101
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java20
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java7
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java21
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java114
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java7
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java86
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/NewCoverage.java58
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java132
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/package-info.java21
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/package-info.java21
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java54
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java6
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java10
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/test/MutableTestCase.java4
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java8
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/DefaultInputFileTest.java6
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java32
27 files changed, 1134 insertions, 10 deletions
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
index a94fe8c055c..48756de3679 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
@@ -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
index 00000000000..2930df6c32a
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java
@@ -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
index 00000000000..6a53ec410e9
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/ItCoverageSensor.java
@@ -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
index 00000000000..3b321ca8129
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/OverallCoverageSensor.java
@@ -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
index 00000000000..224df0522db
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/UtCoverageSensor.java
@@ -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
index 00000000000..1fb232fa3bb
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java
@@ -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
index 00000000000..3a626f91bed
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java
@@ -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
index 00000000000..a8f14d7dfe2
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java
@@ -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();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java
index 4a523b252e1..f2f7b379759 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java
@@ -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.
*/
diff --git a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java
index f6fb5f2d423..9387f3c832e 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java
@@ -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;
@@ -95,6 +97,11 @@ public class DefaultSensorContext implements SensorContext {
}
@Override
+ public NewCoverage newCoverage() {
+ return new DefaultCoverage(sensorStorage);
+ }
+
+ @Override
public NewDependency newDependency() {
return new DefaultDependency(sensorStorage);
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java
index 63109a53d85..be0c84ee4cf 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java
@@ -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
index 00000000000..6a10da86b37
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java
@@ -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"));
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java
index 0455bf55dba..e2787402209 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java
@@ -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;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java
index b8ca587d1b9..a9aae88e06c 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java
@@ -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");
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
index 834325d7792..187c784ff00 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
@@ -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
index 00000000000..fa4fcf9b0c7
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java
@@ -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
index 00000000000..461a7865861
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/NewCoverage.java
@@ -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
index 00000000000..ff0e6aac38a
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java
@@ -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
index 00000000000..4965b41b908
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/package-info.java
@@ -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
index 00000000000..bbf90b94181
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/package-info.java
@@ -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;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
index 278d9ad1741..70f80865045 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
@@ -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) {
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
index 121ba2a874c..6f81ce61226 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
@@ -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);
+
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java
index d4ddac91853..7f9a1b1baf9 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java
@@ -20,18 +20,16 @@
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 {
/**
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/test/MutableTestCase.java b/sonar-plugin-api/src/main/java/org/sonar/api/test/MutableTestCase.java
index a656a76bbb1..50a5e0c21ff 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/test/MutableTestCase.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/test/MutableTestCase.java
@@ -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);
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java b/sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java
index abe7025a02a..a01fa7ae2de 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java
@@ -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";
/**
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/DefaultInputFileTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/DefaultInputFileTest.java
index 6b8b6af9fea..3e3339bf63d 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/DefaultInputFileTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/DefaultInputFileTest.java
@@ -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
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 c1784a24dfc..8fcc6f3a6ce 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
@@ -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();
+ }
}