diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2018-11-14 09:41:25 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-01-16 09:43:00 +0100 |
commit | a87639ce329dfeb47bf291b8aefdad575f11a15b (patch) | |
tree | dd5f0323004154cb58e915d651c709303c756e6a | |
parent | 0392f0dd841ffafde74d170918858fff43b22070 (diff) | |
download | sonarqube-a87639ce329dfeb47bf291b8aefdad575f11a15b.tar.gz sonarqube-a87639ce329dfeb47bf291b8aefdad575f11a15b.zip |
SONAR-11480 Evaluate coverage exclusions at project level
and log a warning when it is used at module level
15 files changed, 431 insertions, 164 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/notifications/AnalysisWarnings.java b/sonar-plugin-api/src/main/java/org/sonar/api/notifications/AnalysisWarnings.java index 46f3243c3c3..cd0e6a869ce 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/notifications/AnalysisWarnings.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/notifications/AnalysisWarnings.java @@ -19,7 +19,7 @@ */ package org.sonar.api.notifications; -import org.sonar.api.batch.ScannerSide; +import org.sonar.api.scanner.ScannerSide; /** * Record user-friendly warnings that will be visible on SonarQube diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java index 0db66a578d3..39719e77536 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java @@ -25,15 +25,15 @@ import org.sonar.api.ExtensionPoint; import org.sonar.api.batch.ScannerSide; import org.sonarsource.api.sonarlint.SonarLintSide; +/** + * @since 5.3 + * @deprecated since 7.6 + */ @ScannerSide @SonarLintSide @ExtensionPoint @FunctionalInterface @ThreadSafe -/** - * @since 5.3 - * @deprecated since 7.6 - */ @Deprecated public interface IssueFilter { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ExtensionUtils.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ExtensionUtils.java index b80e2bfcf3e..a77dfe1bcb0 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ExtensionUtils.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ExtensionUtils.java @@ -36,7 +36,7 @@ public class ExtensionUtils { } return InstantiationStrategy.PER_PROJECT.equals(strategy); } - + public static boolean isDeprecatedScannerSide(Object extension) { return AnnotationUtils.getAnnotation(extension, org.sonar.api.batch.ScannerSide.class) != null; } @@ -45,8 +45,4 @@ public class ExtensionUtils { return AnnotationUtils.getAnnotation(extension, ScannerSide.class) != null; } - public static boolean isType(Object extension, Class<?> extensionClass) { - Class clazz = extension instanceof Class ? (Class) extension : extension.getClass(); - return extensionClass.isAssignableFrom(clazz); - } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/CoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractCoverageExclusions.java index 29cab4c9edf..0690f02f539 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/CoverageExclusions.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractCoverageExclusions.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import java.util.Collection; import java.util.Iterator; +import java.util.function.Function; import javax.annotation.concurrent.Immutable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,28 +33,38 @@ import org.sonar.api.config.Configuration; import org.sonar.api.utils.WildcardPattern; @Immutable -public class CoverageExclusions { - private static final Logger LOG = LoggerFactory.getLogger(CoverageExclusions.class); +public abstract class AbstractCoverageExclusions { + private static final Logger LOG = LoggerFactory.getLogger(AbstractCoverageExclusions.class); + private final Function<DefaultInputFile, String> pathExtractor; + private final String[] coverageExclusionConfig; private Collection<WildcardPattern> exclusionPatterns; - public CoverageExclusions(Configuration settings) { + public AbstractCoverageExclusions(Configuration config, Function<DefaultInputFile, String> pathExtractor) { + this.pathExtractor = pathExtractor; Builder<WildcardPattern> builder = ImmutableList.builder(); - for (String pattern : settings.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY)) { + coverageExclusionConfig = config.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY); + for (String pattern : coverageExclusionConfig) { builder.add(WildcardPattern.create(pattern)); } exclusionPatterns = builder.build(); } + public String[] getCoverageExclusionConfig() { + return coverageExclusionConfig; + } + void log() { - log("Excluded sources for coverage: ", exclusionPatterns); + if (!exclusionPatterns.isEmpty()) { + log("Excluded sources for coverage: ", exclusionPatterns); + } } boolean isExcluded(DefaultInputFile file) { boolean found = false; Iterator<WildcardPattern> iterator = exclusionPatterns.iterator(); while (!found && iterator.hasNext()) { - found = iterator.next().match(file.getModuleRelativePath()); + found = iterator.next().match(pathExtractor.apply(file)); } return found; } @@ -66,8 +77,4 @@ public class CoverageExclusions { } } } - - public boolean shouldExecute() { - return !exclusionPatterns.isEmpty(); - } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractModulePhaseExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractModulePhaseExecutor.java new file mode 100644 index 00000000000..9bcf8b3f410 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractModulePhaseExecutor.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info 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.phases; + +import java.util.Arrays; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.batch.fs.internal.InputModuleHierarchy; +import org.sonar.api.notifications.AnalysisWarnings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; +import org.sonar.scanner.rule.QProfileVerifier; +import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.scanner.scan.filesystem.FileIndexer; + +public abstract class AbstractModulePhaseExecutor { + + private static final Logger LOG = Loggers.get(AbstractModulePhaseExecutor.class); + + private final PostJobsExecutor postJobsExecutor; + private final SensorsExecutor sensorsExecutor; + private final DefaultModuleFileSystem fs; + private final QProfileVerifier profileVerifier; + private final IssueExclusionsLoader issueExclusionsLoader; + private final InputModuleHierarchy hierarchy; + private final FileIndexer fileIndexer; + private final ModuleCoverageExclusions moduleCoverageExclusions; + private final ProjectCoverageExclusions projectCoverageExclusions; + private final AnalysisWarnings analysisWarnings; + private boolean warnCoverageAlreadyLogged; + + public AbstractModulePhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, InputModuleHierarchy hierarchy, DefaultModuleFileSystem fs, + QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, FileIndexer fileIndexer, + ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, + AnalysisWarnings analysisWarnings) { + this.postJobsExecutor = postJobsExecutor; + this.sensorsExecutor = sensorsExecutor; + this.fs = fs; + this.profileVerifier = profileVerifier; + this.issueExclusionsLoader = issueExclusionsLoader; + this.hierarchy = hierarchy; + this.fileIndexer = fileIndexer; + this.moduleCoverageExclusions = moduleCoverageExclusions; + this.projectCoverageExclusions = projectCoverageExclusions; + this.analysisWarnings = analysisWarnings; + } + + /** + * Executed on each module + */ + public final void execute(DefaultInputModule module) { + // Index the filesystem + fileIndexer.index(); + + // Log detected languages and their profiles after FS is indexed and languages detected + profileVerifier.execute(); + + // Initialize issue exclusions + initIssueExclusions(); + + // Initialize coverage exclusions + evaluateCoverageExclusions(module); + + sensorsExecutor.execute(); + + afterSensors(); + + if (hierarchy.isRoot(module)) { + executeOnRoot(); + postJobsExecutor.execute(); + } + } + + private void evaluateCoverageExclusions(DefaultInputModule module) { + if (!Arrays.equals(moduleCoverageExclusions.getCoverageExclusionConfig(), projectCoverageExclusions.getCoverageExclusionConfig())) { + moduleCoverageExclusions.log(); + } + for (InputFile inputFile : fs.inputFiles(fs.predicates().all())) { + boolean excludedByProjectConfiguration = projectCoverageExclusions.isExcluded((DefaultInputFile) inputFile); + if (excludedByProjectConfiguration) { + ((DefaultInputFile) inputFile).setExcludedForCoverage(true); + LOG.debug("File {} excluded for coverage", inputFile); + continue; + } + boolean excludedByModuleConfig = moduleCoverageExclusions.isExcluded((DefaultInputFile) inputFile); + if (excludedByModuleConfig) { + ((DefaultInputFile) inputFile).setExcludedForCoverage(true); + if (Arrays.equals(moduleCoverageExclusions.getCoverageExclusionConfig(), projectCoverageExclusions.getCoverageExclusionConfig())) { + warnOnce("File '" + inputFile + "' was excluded from coverage because patterns are still evaluated using module relative paths but this is deprecated. " + + "Please update '" + CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY + "' configuration so that patterns refer to project relative paths"); + } else { + warnOnce("Defining coverage exclusions at module level is deprecated. " + + "Move '" + CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY + "' from module '" + module.getName() + "' " + + "to the root project and update patterns to refer to project relative paths"); + } + LOG.debug("File {} excluded for coverage", inputFile); + } + } + + } + + private void warnOnce(String msg) { + if (!warnCoverageAlreadyLogged) { + LOG.warn(msg); + analysisWarnings.addUnique(msg); + warnCoverageAlreadyLogged = true; + } + } + + protected void afterSensors() { + } + + protected abstract void executeOnRoot(); + + private void initIssueExclusions() { + if (issueExclusionsLoader.shouldExecute()) { + for (InputFile inputFile : fs.inputFiles(fs.predicates().all())) { + issueExclusionsLoader.addMulticriteriaPatterns(((DefaultInputFile) inputFile).getModuleRelativePath(), inputFile.key()); + } + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractPhaseExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractPhaseExecutor.java deleted file mode 100644 index a7196b89135..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractPhaseExecutor.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 SonarSource SA - * mailto:info 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.phases; - -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.fs.internal.DefaultInputFile; -import org.sonar.api.batch.fs.internal.DefaultInputModule; -import org.sonar.api.batch.fs.internal.InputModuleHierarchy; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; -import org.sonar.scanner.rule.QProfileVerifier; -import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; -import org.sonar.scanner.scan.filesystem.FileIndexer; - -public abstract class AbstractPhaseExecutor { - - private static final Logger LOG = Loggers.get(AbstractPhaseExecutor.class); - - private final PostJobsExecutor postJobsExecutor; - private final SensorsExecutor sensorsExecutor; - private final DefaultModuleFileSystem fs; - private final QProfileVerifier profileVerifier; - private final IssueExclusionsLoader issueExclusionsLoader; - private final InputModuleHierarchy hierarchy; - private final FileIndexer fileIndexer; - private final CoverageExclusions coverageExclusions; - - public AbstractPhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, InputModuleHierarchy hierarchy, DefaultModuleFileSystem fs, - QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, FileIndexer fileIndexer, CoverageExclusions coverageExclusions) { - this.postJobsExecutor = postJobsExecutor; - this.sensorsExecutor = sensorsExecutor; - this.fs = fs; - this.profileVerifier = profileVerifier; - this.issueExclusionsLoader = issueExclusionsLoader; - this.hierarchy = hierarchy; - this.fileIndexer = fileIndexer; - this.coverageExclusions = coverageExclusions; - } - - /** - * Executed on each module - */ - public final void execute(DefaultInputModule module) { - // Index the filesystem - fileIndexer.index(); - - // Log detected languages and their profiles after FS is indexed and languages detected - profileVerifier.execute(); - - // Initialize issue exclusions - initIssueExclusions(); - - // Initialize coverage exclusions - initCoverageExclusions(); - - sensorsExecutor.execute(); - - afterSensors(); - - if (hierarchy.isRoot(module)) { - executeOnRoot(); - postJobsExecutor.execute(); - } - } - - private void initCoverageExclusions() { - if (coverageExclusions.shouldExecute()) { - coverageExclusions.log(); - - for (InputFile inputFile : fs.inputFiles(fs.predicates().all())) { - boolean excluded = coverageExclusions.isExcluded((DefaultInputFile) inputFile); - if (excluded) { - ((DefaultInputFile) inputFile).setExcludedForCoverage(true); - LOG.debug("File {} excluded for coverage", inputFile); - } - } - - } - } - - protected void afterSensors() { - } - - protected abstract void executeOnRoot(); - - private void initIssueExclusions() { - if (issueExclusionsLoader.shouldExecute()) { - for (InputFile inputFile : fs.inputFiles(fs.predicates().all())) { - issueExclusionsLoader.addMulticriteriaPatterns(((DefaultInputFile) inputFile).getModuleRelativePath(), inputFile.key()); - } - } - } -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/IssuesPhaseExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/IssuesPhaseExecutor.java index f86170578d3..5f1058bd920 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/IssuesPhaseExecutor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/IssuesPhaseExecutor.java @@ -22,6 +22,7 @@ package org.sonar.scanner.phases; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; +import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; import org.sonar.scanner.issue.tracking.IssueTransition; import org.sonar.scanner.rule.QProfileVerifier; @@ -29,7 +30,7 @@ import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; import org.sonar.scanner.scan.filesystem.FileIndexer; import org.sonar.scanner.scan.report.IssuesReports; -public final class IssuesPhaseExecutor extends AbstractPhaseExecutor { +public final class IssuesPhaseExecutor extends AbstractModulePhaseExecutor { private static final Logger LOG = LoggerFactory.getLogger(IssuesPhaseExecutor.class); @@ -37,11 +38,12 @@ public final class IssuesPhaseExecutor extends AbstractPhaseExecutor { private final IssueTransition localIssueTracking; public IssuesPhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, - IssuesReports jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, - IssueExclusionsLoader issueExclusionsLoader, IssueTransition localIssueTracking, InputModuleHierarchy moduleHierarchy, FileIndexer fileIndexer, - CoverageExclusions coverageExclusions) { + IssuesReports jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, + IssueExclusionsLoader issueExclusionsLoader, IssueTransition localIssueTracking, InputModuleHierarchy moduleHierarchy, FileIndexer fileIndexer, + ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, + AnalysisWarnings analysisWarnings) { super(postJobsExecutor, sensorsExecutor, moduleHierarchy, fs, profileVerifier, issueExclusionsLoader, fileIndexer, - coverageExclusions); + moduleCoverageExclusions, projectCoverageExclusions, analysisWarnings); this.issuesReport = jsonReport; this.localIssueTracking = localIssueTracking; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ModuleCoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ModuleCoverageExclusions.java new file mode 100644 index 00000000000..124d93531cd --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ModuleCoverageExclusions.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info 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.phases; + +import javax.annotation.concurrent.Immutable; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Configuration; + +@Immutable +public class ModuleCoverageExclusions extends AbstractCoverageExclusions { + + public ModuleCoverageExclusions(Configuration config) { + super(config, DefaultInputFile::getModuleRelativePath); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ProjectCoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ProjectCoverageExclusions.java new file mode 100644 index 00000000000..e4139a214fb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ProjectCoverageExclusions.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info 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.phases; + +import javax.annotation.concurrent.Immutable; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Configuration; + +@Immutable +public class ProjectCoverageExclusions extends AbstractCoverageExclusions { + + public ProjectCoverageExclusions(Configuration projectConfig) { + super(projectConfig, DefaultInputFile::getProjectRelativePath); + log(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PublishPhaseExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PublishPhaseExecutor.java index e462360251a..50e1c75b666 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PublishPhaseExecutor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PublishPhaseExecutor.java @@ -20,6 +20,7 @@ package org.sonar.scanner.phases; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; +import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.scanner.cpd.CpdExecutor; import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; import org.sonar.scanner.report.ReportPublisher; @@ -28,16 +29,19 @@ import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; import org.sonar.scanner.scan.filesystem.FileIndexer; import org.sonar.scanner.scm.ScmPublisher; -public final class PublishPhaseExecutor extends AbstractPhaseExecutor { +public final class PublishPhaseExecutor extends AbstractModulePhaseExecutor { private final ReportPublisher reportPublisher; private final CpdExecutor cpdExecutor; private final ScmPublisher scm; public PublishPhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, - ReportPublisher reportPublisher, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, - CpdExecutor cpdExecutor, ScmPublisher scm, InputModuleHierarchy hierarchy, FileIndexer fileIndexer, CoverageExclusions coverageExclusions) { - super(postJobsExecutor, sensorsExecutor, hierarchy, fs, profileVerifier, issueExclusionsLoader, fileIndexer, coverageExclusions); + ReportPublisher reportPublisher, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, + CpdExecutor cpdExecutor, ScmPublisher scm, InputModuleHierarchy hierarchy, FileIndexer fileIndexer, + ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, + AnalysisWarnings analysisWarnings) { + super(postJobsExecutor, sensorsExecutor, hierarchy, fs, profileVerifier, issueExclusionsLoader, fileIndexer, moduleCoverageExclusions, + projectCoverageExclusions, analysisWarnings); this.reportPublisher = reportPublisher; this.cpdExecutor = cpdExecutor; this.scm = scm; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java index aa12cc96b7f..5a45671270a 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java @@ -39,9 +39,9 @@ import org.sonar.scanner.issue.ignore.pattern.IssueExclusionPatternInitializer; import org.sonar.scanner.issue.ignore.pattern.IssueInclusionPatternInitializer; import org.sonar.scanner.issue.ignore.pattern.PatternMatcher; import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; -import org.sonar.scanner.phases.AbstractPhaseExecutor; -import org.sonar.scanner.phases.CoverageExclusions; +import org.sonar.scanner.phases.AbstractModulePhaseExecutor; import org.sonar.scanner.phases.IssuesPhaseExecutor; +import org.sonar.scanner.phases.ModuleCoverageExclusions; import org.sonar.scanner.phases.PostJobsExecutor; import org.sonar.scanner.phases.PublishPhaseExecutor; import org.sonar.scanner.phases.SensorsExecutor; @@ -125,7 +125,7 @@ public class ModuleScanContainer extends ComponentContainer { DefaultSensorContext.class, ScannerExtensionDictionnary.class, ModuleIssueFilters.class, - CoverageExclusions.class, + ModuleCoverageExclusions.class, // issues ModuleIssues.class, @@ -153,7 +153,7 @@ public class ModuleScanContainer extends ComponentContainer { @Override protected void doAfterStart() { - getComponentByType(AbstractPhaseExecutor.class).execute(module); + getComponentByType(AbstractModulePhaseExecutor.class).execute(module); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java index 5b9f43f3638..a4dea8452bf 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java @@ -58,6 +58,7 @@ import org.sonar.scanner.issue.tracking.ServerIssueRepository; import org.sonar.scanner.issue.tracking.ServerLineHashesLoader; import org.sonar.scanner.mediumtest.ScanTaskObservers; import org.sonar.scanner.notifications.DefaultAnalysisWarnings; +import org.sonar.scanner.phases.ProjectCoverageExclusions; import org.sonar.scanner.report.ActiveRulesPublisher; import org.sonar.scanner.report.AnalysisContextReportPublisher; import org.sonar.scanner.report.AnalysisWarningsPublisher; @@ -204,6 +205,8 @@ public class ProjectScanContainer extends ComponentContainer { ScannerProperties.class, new ProjectConfigurationProvider(), + ProjectCoverageExclusions.class, + // Report ScannerMetrics.class, ReportPublisher.class, diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java index e116b9e2dfc..5e1c10e6712 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; @@ -40,11 +41,14 @@ import static org.assertj.core.api.Assertions.tuple; public class CoverageMediumTest { + private final List<String> logs = new ArrayList<>(); + @Rule public TemporaryFolder temp = new TemporaryFolder(); @Rule public ScannerMediumTester tester = new ScannerMediumTester() + .setLogOutput((msg, level) -> logs.add(msg)) .registerPlugin("xoo", new XooPlugin()) .addDefaultQProfile("xoo", "Sonar Way"); @@ -65,9 +69,6 @@ public class CoverageMediumTest { .put("sonar.task", "scan") .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") - .put("sonar.projectName", "Foo Project") - .put("sonar.projectVersion", "1.0-SNAPSHOT") - .put("sonar.projectDescription", "Description of Foo Project") .put("sonar.sources", "src") .build()) .execute(); @@ -104,9 +105,6 @@ public class CoverageMediumTest { .put("sonar.task", "scan") .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") - .put("sonar.projectName", "Foo Project") - .put("sonar.projectVersion", "1.0-SNAPSHOT") - .put("sonar.projectDescription", "Description of Foo Project") .put("sonar.sources", "src") .build()) .execute(); @@ -129,7 +127,7 @@ public class CoverageMediumTest { } @Test - public void exclusions() throws IOException { + public void exclusionsForSimpleProject() throws IOException { File baseDir = temp.getRoot(); File srcDir = new File(baseDir, "src"); @@ -145,9 +143,6 @@ public class CoverageMediumTest { .put("sonar.task", "scan") .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") - .put("sonar.projectName", "Foo Project") - .put("sonar.projectVersion", "1.0-SNAPSHOT") - .put("sonar.projectDescription", "Description of Foo Project") .put("sonar.sources", "src") .put("sonar.coverage.exclusions", "**/sample.xoo") .build()) @@ -163,6 +158,92 @@ public class CoverageMediumTest { } @Test + public void warn_user_for_outdated_inherited_exclusions_for_multi_module_project() throws IOException { + + File baseDir = temp.getRoot(); + File baseDirModuleA = new File(baseDir, "moduleA"); + File baseDirModuleB = new File(baseDir, "moduleB"); + File srcDirA = new File(baseDirModuleA, "src"); + srcDirA.mkdirs(); + File srcDirB = new File(baseDirModuleB, "src"); + srcDirB.mkdirs(); + + File xooFileA = new File(srcDirA, "sample.xoo"); + File xooUtCoverageFileA = new File(srcDirA, "sample.xoo.coverage"); + FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); + FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8); + + File xooFileB = new File(srcDirB, "sample.xoo"); + File xooUtCoverageFileB = new File(srcDirB, "sample.xoo.coverage"); + FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); + FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.sources", "src") + .put("sonar.modules", "moduleA,moduleB") + .put("sonar.coverage.exclusions", "src/sample.xoo") + .build()) + .execute(); + + InputFile fileA = result.inputFile("moduleA/src/sample.xoo"); + assertThat(result.coverageFor(fileA, 2)).isNull(); + + InputFile fileB = result.inputFile("moduleB/src/sample.xoo"); + assertThat(result.coverageFor(fileB, 2)).isNull(); + + assertThat(logs).contains("File 'moduleA/src/sample.xoo' was excluded from coverage because patterns are still " + + "evaluated using module relative paths but this is deprecated. Please update 'sonar.coverage.exclusions' " + + "configuration so that patterns refer to project relative paths"); + } + + @Test + public void warn_user_for_outdated_module_exclusions_for_multi_module_project() throws IOException { + + File baseDir = temp.getRoot(); + File baseDirModuleA = new File(baseDir, "moduleA"); + File baseDirModuleB = new File(baseDir, "moduleB"); + File srcDirA = new File(baseDirModuleA, "src"); + srcDirA.mkdirs(); + File srcDirB = new File(baseDirModuleB, "src"); + srcDirB.mkdirs(); + + File xooFileA = new File(srcDirA, "sample.xoo"); + File xooUtCoverageFileA = new File(srcDirA, "sample.xoo.coverage"); + FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); + FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8); + + File xooFileB = new File(srcDirB, "sample.xoo"); + File xooUtCoverageFileB = new File(srcDirB, "sample.xoo.coverage"); + FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); + FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.sources", "src") + .put("sonar.modules", "moduleA,moduleB") + .put("moduleB.sonar.coverage.exclusions", "src/sample.xoo") + .build()) + .execute(); + + InputFile fileA = result.inputFile("moduleA/src/sample.xoo"); + assertThat(result.coverageFor(fileA, 2)).isNotNull(); + + InputFile fileB = result.inputFile("moduleB/src/sample.xoo"); + assertThat(result.coverageFor(fileB, 2)).isNull(); + + assertThat(logs).contains("Defining coverage exclusions at module level is deprecated. " + + "Move 'sonar.coverage.exclusions' from module 'moduleB' " + + "to the root project and update patterns to refer to project relative paths"); + } + + @Test public void fallbackOnExecutableLines() throws IOException { File baseDir = temp.getRoot(); @@ -179,9 +260,6 @@ public class CoverageMediumTest { .put("sonar.task", "scan") .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") - .put("sonar.projectName", "Foo Project") - .put("sonar.projectVersion", "1.0-SNAPSHOT") - .put("sonar.projectDescription", "Description of Foo Project") .put("sonar.sources", "src") .build()) .execute(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/CoverageExclusionsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageExclusionsTest.java index 9c0baf50c15..e06b46e342a 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/CoverageExclusionsTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageExclusionsTest.java @@ -19,8 +19,11 @@ */ package org.sonar.scanner.phases; +import java.io.File; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.config.PropertyDefinitions; @@ -29,29 +32,38 @@ import org.sonar.core.config.ExclusionProperties; import static org.assertj.core.api.Assertions.assertThat; -public class CoverageExclusionsTest { +public class ModuleCoverageExclusionsTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); private MapSettings settings; - private CoverageExclusions coverageExclusions; + private ModuleCoverageExclusions coverageExclusions; + private File baseDir; @Before - public void prepare() { + public void prepare() throws Exception { settings = new MapSettings(new PropertyDefinitions(ExclusionProperties.all())); + baseDir = temp.newFolder(); } @Test public void shouldExcludeFileBasedOnPattern() { - DefaultInputFile file = new TestInputFileBuilder("foo", "src/org/polop/File.php").build(); + DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php")) + .setProjectBaseDir(baseDir.toPath()) + .build(); settings.setProperty("sonar.coverage.exclusions", "src/org/polop/*"); - coverageExclusions = new CoverageExclusions(settings.asConfig()); + coverageExclusions = new ModuleCoverageExclusions(settings.asConfig()); assertThat(coverageExclusions.isExcluded(file)).isTrue(); } @Test public void shouldNotExcludeFileBasedOnPattern() { - DefaultInputFile file = new TestInputFileBuilder("foo", "src/org/polop/File.php").build(); + DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php")) + .setProjectBaseDir(baseDir.toPath()) + .build(); settings.setProperty("sonar.coverage.exclusions", "src/org/other/*"); - coverageExclusions = new CoverageExclusions(settings.asConfig()); + coverageExclusions = new ModuleCoverageExclusions(settings.asConfig()); assertThat(coverageExclusions.isExcluded(file)).isFalse(); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ProjectCoverageExclusionsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ProjectCoverageExclusionsTest.java new file mode 100644 index 00000000000..9315cdeb943 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ProjectCoverageExclusionsTest.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info 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.phases; + +import java.io.File; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.core.config.ExclusionProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectCoverageExclusionsTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private MapSettings settings; + private ProjectCoverageExclusions coverageExclusions; + private File baseDir; + + @Before + public void prepare() throws Exception { + settings = new MapSettings(new PropertyDefinitions(ExclusionProperties.all())); + baseDir = temp.newFolder(); + } + + @Test + public void shouldExcludeFileBasedOnPattern() { + DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php")) + .setProjectBaseDir(baseDir.toPath()) + .build(); + settings.setProperty("sonar.coverage.exclusions", "moduleA/src/org/polop/*"); + coverageExclusions = new ProjectCoverageExclusions(settings.asConfig()); + assertThat(coverageExclusions.isExcluded(file)).isTrue(); + } + + @Test + public void shouldNotExcludeFileBasedOnPattern() { + DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php")) + .setProjectBaseDir(baseDir.toPath()) + .build(); + settings.setProperty("sonar.coverage.exclusions", "moduleA/src/org/other/*"); + coverageExclusions = new ProjectCoverageExclusions(settings.asConfig()); + assertThat(coverageExclusions.isExcluded(file)).isFalse(); + } +} |