aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2018-11-14 09:41:25 +0100
committersonartech <sonartech@sonarsource.com>2019-01-16 09:43:00 +0100
commita87639ce329dfeb47bf291b8aefdad575f11a15b (patch)
treedd5f0323004154cb58e915d651c709303c756e6a
parent0392f0dd841ffafde74d170918858fff43b22070 (diff)
downloadsonarqube-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
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/notifications/AnalysisWarnings.java2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java8
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ExtensionUtils.java6
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractCoverageExclusions.java (renamed from sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/CoverageExclusions.java)27
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractModulePhaseExecutor.java142
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractPhaseExecutor.java111
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/IssuesPhaseExecutor.java12
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ModuleCoverageExclusions.java32
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ProjectCoverageExclusions.java33
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PublishPhaseExecutor.java12
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java8
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java3
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java104
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageExclusionsTest.java (renamed from sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/CoverageExclusionsTest.java)26
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ProjectCoverageExclusionsTest.java69
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();
+ }
+}