Browse Source

SONAR-8310, SONAR-8314 Make generic coverage plugin a core feature

tags/6.2-RC1
Julien HENRY 7 years ago
parent
commit
9ddf6fda88
43 changed files with 1396 additions and 111 deletions
  1. 0
    0
      it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties
  2. 0
    0
      it/it-projects/testing/xoo-sample-with-tests-execution-details/src/main/xoo/sample/Sample.xoo
  3. 0
    0
      it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo
  4. 0
    0
      it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo.test
  5. 6
    0
      it/it-projects/testing/xoo-sample-with-tests-execution-measures/sonar-project.properties
  6. 12
    0
      it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/main/xoo/sample/Sample.xoo
  7. 32
    0
      it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo
  8. 1
    1
      it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo.measures
  9. 18
    4
      it/it-tests/src/test/java/it/test/TestExecutionTest.java
  10. 9
    0
      sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java
  11. 2
    2
      sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java
  12. 4
    4
      sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/ScannerPerspectives.java
  13. 3
    3
      sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java
  14. 3
    3
      sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java
  15. 194
    0
      sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParser.java
  16. 126
    0
      sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageSensor.java
  17. 159
    0
      sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParser.java
  18. 88
    0
      sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionSensor.java
  19. 24
    0
      sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/package-info.java
  20. 4
    4
      sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java
  21. 2
    2
      sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java
  22. 74
    28
      sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java
  23. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
  24. 1
    12
      sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java
  25. 2
    2
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
  26. 3
    3
      sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java
  27. 3
    3
      sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java
  28. 3
    3
      sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java
  29. 224
    0
      sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParserTest.java
  30. 167
    0
      sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParserTest.java
  31. 4
    9
      sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java
  32. 139
    0
      sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java
  33. 6
    3
      sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java
  34. 2
    13
      sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java
  35. 2
    11
      sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java
  36. 9
    0
      sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage.xml
  37. 8
    0
      sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage2.xml
  38. 3
    0
      sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/sonar-project.properties
  39. 8
    0
      sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/NoConditions.xoo
  40. 6
    0
      sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/WithConditions.xoo
  41. 14
    0
      sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/coverage.xml
  42. 15
    0
      sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest.xml
  43. 15
    0
      sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest2.xml

it/it-projects/testing/xoo-sample-with-tests-execution/sonar-project.properties → it/it-projects/testing/xoo-sample-with-tests-execution-details/sonar-project.properties View File


it/it-projects/testing/xoo-sample-with-tests-execution/src/main/xoo/sample/Sample.xoo → it/it-projects/testing/xoo-sample-with-tests-execution-details/src/main/xoo/sample/Sample.xoo View File


it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo → it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo View File


it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.test → it/it-projects/testing/xoo-sample-with-tests-execution-details/src/test/xoo/sample/SampleTest.xoo.test View File


+ 6
- 0
it/it-projects/testing/xoo-sample-with-tests-execution-measures/sonar-project.properties View File

@@ -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

+ 12
- 0
it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/main/xoo/sample/Sample.xoo View File

@@ -0,0 +1,12 @@
package sample;

public class Sample {
public Sample(int i) {
int j = i++;
}
private String myMethod() {
return "hello";
}
}

+ 32
- 0
it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo View File

@@ -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");
}
}

it/it-projects/testing/xoo-sample-with-tests-execution/src/test/xoo/sample/SampleTest.xoo.measures → it/it-projects/testing/xoo-sample-with-tests-execution-measures/src/test/xoo/sample/SampleTest.xoo.measures View File

@@ -1,4 +1,4 @@
tests:4
tests:3
test_execution_time:8
skipped_tests:1
test_errors:1

+ 18
- 4
it/it-tests/src/test/java/it/test/TestExecutionTest.java View 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);
}
}

+ 9
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/BatchComponents.java View 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>());

+ 2
- 2
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilder.java View 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);
}

sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/BatchPerspectives.java → sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/perspectives/ScannerPerspectives.java View File

@@ -29,13 +29,13 @@ import org.sonar.api.resources.Resource;
import org.sonar.scanner.index.BatchComponentCache;
import org.sonar.scanner.index.DefaultIndex;

public class BatchPerspectives implements ResourcePerspectives {
public class ScannerPerspectives implements ResourcePerspectives {

private final Map<Class<?>, PerspectiveBuilder<?>> builders = Maps.newHashMap();
private final DefaultIndex resourceIndex;
private final BatchComponentCache componentCache;

public BatchPerspectives(PerspectiveBuilder[] builders, DefaultIndex resourceIndex, BatchComponentCache componentCache) {
public ScannerPerspectives(PerspectiveBuilder[] builders, DefaultIndex resourceIndex, BatchComponentCache componentCache) {
this.resourceIndex = resourceIndex;
this.componentCache = componentCache;
for (PerspectiveBuilder builder : builders) {
@@ -52,7 +52,7 @@ public class BatchPerspectives implements ResourcePerspectives {
}
if (indexedResource != null) {
PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource));
return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource).inputComponent());
}
return null;
}
@@ -60,7 +60,7 @@ public class BatchPerspectives implements ResourcePerspectives {
@Override
public <P extends Perspective> P as(Class<P> perspectiveClass, InputPath inputPath) {
PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
return builder.loadPerspective(perspectiveClass, componentCache.get(inputPath));
return builder.loadPerspective(perspectiveClass, inputPath);
}

private <T extends Perspective> PerspectiveBuilder<T> builderFor(Class<T> clazz) {

+ 3
- 3
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestPlanBuilder.java View 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());

+ 3
- 3
sonar-scanner-engine/src/main/java/org/sonar/scanner/deprecated/test/TestableBuilder.java View File

@@ -20,12 +20,12 @@
package org.sonar.scanner.deprecated.test;

import javax.annotation.CheckForNull;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.test.MutableTestable;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
import org.sonar.scanner.index.BatchComponent;

public class TestableBuilder extends PerspectiveBuilder<MutableTestable> {

@@ -35,9 +35,9 @@ public class TestableBuilder extends PerspectiveBuilder<MutableTestable> {

@CheckForNull
@Override
public MutableTestable loadPerspective(Class<MutableTestable> perspectiveClass, BatchComponent component) {
public MutableTestable loadPerspective(Class<MutableTestable> perspectiveClass, InputComponent component) {
if (component.isFile()) {
InputFile inputFile = (InputFile) component.inputComponent();
InputFile inputFile = (InputFile) component;
if (inputFile.type() == Type.MAIN) {
return new DefaultTestable((DefaultInputFile) inputFile);
}

+ 194
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParser.java View File

@@ -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;
}

}

+ 126
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericCoverageSensor.java View File

@@ -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")));
}
}

}

}

+ 159
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParser.java View File

@@ -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;
}

}

+ 88
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/GenericTestExecutionSensor.java View File

@@ -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")));
}
}

}

}

+ 24
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/genericcoverage/package-info.java View File

@@ -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;


+ 4
- 4
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssuable.java View 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

+ 2
- 2
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuableFactory.java View File

@@ -19,10 +19,10 @@
*/
package org.sonar.scanner.issue;

import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.issue.Issuable;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
import org.sonar.scanner.index.BatchComponent;
import org.sonar.scanner.sensor.DefaultSensorContext;

/**
@@ -39,7 +39,7 @@ public class IssuableFactory extends PerspectiveBuilder<Issuable> {
}

@Override
public Issuable loadPerspective(Class<Issuable> perspectiveClass, BatchComponent component) {
public Issuable loadPerspective(Class<Issuable> perspectiveClass, InputComponent component) {
return new DefaultIssuable(component, sensorContext);
}
}

+ 74
- 28
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MeasuresPublisher.java View File

@@ -20,14 +20,21 @@
package org.sonar.scanner.report;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.stream.StreamSupport;
import javax.annotation.Nonnull;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.test.MutableTestPlan;
import org.sonar.api.test.TestCase.Status;
import org.sonar.api.utils.KeyValueFormat;
import org.sonar.scanner.deprecated.test.TestPlanBuilder;
import org.sonar.scanner.index.BatchComponent;
import org.sonar.scanner.index.BatchComponentCache;
import org.sonar.scanner.protocol.output.ScannerReport;
@@ -44,6 +51,16 @@ import static org.sonar.api.measures.CoreMetrics.CONDITIONS_TO_COVER;
import static org.sonar.api.measures.CoreMetrics.CONDITIONS_TO_COVER_KEY;
import static org.sonar.api.measures.CoreMetrics.LINES_TO_COVER;
import static org.sonar.api.measures.CoreMetrics.LINES_TO_COVER_KEY;
import static org.sonar.api.measures.CoreMetrics.SKIPPED_TESTS;
import static org.sonar.api.measures.CoreMetrics.SKIPPED_TESTS_KEY;
import static org.sonar.api.measures.CoreMetrics.TESTS;
import static org.sonar.api.measures.CoreMetrics.TESTS_KEY;
import static org.sonar.api.measures.CoreMetrics.TEST_ERRORS;
import static org.sonar.api.measures.CoreMetrics.TEST_ERRORS_KEY;
import static org.sonar.api.measures.CoreMetrics.TEST_EXECUTION_TIME;
import static org.sonar.api.measures.CoreMetrics.TEST_EXECUTION_TIME_KEY;
import static org.sonar.api.measures.CoreMetrics.TEST_FAILURES;
import static org.sonar.api.measures.CoreMetrics.TEST_FAILURES_KEY;
import static org.sonar.api.measures.CoreMetrics.UNCOVERED_CONDITIONS;
import static org.sonar.api.measures.CoreMetrics.UNCOVERED_CONDITIONS_KEY;
import static org.sonar.api.measures.CoreMetrics.UNCOVERED_LINES;
@@ -100,47 +117,76 @@ public class MeasuresPublisher implements ReportPublisherStep {

private final BatchComponentCache componentCache;
private final MeasureCache measureCache;
private final TestPlanBuilder testPlanBuilder;

public MeasuresPublisher(BatchComponentCache resourceCache, MeasureCache measureCache) {
public MeasuresPublisher(BatchComponentCache resourceCache, MeasureCache measureCache, TestPlanBuilder testPlanBuilder) {
this.componentCache = resourceCache;
this.measureCache = measureCache;
this.testPlanBuilder = testPlanBuilder;
}

@Override
public void publish(ScannerReportWriter writer) {
for (final BatchComponent component : componentCache.all()) {
// Recompute all coverage measures from line data to take into account the possible merge of several reports
DefaultMeasure<String> lineHitsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY);
if (lineHitsMeasure != null) {
Map<Integer, Integer> lineHits = KeyValueFormat.parseIntInt(lineHitsMeasure.value());
measureCache.put(component.key(), LINES_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(LINES_TO_COVER).withValue(lineHits.keySet().size()));
measureCache.put(component.key(), UNCOVERED_LINES_KEY,
new DefaultMeasure<Integer>().forMetric(UNCOVERED_LINES).withValue((int) lineHits.values()
.stream()
.filter(hit -> hit == 0)
.count()));
}
DefaultMeasure<String> conditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.CONDITIONS_BY_LINE_KEY);
DefaultMeasure<String> coveredConditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY);
if (conditionsMeasure != null) {
Map<Integer, Integer> conditions = KeyValueFormat.parseIntInt(conditionsMeasure.value());
Map<Integer, Integer> coveredConditions = coveredConditionsMeasure != null ? KeyValueFormat.parseIntInt(coveredConditionsMeasure.value()) : Collections.emptyMap();
measureCache.put(component.key(), CONDITIONS_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(CONDITIONS_TO_COVER).withValue(conditions
.values()
.stream()
.mapToInt(Integer::intValue)
.sum()));
measureCache.put(component.key(), UNCOVERED_CONDITIONS_KEY,
new DefaultMeasure<Integer>().forMetric(UNCOVERED_CONDITIONS)
.withValue((int) conditions.keySet()
.stream()
.mapToInt(line -> conditions.get(line) - coveredConditions.get(line))
.sum()));
}
updateCoverageFromLineData(component);
// Recompute test execution measures from MutableTestPlan to take into account the possible merge of several reports
updateTestExecutionFromTestPlan(component);

Iterable<DefaultMeasure<?>> scannerMeasures = measureCache.byComponentKey(component.key());
Iterable<ScannerReport.Measure> reportMeasures = transform(scannerMeasures, new MeasureToReportMeasure(component));
writer.writeComponentMeasures(component.batchId(), reportMeasures);
}
}

private void updateTestExecutionFromTestPlan(final BatchComponent component) {
final MutableTestPlan testPlan = testPlanBuilder.loadPerspective(MutableTestPlan.class, component.inputComponent());
if (testPlan == null || Iterables.isEmpty(testPlan.testCases())) {
return;
}
long nonSkippedTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() != Status.SKIPPED).count();
measureCache.put(component.key(), TESTS_KEY, new DefaultMeasure<Integer>().forMetric(TESTS).withValue((int) nonSkippedTests));
long executionTime = StreamSupport.stream(testPlan.testCases().spliterator(), false).mapToLong(t -> t.durationInMs() != null ? t.durationInMs().longValue() : 0L).sum();
measureCache.put(component.key(), TEST_EXECUTION_TIME_KEY, new DefaultMeasure<Long>().forMetric(TEST_EXECUTION_TIME).withValue(executionTime));
long errorTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() == Status.ERROR).count();
measureCache.put(component.key(), TEST_ERRORS_KEY, new DefaultMeasure<Integer>().forMetric(TEST_ERRORS).withValue((int) errorTests));
long skippedTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() == Status.SKIPPED).count();
measureCache.put(component.key(), SKIPPED_TESTS_KEY, new DefaultMeasure<Integer>().forMetric(SKIPPED_TESTS).withValue((int) skippedTests));
long failedTests = StreamSupport.stream(testPlan.testCases().spliterator(), false).filter(t -> t.status() == Status.FAILURE).count();
measureCache.put(component.key(), TEST_FAILURES_KEY, new DefaultMeasure<Integer>().forMetric(TEST_FAILURES).withValue((int) failedTests));
}

private void updateCoverageFromLineData(final BatchComponent component) {
if (!component.isFile() || ((InputFile) component.inputComponent()).type() != Type.MAIN) {
return;
}
DefaultMeasure<String> lineHitsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY);
if (lineHitsMeasure != null) {
Map<Integer, Integer> lineHits = KeyValueFormat.parseIntInt(lineHitsMeasure.value());
measureCache.put(component.key(), LINES_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(LINES_TO_COVER).withValue(lineHits.keySet().size()));
measureCache.put(component.key(), UNCOVERED_LINES_KEY,
new DefaultMeasure<Integer>().forMetric(UNCOVERED_LINES).withValue((int) lineHits.values()
.stream()
.filter(hit -> hit == 0)
.count()));
}
DefaultMeasure<String> conditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.CONDITIONS_BY_LINE_KEY);
DefaultMeasure<String> coveredConditionsMeasure = (DefaultMeasure<String>) measureCache.byMetric(component.key(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY);
if (conditionsMeasure != null) {
Map<Integer, Integer> conditions = KeyValueFormat.parseIntInt(conditionsMeasure.value());
Map<Integer, Integer> coveredConditions = coveredConditionsMeasure != null ? KeyValueFormat.parseIntInt(coveredConditionsMeasure.value()) : Collections.emptyMap();
measureCache.put(component.key(), CONDITIONS_TO_COVER_KEY, new DefaultMeasure<Integer>().forMetric(CONDITIONS_TO_COVER).withValue(conditions
.values()
.stream()
.mapToInt(Integer::intValue)
.sum()));
measureCache.put(component.key(), UNCOVERED_CONDITIONS_KEY,
new DefaultMeasure<Integer>().forMetric(UNCOVERED_CONDITIONS)
.withValue((int) conditions.keySet()
.stream()
.mapToInt(line -> conditions.get(line) - coveredConditions.get(line))
.sum()));
}
}

}

+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java View 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())

+ 1
- 12
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java View 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;
}

+ 2
- 2
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java View 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,


+ 3
- 3
sonar-scanner-engine/src/main/java/org/sonar/scanner/source/HighlightableBuilder.java View 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;

+ 3
- 3
sonar-scanner-engine/src/main/java/org/sonar/scanner/source/SymbolizableBuilder.java View 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;

+ 3
- 3
sonar-scanner-engine/src/test/java/org/sonar/scanner/deprecated/perspectives/PerspectiveBuilderTest.java View File

@@ -20,18 +20,18 @@
package org.sonar.scanner.deprecated.perspectives;

import org.junit.Test;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.component.Perspective;
import org.sonar.scanner.deprecated.perspectives.PerspectiveBuilder;
import org.sonar.scanner.index.BatchComponent;

import static org.assertj.core.api.Assertions.assertThat;

public class PerspectiveBuilderTest {

@Test
public void testGetPerspectiveClass() throws Exception {
PerspectiveBuilder<FakePerspective> builder = new PerspectiveBuilder<FakePerspective>(FakePerspective.class) {
@Override
public FakePerspective loadPerspective(Class<FakePerspective> perspectiveClass, BatchComponent component) {
public FakePerspective loadPerspective(Class<FakePerspective> perspectiveClass, InputComponent component) {
return null;
}
};

+ 224
- 0
sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericCoverageReportParserTest.java View File

@@ -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");
}
}

+ 167
- 0
sonar-scanner-engine/src/test/java/org/sonar/scanner/genericcoverage/GenericTestExecutionReportParserTest.java View File

@@ -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;
}

}

+ 4
- 9
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuableFactoryTest.java View File

@@ -20,13 +20,10 @@
package org.sonar.scanner.issue;

import org.junit.Test;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.issue.Issuable;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
import org.sonar.scanner.DefaultProjectTree;
import org.sonar.scanner.index.BatchComponent;
import org.sonar.scanner.issue.IssuableFactory;
import org.sonar.scanner.issue.ModuleIssues;
import org.sonar.scanner.sensor.DefaultSensorContext;

import static org.assertj.core.api.Assertions.assertThat;
@@ -40,8 +37,7 @@ public class IssuableFactoryTest {
@Test
public void file_should_be_issuable() {
IssuableFactory factory = new IssuableFactory(mock(DefaultSensorContext.class));
BatchComponent component = new BatchComponent(1, File.create("foo/bar.c").setEffectiveKey("foo/bar.c"), null);
Issuable issuable = factory.loadPerspective(Issuable.class, component);
Issuable issuable = factory.loadPerspective(Issuable.class, new DefaultInputFile("foo", "src/Foo.java"));

assertThat(issuable).isNotNull();
assertThat(issuable.issues()).isEmpty();
@@ -50,8 +46,7 @@ public class IssuableFactoryTest {
@Test
public void project_should_be_issuable() {
IssuableFactory factory = new IssuableFactory(mock(DefaultSensorContext.class));
BatchComponent component = new BatchComponent(1, new Project("Foo").setEffectiveKey("foo"), null);
Issuable issuable = factory.loadPerspective(Issuable.class, component);
Issuable issuable = factory.loadPerspective(Issuable.class, new DefaultInputModule("foo"));

assertThat(issuable).isNotNull();
assertThat(issuable.issues()).isEmpty();

+ 139
- 0
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java View File

@@ -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")

);
}

}

+ 6
- 3
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java View 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

+ 2
- 13
sonar-scanner-engine/src/test/java/org/sonar/scanner/source/HighlightableBuilderTest.java View 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();
}

+ 2
- 11
sonar-scanner-engine/src/test/java/org/sonar/scanner/source/SymbolizableBuilderTest.java View 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();
}

+ 9
- 0
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage.xml View File

@@ -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>

+ 8
- 0
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/coverage2.xml View File

@@ -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>

+ 3
- 0
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/sonar-project.properties View File

@@ -0,0 +1,3 @@
sonar.projectKey=sample-generic-coverage
sonar.sources=xources
sonar.language=xoo

+ 8
- 0
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/NoConditions.xoo View File

@@ -0,0 +1,8 @@
package hello;

public class HelloJava {

public static void main(String[] args) {
System.out.println("Hello");
}
}

+ 6
- 0
sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-generic-coverage/xources/hello/WithConditions.xoo View File

@@ -0,0 +1,6 @@
object HelloWorld {
def main(args: Array[String]) {
args.isEmpty ? println("Hello, world of xoo!") : println("Hello, world of empty!")
}
}

+ 14
- 0
sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/coverage.xml View File

@@ -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>

+ 15
- 0
sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest.xml View File

@@ -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>

+ 15
- 0
sonar-scanner-engine/src/test/resources/org/sonar/scanner/genericcoverage/unittest2.xml View File

@@ -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>

Loading…
Cancel
Save