--- /dev/null
+sonar.projectKey=sample-with-tests
+sonar.projectName=Sample with tests
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.sources=src/main/xoo
+sonar.tests=src/test/xoo
+sonar.language=xoo
\ No newline at end of file
--- /dev/null
+package sample;
+
+public class Sample {
+
+ public Sample(int i) {
+ int j = i++;
+ }
+
+ private String myMethod() {
+ return "hello";
+ }
+}
--- /dev/null
+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");
+ }
+
+}
--- /dev/null
+skipped::::SKIPPED:UNIT
+failure:2:Failure::FAILURE:UNIT
+error:2:Error:The stack:ERROR:UNIT
+success:4:::OK:INTEGRATION
\ No newline at end of file
--- /dev/null
+sonar.projectKey=sample-with-tests
+sonar.projectName=Sample with tests
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.sources=src/main/xoo
+sonar.tests=src/test/xoo
+sonar.language=xoo
\ No newline at end of file
--- /dev/null
+package sample;
+
+public class Sample {
+
+ public Sample(int i) {
+ int j = i++;
+ }
+
+ private String myMethod() {
+ return "hello";
+ }
+}
--- /dev/null
+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");
+ }
+
+}
--- /dev/null
+tests:3
+test_execution_time:8
+skipped_tests:1
+test_errors:1
+test_failures:1
\ No newline at end of file
+++ /dev/null
-sonar.projectKey=sample-with-tests
-sonar.projectName=Sample with tests
-sonar.projectVersion=1.0-SNAPSHOT
-sonar.sources=src/main/xoo
-sonar.tests=src/test/xoo
-sonar.language=xoo
\ No newline at end of file
+++ /dev/null
-package sample;
-
-public class Sample {
-
- public Sample(int i) {
- int j = i++;
- }
-
- private String myMethod() {
- return "hello";
- }
-}
+++ /dev/null
-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");
- }
-
-}
+++ /dev/null
-tests:4
-test_execution_time:8
-skipped_tests:1
-test_errors:1
-test_failures:1
\ No newline at end of file
+++ /dev/null
-skipped::::SKIPPED:UNIT
-failure:2:Failure::FAILURE:UNIT
-error:2:Error:The stack:ERROR:UNIT
-success:4:::OK:INTEGRATION
\ No newline at end of file
}
@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);
+ }
}
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;
// 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>());
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.scanner.deprecated.perspectives;
-
-import com.google.common.collect.Maps;
-import java.util.Map;
-import javax.annotation.CheckForNull;
-import org.sonar.api.batch.fs.InputPath;
-import org.sonar.api.component.Perspective;
-import org.sonar.api.component.ResourcePerspectives;
-import org.sonar.api.resources.Resource;
-import org.sonar.scanner.index.BatchComponentCache;
-import org.sonar.scanner.index.DefaultIndex;
-
-public class BatchPerspectives implements ResourcePerspectives {
-
- private final Map<Class<?>, PerspectiveBuilder<?>> builders = Maps.newHashMap();
- private final DefaultIndex resourceIndex;
- private final BatchComponentCache componentCache;
-
- public BatchPerspectives(PerspectiveBuilder[] builders, DefaultIndex resourceIndex, BatchComponentCache componentCache) {
- this.resourceIndex = resourceIndex;
- this.componentCache = componentCache;
- for (PerspectiveBuilder builder : builders) {
- this.builders.put(builder.getPerspectiveClass(), builder);
- }
- }
-
- @Override
- @CheckForNull
- public <P extends Perspective> P as(Class<P> perspectiveClass, Resource resource) {
- Resource indexedResource = resource;
- if (resource.getEffectiveKey() == null) {
- indexedResource = resourceIndex.getResource(resource);
- }
- if (indexedResource != null) {
- PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
- return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource));
- }
- return null;
- }
-
- @Override
- public <P extends Perspective> P as(Class<P> perspectiveClass, InputPath inputPath) {
- PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
- return builder.loadPerspective(perspectiveClass, componentCache.get(inputPath));
- }
-
- private <T extends Perspective> PerspectiveBuilder<T> builderFor(Class<T> clazz) {
- PerspectiveBuilder<T> builder = (PerspectiveBuilder<T>) builders.get(clazz);
- if (builder == null) {
- throw new PerspectiveNotFoundException("Perspective class is not registered: " + clazz);
- }
- return builder;
- }
-}
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> {
}
@CheckForNull
- public abstract T loadPerspective(Class<T> perspectiveClass, BatchComponent component);
+ public abstract T loadPerspective(Class<T> perspectiveClass, InputComponent component);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.deprecated.perspectives;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import org.sonar.api.batch.fs.InputPath;
+import org.sonar.api.component.Perspective;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.resources.Resource;
+import org.sonar.scanner.index.BatchComponentCache;
+import org.sonar.scanner.index.DefaultIndex;
+
+public class ScannerPerspectives implements ResourcePerspectives {
+
+ private final Map<Class<?>, PerspectiveBuilder<?>> builders = Maps.newHashMap();
+ private final DefaultIndex resourceIndex;
+ private final BatchComponentCache componentCache;
+
+ public ScannerPerspectives(PerspectiveBuilder[] builders, DefaultIndex resourceIndex, BatchComponentCache componentCache) {
+ this.resourceIndex = resourceIndex;
+ this.componentCache = componentCache;
+ for (PerspectiveBuilder builder : builders) {
+ this.builders.put(builder.getPerspectiveClass(), builder);
+ }
+ }
+
+ @Override
+ @CheckForNull
+ public <P extends Perspective> P as(Class<P> perspectiveClass, Resource resource) {
+ Resource indexedResource = resource;
+ if (resource.getEffectiveKey() == null) {
+ indexedResource = resourceIndex.getResource(resource);
+ }
+ if (indexedResource != null) {
+ PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
+ return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource).inputComponent());
+ }
+ return null;
+ }
+
+ @Override
+ public <P extends Perspective> P as(Class<P> perspectiveClass, InputPath inputPath) {
+ PerspectiveBuilder<P> builder = builderFor(perspectiveClass);
+ return builder.loadPerspective(perspectiveClass, inputPath);
+ }
+
+ private <T extends Perspective> PerspectiveBuilder<T> builderFor(Class<T> clazz) {
+ PerspectiveBuilder<T> builder = (PerspectiveBuilder<T>) builders.get(clazz);
+ if (builder == null) {
+ throw new PerspectiveNotFoundException("Perspective class is not registered: " + clazz);
+ }
+ return builder;
+ }
+}
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> {
@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());
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> {
@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);
}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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")));
+ }
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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")));
+ }
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
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;
}
@Override
public IssueBuilder newIssueBuilder() {
DefaultIssue newIssue = (DefaultIssue) sensorContext.newIssue();
- return new DeprecatedIssueBuilderWrapper(component.inputComponent(), newIssue);
+ return new DeprecatedIssueBuilderWrapper(component, newIssue);
}
@Override
*/
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;
/**
}
@Override
- public Issuable loadPerspective(Class<Issuable> perspectiveClass, BatchComponent component) {
+ public Issuable loadPerspective(Class<Issuable> perspectiveClass, InputComponent component) {
return new DefaultIssuable(component, sensorContext);
}
}
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;
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;
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()));
+ }
+ }
+
}
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())
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;
@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;
}
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;
IgnoreIssuesFilter.class,
// Perspectives
- BatchPerspectives.class,
+ ScannerPerspectives.class,
HighlightableBuilder.class,
SymbolizableBuilder.class,
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> {
@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;
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> {
@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;
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;
}
};
--- /dev/null
+/*
+ * 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");
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
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;
@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();
@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();
--- /dev/null
+/*
+ * 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")
+
+ );
+ }
+
+}
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;
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
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;
@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();
}
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;
@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();
}
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+sonar.projectKey=sample-generic-coverage
+sonar.sources=xources
+sonar.language=xoo
--- /dev/null
+package hello;
+
+public class HelloJava {
+
+ public static void main(String[] args) {
+ System.out.println("Hello");
+ }
+}
\ No newline at end of file
--- /dev/null
+ object HelloWorld {
+ def main(args: Array[String]) {
+ args.isEmpty ? println("Hello, world of xoo!") : println("Hello, world of empty!")
+ }
+ }
+
\ No newline at end of file
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>