From c96993d22c7b1a41b4c0152985e8193496cdaaae Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Wed, 22 Apr 2015 22:07:54 +0200 Subject: [PATCH] SONAR-6200 New Sensor API to report UT/IT/Overall coverage --- .../main/java/org/sonar/xoo/XooPlugin.java | 8 ++ .../xoo/coverage/AbstractCoverageSensor.java | 102 ++++++++++++++ .../sonar/xoo/coverage/ItCoverageSensor.java | 44 ++++++ .../xoo/coverage/OverallCoverageSensor.java | 44 ++++++ .../sonar/xoo/coverage/UtCoverageSensor.java | 44 ++++++ .../xoo/coverage/ItCoverageSensorTest.java | 95 +++++++++++++ .../coverage/OverallCoverageSensorTest.java | 95 +++++++++++++ .../xoo/coverage/UtCoverageSensorTest.java | 101 ++++++++++++++ .../sonar/batch/mediumtest/TaskResult.java | 20 ++- .../batch/sensor/DefaultSensorContext.java | 7 + .../batch/sensor/DefaultSensorStorage.java | 21 +++ .../mediumtest/coverage/CoverageTest.java | 114 +++++++++++++++ .../highlighting/HighlightingMediumTest.java | 2 +- .../mediumtest/symbol/SymbolMediumTest.java | 2 +- .../sonar/api/batch/sensor/SensorContext.java | 7 + .../batch/sensor/coverage/CoverageType.java | 86 ++++++++++++ .../batch/sensor/coverage/NewCoverage.java | 58 ++++++++ .../coverage/internal/DefaultCoverage.java | 132 ++++++++++++++++++ .../coverage/internal/package-info.java | 21 +++ .../batch/sensor/coverage/package-info.java | 21 +++ .../sensor/internal/SensorContextTester.java | 54 +++++++ .../batch/sensor/internal/SensorStorage.java | 6 + .../api/measures/CoverageMeasuresBuilder.java | 10 +- .../org/sonar/api/test/MutableTestCase.java | 4 + .../java/org/sonar/api/test/TestCase.java | 8 ++ .../fs/internal/DefaultInputFileTest.java | 6 + .../internal/SensorContextTesterTest.java | 32 +++++ 27 files changed, 1134 insertions(+), 10 deletions(-) create mode 100644 plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java create mode 100644 plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/ItCoverageSensor.java create mode 100644 plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/OverallCoverageSensor.java create mode 100644 plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/UtCoverageSensor.java create mode 100644 plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/ItCoverageSensorTest.java create mode 100644 plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/OverallCoverageSensorTest.java create mode 100644 plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/coverage/UtCoverageSensorTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/CoverageType.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/NewCoverage.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/DefaultCoverage.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/internal/package-info.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/coverage/package-info.java 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 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 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; @@ -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); 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.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() + .forMetric(CoreMetrics.LINES_TO_COVER) + .onFile(new DefaultInputFile("com.foo.project", "src/sample.xoo")) + .withValue(2)); + + assertThat(result.measures()).contains(new DefaultMeasure() + .forMetric(CoreMetrics.UNCOVERED_LINES) + .onFile(new DefaultInputFile("com.foo.project", "src/sample.xoo")) + .withValue(0)); + + assertThat(result.measures()).contains(new DefaultMeasure() + .forMetric(CoreMetrics.CONDITIONS_TO_COVER) + .onFile(new DefaultInputFile("com.foo.project", "src/sample.xoo")) + .withValue(2)); + + assertThat(result.measures()).contains(new DefaultMeasure() + .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 hitsByLine = Maps.newTreeMap(); + private SortedMap conditionsByLine = Maps.newTreeMap(); + private SortedMap 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 hitsByLine() { + return Collections.unmodifiableSortedMap(hitsByLine); + } + + public SortedMap conditionsByLine() { + return Collections.unmodifiableSortedMap(conditionsByLine); + } + + public SortedMap 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 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 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 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 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> issuesByComponent = new HashMap<>(); private Map highlightingByComponent = new HashMap<>(); + private Map> coverageByComponent = new HashMap<>(); private List duplications = new ArrayList<>(); private List 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()); + } + 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 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(); + } } -- 2.39.5