]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8310, SONAR-8314 Make generic coverage plugin a core feature
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 19 Oct 2016 14:50:16 +0000 (16:50 +0200)
committerJulien HENRY <henryju@yahoo.fr>
Fri, 21 Oct 2016 11:17:30 +0000 (13:17 +0200)
49 files changed:
it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution-details/src/main/xoo/sample/Sample.xoo [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo.test [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution-measures/sonar-project.properties [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/main/xoo/sample/Sample.xoo [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo.measures [new file with mode: 0644]
it/it-projects/testing/xoo-sample-with-tests-execution/sonar-project.properties [deleted file]
it/it-projects/testing/xoo-sample-with-tests-execution/src/main/xoo/sample/Sample.xoo [deleted file]
it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo [deleted file]
it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.measures [deleted file]
it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.test [deleted file]
it/it-tests/src/test/java/it/test/TestExecutionTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/BatchPerspectives.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/ScannerPerspectives.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParser.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageSensor.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParser.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionSensor.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/package-info.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParserTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParserTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage.xml [new file with mode: 0644]
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage2.xml [new file with mode: 0644]
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/sonar-project.properties [new file with mode: 0644]
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/NoConditions.xoo [new file with mode: 0644]
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/WithConditions.xoo [new file with mode: 0644]
sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/coverage.xml [new file with mode: 0644]
sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest.xml [new file with mode: 0644]
sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest2.xml [new file with mode: 0644]

diff --git a/it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties b/it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties
new file mode 100644 (file)
index 0000000..0f2415d
--- /dev/null
@@ -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-details/src/main/xoo/sample/Sample.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/main/xoo/sample/Sample.xoo
new file mode 100644 (file)
index 0000000..b121097
--- /dev/null
@@ -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-details/src/test/xoo/sample/SampleTest.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo
new file mode 100644 (file)
index 0000000..fe2368f
--- /dev/null
@@ -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-details/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
new file mode 100644 (file)
index 0000000..b025969
--- /dev/null
@@ -0,0 +1,4 @@
+skipped::::SKIPPED:UNIT
+failure:2:Failure::FAILURE:UNIT
+error:2:Error:The stack:ERROR:UNIT
+success:4:::OK:INTEGRATION
\ No newline at end of file
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 (file)
index 0000000..0f2415d
--- /dev/null
@@ -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 (file)
index 0000000..b121097
--- /dev/null
@@ -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 (file)
index 0000000..fe2368f
--- /dev/null
@@ -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-measures/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
new file mode 100644 (file)
index 0000000..de15494
--- /dev/null
@@ -0,0 +1,5 @@
+tests:3
+test_execution_time:8
+skipped_tests:1
+test_errors:1
+test_failures:1
\ No newline at end of file
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/sonar-project.properties
deleted file mode 100644 (file)
index 0f2415d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-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/src/main/xoo/sample/Sample.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution/src/main/xoo/sample/Sample.xoo
deleted file mode 100644 (file)
index b121097..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/src/test/xoo/sample/SampleTest.xoo b/it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo
deleted file mode 100644 (file)
index fe2368f..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-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/src/test/xoo/sample/SampleTest.xoo.measures
deleted file mode 100644 (file)
index 095af4b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-tests:4
-test_execution_time:8
-skipped_tests:1
-test_errors:1
-test_failures:1
\ No newline at end of file
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/src/test/xoo/sample/SampleTest.xoo.test
deleted file mode 100644 (file)
index b025969..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-skipped::::SKIPPED:UNIT
-failure:2:Failure::FAILURE:UNIT
-error:2:Error:The stack:ERROR:UNIT
-success:4:::OK:INTEGRATION
\ No newline at end of file
index f0d11de053385c5571c5a5054a54f244b5dc5207..34c75e01a4791fad08a607f1ca2d8a6dfec68d6f 100644 (file)
@@ -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);
+  }
 }
index 51dc68f024c92d14b6630d82b2574ba36e3f66ae..d619a516cac0ebd1794d73dbd3e000639332a492 100644 (file)
@@ -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/BatchPerspectives.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/BatchPerspectives.java
deleted file mode 100644 (file)
index 1e89551..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.deprecated.perspectives;
-
-import com.google.common.collect.Maps;
-import java.util.Map;
-import javax.annotation.CheckForNull;
-import org.sonar.api.batch.fs.InputPath;
-import org.sonar.api.component.Perspective;
-import org.sonar.api.component.ResourcePerspectives;
-import org.sonar.api.resources.Resource;
-import org.sonar.scanner.index.BatchComponentCache;
-import org.sonar.scanner.index.DefaultIndex;
-
-public class BatchPerspectives 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) {
-    this.resourceIndex = resourceIndex;
-    this.componentCache = componentCache;
-    for (PerspectiveBuilder builder : builders) {
-      this.builders.put(builder.getPerspectiveClass(), builder);
-    }
-  }
-
-  @Override
-  @CheckForNull
-  public <P extends Perspective> P as(Class<P> perspectiveClass, Resource resource) {
-    Resource indexedResource = resource;
-    if (resource.getEffectiveKey() == null) {
-      indexedResource = resourceIndex.getResource(resource);
-    }
-    if (indexedResource != null) {
-      PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
-      return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource));
-    }
-    return null;
-  }
-
-  @Override
-  public <P extends Perspective> P as(Class<P> perspectiveClass, InputPath inputPath) {
-    PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
-    return builder.loadPerspective(perspectiveClass, componentCache.get(inputPath));
-  }
-
-  private <T extends Perspective> PerspectiveBuilder<T> builderFor(Class<T> clazz) {
-    PerspectiveBuilder<T> builder = (PerspectiveBuilder<T>) builders.get(clazz);
-    if (builder == null) {
-      throw new PerspectiveNotFoundException("Perspective class is not registered: " + clazz);
-    }
-    return builder;
-  }
-}
index fd1632b9172d1c18b036bee487f2aaa09b2a8dda..255968e45f2dad44bcb1ebd081398a84d12a1340 100644 (file)
@@ -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/ScannerPerspectives.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/ScannerPerspectives.java
new file mode 100644 (file)
index 0000000..1903ef3
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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.deprecated.perspectives;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import org.sonar.api.batch.fs.InputPath;
+import org.sonar.api.component.Perspective;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.resources.Resource;
+import org.sonar.scanner.index.BatchComponentCache;
+import org.sonar.scanner.index.DefaultIndex;
+
+public class ScannerPerspectives implements ResourcePerspectives {
+
+  private final Map<Class<?>, PerspectiveBuilder<?>> builders = Maps.newHashMap();
+  private final DefaultIndex resourceIndex;
+  private final BatchComponentCache componentCache;
+
+  public ScannerPerspectives(PerspectiveBuilder[] builders, DefaultIndex resourceIndex, BatchComponentCache componentCache) {
+    this.resourceIndex = resourceIndex;
+    this.componentCache = componentCache;
+    for (PerspectiveBuilder builder : builders) {
+      this.builders.put(builder.getPerspectiveClass(), builder);
+    }
+  }
+
+  @Override
+  @CheckForNull
+  public <P extends Perspective> P as(Class<P> perspectiveClass, Resource resource) {
+    Resource indexedResource = resource;
+    if (resource.getEffectiveKey() == null) {
+      indexedResource = resourceIndex.getResource(resource);
+    }
+    if (indexedResource != null) {
+      PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
+      return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource).inputComponent());
+    }
+    return null;
+  }
+
+  @Override
+  public <P extends Perspective> P as(Class<P> perspectiveClass, InputPath inputPath) {
+    PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
+    return builder.loadPerspective(perspectiveClass, inputPath);
+  }
+
+  private <T extends Perspective> PerspectiveBuilder<T> builderFor(Class<T> clazz) {
+    PerspectiveBuilder<T> builder = (PerspectiveBuilder<T>) builders.get(clazz);
+    if (builder == null) {
+      throw new PerspectiveNotFoundException("Perspective class is not registered: " + clazz);
+    }
+    return builder;
+  }
+}
index 94fbfd0ef18e65bc8023939b589ac81a6da1cec2..580b0b109443e9fde369d6186f099af1c0b2de26 100644 (file)
@@ -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());
index 4c9c3de46fb22467ba67665820562254968e5c64..a03c3514f4f2ef6421fa1fb0bef42b727d70b53e 100644 (file)
 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 (file)
index 0000000..2ccd7a0
--- /dev/null
@@ -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 (file)
index 0000000..4a4c601
--- /dev/null
@@ -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 (file)
index 0000000..2762ec3
--- /dev/null
@@ -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 (file)
index 0000000..a4db690
--- /dev/null
@@ -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 (file)
index 0000000..46f0286
--- /dev/null
@@ -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;
+
index 2a4495964842b7603bcc3551fcc915e8ac903732..28c2502430a35adf635a681c9e09a7b1b56d363e 100644 (file)
@@ -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
index 85177696c539f4b8d684ff6ee8d6001be66f72fe..47f2ea3bcd3e09f418c3c83f049ea4b3a2d2377c 100644 (file)
  */
 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);
   }
 }
index 8eb6fe002513c1572693465c4e81b29e49b1a486..4686aaea47a0714e3607e62807430f76bcc70ad6 100644 (file)
 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()));
+    }
+  }
+
 }
index cb341a96a3b72490dfcd8a9471176b3459a3a329..7cb5611cdba89c24b0a2e118f8e75f337ac5731e 100644 (file)
@@ -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())
index e5f7cc0cbe192dcd720b7b39907f710d42c6fb09..d37c9439e8bc08b3e3a90d8c817f37621df75409 100644 (file)
@@ -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;
       }
index 72f95401c530dc1a05339eb863983484ebcf2214..5ab42b57c2eec173562d318b88d044aba5aea931 100644 (file)
@@ -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,
 
index 5f062b22946495a12fff5c4ccf86fda4c715a8f1..da793d3e9815ad6d3e3664b194911078c536192e 100644 (file)
@@ -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;
index e805c6dbcc4b7cba120190cc4a6eee44f51f839f..947670bf769b7c0fc5dd3af015f3f168805960d2 100644 (file)
@@ -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;
index 791db326ae73fb9be280a52ef995105a8c52d0b0..934141f862d4e9c0c1fa875136f2b21b3bc20c90 100644 (file)
 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 (file)
index 0000000..fb41e6b
--- /dev/null
@@ -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 (file)
index 0000000..3ee34b3
--- /dev/null
@@ -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;
+  }
+
+}
index 07038c5832f1845e3514d0633c9544c2ebb996f9..996e506685655680fa6a83f560ac8f1879c13100 100644 (file)
 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 (file)
index 0000000..f14e6c7
--- /dev/null
@@ -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")
+
+    );
+  }
+
+}
index 37edc1a0389d7b9e2f39263285571296049b62a0..190907e001d9daba7d2374390a6bb6f22a3094ff 100644 (file)
@@ -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
index fd07607430d70432951f3e8d9c489876c3168922..f68fe762e420dd774285d6d2da75540fb12d6cf6 100644 (file)
@@ -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();
   }
index 9f50909105cfab279b861d769632db0904394045..9a5e2edbc9fa47f91cf3fb500a0e6e61827ec575 100644 (file)
@@ -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 (file)
index 0000000..e12af5e
--- /dev/null
@@ -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 (file)
index 0000000..1a1e02b
--- /dev/null
@@ -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 (file)
index 0000000..ac18398
--- /dev/null
@@ -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 (file)
index 0000000..1d9c60d
--- /dev/null
@@ -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 (file)
index 0000000..8f46910
--- /dev/null
@@ -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 (file)
index 0000000..74dd213
--- /dev/null
@@ -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 (file)
index 0000000..f1d12ec
--- /dev/null
@@ -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 (file)
index 0000000..35ff4ad
--- /dev/null
@@ -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>