diff --git a/integration-tests/features/importing_coverage.feature b/integration-tests/features/importing_coverage.feature index a6a3f64de3..99099eee8b 100644 --- a/integration-tests/features/importing_coverage.feature +++ b/integration-tests/features/importing_coverage.feature @@ -36,71 +36,71 @@ Feature: Importing coverage data | overall_branch_coverage | 50.0 | -# Scenario: Importing coverage reports zeroing coverage for untouched files -# GIVEN the project "coverage_project" + Scenario: Importing coverage reports zeroing coverage for untouched files + GIVEN the project "coverage_project" -# WHEN I run sonar-scanner with following options: -# """ -# -Dsonar.cxx.coverage.reportPath=ut-coverage.xml -# -Dsonar.cxx.coverage.itReportPath=it-coverage.xml -# -Dsonar.cxx.coverage.overallReportPath=overall-coverage.xml -# -Dsonar.cxx.coverage.forceZeroCoverage=True -# """ + WHEN I run sonar-scanner with following options: + """ + -Dsonar.cxx.coverage.reportPath=ut-coverage.xml + -Dsonar.cxx.coverage.itReportPath=it-coverage.xml + -Dsonar.cxx.coverage.overallReportPath=overall-coverage.xml + -Dsonar.cxx.coverage.forceZeroCoverage=True + """ -# THEN the analysis finishes successfully -# AND the analysis in server has completed -# AND the analysis log contains no error/warning messages except those matching: -# """ -# .*WARN.*Unable to get a valid mac address, will use a dummy address -# .*WARN.*cannot find the sources for '#include ' -# """ -# AND the following metrics have following values: -# | metric | value | -# | coverage | 8.5 | -# | line_coverage | 5.5 | -# | branch_coverage | 50 | -# | it_coverage | 20.3 | -# | it_line_coverage | 14.8 | -# | it_branch_coverage | 50 | -# | overall_coverage | 28.1 | -# | overall_line_coverage | 22 | -# | overall_branch_coverage | 50 | + THEN the analysis finishes successfully + AND the analysis in server has completed + AND the analysis log contains no error/warning messages except those matching: + """ + .*WARN.*Unable to get a valid mac address, will use a dummy address + .*WARN.*cannot find the sources for '#include ' + """ + AND the following metrics have following values: + | metric | value | + | coverage | 5.9 | + | line_coverage | 3.1 | + | branch_coverage | 50 | + | it_coverage | 14.1 | + | it_line_coverage | 8.2 | + | it_branch_coverage | 50 | + | overall_coverage | 20.0 | + | overall_line_coverage | 12.5 | + | overall_branch_coverage | 50 | -# Scenario: Zeroing coverage measures without importing reports + Scenario: Zeroing coverage measures without importing reports -# If we don't pass coverage reports *and* request zeroing untouched -# files at the same time, all coverage measures, except the branch -# ones, should be 'zero'. The branch coverage measures remain 'None', -# since its currently ignored by the 'force zero...' -# implementation + If we don't pass coverage reports *and* request zeroing untouched + files at the same time, all coverage measures, except the branch + ones, should be 'zero'. The branch coverage measures remain 'None', + since its currently ignored by the 'force zero...' + implementation -# GIVEN the project "coverage_project" + GIVEN the project "coverage_project" -# WHEN I run sonar-scanner with following options: -# """ -# -Dsonar.cxx.coverage.reportPath=dummy.xml -# -Dsonar.cxx.coverage.itReportPath=dummy.xml -# -Dsonar.cxx.coverage.overallReportPath=dummy.xml -# -Dsonar.cxx.coverage.forceZeroCoverage=True -# """ + WHEN I run sonar-scanner with following options: + """ + -Dsonar.cxx.coverage.reportPath=dummy.xml + -Dsonar.cxx.coverage.itReportPath=dummy.xml + -Dsonar.cxx.coverage.overallReportPath=dummy.xml + -Dsonar.cxx.coverage.forceZeroCoverage=True + """ -# THEN the analysis finishes successfully -# AND the analysis in server has completed -# AND the analysis log contains no error/warning messages except those matching: -# """ -# .*WARN.*Unable to get a valid mac address, will use a dummy address -# .*WARN.*cannot find the sources for '#include ' -# .*WARN.*Cannot find a report for '.*' -# """ -# AND the following metrics have following values: -# | metric | value | -# | coverage | 0 | -# | line_coverage | 0 | -# | branch_coverage | None | -# | it_coverage | 0 | -# | it_line_coverage | 0 | -# | it_branch_coverage | None | -# | overall_coverage | 0 | -# | overall_line_coverage | 0 | -# | overall_branch_coverage | None | + THEN the analysis finishes successfully + AND the analysis in server has completed + AND the analysis log contains no error/warning messages except those matching: + """ + .*WARN.*Unable to get a valid mac address, will use a dummy address + .*WARN.*cannot find the sources for '#include ' + .*WARN.*Cannot find a report for '.*' + """ + AND the following metrics have following values: + | metric | value | + | coverage | 0 | + | line_coverage | 0 | + | branch_coverage | None | + | it_coverage | 0 | + | it_line_coverage | 0 | + | it_branch_coverage | None | + | overall_coverage | 0 | + | overall_line_coverage | 0 | + | overall_branch_coverage | None | diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java index 3a54d67054..5061270672 100644 --- a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java @@ -343,6 +343,15 @@ private static List testingAndCoverageProperties() { .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) .index(3) .build(), + PropertyDefinition.builder(CxxCoverageSensor.FORCE_ZERO_COVERAGE_KEY) + .defaultValue("False") + .name("Enable Force Zero Coverage") + .description("Set files without coverage reports to zero coverage. Default is 'False'.") + .subCategory(subcateg) + .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) + .type(PropertyType.BOOLEAN) + .index(4) + .build(), PropertyDefinition.builder(CxxXunitSensor.REPORT_PATH_KEY) .name("Unit test execution report(s)") .description("Path to unit test execution report(s), relative to projects root." @@ -350,7 +359,7 @@ private static List testingAndCoverageProperties() { + " Use Ant-style wildcards if neccessary.") .subCategory(subcateg) .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .index(5) + .index(6) .build(), PropertyDefinition.builder(CxxXunitSensor.XSLT_URL_KEY) .name("XSLT transformer") @@ -358,22 +367,22 @@ private static List testingAndCoverageProperties() { + " To import a report in an other format, set this property to an URL to a XSLT stylesheet which is able to perform the according transformation.") .subCategory(subcateg) .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .index(6) + .index(7) .build(), PropertyDefinition.builder(CxxUnitTestResultsProvider.VISUAL_STUDIO_TEST_RESULTS_PROPERTY_KEY) .name("Visual Studio Test Reports Paths") .description("Example: \"report.trx\", \"report1.trx,report2.trx\" or \"C:/report.trx\"") .subCategory(subcateg) .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .index(8) + .index(9) .build(), PropertyDefinition.builder(CxxUnitTestResultsProvider.NUNIT_TEST_RESULTS_PROPERTY_KEY) .name("Nunit Test Reports Paths") .description("Example: \"nunit.xml\", \"nunit1.xml,nunit2.xml\" or \"C:/nunit.xml\"") .subCategory(subcateg) .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .index(9) - .build() + .index(10) + .build() )); } diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensor.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensor.java index 2e2647ef42..20a2be033e 100644 --- a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensor.java +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensor.java @@ -25,8 +25,12 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; import javax.xml.stream.XMLStreamException; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.fs.InputFile; @@ -37,7 +41,6 @@ import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.cxx.CxxLanguage; -import org.sonar.plugins.cxx.CxxPlugin; import org.sonar.plugins.cxx.utils.CxxReportSensor; import org.sonar.plugins.cxx.utils.CxxUtils; @@ -49,6 +52,7 @@ public class CxxCoverageSensor extends CxxReportSensor { public static final String REPORT_PATH_KEY = "sonar.cxx.coverage.reportPath"; public static final String IT_REPORT_PATH_KEY = "sonar.cxx.coverage.itReportPath"; public static final String OVERALL_REPORT_PATH_KEY = "sonar.cxx.coverage.overallReportPath"; + public static final String FORCE_ZERO_COVERAGE_KEY = "sonar.cxx.coverage.forceZeroCoverage"; private final List parsers = new LinkedList<>(); private final CxxCoverageCache cache; @@ -72,8 +76,7 @@ public void describe(SensorDescriptor descriptor) { /** * {@inheritDoc} */ - @Override - public void execute(SensorContext context) { + public void execute(SensorContext context, Map> linesOfCode) { Map coverageMeasures = null; Map itCoverageMeasures = null; @@ -101,6 +104,66 @@ public void execute(SensorContext context) { overallCoverageMeasures = processReports(context, overallReports, this.cache.overallCoverageCache()); saveMeasures(context, overallCoverageMeasures, CoverageType.OVERALL); } + + if (settings.getBoolean(FORCE_ZERO_COVERAGE_KEY)) { + LOG.debug("Zeroing coverage information for untouched files"); + zeroMeasuresWithoutReports(context, coverageMeasures, itCoverageMeasures, overallCoverageMeasures, linesOfCode); + } + } + + private void zeroMeasuresWithoutReports( + SensorContext context, + Map coverageMeasures, + Map itCoverageMeasures, + Map overallCoverageMeasures, + Map> linesOfCode + ) { + FileSystem fileSystem = context.fileSystem(); + FilePredicates p = fileSystem.predicates(); + Iterable inputFiles = fileSystem.inputFiles(p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CxxLanguage.KEY))); + + for (InputFile inputFile : inputFiles) { + Set linesOfCodeForFile = linesOfCode.get(inputFile); + String file = CxxUtils.normalizePath(inputFile.absolutePath()); + + if (coverageMeasures != null && !coverageMeasures.containsKey(file)) { + saveZeroValueForResource(inputFile, context, CoverageType.UNIT, linesOfCodeForFile); + } + + if (itCoverageMeasures != null && !itCoverageMeasures.containsKey(file)) { + saveZeroValueForResource(inputFile, context, CoverageType.IT, linesOfCodeForFile); + } + + if (overallCoverageMeasures != null && !overallCoverageMeasures.containsKey(file)) { + saveZeroValueForResource(inputFile, context, CoverageType.OVERALL, linesOfCodeForFile); + } + } + } + + private void saveZeroValueForResource(InputFile inputFile, SensorContext context, CoverageType ctype, @Nullable Set linesOfCode) { + if (linesOfCode != null) { + LOG.debug("Zeroing {} coverage measures for file '{}'", ctype, inputFile.relativePath()); + + NewCoverage newCoverage = context.newCoverage() + .onFile(inputFile) + .ofType(ctype); + + for (Integer line : linesOfCode) { + try { + newCoverage.lineHits(line, 0); + } catch (Exception ex) { + LOG.error("Cannot save Line Hits for Line '{}' '{}' : '{}', ignoring measure", inputFile.relativePath(), line, ex.getMessage()); + CxxUtils.ValidateRecovery(ex, settings); + } + } + + try { + newCoverage.save(); + } catch (Exception ex) { + LOG.error("Cannot save measure '{}' : '{}', ignoring measure", inputFile.relativePath(), ex.getMessage()); + CxxUtils.ValidateRecovery(ex, settings); + } + } } private Map processReports(final SensorContext context, List reports, Map> cacheCov) { diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/metrics/FileLinesVisitor.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/metrics/FileLinesVisitor.java new file mode 100644 index 0000000000..1b547480ec --- /dev/null +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/metrics/FileLinesVisitor.java @@ -0,0 +1,100 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2016 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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.plugins.cxx.metrics; + +import com.google.common.collect.Sets; +import com.sonar.sslr.api.AstAndTokenVisitor; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.GenericTokenType; +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.cxx.api.CxxMetric; +import org.sonar.squidbridge.SquidAstVisitor; + +/** + * Visitor that computes {@link CoreMetrics#NCLOC_DATA_KEY} and + * {@link CoreMetrics#COMMENT_LINES_DATA_KEY} metrics used by the DevCockpit. + */ +public class FileLinesVisitor extends SquidAstVisitor implements AstAndTokenVisitor { + + private final FileLinesContextFactory fileLinesContextFactory; + + private Set linesOfCode = Sets.newHashSet(); + private Set linesOfComments = Sets.newHashSet(); + private final FileSystem fileSystem; + private final Map> allLinesOfCode; + + public FileLinesVisitor(FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem, Map> linesOfCode) { + this.fileLinesContextFactory = fileLinesContextFactory; + this.fileSystem = fileSystem; + this.allLinesOfCode = linesOfCode; + } + + @Override + public void visitToken(Token token) { + if (token.getType().equals(GenericTokenType.EOF)) { + return; + } + + linesOfCode.add(token.getLine()); + + List trivias = token.getTrivia(); + for (Trivia trivia : trivias) { + if (trivia.isComment()) { + linesOfComments.add(trivia.getToken().getLine()); + } + } + } + + @Override + public void leaveFile(AstNode astNode) { + InputFile inputFile = fileSystem.inputFile(fileSystem.predicates().is(getContext().getFile())); + if (inputFile == null) { + throw new IllegalStateException("InputFile is null, but it should not be."); + } + FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile); + + int fileLength = getContext().peekSourceCode().getInt(CxxMetric.LINES); + for (int line = 1; line <= fileLength; line++) { + if (linesOfCode.contains(line)) { + fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1); + } + if (linesOfComments.contains(line)) { + fileLinesContext.setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, line, 1); + } + } + fileLinesContext.save(); + + allLinesOfCode.put(inputFile, linesOfCode); + + linesOfCode = Sets.newHashSet(); + linesOfComments = Sets.newHashSet(); + } + +} diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/metrics/package-info.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/metrics/package-info.java new file mode 100644 index 0000000000..c2cde7955a --- /dev/null +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/metrics/package-info.java @@ -0,0 +1,27 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2016 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 to for visitors to create metrics + */ +@ParametersAreNonnullByDefault +package org.sonar.plugins.cxx.metrics; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java index 3fa0f51665..0107e1e897 100644 --- a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/squid/CxxSquidSensor.java @@ -56,14 +56,21 @@ import org.sonar.squidbridge.indexer.QueryByParent; import org.sonar.squidbridge.indexer.QueryByType; import com.sonar.sslr.api.Grammar; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; import org.sonar.api.ce.measure.RangeDistributionBuilder; +import org.sonar.api.measures.FileLinesContextFactory; import org.sonar.api.rule.RuleKey; import org.sonar.cxx.parser.CxxParser; +import org.sonar.plugins.cxx.CxxPlugin.CxxCoverageAggregator; +import org.sonar.plugins.cxx.coverage.CxxCoverageSensor; import org.sonar.plugins.cxx.cpd.CxxCpdVisitor; import org.sonar.plugins.cxx.highlighter.CxxHighlighter; +import org.sonar.plugins.cxx.metrics.FileLinesVisitor; /** @@ -74,6 +81,7 @@ public final class CxxSquidSensor implements Sensor { private static final Number[] LIMITS_COMPLEXITY_METHODS = {1, 2, 4, 6, 8, 10, 12, 20, 30}; private static final Number[] LIMITS_COMPLEXITY_FILES = {0, 5, 10, 20, 30, 60, 90}; + private final FileLinesContextFactory fileLinesContextFactory; private final CxxChecks checks; private ActiveRules rules; @@ -84,18 +92,19 @@ public final class CxxSquidSensor implements Sensor { /** * {@inheritDoc} */ - public CxxSquidSensor(Settings settings, CheckFactory checkFactory, ActiveRules rules) { - this(settings, checkFactory, rules, null); + public CxxSquidSensor(Settings settings, FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, ActiveRules rules) { + this(settings, fileLinesContextFactory, checkFactory, rules, null); } /** * {@inheritDoc} */ - public CxxSquidSensor(Settings settings, CheckFactory checkFactory, ActiveRules rules, + public CxxSquidSensor(Settings settings, FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, ActiveRules rules, @Nullable CustomCxxRulesDefinition[] customRulesDefinition) { this.checks = CxxChecks.createCxxCheck(checkFactory) .addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks()) .addCustomChecks(customRulesDefinition); + this.fileLinesContextFactory = fileLinesContextFactory; this.rules = rules; this.settings = settings; } @@ -110,9 +119,13 @@ public void describe(SensorDescriptor descriptor) { */ @Override public void execute(SensorContext context) { + Map> linesOfCode = new HashMap<>(); + List> visitors = new ArrayList<>((Collection) checks.all()); visitors.add(new CxxHighlighter(context)); + visitors.add(new FileLinesVisitor(fileLinesContextFactory, context.fileSystem(), linesOfCode)); visitors.add(new CxxCpdVisitor(context, settings.getBoolean(CxxPlugin.CPD_IGNORE_LITERALS_KEY), settings.getBoolean(CxxPlugin.CPD_IGNORE_IDENTIFIERS_KEY))); + this.scanner = CxxAstScanner.create(createConfiguration(context.fileSystem(), settings), context, visitors.toArray(new SquidAstVisitor[visitors.size()])); @@ -127,6 +140,8 @@ public void execute(SensorContext context) { } scanner.scanFiles(files); + (new CxxCoverageSensor(settings, new CxxCoverageAggregator())).execute(context, linesOfCode); + Collection squidSourceFiles = scanner.getIndex().search(new QueryByType(SourceFile.class)); save(squidSourceFiles, context); } diff --git a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java index e1c7d4aaab..c460d3b4ac 100644 --- a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java +++ b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java @@ -32,6 +32,6 @@ public void testGetExtensions() throws Exception { Plugin.Context context = new Plugin.Context(SonarQubeVersion.V5_6); CxxPlugin plugin = new CxxPlugin(); plugin.define(context); - assertThat(context.getExtensions()).hasSize(64); + assertThat(context.getExtensions()).hasSize(65); } } diff --git a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensorTest.java b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensorTest.java index 3dfd086d9e..1a9b6e5f12 100644 --- a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensorTest.java +++ b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/coverage/CxxCoverageSensorTest.java @@ -19,6 +19,9 @@ */ package org.sonar.plugins.cxx.coverage; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import static org.fest.assertions.Assertions.assertThat; import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.config.Settings; @@ -29,6 +32,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.coverage.CoverageType; import org.sonar.api.batch.sensor.internal.SensorContextTester; @@ -54,8 +58,11 @@ public void shouldReportCorrectCoverageForAllTypesOfCoverage() { context.fileSystem().add(new DefaultInputFile("myProjectKey", "sources/application/main.cpp").setLanguage("cpp").initMetadata("asd\nasdas\nasda\n\n\n\n\n\n")); context.fileSystem().add(new DefaultInputFile("myProjectKey", "sources/utils/utils.cpp").setLanguage("cpp").initMetadata("asd\nasdas\nasda\n")); context.fileSystem().add(new DefaultInputFile("myProjectKey", "sources/utils/code_chunks.cpp").setLanguage("cpp").initMetadata("asd\nasdas\nasda\n")); + + Map> linesOfCode = new HashMap<>(); sensor = new CxxCoverageSensor(settings, new CxxCoverageAggregator()); - sensor.execute(context); + sensor.execute(context, linesOfCode); + assertThat(context.lineHits("myProjectKey:sources/utils/code_chunks.cpp", CoverageType.UNIT, 1)).isEqualTo(1); assertThat(context.lineHits("myProjectKey:sources/utils/code_chunks.cpp", CoverageType.UNIT, 3)).isEqualTo(4); assertThat(context.lineHits("myProjectKey:sources/utils/utils.cpp", CoverageType.UNIT, 2)).isEqualTo(0); @@ -127,8 +134,9 @@ public void shouldReportCoverageWhenVisualStudioCase() { context.fileSystem().add(new DefaultInputFile("myProjectKey", "project2/source1.cpp").setLanguage("cpp").initMetadata("asd\nasdas\nasda\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")); context.fileSystem().add(new DefaultInputFile("myProjectKey", "project2/source2.cpp").setLanguage("cpp").initMetadata("asd\nasdas\nasda\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")); + Map> linesOfCode = new HashMap<>(); sensor = new CxxCoverageSensor(settings, new CxxCoverageCache()); - sensor.execute(context); + sensor.execute(context, linesOfCode); assertThat(context.lineHits("myProjectKey:project1/source1.cpp", CoverageType.UNIT, 4)).isEqualTo(0); assertThat(context.lineHits("myProjectKey:project2/source1.cpp", CoverageType.UNIT, 4)).isEqualTo(1); assertThat(context.lineHits("myProjectKey:project2/source2.cpp", CoverageType.UNIT, 4)).isEqualTo(1); diff --git a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/metrics/FileLinesVisitorTest.java b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/metrics/FileLinesVisitorTest.java new file mode 100644 index 0000000000..1d99b6ee82 --- /dev/null +++ b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/metrics/FileLinesVisitorTest.java @@ -0,0 +1,70 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2016 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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.plugins.cxx.metrics; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Set; +import org.junit.Test; +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +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.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.cxx.CxxAstScanner; + +public class FileLinesVisitorTest { + + @Test + public void test() throws UnsupportedEncodingException, IOException { + SensorContextTester sensorContext = SensorContextTester.create(new File(".")); + + String fileName = "src/test/resources/org/sonar/plugins/cxx/ncloc.cc"; + String content = new String(Files.readAllBytes(new File(sensorContext.fileSystem().baseDir(), fileName).toPath()), "UTF-8"); + DefaultInputFile inputFile = new DefaultInputFile("myProjectKey", fileName); + sensorContext.fileSystem().add(inputFile.initMetadata(content)); + + FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); + FileLinesContext fileLinesContext = mock(FileLinesContext.class); + + when(fileLinesContextFactory.createFor(inputFile)).thenReturn(fileLinesContext); + + HashMap> linesOfCode = new HashMap<>(); + FileLinesVisitor visitor = new FileLinesVisitor(fileLinesContextFactory, sensorContext.fileSystem(), linesOfCode); + + CxxAstScanner.scanSingleFile(inputFile, sensorContext, visitor); + + assertThat(linesOfCode).hasSize(1); + assertThat(linesOfCode.get(inputFile)).containsOnly( + 8, 9, 10, 11, + 14, 15, 16, 17, 18, 19, + 21, 22, 23, 24, 25, 26, 27, + 31, 32, 34, 35, 36, 37, + 42, 43, 44, 45, 46 + ); + } + +} diff --git a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java index 9a84a6ec77..b0d7ee7722 100644 --- a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java +++ b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java @@ -28,7 +28,9 @@ import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.rule.ActiveRules; @@ -39,6 +41,8 @@ import org.sonar.plugins.cxx.TestUtils; import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; import org.sonar.api.measures.Metric; import org.sonar.plugins.cxx.CxxLanguage; @@ -52,7 +56,10 @@ public void setUp() { settings = new Settings(); ActiveRules rules = mock(ActiveRules.class); CheckFactory checkFactory = new CheckFactory(rules); - sensor = new CxxSquidSensor(settings, checkFactory, rules, null); + FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); + FileLinesContext fileLinesContext = mock(FileLinesContext.class); + when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext); + sensor = new CxxSquidSensor(settings, fileLinesContextFactory, checkFactory, rules, null); } @Test diff --git a/sonar-cxx-plugin/src/test/resources/org/sonar/plugins/cxx/ncloc.cc b/sonar-cxx-plugin/src/test/resources/org/sonar/plugins/cxx/ncloc.cc new file mode 100644 index 0000000000..1c43988b3a --- /dev/null +++ b/sonar-cxx-plugin/src/test/resources/org/sonar/plugins/cxx/ncloc.cc @@ -0,0 +1,48 @@ +/* + Header +*/ + +#include "ncloc.h" + +// comment +void func1() +{ + h1 = 0; +} + +/* comment */ +void func2() +{ + const char* txt = + "Hello " + " World!"; +} + +void func3( + int a, + int b + ) +{ + return a + b; +} + +#define NUMBER 10 + +void func4() +{ + // comment + for(int iii=0; iii