aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties (renamed from it/it-projects/testing/xoo-sample-with-tests-execution/sonar-project.properties)0
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-details/src/main/xoo/sample/Sample.xoo (renamed from it/it-projects/testing/xoo-sample-with-tests-execution/src/main/xoo/sample/Sample.xoo)0
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo (renamed from it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo)0
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo.test (renamed from it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.test)0
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-measures/sonar-project.properties6
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/main/xoo/sample/Sample.xoo12
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo32
-rw-r--r--it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo.measures (renamed from it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.measures)2
-rw-r--r--it/it-tests/src/test/java/it/test/TestExecutionTest.java22
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java9
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/ScannerPerspectives.java (renamed from sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/BatchPerspectives.java)8
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java6
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java6
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParser.java194
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageSensor.java126
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParser.java159
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionSensor.java88
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/package-info.java24
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java8
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java102
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java2
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java13
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java6
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java6
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java6
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParserTest.java224
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParserTest.java167
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java13
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java139
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java9
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java15
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java13
-rw-r--r--sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage.xml9
-rw-r--r--sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage2.xml8
-rw-r--r--sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/sonar-project.properties3
-rw-r--r--sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/NoConditions.xoo8
-rw-r--r--sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/WithConditions.xoo6
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/coverage.xml14
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest.xml15
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest2.xml15
43 files changed, 1396 insertions, 111 deletions
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution/sonar-project.properties b/it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties
index 0f2415d0320..0f2415d0320 100644
--- a/it/it-projects/testing/xoo-sample-with-tests-execution/sonar-project.properties
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution/src/main/xoo/sample/Sample.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/main/xoo/sample/Sample.xoo
index b1210973dd9..b1210973dd9 100644
--- a/it/it-projects/testing/xoo-sample-with-tests-execution/src/main/xoo/sample/Sample.xoo
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/main/xoo/sample/Sample.xoo
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo
index fe2368fefaa..fe2368fefaa 100644
--- a/it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.test b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo.test
index b0259693199..b0259693199 100644
--- a/it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.test
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo.test
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution-measures/sonar-project.properties b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/sonar-project.properties
new file mode 100644
index 00000000000..0f2415d0320
--- /dev/null
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/sonar-project.properties
@@ -0,0 +1,6 @@
+sonar.projectKey=sample-with-tests
+sonar.projectName=Sample with tests
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.sources=src/main/xoo
+sonar.tests=src/test/xoo
+sonar.language=xoo \ No newline at end of file
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/main/xoo/sample/Sample.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/main/xoo/sample/Sample.xoo
new file mode 100644
index 00000000000..b1210973dd9
--- /dev/null
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/main/xoo/sample/Sample.xoo
@@ -0,0 +1,12 @@
+package sample;
+
+public class Sample {
+
+ public Sample(int i) {
+ int j = i++;
+ }
+
+ private String myMethod() {
+ return "hello";
+ }
+}
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo
new file mode 100644
index 00000000000..fe2368fefaa
--- /dev/null
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo
@@ -0,0 +1,32 @@
+package sample;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import static org.junit.Assert.assertThat;
+
+public class SampleTest {
+
+ @Test
+ @Ignore
+ public void skipped() {
+ Sample sample = new Sample(1);
+ assertThat(sample.getI(), CoreMatchers.is(1));
+ }
+
+ @Test
+ public void failure() {
+ fail();
+ }
+
+ @Test
+ public void error() {
+ throw new IllegalStateException("Foo");
+ }
+
+ @Test
+ public void success() {
+ System.out.println("OK");
+ }
+
+}
diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.measures b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo.measures
index 095af4b5e7b..de1549480b5 100644
--- a/it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.measures
+++ b/it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo.measures
@@ -1,4 +1,4 @@
-tests:4
+tests:3
test_execution_time:8
skipped_tests:1
test_errors:1
diff --git a/it/it-tests/src/test/java/it/test/TestExecutionTest.java b/it/it-tests/src/test/java/it/test/TestExecutionTest.java
index f0d11de0533..34c75e01a47 100644
--- a/it/it-tests/src/test/java/it/test/TestExecutionTest.java
+++ b/it/it-tests/src/test/java/it/test/TestExecutionTest.java
@@ -44,19 +44,33 @@ public class TestExecutionTest {
}
@Test
- public void test_execution() throws Exception {
- orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-with-tests-execution")));
+ public void test_execution_details() throws Exception {
+ orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-with-tests-execution-details")));
Resource project = orchestrator.getServer().getWsClient()
.find(ResourceQuery.createForMetrics("sample-with-tests", "test_success_density", "test_failures", "test_errors", "tests", "skipped_tests", "test_execution_time"));
- assertThat(project.getMeasureValue("test_success_density")).isEqualTo(50.0);
+ assertThat(project.getMeasureValue("test_success_density")).isEqualTo(33.3);
assertThat(project.getMeasureIntValue("test_failures")).isEqualTo(1);
assertThat(project.getMeasureIntValue("test_errors")).isEqualTo(1);
- assertThat(project.getMeasureIntValue("tests")).isEqualTo(4);
+ assertThat(project.getMeasureIntValue("tests")).isEqualTo(3);
assertThat(project.getMeasureIntValue("skipped_tests")).isEqualTo(1);
assertThat(project.getMeasureIntValue("test_execution_time")).isEqualTo(8);
String json = orchestrator.getServer().adminWsClient().get("api/tests/list", "testFileKey", "sample-with-tests:src/test/xoo/sample/SampleTest.xoo");
JSONAssert.assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/TestExecutionTest/expected.json"), "UTF-8"), json, false);
}
+
+ @Test
+ public void test_execution_measures() throws Exception {
+ orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-with-tests-execution-measures")));
+
+ Resource project = orchestrator.getServer().getWsClient()
+ .find(ResourceQuery.createForMetrics("sample-with-tests", "test_success_density", "test_failures", "test_errors", "tests", "skipped_tests", "test_execution_time"));
+ assertThat(project.getMeasureValue("test_success_density")).isEqualTo(33.3);
+ assertThat(project.getMeasureIntValue("test_failures")).isEqualTo(1);
+ assertThat(project.getMeasureIntValue("test_errors")).isEqualTo(1);
+ assertThat(project.getMeasureIntValue("tests")).isEqualTo(3);
+ assertThat(project.getMeasureIntValue("skipped_tests")).isEqualTo(1);
+ assertThat(project.getMeasureIntValue("test_execution_time")).isEqualTo(8);
+ }
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java
index 51dc68f024c..d619a516cac 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java
@@ -27,6 +27,8 @@ import org.sonar.core.component.DefaultResourceTypes;
import org.sonar.core.config.CorePropertyDefinitions;
import org.sonar.core.issue.tracking.Tracker;
import org.sonar.scanner.cpd.CpdComponents;
+import org.sonar.scanner.genericcoverage.GenericCoverageSensor;
+import org.sonar.scanner.genericcoverage.GenericTestExecutionSensor;
import org.sonar.scanner.issue.tracking.ServerIssueFromWs;
import org.sonar.scanner.issue.tracking.TrackedIssue;
import org.sonar.scanner.scan.report.ConsoleReport;
@@ -71,6 +73,13 @@ public class BatchComponents {
// CPD
components.addAll(CpdComponents.all());
+
+ // Generic coverage
+ components.add(GenericCoverageSensor.class);
+ components.addAll(GenericCoverageSensor.properties());
+ components.add(GenericTestExecutionSensor.class);
+ components.addAll(GenericTestExecutionSensor.properties());
+
} else {
// Issues tracking
components.add(new Tracker<TrackedIssue, ServerIssueFromWs>());
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java
index fd1632b9172..255968e45f2 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java
@@ -21,8 +21,8 @@ package org.sonar.scanner.deprecated.perspectives;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.ScannerSide;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.component.Perspective;
-import org.sonar.scanner.index.BatchComponent;
@ScannerSide
public abstract class PerspectiveBuilder<T extends Perspective> {
@@ -38,5 +38,5 @@ public abstract class PerspectiveBuilder<T extends Perspective> {
}
@CheckForNull
- public abstract T loadPerspective(Class<T> perspectiveClass, BatchComponent component);
+ public abstract T loadPerspective(Class<T> perspectiveClass, InputComponent component);
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/BatchPerspectives.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/ScannerPerspectives.java
index 1e89551a32f..1903ef38b12 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/BatchPerspectives.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/ScannerPerspectives.java
@@ -29,13 +29,13 @@ import org.sonar.api.resources.Resource;
import org.sonar.scanner.index.BatchComponentCache;
import org.sonar.scanner.index.DefaultIndex;
-public class BatchPerspectives implements ResourcePerspectives {
+public class ScannerPerspectives implements ResourcePerspectives {
private final Map<Class<?>, PerspectiveBuilder<?>> builders = Maps.newHashMap();
private final DefaultIndex resourceIndex;
private final BatchComponentCache componentCache;
- public BatchPerspectives(PerspectiveBuilder[] builders, DefaultIndex resourceIndex, BatchComponentCache componentCache) {
+ public ScannerPerspectives(PerspectiveBuilder[] builders, DefaultIndex resourceIndex, BatchComponentCache componentCache) {
this.resourceIndex = resourceIndex;
this.componentCache = componentCache;
for (PerspectiveBuilder builder : builders) {
@@ -52,7 +52,7 @@ public class BatchPerspectives implements ResourcePerspectives {
}
if (indexedResource != null) {
PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
- return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource));
+ return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource).inputComponent());
}
return null;
}
@@ -60,7 +60,7 @@ public class BatchPerspectives implements ResourcePerspectives {
@Override
public <P extends Perspective> P as(Class<P> perspectiveClass, InputPath inputPath) {
PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
- return builder.loadPerspective(perspectiveClass, componentCache.get(inputPath));
+ return builder.loadPerspective(perspectiveClass, inputPath);
}
private <T extends Perspective> PerspectiveBuilder<T> builderFor(Class<T> clazz) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java
index 94fbfd0ef18..580b0b10944 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java
@@ -22,11 +22,11 @@ package org.sonar.scanner.deprecated.test;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.CheckForNull;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.test.MutableTestPlan;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
-import org.sonar.scanner.index.BatchComponent;
public class TestPlanBuilder extends PerspectiveBuilder<MutableTestPlan> {
@@ -38,9 +38,9 @@ public class TestPlanBuilder extends PerspectiveBuilder<MutableTestPlan> {
@CheckForNull
@Override
- public MutableTestPlan loadPerspective(Class<MutableTestPlan> perspectiveClass, BatchComponent component) {
+ public MutableTestPlan loadPerspective(Class<MutableTestPlan> perspectiveClass, InputComponent component) {
if (component.isFile()) {
- InputFile inputFile = (InputFile) component.inputComponent();
+ InputFile inputFile = (InputFile) component;
if (inputFile.type() == Type.TEST) {
if (!testPlanByFile.containsKey(inputFile)) {
testPlanByFile.put(inputFile, new DefaultTestPlan());
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java
index 4c9c3de46fb..a03c3514f4f 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java
@@ -20,12 +20,12 @@
package org.sonar.scanner.deprecated.test;
import javax.annotation.CheckForNull;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.test.MutableTestable;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
-import org.sonar.scanner.index.BatchComponent;
public class TestableBuilder extends PerspectiveBuilder<MutableTestable> {
@@ -35,9 +35,9 @@ public class TestableBuilder extends PerspectiveBuilder<MutableTestable> {
@CheckForNull
@Override
- public MutableTestable loadPerspective(Class<MutableTestable> perspectiveClass, BatchComponent component) {
+ public MutableTestable loadPerspective(Class<MutableTestable> perspectiveClass, InputComponent component) {
if (component.isFile()) {
- InputFile inputFile = (InputFile) component.inputComponent();
+ InputFile inputFile = (InputFile) component;
if (inputFile.type() == Type.MAIN) {
return new DefaultTestable((DefaultInputFile) inputFile);
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParser.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParser.java
new file mode 100644
index 00000000000..2ccd7a0abaa
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParser.java
@@ -0,0 +1,194 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.genericcoverage;
+
+import com.google.common.base.Preconditions;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.xml.stream.XMLStreamException;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.coverage.NewCoverage;
+import org.sonar.api.utils.StaxParser;
+
+public class GenericCoverageReportParser {
+
+ private static final String LINE_NUMBER_ATTR = "lineNumber";
+ private static final String COVERED_ATTR = "covered";
+ private static final String BRANCHES_TO_COVER_ATTR = "branchesToCover";
+ private static final String COVERED_BRANCHES_ATTR = "coveredBranches";
+
+ private static final int MAX_STORED_UNKNOWN_FILE_PATHS = 5;
+
+ private int numberOfUnknownFiles;
+ private final List<String> firstUnknownFiles = new ArrayList<>();
+ private final Set<String> matchedFileKeys = new HashSet<>();
+
+ public void parse(java.io.File reportFile, SensorContext context) {
+ try (InputStream inputStream = new FileInputStream(reportFile)) {
+ parse(inputStream, context);
+ } catch (Exception e) {
+ throw new IllegalStateException("Error during parsing of coverage report " + reportFile, e);
+ }
+ }
+
+ void parse(InputStream inputStream, SensorContext context) throws XMLStreamException {
+ new StaxParser(rootCursor -> {
+ rootCursor.advance();
+ parseRootNode(rootCursor, context);
+ }).parse(inputStream);
+ }
+
+ private void parseRootNode(SMHierarchicCursor rootCursor, SensorContext context) throws XMLStreamException {
+ checkElementName(rootCursor, "coverage");
+ String version = rootCursor.getAttrValue("version");
+ if (!"1".equals(version)) {
+ throw new IllegalStateException("Unknown report version: " + version + ". This parser only handles version 1.");
+ }
+ parseFiles(rootCursor.childElementCursor(), context);
+ }
+
+ private void parseFiles(SMInputCursor fileCursor, SensorContext context) throws XMLStreamException {
+ while (fileCursor.getNext() != null) {
+ checkElementName(fileCursor, "file");
+ String filePath = mandatoryAttribute(fileCursor, "path");
+ InputFile inputFile = context.fileSystem().inputFile(context.fileSystem().predicates().hasPath(filePath));
+ if (inputFile == null) {
+ numberOfUnknownFiles++;
+ if (numberOfUnknownFiles <= MAX_STORED_UNKNOWN_FILE_PATHS) {
+ firstUnknownFiles.add(filePath);
+ }
+ continue;
+ }
+ Preconditions.checkState(
+ inputFile.language() != null,
+ "Line %s of report refers to a file with an unknown language: %s",
+ fileCursor.getCursorLocation().getLineNumber(),
+ filePath);
+ matchedFileKeys.add(inputFile.absolutePath());
+
+ NewCoverage newCoverage = context.newCoverage().onFile(inputFile);
+ SMInputCursor lineToCoverCursor = fileCursor.childElementCursor();
+ while (lineToCoverCursor.getNext() != null) {
+ parseLineToCover(lineToCoverCursor, newCoverage);
+ }
+ newCoverage.save();
+ }
+ }
+
+ private static void parseLineToCover(SMInputCursor cursor, NewCoverage newCoverage)
+ throws XMLStreamException {
+ checkElementName(cursor, "lineToCover");
+ String lineNumberAsString = mandatoryAttribute(cursor, LINE_NUMBER_ATTR);
+ int lineNumber = intValue(lineNumberAsString, cursor, LINE_NUMBER_ATTR, 1);
+
+ boolean covered = getCoveredValue(cursor);
+ newCoverage.lineHits(lineNumber, covered ? 1 : 0);
+
+ String branchesToCoverAsString = cursor.getAttrValue(BRANCHES_TO_COVER_ATTR);
+ if (branchesToCoverAsString != null) {
+ int branchesToCover = intValue(branchesToCoverAsString, cursor, BRANCHES_TO_COVER_ATTR, 0);
+ String coveredBranchesAsString = cursor.getAttrValue(COVERED_BRANCHES_ATTR);
+ int coveredBranches = 0;
+ if (coveredBranchesAsString != null) {
+ coveredBranches = intValue(coveredBranchesAsString, cursor, COVERED_BRANCHES_ATTR, 0);
+ if (coveredBranches > branchesToCover) {
+ throw new IllegalStateException("\"coveredBranches\" should not be greater than \"branchesToCover\" on line " + cursor.getCursorLocation().getLineNumber());
+ }
+ }
+ newCoverage.conditions(lineNumber, branchesToCover, coveredBranches);
+ }
+ }
+
+ private static boolean getCoveredValue(SMInputCursor cursor) throws XMLStreamException {
+ String coveredAsString = mandatoryAttribute(cursor, COVERED_ATTR);
+ if (!"true".equalsIgnoreCase(coveredAsString) && !"false".equalsIgnoreCase(coveredAsString)) {
+ throw new IllegalStateException(expectedMessage("boolean value", COVERED_ATTR, coveredAsString, cursor.getCursorLocation().getLineNumber()));
+ }
+ return Boolean.parseBoolean(coveredAsString);
+ }
+
+ static void checkElementName(SMInputCursor cursor, String expectedName) throws XMLStreamException {
+ String elementName = cursor.getLocalName();
+ if (!expectedName.equals(elementName)) {
+ throw new IllegalStateException("Unknown XML node, expected \"" + expectedName + "\" but got \"" + elementName + "\" at line " + cursor.getCursorLocation().getLineNumber());
+ }
+ }
+
+ static String mandatoryAttribute(SMInputCursor cursor, String attributeName) throws XMLStreamException {
+ String attributeValue = cursor.getAttrValue(attributeName);
+ if (attributeValue == null) {
+ throw new IllegalStateException(
+ "Missing attribute \"" + attributeName + "\" in element \"" + cursor.getLocalName() + "\" at line " + cursor.getCursorLocation().getLineNumber());
+ }
+ return attributeValue;
+ }
+
+ static int intValue(String stringValue, SMInputCursor cursor, String attributeName, int minimum) throws XMLStreamException {
+ int intValue;
+ try {
+ intValue = Integer.valueOf(stringValue);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(expectedMessage("integer value", attributeName, stringValue, cursor.getCursorLocation().getLineNumber()), e);
+ }
+ if (intValue < minimum) {
+ throw new IllegalStateException("Value of attribute \"" + attributeName + "\" at line " + cursor.getCursorLocation().getLineNumber() + " is \"" + intValue
+ + "\" but it should be greater than or equal to " + minimum);
+ }
+ return intValue;
+ }
+
+ static long longValue(String stringValue, SMInputCursor cursor, String attributeName, long minimum) throws XMLStreamException {
+ long longValue;
+ try {
+ longValue = Long.valueOf(stringValue);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(expectedMessage("long value", attributeName, stringValue, cursor.getCursorLocation().getLineNumber()), e);
+ }
+ if (longValue < minimum) {
+ throw new IllegalStateException("Value of attribute \"" + attributeName + "\" at line " + cursor.getCursorLocation().getLineNumber() + " is \"" + longValue
+ + "\" but it should be greater than or equal to " + minimum);
+ }
+ return longValue;
+ }
+
+ private static String expectedMessage(String expected, String attributeName, String stringValue, int line) {
+ return "Expected " + expected + " for attribute \"" + attributeName + "\" at line " + line + " but got \"" + stringValue + "\"";
+ }
+
+ public int numberOfMatchedFiles() {
+ return matchedFileKeys.size();
+ }
+
+ public int numberOfUnknownFiles() {
+ return numberOfUnknownFiles;
+ }
+
+ public List<String> firstUnknownFiles() {
+ return firstUnknownFiles;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageSensor.java
new file mode 100644
index 00000000000..4a4c6012c4f
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageSensor.java
@@ -0,0 +1,126 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.genericcoverage;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.api.batch.Initializer;
+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.config.PropertyDefinition;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static org.sonar.api.CoreProperties.CATEGORY_CODE_COVERAGE;
+
+public class GenericCoverageSensor extends Initializer implements Sensor {
+
+ private static final Logger LOG = Loggers.get(GenericCoverageSensor.class);
+
+ private static final String REPORT_PATH_PROPERTY_KEY = "sonar.coverageReportPaths";
+ /**
+ * @deprecated since 6.2
+ */
+ @Deprecated
+ private static final String OLD_REPORT_PATH_PROPERTY_KEY = "sonar.genericcoverage.reportPath";
+ /**
+ * @deprecated since 6.2
+ */
+ @Deprecated
+ private static final String OLD_COVERAGE_REPORT_PATHS_PROPERTY_KEY = "sonar.genericcoverage.reportPaths";
+ /**
+ * @deprecated since 6.2
+ */
+ @Deprecated
+ private static final String OLD_IT_COVERAGE_REPORT_PATHS_PROPERTY_KEY = "sonar.genericcoverage.itReportPaths";
+ /**
+ * @deprecated since 6.2
+ */
+ @Deprecated
+ private static final String OLD_OVERALL_COVERAGE_REPORT_PATHS_PROPERTY_KEY = "sonar.genericcoverage.overallReportPaths";
+
+ private final Settings settings;
+
+ public GenericCoverageSensor(Settings settings) {
+ this.settings = settings;
+ }
+
+ public static ImmutableList<PropertyDefinition> properties() {
+ return ImmutableList.of(
+
+ PropertyDefinition.builder(REPORT_PATH_PROPERTY_KEY)
+ .name("Coverage report paths")
+ .description("List of comma-separated paths (absolute or relative) containing coverage report.")
+ .category(CATEGORY_CODE_COVERAGE)
+ .onQualifiers(Qualifiers.PROJECT)
+ .deprecatedKey(OLD_COVERAGE_REPORT_PATHS_PROPERTY_KEY)
+ .build());
+
+ }
+
+ @Override
+ public void execute() {
+ Set<String> reportPaths = new LinkedHashSet<>();
+ reportPaths.addAll(Arrays.asList(settings.getStringArray(REPORT_PATH_PROPERTY_KEY)));
+ loadDeprecated(reportPaths, OLD_REPORT_PATH_PROPERTY_KEY);
+ loadDeprecated(reportPaths, OLD_IT_COVERAGE_REPORT_PATHS_PROPERTY_KEY);
+ loadDeprecated(reportPaths, OLD_OVERALL_COVERAGE_REPORT_PATHS_PROPERTY_KEY);
+ if (!reportPaths.isEmpty()) {
+ settings.setProperty(REPORT_PATH_PROPERTY_KEY, reportPaths.stream().collect(Collectors.joining(",")));
+ }
+ }
+
+ private void loadDeprecated(Set<String> reportPaths, String propertyKey) {
+ if (settings.hasKey(propertyKey)) {
+ LOG.warn("Property '{}' is deprecated. Please use '{}' instead.", propertyKey, REPORT_PATH_PROPERTY_KEY);
+ reportPaths.addAll(Arrays.asList(settings.getStringArray(propertyKey)));
+ }
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("Generic Coverage Report")
+ .requireProperty(REPORT_PATH_PROPERTY_KEY);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (String reportPath : settings.getStringArray(REPORT_PATH_PROPERTY_KEY)) {
+ File reportFile = context.fileSystem().resolvePath(reportPath);
+ LOG.info("Parsing {}", reportFile);
+ GenericCoverageReportParser parser = new GenericCoverageReportParser();
+ parser.parse(reportFile, context);
+ LOG.info("Imported coverage data for {} files", parser.numberOfMatchedFiles());
+ int numberOfUnknownFiles = parser.numberOfUnknownFiles();
+ if (numberOfUnknownFiles > 0) {
+ LOG.info("Coverage data ignored for " + numberOfUnknownFiles + " unknown files, including:\n" + parser.firstUnknownFiles().stream().collect(Collectors.joining("\n")));
+ }
+ }
+
+ }
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParser.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParser.java
new file mode 100644
index 00000000000..2762ec37506
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParser.java
@@ -0,0 +1,159 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.genericcoverage;
+
+import com.google.common.base.Preconditions;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.xml.stream.XMLStreamException;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.test.MutableTestCase;
+import org.sonar.api.test.MutableTestPlan;
+import org.sonar.api.test.TestCase;
+import org.sonar.api.utils.StaxParser;
+import org.sonar.scanner.deprecated.test.TestPlanBuilder;
+
+import static org.sonar.scanner.genericcoverage.GenericCoverageReportParser.checkElementName;
+import static org.sonar.scanner.genericcoverage.GenericCoverageReportParser.longValue;
+import static org.sonar.scanner.genericcoverage.GenericCoverageReportParser.mandatoryAttribute;
+
+public class GenericTestExecutionReportParser {
+
+ private static final String NAME_ATTR = "name";
+ private static final String DURATION_ATTR = "duration";
+ private static final String MESSAGE_ATTR = "message";
+ public static final String OK = "ok";
+ public static final String ERROR = "error";
+ public static final String FAILURE = "failure";
+ public static final String SKIPPED = "skipped";
+
+ private static final int MAX_STORED_UNKNOWN_FILE_PATHS = 5;
+
+ private final TestPlanBuilder testPlanBuilder;
+
+ private int numberOfUnknownFiles;
+ private final List<String> firstUnknownFiles = new ArrayList<>();
+ private final Set<String> matchedFileKeys = new HashSet<>();
+
+ public GenericTestExecutionReportParser(TestPlanBuilder testPlanBuilder) {
+ this.testPlanBuilder = testPlanBuilder;
+ }
+
+ public void parse(java.io.File reportFile, SensorContext context) {
+ try (InputStream inputStream = new FileInputStream(reportFile)) {
+ parse(inputStream, context);
+ } catch (Exception e) {
+ throw new IllegalStateException("Error during parsing of test execution report " + reportFile, e);
+ }
+ }
+
+ public void parse(InputStream inputStream, SensorContext context) throws XMLStreamException {
+ new StaxParser(rootCursor -> {
+ rootCursor.advance();
+ parseRootNode(rootCursor, context);
+ }).parse(inputStream);
+ }
+
+ private void parseRootNode(SMHierarchicCursor rootCursor, SensorContext context) throws XMLStreamException {
+ checkElementName(rootCursor, "unitTest");
+ String version = rootCursor.getAttrValue("version");
+ if (!"1".equals(version)) {
+ throw new IllegalStateException("Unknown report version: " + version + ". This parser only handles version 1.");
+ }
+ parseFiles(rootCursor.childElementCursor(), context);
+ }
+
+ private void parseFiles(SMInputCursor fileCursor, SensorContext context) throws XMLStreamException {
+ while (fileCursor.getNext() != null) {
+ checkElementName(fileCursor, "file");
+ String filePath = mandatoryAttribute(fileCursor, "path");
+ InputFile inputFile = context.fileSystem().inputFile(context.fileSystem().predicates().hasPath(filePath));
+ if (inputFile == null) {
+ numberOfUnknownFiles++;
+ if (numberOfUnknownFiles <= MAX_STORED_UNKNOWN_FILE_PATHS) {
+ firstUnknownFiles.add(filePath);
+ }
+ continue;
+ }
+ Preconditions.checkState(
+ inputFile.language() != null,
+ "Line %s of report refers to a file with an unknown language: %s",
+ fileCursor.getCursorLocation().getLineNumber(),
+ filePath);
+ Preconditions.checkState(
+ inputFile.type() != InputFile.Type.MAIN,
+ "Line %s of report refers to a file which is not configured as a test file: %s",
+ fileCursor.getCursorLocation().getLineNumber(),
+ filePath);
+ matchedFileKeys.add(inputFile.absolutePath());
+
+ MutableTestPlan testPlan = testPlanBuilder.loadPerspective(MutableTestPlan.class, inputFile);
+ SMInputCursor testCaseCursor = fileCursor.childElementCursor();
+ while (testCaseCursor.getNext() != null) {
+ parseTestCase(testCaseCursor, testPlan);
+ }
+ }
+ }
+
+ private void parseTestCase(SMInputCursor cursor, MutableTestPlan testPlan) throws XMLStreamException {
+ checkElementName(cursor, "testCase");
+ MutableTestCase testCase = testPlan.addTestCase(mandatoryAttribute(cursor, NAME_ATTR));
+ TestCase.Status status = TestCase.Status.OK;
+ testCase.setDurationInMs(longValue(mandatoryAttribute(cursor, DURATION_ATTR), cursor, DURATION_ATTR, 0));
+
+ SMInputCursor child = cursor.descendantElementCursor();
+ if (child.getNext() != null) {
+ String elementName = child.getLocalName();
+ if (SKIPPED.equals(elementName)) {
+ status = TestCase.Status.SKIPPED;
+ } else if (FAILURE.equals(elementName)) {
+ status = TestCase.Status.FAILURE;
+ } else if (ERROR.equals(elementName)) {
+ status = TestCase.Status.ERROR;
+ }
+ testCase.setStatus(status);
+ if (TestCase.Status.OK != status) {
+ testCase.setMessage(mandatoryAttribute(child, MESSAGE_ATTR));
+ testCase.setStackTrace(child.collectDescendantText());
+ }
+ }
+
+ }
+
+ public int numberOfMatchedFiles() {
+ return matchedFileKeys.size();
+ }
+
+ public int numberOfUnknownFiles() {
+ return numberOfUnknownFiles;
+ }
+
+ public List<String> firstUnknownFiles() {
+ return firstUnknownFiles;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionSensor.java
new file mode 100644
index 00000000000..a4db6902285
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionSensor.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.genericcoverage;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.util.stream.Collectors;
+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.config.PropertyDefinition;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.scanner.deprecated.test.TestPlanBuilder;
+
+import static org.sonar.api.CoreProperties.CATEGORY_CODE_COVERAGE;
+
+public class GenericTestExecutionSensor implements Sensor {
+
+ private static final Logger LOG = Loggers.get(GenericTestExecutionSensor.class);
+
+ private static final String REPORT_PATHS_PROPERTY_KEY = "sonar.testExecutionReportPaths";
+ /**
+ * @deprecated since 6.2
+ */
+ @Deprecated
+ private static final String OLD_UNIT_TEST_REPORT_PATHS_PROPERTY_KEY = "sonar.genericcoverage.unitTestReportPaths";
+
+ private final TestPlanBuilder testPlanBuilder;
+
+ public GenericTestExecutionSensor(TestPlanBuilder testPlanBuilder) {
+ this.testPlanBuilder = testPlanBuilder;
+ }
+
+ public static ImmutableList<PropertyDefinition> properties() {
+ return ImmutableList.of(
+
+ PropertyDefinition.builder(REPORT_PATHS_PROPERTY_KEY)
+ .name("Unit tests results report paths")
+ .description("List of comma-separated paths (absolute or relative) containing unit tests results report.")
+ .category(CATEGORY_CODE_COVERAGE)
+ .onQualifiers(Qualifiers.PROJECT)
+ .deprecatedKey(OLD_UNIT_TEST_REPORT_PATHS_PROPERTY_KEY)
+ .build());
+
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("Generic Tests Excution Report")
+ .requireProperty(REPORT_PATHS_PROPERTY_KEY);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (String reportPath : context.settings().getStringArray(REPORT_PATHS_PROPERTY_KEY)) {
+ File reportFile = context.fileSystem().resolvePath(reportPath);
+ LOG.info("Parsing {}", reportFile);
+ GenericTestExecutionReportParser parser = new GenericTestExecutionReportParser(testPlanBuilder);
+ parser.parse(reportFile, context);
+ LOG.info("Imported coverage data for {} files", parser.numberOfMatchedFiles());
+ int numberOfUnknownFiles = parser.numberOfUnknownFiles();
+ if (numberOfUnknownFiles > 0) {
+ LOG.info("Coverage data ignored for " + numberOfUnknownFiles + " unknown files, including:\n" + parser.firstUnknownFiles().stream().collect(Collectors.joining("\n")));
+ }
+ }
+
+ }
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/package-info.java
new file mode 100644
index 00000000000..46f0286f222
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.scanner.genericcoverage;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java
index 2a449596484..28c2502430a 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java
@@ -21,21 +21,21 @@ package org.sonar.scanner.issue;
import java.util.Collections;
import java.util.List;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issue;
-import org.sonar.scanner.index.BatchComponent;
/**
* @since 3.6
*/
public class DefaultIssuable implements Issuable {
- private final BatchComponent component;
+ private final InputComponent component;
private final SensorContext sensorContext;
- DefaultIssuable(BatchComponent component, SensorContext sensorContext) {
+ DefaultIssuable(InputComponent component, SensorContext sensorContext) {
this.component = component;
this.sensorContext = sensorContext;
}
@@ -43,7 +43,7 @@ public class DefaultIssuable implements Issuable {
@Override
public IssueBuilder newIssueBuilder() {
DefaultIssue newIssue = (DefaultIssue) sensorContext.newIssue();
- return new DeprecatedIssueBuilderWrapper(component.inputComponent(), newIssue);
+ return new DeprecatedIssueBuilderWrapper(component, newIssue);
}
@Override
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java
index 85177696c53..47f2ea3bcd3 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java
@@ -19,10 +19,10 @@
*/
package org.sonar.scanner.issue;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.issue.Issuable;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
-import org.sonar.scanner.index.BatchComponent;
import org.sonar.scanner.sensor.DefaultSensorContext;
/**
@@ -39,7 +39,7 @@ public class IssuableFactory extends PerspectiveBuilder<Issuable> {
}
@Override
- public Issuable loadPerspective(Class<Issuable> perspectiveClass, BatchComponent component) {
+ public Issuable loadPerspective(Class<Issuable> perspectiveClass, InputComponent component) {
return new DefaultIssuable(component, sensorContext);
}
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java
index 8eb6fe00251..4686aaea47a 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java
@@ -20,14 +20,21 @@
package org.sonar.scanner.report;
import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
+import java.util.stream.StreamSupport;
import javax.annotation.Nonnull;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.test.MutableTestPlan;
+import org.sonar.api.test.TestCase.Status;
import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.scanner.deprecated.test.TestPlanBuilder;
import org.sonar.scanner.index.BatchComponent;
import org.sonar.scanner.index.BatchComponentCache;
import org.sonar.scanner.protocol.output.ScannerReport;
@@ -44,6 +51,16 @@ import static org.sonar.api.measures.CoreMetrics.CONDITIONS_TO_COVER;
import static org.sonar.api.measures.CoreMetrics.CONDITIONS_TO_COVER_KEY;
import static org.sonar.api.measures.CoreMetrics.LINES_TO_COVER;
import static org.sonar.api.measures.CoreMetrics.LINES_TO_COVER_KEY;
+import static org.sonar.api.measures.CoreMetrics.SKIPPED_TESTS;
+import static org.sonar.api.measures.CoreMetrics.SKIPPED_TESTS_KEY;
+import static org.sonar.api.measures.CoreMetrics.TESTS;
+import static org.sonar.api.measures.CoreMetrics.TESTS_KEY;
+import static org.sonar.api.measures.CoreMetrics.TEST_ERRORS;
+import static org.sonar.api.measures.CoreMetrics.TEST_ERRORS_KEY;
+import static org.sonar.api.measures.CoreMetrics.TEST_EXECUTION_TIME;
+import static org.sonar.api.measures.CoreMetrics.TEST_EXECUTION_TIME_KEY;
+import static org.sonar.api.measures.CoreMetrics.TEST_FAILURES;
+import static org.sonar.api.measures.CoreMetrics.TEST_FAILURES_KEY;
import static org.sonar.api.measures.CoreMetrics.UNCOVERED_CONDITIONS;
import static org.sonar.api.measures.CoreMetrics.UNCOVERED_CONDITIONS_KEY;
import static org.sonar.api.measures.CoreMetrics.UNCOVERED_LINES;
@@ -100,47 +117,76 @@ public class MeasuresPublisher implements ReportPublisherStep {
private final BatchComponentCache componentCache;
private final MeasureCache measureCache;
+ private final TestPlanBuilder testPlanBuilder;
- public MeasuresPublisher(BatchComponentCache resourceCache, MeasureCache measureCache) {
+ public MeasuresPublisher(BatchComponentCache resourceCache, MeasureCache measureCache, TestPlanBuilder testPlanBuilder) {
this.componentCache = resourceCache;
this.measureCache = measureCache;
+ this.testPlanBuilder = testPlanBuilder;
}
@Override
public void publish(ScannerReportWriter writer) {
for (final BatchComponent component : componentCache.all()) {
// Recompute all coverage measures from line data to take into account the possible merge of several reports
- DefaultMeasure<String> lineHitsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY);
- if (lineHitsMeasure != null) {
- Map<Integer, Integer> lineHits = KeyValueFormat.parseIntInt(lineHitsMeasure.value());
- measureCache.put(component.key(), LINES_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(LINES_TO_COVER).withValue(lineHits.keySet().size()));
- measureCache.put(component.key(), UNCOVERED_LINES_KEY,
- new DefaultMeasure<Integer>().forMetric(UNCOVERED_LINES).withValue((int) lineHits.values()
- .stream()
- .filter(hit -> hit == 0)
- .count()));
- }
- DefaultMeasure<String> conditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.CONDITIONS_BY_LINE_KEY);
- DefaultMeasure<String> coveredConditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY);
- if (conditionsMeasure != null) {
- Map<Integer, Integer> conditions = KeyValueFormat.parseIntInt(conditionsMeasure.value());
- Map<Integer, Integer> coveredConditions = coveredConditionsMeasure != null ? KeyValueFormat.parseIntInt(coveredConditionsMeasure.value()) : Collections.emptyMap();
- measureCache.put(component.key(), CONDITIONS_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(CONDITIONS_TO_COVER).withValue(conditions
- .values()
- .stream()
- .mapToInt(Integer::intValue)
- .sum()));
- measureCache.put(component.key(), UNCOVERED_CONDITIONS_KEY,
- new DefaultMeasure<Integer>().forMetric(UNCOVERED_CONDITIONS)
- .withValue((int) conditions.keySet()
- .stream()
- .mapToInt(line -> conditions.get(line) - coveredConditions.get(line))
- .sum()));
- }
+ updateCoverageFromLineData(component);
+ // Recompute test execution measures from MutableTestPlan to take into account the possible merge of several reports
+ updateTestExecutionFromTestPlan(component);
+
Iterable<DefaultMeasure<?>> scannerMeasures = measureCache.byComponentKey(component.key());
Iterable<ScannerReport.Measure> reportMeasures = transform(scannerMeasures, new MeasureToReportMeasure(component));
writer.writeComponentMeasures(component.batchId(), reportMeasures);
}
}
+ private void updateTestExecutionFromTestPlan(final BatchComponent component) {
+ final MutableTestPlan testPlan = testPlanBuilder.loadPerspective(MutableTestPlan.class, component.inputComponent());
+ if (testPlan == null || Iterables.isEmpty(testPlan.testCases())) {
+ return;
+ }
+ long nonSkippedTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() != Status.SKIPPED).count();
+ measureCache.put(component.key(), TESTS_KEY, new DefaultMeasure<Integer>().forMetric(TESTS).withValue((int) nonSkippedTests));
+ long executionTime = StreamSupport.stream(testPlan.testCases().spliterator(), false).mapToLong(t -> t.durationInMs() != null ? t.durationInMs().longValue() : 0L).sum();
+ measureCache.put(component.key(), TEST_EXECUTION_TIME_KEY, new DefaultMeasure<Long>().forMetric(TEST_EXECUTION_TIME).withValue(executionTime));
+ long errorTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() == Status.ERROR).count();
+ measureCache.put(component.key(), TEST_ERRORS_KEY, new DefaultMeasure<Integer>().forMetric(TEST_ERRORS).withValue((int) errorTests));
+ long skippedTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() == Status.SKIPPED).count();
+ measureCache.put(component.key(), SKIPPED_TESTS_KEY, new DefaultMeasure<Integer>().forMetric(SKIPPED_TESTS).withValue((int) skippedTests));
+ long failedTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() == Status.FAILURE).count();
+ measureCache.put(component.key(), TEST_FAILURES_KEY, new DefaultMeasure<Integer>().forMetric(TEST_FAILURES).withValue((int) failedTests));
+ }
+
+ private void updateCoverageFromLineData(final BatchComponent component) {
+ if (!component.isFile() || ((InputFile) component.inputComponent()).type() != Type.MAIN) {
+ return;
+ }
+ DefaultMeasure<String> lineHitsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY);
+ if (lineHitsMeasure != null) {
+ Map<Integer, Integer> lineHits = KeyValueFormat.parseIntInt(lineHitsMeasure.value());
+ measureCache.put(component.key(), LINES_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(LINES_TO_COVER).withValue(lineHits.keySet().size()));
+ measureCache.put(component.key(), UNCOVERED_LINES_KEY,
+ new DefaultMeasure<Integer>().forMetric(UNCOVERED_LINES).withValue((int) lineHits.values()
+ .stream()
+ .filter(hit -> hit == 0)
+ .count()));
+ }
+ DefaultMeasure<String> conditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.CONDITIONS_BY_LINE_KEY);
+ DefaultMeasure<String> coveredConditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY);
+ if (conditionsMeasure != null) {
+ Map<Integer, Integer> conditions = KeyValueFormat.parseIntInt(conditionsMeasure.value());
+ Map<Integer, Integer> coveredConditions = coveredConditionsMeasure != null ? KeyValueFormat.parseIntInt(coveredConditionsMeasure.value()) : Collections.emptyMap();
+ measureCache.put(component.key(), CONDITIONS_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(CONDITIONS_TO_COVER).withValue(conditions
+ .values()
+ .stream()
+ .mapToInt(Integer::intValue)
+ .sum()));
+ measureCache.put(component.key(), UNCOVERED_CONDITIONS_KEY,
+ new DefaultMeasure<Integer>().forMetric(UNCOVERED_CONDITIONS)
+ .withValue((int) conditions.keySet()
+ .stream()
+ .mapToInt(line -> conditions.get(line) - coveredConditions.get(line))
+ .sum()));
+ }
+ }
+
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
index cb341a96a3b..7cb5611cdba 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
@@ -60,7 +60,7 @@ public class MetadataPublisher implements ReportPublisherStep {
builder.setBranch(branch);
}
for (QProfile qp : qProfiles.findAll()) {
- builder.getMutableQprofilesPerLanguage().put(qp.getLanguage(), org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile.newBuilder()
+ builder.getMutableQprofilesPerLanguage().put(qp.getLanguage(), ScannerReport.Metadata.QProfile.newBuilder()
.setKey(qp.getKey())
.setLanguage(qp.getLanguage())
.setName(qp.getName())
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java
index e5f7cc0cbe1..d37c9439e8b 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java
@@ -24,8 +24,6 @@ import com.google.common.collect.Iterables;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nonnull;
-import org.sonar.api.batch.fs.InputFile.Type;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.test.CoverageBlock;
import org.sonar.api.test.MutableTestCase;
import org.sonar.api.test.MutableTestPlan;
@@ -115,16 +113,7 @@ public class TestExecutionAndCoveragePublisher implements ReportPublisherStep {
@Override
public void publish(ScannerReportWriter writer) {
for (final BatchComponent component : componentCache.all()) {
- if (!component.isFile()) {
- continue;
- }
-
- DefaultInputFile inputFile = (DefaultInputFile) component.inputComponent();
- if (inputFile.type() != Type.TEST) {
- continue;
- }
-
- final MutableTestPlan testPlan = testPlanBuilder.loadPerspective(MutableTestPlan.class, component);
+ final MutableTestPlan testPlan = testPlanBuilder.loadPerspective(MutableTestPlan.class, component.inputComponent());
if (testPlan == null || Iterables.isEmpty(testPlan.testCases())) {
continue;
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
index 72f95401c53..5ab42b57c2e 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
@@ -35,7 +35,7 @@ import org.sonar.scanner.bootstrap.BatchExtensionDictionnary;
import org.sonar.scanner.bootstrap.ExtensionInstaller;
import org.sonar.scanner.bootstrap.ExtensionUtils;
import org.sonar.scanner.deprecated.DeprecatedSensorContext;
-import org.sonar.scanner.deprecated.perspectives.BatchPerspectives;
+import org.sonar.scanner.deprecated.perspectives.ScannerPerspectives;
import org.sonar.scanner.events.EventBus;
import org.sonar.scanner.index.BatchComponentCache;
import org.sonar.scanner.index.DefaultIndex;
@@ -162,7 +162,7 @@ public class ModuleScanContainer extends ComponentContainer {
IgnoreIssuesFilter.class,
// Perspectives
- BatchPerspectives.class,
+ ScannerPerspectives.class,
HighlightableBuilder.class,
SymbolizableBuilder.class,
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java
index 5f062b22946..da793d3e981 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java
@@ -21,12 +21,12 @@ package org.sonar.scanner.source;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.AnalysisMode;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.internal.SensorStorage;
import org.sonar.api.source.Highlightable;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
-import org.sonar.scanner.index.BatchComponent;
public class HighlightableBuilder extends PerspectiveBuilder<Highlightable> {
@@ -41,9 +41,9 @@ public class HighlightableBuilder extends PerspectiveBuilder<Highlightable> {
@CheckForNull
@Override
- public Highlightable loadPerspective(Class<Highlightable> perspectiveClass, BatchComponent component) {
+ public Highlightable loadPerspective(Class<Highlightable> perspectiveClass, InputComponent component) {
if (component.isFile()) {
- InputFile path = (InputFile) component.inputComponent();
+ InputFile path = (InputFile) component;
return new DefaultHighlightable((DefaultInputFile) path, sensorStorage, analysisMode);
}
return null;
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java
index e805c6dbcc4..947670bf769 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java
@@ -21,11 +21,11 @@ package org.sonar.scanner.source;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.AnalysisMode;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.source.Symbolizable;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
-import org.sonar.scanner.index.BatchComponent;
import org.sonar.scanner.sensor.DefaultSensorStorage;
public class SymbolizableBuilder extends PerspectiveBuilder<Symbolizable> {
@@ -41,9 +41,9 @@ public class SymbolizableBuilder extends PerspectiveBuilder<Symbolizable> {
@CheckForNull
@Override
- public Symbolizable loadPerspective(Class<Symbolizable> perspectiveClass, BatchComponent component) {
+ public Symbolizable loadPerspective(Class<Symbolizable> perspectiveClass, InputComponent component) {
if (component.isFile()) {
- InputFile path = (InputFile) component.inputComponent();
+ InputFile path = (InputFile) component;
return new DefaultSymbolizable((DefaultInputFile) path, sensorStorage, analysisMode);
}
return null;
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java
index 791db326ae7..934141f862d 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java
@@ -20,18 +20,18 @@
package org.sonar.scanner.deprecated.perspectives;
import org.junit.Test;
+import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.component.Perspective;
-import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
-import org.sonar.scanner.index.BatchComponent;
import static org.assertj.core.api.Assertions.assertThat;
public class PerspectiveBuilderTest {
+
@Test
public void testGetPerspectiveClass() throws Exception {
PerspectiveBuilder<FakePerspective> builder = new PerspectiveBuilder<FakePerspective>(FakePerspective.class) {
@Override
- public FakePerspective loadPerspective(Class<FakePerspective> perspectiveClass, BatchComponent component) {
+ public FakePerspective loadPerspective(Class<FakePerspective> perspectiveClass, InputComponent component) {
return null;
}
};
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParserTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParserTest.java
new file mode 100644
index 00000000000..fb41e6b2ad9
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParserTest.java
@@ -0,0 +1,224 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.genericcoverage;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GenericCoverageReportParserTest {
+ private DefaultInputFile fileWithBranches;
+ private DefaultInputFile fileWithoutBranch;
+ private DefaultInputFile emptyFile;
+ private SensorContextTester context;
+
+ @Before
+ public void before() {
+ context = SensorContextTester.create(new File(""));
+ fileWithBranches = setupFile("src/main/java/com/example/ClassWithBranches.java");
+ fileWithoutBranch = setupFile("src/main/java/com/example/ClassWithoutBranch.java");
+ emptyFile = setupFile("src/main/java/com/example/EmptyClass.java");
+ }
+
+ @Test
+ public void empty_file() throws Exception {
+ addFileToFs(emptyFile);
+ GenericCoverageReportParser parser = new GenericCoverageReportParser();
+ parser.parse(this.getClass().getResourceAsStream("coverage.xml"), context);
+ assertThat(parser.numberOfMatchedFiles()).isEqualTo(1);
+ assertThat(parser.numberOfUnknownFiles()).isEqualTo(3);
+ assertThat(parser.firstUnknownFiles()).hasSize(3);
+ }
+
+ @Test
+ public void file_without_branch() throws Exception {
+ addFileToFs(fileWithoutBranch);
+ GenericCoverageReportParser parser = new GenericCoverageReportParser();
+ parser.parse(this.getClass().getResourceAsStream("coverage.xml"), context);
+ assertThat(parser.numberOfMatchedFiles()).isEqualTo(1);
+
+ assertThat(context.lineHits(fileWithoutBranch.key(), 2)).isEqualTo(0);
+ assertThat(context.lineHits(fileWithoutBranch.key(), 3)).isEqualTo(1);
+ assertThat(context.lineHits(fileWithoutBranch.key(), 4)).isNull();
+ assertThat(context.lineHits(fileWithoutBranch.key(), 5)).isEqualTo(1);
+ assertThat(context.lineHits(fileWithoutBranch.key(), 6)).isEqualTo(0);
+ }
+
+ @Test
+ public void file_with_branches() throws Exception {
+ addFileToFs(fileWithBranches);
+ GenericCoverageReportParser parser = new GenericCoverageReportParser();
+ parser.parse(this.getClass().getResourceAsStream("coverage.xml"), context);
+ assertThat(parser.numberOfMatchedFiles()).isEqualTo(1);
+
+ assertThat(context.lineHits(fileWithBranches.key(), 3)).isEqualTo(1);
+ assertThat(context.lineHits(fileWithBranches.key(), 4)).isEqualTo(1);
+
+ assertThat(context.conditions(fileWithBranches.key(), 3)).isEqualTo(8);
+ assertThat(context.conditions(fileWithBranches.key(), 4)).isEqualTo(2);
+
+ assertThat(context.coveredConditions(fileWithBranches.key(), 3)).isEqualTo(5);
+ assertThat(context.coveredConditions(fileWithBranches.key(), 4)).isEqualTo(0);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_invalid_root_node_name() throws Exception {
+ new GenericCoverageReportParser().parse(new ByteArrayInputStream("<mycoverage version=\"1\"></mycoverage>".getBytes()), context);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_invalid_report_version() throws Exception {
+ parseCoverageReport("<coverage version=\"2\"></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_no_report_version() throws Exception {
+ parseCoverageReport("<coverage></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_invalid_file_node_name() throws Exception {
+ parseCoverageReport("<coverage version=\"1\"><xx></xx></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unitTest_invalid_file_node_name() throws Exception {
+ parseCoverageReport("<unitTest version=\"1\"><xx></xx></unitTest>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_missing_path_attribute() throws Exception {
+ parseCoverageReport("<coverage version=\"1\"><file></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unitTest_missing_path_attribute() throws Exception {
+ parseCoverageReport("<unitTest version=\"1\"><file></file></unitTest>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_invalid_lineToCover_node_name() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\"><xx/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_missing_lineNumber_in_lineToCover() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\"><lineToCover covered=\"true\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_lineNumber_in_lineToCover_should_be_a_number() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\"><lineToCover lineNumber=\"x\" covered=\"true\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_lineNumber_in_lineToCover_should_be_positive() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\"><lineToCover lineNumber=\"0\" covered=\"true\"/></file></coverage>");
+ }
+
+ @Test
+ public void coverage_lineNumber_in_lineToCover_can_appear_several_times_for_same_file() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\">"
+ + "<lineToCover lineNumber=\"1\" covered=\"true\"/>"
+ + "<lineToCover lineNumber=\"1\" covered=\"true\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_missing_covered_in_lineToCover() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\"><lineToCover lineNumber=\"3\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_covered_in_lineToCover_should_be_a_boolean() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\"><lineToCover lineNumber=\"3\" covered=\"x\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_branchesToCover_in_lineToCover_should_be_a_number() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\">"
+ + "<lineToCover lineNumber=\"1\" covered=\"true\" branchesToCover=\"x\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_branchesToCover_in_lineToCover_should_not_be_negative() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\">"
+ + "<lineToCover lineNumber=\"1\" covered=\"true\" branchesToCover=\"-1\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_coveredBranches_in_lineToCover_should_be_a_number() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\">"
+ + "<lineToCover lineNumber=\"1\" covered=\"true\" branchesToCover=\"2\" coveredBranches=\"x\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_coveredBranches_in_lineToCover_should_not_be_negative() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\">"
+ + "<lineToCover lineNumber=\"1\" covered=\"true\" branchesToCover=\"2\" coveredBranches=\"-1\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void coverage_coveredBranches_should_not_be_greater_than_branchesToCover() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseCoverageReport("<coverage version=\"1\"><file path=\"file1\">"
+ + "<lineToCover lineNumber=\"1\" covered=\"true\" branchesToCover=\"2\" coveredBranches=\"3\"/></file></coverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnknownFile() throws Exception {
+ parseCoverageReportFile("xxx.xml");
+ }
+
+ private void addFileToFs(DefaultInputFile inputFile) {
+ context.fileSystem().add(inputFile);
+ }
+
+ private void parseCoverageReport(String string) throws Exception {
+ new GenericCoverageReportParser().parse(new ByteArrayInputStream(string.getBytes()), context);
+ }
+
+ private void parseCoverageReportFile(String reportLocation) throws Exception {
+ new GenericCoverageReportParser().parse(new File(reportLocation), context);
+ }
+
+ private DefaultInputFile setupFile(String path) {
+ return new DefaultInputFile(context.module().key(), path)
+ .setLanguage("bla")
+ .setType(InputFile.Type.TEST)
+ .initMetadata("1\n2\n3\n4\n5\n6");
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParserTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParserTest.java
new file mode 100644
index 00000000000..3ee34b3f4f0
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParserTest.java
@@ -0,0 +1,167 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.genericcoverage;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+import org.sonar.api.test.MutableTestCase;
+import org.sonar.api.test.MutableTestPlan;
+import org.sonar.scanner.deprecated.test.TestPlanBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class GenericTestExecutionReportParserTest {
+
+ private TestPlanBuilder testPlanBuilder;
+ private DefaultInputFile fileWithBranches;
+ private DefaultInputFile emptyFile;
+ private SensorContextTester context;
+ private MutableTestPlan testPlan;
+
+ @Before
+ public void before() {
+ context = SensorContextTester.create(new File(""));
+ fileWithBranches = setupFile("src/main/java/com/example/ClassWithBranches.java");
+ emptyFile = setupFile("src/main/java/com/example/EmptyClass.java");
+ testPlanBuilder = mock(TestPlanBuilder.class);
+
+ MutableTestCase testCase = mockMutableTestCase();
+ testPlan = mockMutableTestPlan(testCase);
+
+ when(testPlanBuilder.loadPerspective(eq(MutableTestPlan.class), any(InputFile.class))).thenReturn(testPlan);
+ }
+
+ @Test
+ public void ut_empty_file() throws Exception {
+ addFileToFs(emptyFile);
+ GenericTestExecutionReportParser parser = parseReportFile("unittest.xml");
+ assertThat(parser.numberOfMatchedFiles()).isEqualTo(1);
+ assertThat(parser.numberOfUnknownFiles()).isEqualTo(1);
+ assertThat(parser.firstUnknownFiles()).hasSize(1);
+ }
+
+ @Test
+ public void file_with_unittests() throws Exception {
+ addFileToFs(fileWithBranches);
+ GenericTestExecutionReportParser parser = parseReportFile("unittest2.xml");
+ assertThat(parser.numberOfMatchedFiles()).isEqualTo(1);
+
+ verify(testPlan).addTestCase("test1");
+ verify(testPlan).addTestCase("test2");
+ verify(testPlan).addTestCase("test3");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unittest_invalid_root_node_name() throws Exception {
+ parseUnitTestReport("<mycoverage version=\"1\"></mycoverage>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unittest_invalid_report_version() throws Exception {
+ parseUnitTestReport("<unitTest version=\"2\"></unitTest>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unittest_duration_in_testCase_should_be_a_number() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseUnitTestReport("<unitTest version=\"1\"><file path=\"file1\">"
+ + "<testCase name=\"test1\" duration=\"aaa\"/></file></unitTest>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unittest_failure_should_have_a_message() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseUnitTestReport("<unitTest version=\"1\"><file path=\"file1\">"
+ + "<testCase name=\"test1\" duration=\"2\"><failure /></testCase></file></unitTest>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unittest_error_should_have_a_message() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseUnitTestReport("<unitTest version=\"1\"><file path=\"file1\">"
+ + "<testCase name=\"test1\" duration=\"2\"><error /></testCase></file></unitTest>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unittest_skipped_should_have_a_message() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseUnitTestReport("<unitTest version=\"1\"><file path=\"file1\">"
+ + "<testCase name=\"test1\" duration=\"2\"><skipped notmessage=\"\"/></testCase></file></unitTest>");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unittest_duration_in_testCase_should_not_be_negative() throws Exception {
+ addFileToFs(setupFile("file1"));
+ parseUnitTestReport("<unitTest version=\"1\"><file path=\"file1\">"
+ + "<testCase name=\"test1\" duration=\"-5\"/></file></unitTest>");
+ }
+
+ private void addFileToFs(DefaultInputFile inputFile) {
+ context.fileSystem().add(inputFile);
+ }
+
+ private GenericTestExecutionReportParser parseUnitTestReport(String string) throws Exception {
+ GenericTestExecutionReportParser parser = new GenericTestExecutionReportParser(testPlanBuilder);
+ parser.parse(new ByteArrayInputStream(string.getBytes()), context);
+ return parser;
+ }
+
+ private GenericTestExecutionReportParser parseReportFile(String reportLocation) throws Exception {
+ GenericTestExecutionReportParser parser = new GenericTestExecutionReportParser(testPlanBuilder);
+ parser.parse(this.getClass().getResourceAsStream(reportLocation), context);
+ return parser;
+ }
+
+ private DefaultInputFile setupFile(String path) {
+ return new DefaultInputFile(context.module().key(), path)
+ .setLanguage("bla")
+ .setType(InputFile.Type.TEST)
+ .initMetadata("1\n2\n3\n4\n5\n6");
+ }
+
+ private MutableTestPlan mockMutableTestPlan(MutableTestCase testCase) {
+ MutableTestPlan testPlan = mock(MutableTestPlan.class);
+ when(testPlan.addTestCase(anyString())).thenReturn(testCase);
+ return testPlan;
+ }
+
+ private MutableTestCase mockMutableTestCase() {
+ MutableTestCase testCase = mock(MutableTestCase.class);
+ when(testCase.setDurationInMs(anyLong())).thenReturn(testCase);
+ when(testCase.setStatus(any(org.sonar.api.test.TestCase.Status.class))).thenReturn(testCase);
+ when(testCase.setMessage(anyString())).thenReturn(testCase);
+ when(testCase.setStackTrace(anyString())).thenReturn(testCase);
+ when(testCase.setType(anyString())).thenReturn(testCase);
+ return testCase;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java
index 07038c5832f..996e5066856 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java
@@ -20,13 +20,10 @@
package org.sonar.scanner.issue;
import org.junit.Test;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.issue.Issuable;
-import org.sonar.api.resources.File;
-import org.sonar.api.resources.Project;
import org.sonar.scanner.DefaultProjectTree;
-import org.sonar.scanner.index.BatchComponent;
-import org.sonar.scanner.issue.IssuableFactory;
-import org.sonar.scanner.issue.ModuleIssues;
import org.sonar.scanner.sensor.DefaultSensorContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,8 +37,7 @@ public class IssuableFactoryTest {
@Test
public void file_should_be_issuable() {
IssuableFactory factory = new IssuableFactory(mock(DefaultSensorContext.class));
- BatchComponent component = new BatchComponent(1, File.create("foo/bar.c").setEffectiveKey("foo/bar.c"), null);
- Issuable issuable = factory.loadPerspective(Issuable.class, component);
+ Issuable issuable = factory.loadPerspective(Issuable.class, new DefaultInputFile("foo", "src/Foo.java"));
assertThat(issuable).isNotNull();
assertThat(issuable.issues()).isEmpty();
@@ -50,8 +46,7 @@ public class IssuableFactoryTest {
@Test
public void project_should_be_issuable() {
IssuableFactory factory = new IssuableFactory(mock(DefaultSensorContext.class));
- BatchComponent component = new BatchComponent(1, new Project("Foo").setEffectiveKey("foo"), null);
- Issuable issuable = factory.loadPerspective(Issuable.class, component);
+ Issuable issuable = factory.loadPerspective(Issuable.class, new DefaultInputModule("foo"));
assertThat(issuable).isNotNull();
assertThat(issuable.issues()).isEmpty();
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java
new file mode 100644
index 00000000000..f14e6c73286
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java
@@ -0,0 +1,139 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.mediumtest.coverage;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.scanner.mediumtest.BatchMediumTester;
+import org.sonar.scanner.mediumtest.TaskResult;
+import org.sonar.xoo.XooPlugin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class GenericCoverageMediumTest {
+
+ 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 singleReport() throws IOException {
+
+ File projectDir = new File("src/test/resources/mediumtest/xoo/sample-generic-coverage");
+
+ TaskResult result = tester
+ .newScanTask(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.coverageReportPaths", "coverage.xml")
+ .start();
+
+ InputFile noConditions = result.inputFile("xources/hello/NoConditions.xoo");
+ assertThat(result.coverageFor(noConditions, 6).getHits()).isTrue();
+ assertThat(result.coverageFor(noConditions, 6).getConditions()).isEqualTo(0);
+ assertThat(result.coverageFor(noConditions, 6).getCoveredConditions()).isEqualTo(0);
+
+ assertThat(result.coverageFor(noConditions, 7).getHits()).isFalse();
+
+ assertThat(result.allMeasures().get(noConditions.key())).extracting("metricKey", "intValue.value", "stringValue.value")
+ .containsOnly(
+ tuple(CoreMetrics.LINES_KEY, 8, ""),
+ tuple(CoreMetrics.LINES_TO_COVER_KEY, 2, ""),
+ tuple(CoreMetrics.UNCOVERED_LINES_KEY, 1, ""),
+ tuple(CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, 0, "6=1;7=0"));
+
+ InputFile withConditions = result.inputFile("xources/hello/WithConditions.xoo");
+ assertThat(result.coverageFor(withConditions, 3).getHits()).isTrue();
+ assertThat(result.coverageFor(withConditions, 3).getConditions()).isEqualTo(2);
+ assertThat(result.coverageFor(withConditions, 3).getCoveredConditions()).isEqualTo(1);
+
+ assertThat(result.allMeasures().get(withConditions.key())).extracting("metricKey", "intValue.value", "stringValue.value")
+ .containsOnly(
+ tuple(CoreMetrics.LINES_KEY, 6, ""),
+ tuple(CoreMetrics.LINES_TO_COVER_KEY, 1, ""),
+ tuple(CoreMetrics.UNCOVERED_LINES_KEY, 0, ""),
+ tuple(CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, 0, "3=1"),
+ tuple(CoreMetrics.CONDITIONS_TO_COVER_KEY, 2, ""),
+ tuple(CoreMetrics.UNCOVERED_CONDITIONS_KEY, 1, ""),
+ tuple(CoreMetrics.CONDITIONS_BY_LINE_KEY, 0, "3=2"),
+ tuple(CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, 0, "3=1")
+
+ );
+ }
+
+ @Test
+ public void twoReports() throws IOException {
+
+ File projectDir = new File("src/test/resources/mediumtest/xoo/sample-generic-coverage");
+
+ TaskResult result = tester
+ .newScanTask(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.coverageReportPaths", "coverage.xml,coverage2.xml")
+ .start();
+
+ InputFile noConditions = result.inputFile("xources/hello/NoConditions.xoo");
+ assertThat(result.coverageFor(noConditions, 6).getHits()).isTrue();
+ assertThat(result.coverageFor(noConditions, 6).getConditions()).isEqualTo(0);
+ assertThat(result.coverageFor(noConditions, 6).getCoveredConditions()).isEqualTo(0);
+
+ assertThat(result.coverageFor(noConditions, 7).getHits()).isTrue();
+
+ assertThat(result.allMeasures().get(noConditions.key())).extracting("metricKey", "intValue.value", "stringValue.value")
+ .containsOnly(
+ tuple(CoreMetrics.LINES_KEY, 8, ""),
+ tuple(CoreMetrics.LINES_TO_COVER_KEY, 2, ""),
+ tuple(CoreMetrics.UNCOVERED_LINES_KEY, 0, ""),
+ tuple(CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, 0, "6=1;7=1"));
+
+ InputFile withConditions = result.inputFile("xources/hello/WithConditions.xoo");
+ assertThat(result.coverageFor(withConditions, 3).getHits()).isTrue();
+ assertThat(result.coverageFor(withConditions, 3).getConditions()).isEqualTo(2);
+ assertThat(result.coverageFor(withConditions, 3).getCoveredConditions()).isEqualTo(2);
+
+ assertThat(result.allMeasures().get(withConditions.key())).extracting("metricKey", "intValue.value", "stringValue.value")
+ .containsOnly(
+ tuple(CoreMetrics.LINES_KEY, 6, ""),
+ tuple(CoreMetrics.LINES_TO_COVER_KEY, 1, ""),
+ tuple(CoreMetrics.UNCOVERED_LINES_KEY, 0, ""),
+ tuple(CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, 0, "3=2"),
+ tuple(CoreMetrics.CONDITIONS_TO_COVER_KEY, 2, ""),
+ tuple(CoreMetrics.UNCOVERED_CONDITIONS_KEY, 0, ""),
+ tuple(CoreMetrics.CONDITIONS_BY_LINE_KEY, 0, "3=2"),
+ tuple(CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, 0, "3=2")
+
+ );
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java
index 37edc1a0389..190907e001d 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java
@@ -28,10 +28,13 @@ 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.fs.internal.DefaultInputModule;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.resources.Project;
import org.sonar.core.util.CloseableIterator;
+import org.sonar.scanner.deprecated.test.TestPlanBuilder;
import org.sonar.scanner.index.BatchComponentCache;
import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.scanner.protocol.output.ScannerReportReader;
@@ -65,11 +68,11 @@ public class MeasuresPublisherTest {
Project p = new Project("foo").setAnalysisDate(new Date(1234567L));
BatchComponentCache resourceCache = new BatchComponentCache();
sampleFile = org.sonar.api.resources.File.create("src/Foo.php").setEffectiveKey(FILE_KEY);
- resourceCache.add(p, null);
- resourceCache.add(sampleFile, null);
+ resourceCache.add(p, null).setInputComponent(new DefaultInputModule("foo"));
+ resourceCache.add(sampleFile, null).setInputComponent(new DefaultInputFile("foo", "src/Foo.php"));
measureCache = mock(MeasureCache.class);
when(measureCache.byComponentKey(anyString())).thenReturn(Collections.<DefaultMeasure<?>>emptyList());
- publisher = new MeasuresPublisher(resourceCache, measureCache);
+ publisher = new MeasuresPublisher(resourceCache, measureCache, mock(TestPlanBuilder.class));
}
@Test
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java
index fd07607430d..f68fe762e42 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java
@@ -24,13 +24,7 @@ import org.sonar.api.batch.AnalysisMode;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.sensor.internal.SensorStorage;
-import org.sonar.api.resources.File;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
import org.sonar.api.source.Highlightable;
-import org.sonar.scanner.index.BatchComponent;
-import org.sonar.scanner.source.DefaultHighlightable;
-import org.sonar.scanner.source.HighlightableBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -39,21 +33,16 @@ public class HighlightableBuilderTest {
@Test
public void should_load_default_perspective() {
- Resource file = File.create("foo.c").setEffectiveKey("myproject:path/to/foo.c");
- BatchComponent component = new BatchComponent(1, file, null).setInputComponent(new DefaultInputFile("foo", "foo.c"));
-
HighlightableBuilder builder = new HighlightableBuilder(mock(SensorStorage.class), mock(AnalysisMode.class));
- Highlightable perspective = builder.loadPerspective(Highlightable.class, component);
+ Highlightable perspective = builder.loadPerspective(Highlightable.class, new DefaultInputFile("foo", "foo.c"));
assertThat(perspective).isNotNull().isInstanceOf(DefaultHighlightable.class);
}
@Test
public void project_should_not_be_highlightable() {
- BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null).setInputComponent(new DefaultInputModule("struts"));
-
HighlightableBuilder builder = new HighlightableBuilder(mock(SensorStorage.class), mock(AnalysisMode.class));
- Highlightable perspective = builder.loadPerspective(Highlightable.class, component);
+ Highlightable perspective = builder.loadPerspective(Highlightable.class, new DefaultInputModule("struts"));
assertThat(perspective).isNull();
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java
index 9f50909105c..9a5e2edbc9f 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java
@@ -24,11 +24,7 @@ import org.sonar.api.batch.AnalysisMode;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.component.Perspective;
-import org.sonar.api.resources.File;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
import org.sonar.api.source.Symbolizable;
-import org.sonar.scanner.index.BatchComponent;
import org.sonar.scanner.sensor.DefaultSensorStorage;
import static org.assertj.core.api.Assertions.assertThat;
@@ -38,21 +34,16 @@ public class SymbolizableBuilderTest {
@Test
public void should_load_perspective() {
- Resource file = File.create("foo.c").setEffectiveKey("myproject:path/to/foo.c");
- BatchComponent component = new BatchComponent(1, file, null).setInputComponent(new DefaultInputFile("foo", "foo.c"));
-
SymbolizableBuilder perspectiveBuilder = new SymbolizableBuilder(mock(DefaultSensorStorage.class), mock(AnalysisMode.class));
- Perspective perspective = perspectiveBuilder.loadPerspective(Symbolizable.class, component);
+ Perspective perspective = perspectiveBuilder.loadPerspective(Symbolizable.class, new DefaultInputFile("foo", "foo.c"));
assertThat(perspective).isInstanceOf(Symbolizable.class);
}
@Test
public void project_should_not_be_highlightable() {
- BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null).setInputComponent(new DefaultInputModule("struts"));
-
SymbolizableBuilder builder = new SymbolizableBuilder(mock(DefaultSensorStorage.class), mock(AnalysisMode.class));
- Perspective perspective = builder.loadPerspective(Symbolizable.class, component);
+ Perspective perspective = builder.loadPerspective(Symbolizable.class, new DefaultInputModule("struts"));
assertThat(perspective).isNull();
}
diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage.xml b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage.xml
new file mode 100644
index 00000000000..e12af5e2fb2
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage.xml
@@ -0,0 +1,9 @@
+<coverage version="1">
+ <file path="xources/hello/NoConditions.xoo">
+ <lineToCover lineNumber="6" covered="true"/>
+ <lineToCover lineNumber="7" covered="false"/>
+ </file>
+ <file path="xources/hello/WithConditions.xoo">
+ <lineToCover lineNumber="3" covered="true" branchesToCover="2" coveredBranches="1"/>
+ </file>
+</coverage>
diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage2.xml b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage2.xml
new file mode 100644
index 00000000000..1a1e02b0f7b
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage2.xml
@@ -0,0 +1,8 @@
+<coverage version="1">
+ <file path="xources/hello/NoConditions.xoo">
+ <lineToCover lineNumber="7" covered="true"/>
+ </file>
+ <file path="xources/hello/WithConditions.xoo">
+ <lineToCover lineNumber="3" covered="true" branchesToCover="2" coveredBranches="2"/>
+ </file>
+</coverage>
diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/sonar-project.properties b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/sonar-project.properties
new file mode 100644
index 00000000000..ac18398a604
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/sonar-project.properties
@@ -0,0 +1,3 @@
+sonar.projectKey=sample-generic-coverage
+sonar.sources=xources
+sonar.language=xoo
diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/NoConditions.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/NoConditions.xoo
new file mode 100644
index 00000000000..1d9c60d56b7
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/NoConditions.xoo
@@ -0,0 +1,8 @@
+package hello;
+
+public class HelloJava {
+
+ public static void main(String[] args) {
+ System.out.println("Hello");
+ }
+} \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/WithConditions.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/WithConditions.xoo
new file mode 100644
index 00000000000..8f469103183
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/WithConditions.xoo
@@ -0,0 +1,6 @@
+ object HelloWorld {
+ def main(args: Array[String]) {
+ args.isEmpty ? println("Hello, world of xoo!") : println("Hello, world of empty!")
+ }
+ }
+ \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/coverage.xml b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/coverage.xml
new file mode 100644
index 00000000000..74dd213c413
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/coverage.xml
@@ -0,0 +1,14 @@
+<coverage version="1">
+ <file path="src/main/java/com/example/NonExisting.java"/>
+ <file path="src/main/java/com/example/EmptyClass.java"/>
+ <file path="src/main/java/com/example/ClassWithoutBranch.java">
+ <lineToCover lineNumber="2" covered="false"/>
+ <lineToCover lineNumber="3" covered="true"/>
+ <lineToCover lineNumber="5" covered="true"/>
+ <lineToCover lineNumber="6" covered="false"/>
+ </file>
+ <file path="src/main/java/com/example/ClassWithBranches.java">
+ <lineToCover lineNumber="3" covered="true" branchesToCover="8" coveredBranches="5"/>
+ <lineToCover lineNumber="4" covered="true" branchesToCover="2"/>
+ </file>
+</coverage>
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest.xml b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest.xml
new file mode 100644
index 00000000000..f1d12ec0f50
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest.xml
@@ -0,0 +1,15 @@
+<unitTest version="1">
+ <file path="src/main/java/com/example/EmptyClass.java"/>
+ <file path="src/main/java/com/example/ClassWithoutBranch.java">
+ <testCase name="test1" duration="5"/>
+ <testCase name="test2" duration="500">
+ <skipped message="short message">other</skipped>
+ </testCase>
+ <testCase name="test3" duration="100">
+ <failure message="short">stacktrace</failure>
+ </testCase>
+ <testCase name="test4" duration="500">
+ <error message="short">stacktrace</error>
+ </testCase>
+ </file>
+</unitTest>
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest2.xml b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest2.xml
new file mode 100644
index 00000000000..35ff4ad5912
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest2.xml
@@ -0,0 +1,15 @@
+<unitTest version="1">
+ <file path="src/main/java/com/example/EmptyClass.java"/>
+ <file path="src/main/java/com/example/ClassWithBranches.java">
+ <testCase name="test1" duration="500">
+ <skipped message="short message">other</skipped>
+ </testCase>
+ <testCase name="test2" duration="300">
+ <failure message="short">stacktrace</failure>
+ </testCase>
+ <testCase name="test3" duration="300" />
+ <testCase name="test4" duration="300">
+ <ok message="aaa">long</ok>
+ </testCase>
+ </file>
+</unitTest>