@@ -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 |
@@ -0,0 +1,12 @@ | |||
package sample; | |||
public class Sample { | |||
public Sample(int i) { | |||
int j = i++; | |||
} | |||
private String myMethod() { | |||
return "hello"; | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
tests:4 | |||
tests:3 | |||
test_execution_time:8 | |||
skipped_tests:1 | |||
test_errors:1 |
@@ -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); | |||
} | |||
} |
@@ -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>()); |
@@ -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); | |||
} |
@@ -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) { |
@@ -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()); |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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"))); | |||
} | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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"))); | |||
} | |||
} | |||
} | |||
} |
@@ -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; | |||
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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())); | |||
} | |||
} | |||
} |
@@ -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()) |
@@ -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; | |||
} |
@@ -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, | |||
@@ -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; |
@@ -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; |
@@ -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; | |||
} | |||
}; |
@@ -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"); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); |
@@ -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") | |||
); | |||
} | |||
} |
@@ -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 |
@@ -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(); | |||
} |
@@ -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(); | |||
} |
@@ -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> |
@@ -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> |
@@ -0,0 +1,3 @@ | |||
sonar.projectKey=sample-generic-coverage | |||
sonar.sources=xources | |||
sonar.language=xoo |
@@ -0,0 +1,8 @@ | |||
package hello; | |||
public class HelloJava { | |||
public static void main(String[] args) { | |||
System.out.println("Hello"); | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
object HelloWorld { | |||
def main(args: Array[String]) { | |||
args.isEmpty ? println("Hello, world of xoo!") : println("Hello, world of empty!") | |||
} | |||
} | |||
@@ -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> |
@@ -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> |
@@ -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> |