]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11480 Evaluate coverage exclusions at project level
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 14 Nov 2018 08:41:25 +0000 (09:41 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:00 +0000 (09:43 +0100)
and log a warning when it is used at module level

17 files changed:
sonar-plugin-api/src/main/java/org/sonar/api/notifications/AnalysisWarnings.java
sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ExtensionUtils.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractCoverageExclusions.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractModulePhaseExecutor.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractPhaseExecutor.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/CoverageExclusions.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/IssuesPhaseExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ModuleCoverageExclusions.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ProjectCoverageExclusions.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PublishPhaseExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/CoverageExclusionsTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageExclusionsTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ProjectCoverageExclusionsTest.java [new file with mode: 0644]

index 46f3243c3c3eab95b7a97e41db106261361e777f..cd0e6a869ce889ff91b46b04a3a787199e50aee5 100644 (file)
@@ -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
index 0db66a578d3f71daf6c2445e813548e2e0d1e6e6..39719e775362ea9165a7ba1864940e7375d3748d 100644 (file)
@@ -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 {
 
index b80e2bfcf3e0cf689ea3456e897b8225207f182a..a77dfe1bcb01128cbb98c24d8c5fbd3258c5818d 100644 (file)
@@ -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/AbstractCoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractCoverageExclusions.java
new file mode 100644 (file)
index 0000000..0690f02
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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 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;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.WildcardPattern;
+
+@Immutable
+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 AbstractCoverageExclusions(Configuration config, Function<DefaultInputFile, String> pathExtractor) {
+    this.pathExtractor = pathExtractor;
+    Builder<WildcardPattern> builder = ImmutableList.builder();
+    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() {
+    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(pathExtractor.apply(file));
+    }
+    return found;
+  }
+
+  private static void log(String title, Collection<WildcardPattern> patterns) {
+    if (!patterns.isEmpty()) {
+      LOG.info(title);
+      for (WildcardPattern pattern : patterns) {
+        LOG.info("  {}", pattern);
+      }
+    }
+  }
+}
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 (file)
index 0000000..9bcf8b3
--- /dev/null
@@ -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 (file)
index a7196b8..0000000
+++ /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/CoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/CoverageExclusions.java
deleted file mode 100644 (file)
index 29cab4c..0000000
+++ /dev/null
@@ -1,73 +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 com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
-import java.util.Collection;
-import java.util.Iterator;
-import javax.annotation.concurrent.Immutable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-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);
-
-  private Collection<WildcardPattern> exclusionPatterns;
-
-  public CoverageExclusions(Configuration settings) {
-    Builder<WildcardPattern> builder = ImmutableList.builder();
-    for (String pattern : settings.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY)) {
-      builder.add(WildcardPattern.create(pattern));
-    }
-    exclusionPatterns = builder.build();
-  }
-
-  void log() {
-    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());
-    }
-    return found;
-  }
-
-  private static void log(String title, Collection<WildcardPattern> patterns) {
-    if (!patterns.isEmpty()) {
-      LOG.info(title);
-      for (WildcardPattern pattern : patterns) {
-        LOG.info("  {}", pattern);
-      }
-    }
-  }
-
-  public boolean shouldExecute() {
-    return !exclusionPatterns.isEmpty();
-  }
-}
index f86170578d3f01e6c4adc99addb98debf8492179..5f1058bd92058f1537c331380323c1205a75008c 100644 (file)
@@ -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 (file)
index 0000000..124d935
--- /dev/null
@@ -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 (file)
index 0000000..e4139a2
--- /dev/null
@@ -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();
+  }
+}
index e462360251a1a653957bccca84623c76792c36e1..50e1c75b66607fe606eaf07fb76e339da58c0e87 100644 (file)
@@ -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;
index aa12cc96b7ff967cd8aeabc86ec57bf04b64b7bf..5a45671270ae52377321254485dce1967f922956 100644 (file)
@@ -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);
   }
 
 }
index 5b9f43f363882872b3556ae44037a6bf742602a8..a4dea8452bfbca34d912005a62bc0462718bb57c 100644 (file)
@@ -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,
index e116b9e2dfc31ff86bbea32d5124e9edff78df84..5e1c10e6712e8973d7f1fb10d55c9d6cb40c3b85 100644 (file)
@@ -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())
@@ -162,6 +157,92 @@ public class CoverageMediumTest {
         CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY);
   }
 
+  @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 {
 
@@ -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/CoverageExclusionsTest.java
deleted file mode 100644 (file)
index 9c0baf5..0000000
+++ /dev/null
@@ -1,57 +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.junit.Before;
-import org.junit.Test;
-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 CoverageExclusionsTest {
-
-  private MapSettings settings;
-  private CoverageExclusions coverageExclusions;
-
-  @Before
-  public void prepare() {
-    settings = new MapSettings(new PropertyDefinitions(ExclusionProperties.all()));
-  }
-
-  @Test
-  public void shouldExcludeFileBasedOnPattern() {
-    DefaultInputFile file = new TestInputFileBuilder("foo", "src/org/polop/File.php").build();
-    settings.setProperty("sonar.coverage.exclusions", "src/org/polop/*");
-    coverageExclusions = new CoverageExclusions(settings.asConfig());
-    assertThat(coverageExclusions.isExcluded(file)).isTrue();
-  }
-
-  @Test
-  public void shouldNotExcludeFileBasedOnPattern() {
-    DefaultInputFile file = new TestInputFileBuilder("foo", "src/org/polop/File.php").build();
-    settings.setProperty("sonar.coverage.exclusions", "src/org/other/*");
-    coverageExclusions = new CoverageExclusions(settings.asConfig());
-    assertThat(coverageExclusions.isExcluded(file)).isFalse();
-  }
-}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageExclusionsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageExclusionsTest.java
new file mode 100644 (file)
index 0000000..e06b46e
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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 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 ModuleCoverageExclusionsTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  private MapSettings settings;
+  private ModuleCoverageExclusions 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", "src/org/polop/*");
+    coverageExclusions = new ModuleCoverageExclusions(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", "src/org/other/*");
+    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 (file)
index 0000000..9315cde
--- /dev/null
@@ -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();
+  }
+}