]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11598 Rework CPD exclusions
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 20 Dec 2018 10:48:17 +0000 (11:48 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:10 +0000 (09:43 +0100)
21 files changed:
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokensTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/JavaCpdBlockIndexerSensor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/AbstractCoverageAndDuplicationExclusions.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/AbstractCoverageExclusions.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleCoverageAndDuplicationExclusions.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleCoverageExclusions.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectCoverageAndDuplicationExclusions.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectCoverageExclusions.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFileIndexer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/JavaCpdBlockIndexerSensorTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/cpd/CpdMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageAndDuplicationExclusionsTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageExclusionsTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ProjectCoverageExclusionsTest.java

index 0b60c5e227c889242658343f437dc8e26c2f66d3..c0fd7c38bcce7a60efdf6ad88c209a5491946438 100644 (file)
@@ -60,6 +60,7 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile
   private Metadata metadata;
   private boolean published;
   private boolean excludedForCoverage;
+  private boolean excludedForDuplication;
   private final Set<Integer> noSonarLines = new HashSet<>();
   private boolean ignoreAllIssues;
   private Collection<int[]> ignoreIssuesOnlineRanges = new ArrayList<>();
@@ -127,6 +128,15 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile
     return excludedForCoverage;
   }
 
+  public DefaultInputFile setExcludedForDuplication(boolean excludedForDuplication) {
+    this.excludedForDuplication = excludedForDuplication;
+    return this;
+  }
+
+  public boolean isExcludedForDuplication() {
+    return excludedForDuplication;
+  }
+
   /**
    * @deprecated since 6.6
    */
index d2cae805cacf19b66843a04bee1118a19cb972bd..a30a4accc76735512767dcc60f934255a97ed6bd 100644 (file)
  */
 package org.sonar.api.batch.sensor.cpd.internal;
 
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
-import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.TextRange;
-import org.sonar.api.batch.fs.internal.PathPattern;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.sensor.cpd.NewCpdTokens;
 import org.sonar.api.batch.sensor.internal.DefaultStorable;
 import org.sonar.api.batch.sensor.internal.SensorStorage;
-import org.sonar.api.config.Configuration;
 
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Collections.unmodifiableList;
@@ -37,7 +34,6 @@ import static java.util.Objects.requireNonNull;
 
 public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
 
-  private final Configuration config;
   private final List<TokensLine> result = new ArrayList<>();
   private InputFile inputFile;
   private int startLine = Integer.MIN_VALUE;
@@ -45,22 +41,14 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
   private int currentIndex = 0;
   private StringBuilder sb = new StringBuilder();
   private TextRange lastRange;
-  private boolean excluded;
 
-  public DefaultCpdTokens(Configuration config, SensorStorage storage) {
+  public DefaultCpdTokens(SensorStorage storage) {
     super(storage);
-    this.config = config;
   }
 
   @Override
   public DefaultCpdTokens onFile(InputFile inputFile) {
     this.inputFile = requireNonNull(inputFile, "file can't be null");
-    String[] cpdExclusions = config.getStringArray(CoreProperties.CPD_EXCLUSIONS);
-    for (PathPattern cpdExclusion : PathPattern.create(cpdExclusions)) {
-      if (cpdExclusion.match(inputFile.path(), Paths.get(inputFile.relativePath()))) {
-        this.excluded = true;
-      }
-    }
     return this;
   }
 
@@ -85,7 +73,7 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
     requireNonNull(range, "Range should not be null");
     requireNonNull(image, "Image should not be null");
     checkInputFileNotNull();
-    if (excluded) {
+    if (isExcludedForDuplication()) {
       return this;
     }
     checkState(lastRange == null || lastRange.end().compareTo(range.start()) <= 0,
@@ -106,6 +94,10 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
     return this;
   }
 
+  private boolean isExcludedForDuplication() {
+    return ((DefaultInputFile) inputFile).isExcludedForDuplication();
+  }
+
   public List<TokensLine> getTokenLines() {
     return unmodifiableList(new ArrayList<>(result));
   }
@@ -120,7 +112,7 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
   @Override
   protected void doSave() {
     checkState(inputFile != null, "Call onFile() first");
-    if (excluded) {
+    if (isExcludedForDuplication()) {
       return;
     }
     addNewTokensLine(result, startIndex, currentIndex, startLine, sb);
index e8cc9f81b8f47f89800ff8418e37ad3b7949b63f..0ee4c211f7b0304bd721d0f41767537e7436adc1 100644 (file)
@@ -323,7 +323,7 @@ public class SensorContextTester implements SensorContext {
 
   @Override
   public NewCpdTokens newCpdTokens() {
-    return new DefaultCpdTokens(config(), sensorStorage);
+    return new DefaultCpdTokens(sensorStorage);
   }
 
   @Override
index 2ccd9fd01cfdc040de448e5687cea533cb06762b..44827ebcebb85963344fcc55428c6ed064ca2265 100644 (file)
  */
 package org.sonar.api.batch.sensor.cpd.internal;
 
-import java.io.File;
 import org.junit.Test;
-import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.batch.sensor.internal.SensorStorage;
-import org.sonar.api.config.internal.MapSettings;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
@@ -35,7 +33,7 @@ import static org.mockito.Mockito.verifyZeroInteractions;
 
 public class DefaultCpdTokensTest {
 
-  private static final InputFile INPUT_FILE = new TestInputFileBuilder("foo", "src/Foo.java")
+  private final DefaultInputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java")
     .setLines(2)
     .setOriginalLineStartOffsets(new int[] {0, 50})
     .setOriginalLineEndOffsets(new int[] {49, 100})
@@ -45,22 +43,22 @@ public class DefaultCpdTokensTest {
   @Test
   public void save_no_tokens() {
     SensorStorage sensorStorage = mock(SensorStorage.class);
-    DefaultCpdTokens tokens = new DefaultCpdTokens(new MapSettings().asConfig(), sensorStorage)
-      .onFile(INPUT_FILE);
+    DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage)
+      .onFile(inputFile);
 
     tokens.save();
 
     verify(sensorStorage).store(tokens);
 
-    assertThat(tokens.inputFile()).isEqualTo(INPUT_FILE);
+    assertThat(tokens.inputFile()).isEqualTo(inputFile);
   }
 
   @Test
   public void save_one_token() {
     SensorStorage sensorStorage = mock(SensorStorage.class);
-    DefaultCpdTokens tokens = new DefaultCpdTokens(new MapSettings().asConfig(), sensorStorage)
-      .onFile(INPUT_FILE)
-      .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo");
+    DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage)
+      .onFile(inputFile)
+      .addToken(inputFile.newRange(1, 2, 1, 5), "foo");
 
     tokens.save();
 
@@ -70,13 +68,12 @@ public class DefaultCpdTokensTest {
   }
 
   @Test
-  public void handle_exclusions_by_pattern() {
+  public void handle_exclusions() {
     SensorStorage sensorStorage = mock(SensorStorage.class);
-    MapSettings settings = new MapSettings();
-    settings.setProperty("sonar.cpd.exclusions", "src/Foo.java,another");
-    DefaultCpdTokens tokens = new DefaultCpdTokens(settings.asConfig(), sensorStorage)
-      .onFile(INPUT_FILE)
-      .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo");
+    inputFile.setExcludedForDuplication(true);
+    DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage)
+      .onFile(inputFile)
+      .addToken(inputFile.newRange(1, 2, 1, 5), "foo");
 
     tokens.save();
 
@@ -88,12 +85,12 @@ public class DefaultCpdTokensTest {
   @Test
   public void save_many_tokens() {
     SensorStorage sensorStorage = mock(SensorStorage.class);
-    DefaultCpdTokens tokens = new DefaultCpdTokens(new MapSettings().asConfig(), sensorStorage)
-      .onFile(INPUT_FILE)
-      .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo")
-      .addToken(INPUT_FILE.newRange(1, 6, 1, 10), "bar")
-      .addToken(INPUT_FILE.newRange(1, 20, 1, 25), "biz")
-      .addToken(INPUT_FILE.newRange(2, 1, 2, 10), "next");
+    DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage)
+      .onFile(inputFile)
+      .addToken(inputFile.newRange(1, 2, 1, 5), "foo")
+      .addToken(inputFile.newRange(1, 6, 1, 10), "bar")
+      .addToken(inputFile.newRange(1, 20, 1, 25), "biz")
+      .addToken(inputFile.newRange(2, 1, 2, 10), "next");
 
     tokens.save();
 
@@ -109,7 +106,7 @@ public class DefaultCpdTokensTest {
   @Test
   public void basic_validation() {
     SensorStorage sensorStorage = mock(SensorStorage.class);
-    DefaultCpdTokens tokens = new DefaultCpdTokens(new MapSettings().asConfig(), sensorStorage);
+    DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage);
     try {
       tokens.save();
       fail("Expected exception");
@@ -117,7 +114,7 @@ public class DefaultCpdTokensTest {
       assertThat(e).hasMessage("Call onFile() first");
     }
     try {
-      tokens.addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo");
+      tokens.addToken(inputFile.newRange(1, 2, 1, 5), "foo");
       fail("Expected exception");
     } catch (Exception e) {
       assertThat(e).hasMessage("Call onFile() first");
@@ -129,7 +126,7 @@ public class DefaultCpdTokensTest {
       assertThat(e).hasMessage("Range should not be null");
     }
     try {
-      tokens.addToken(INPUT_FILE.newRange(1, 2, 1, 5), null);
+      tokens.addToken(inputFile.newRange(1, 2, 1, 5), null);
       fail("Expected exception");
     } catch (Exception e) {
       assertThat(e).hasMessage("Image should not be null");
@@ -139,12 +136,12 @@ public class DefaultCpdTokensTest {
   @Test
   public void validate_tokens_order() {
     SensorStorage sensorStorage = mock(SensorStorage.class);
-    DefaultCpdTokens tokens = new DefaultCpdTokens(new MapSettings().asConfig(), sensorStorage)
-      .onFile(INPUT_FILE)
-      .addToken(INPUT_FILE.newRange(1, 6, 1, 10), "bar");
+    DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage)
+      .onFile(inputFile)
+      .addToken(inputFile.newRange(1, 6, 1, 10), "bar");
 
     try {
-      tokens.addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo");
+      tokens.addToken(inputFile.newRange(1, 2, 1, 5), "foo");
       fail("Expected exception");
     } catch (Exception e) {
       assertThat(e).hasMessage("Tokens of file src/Foo.java should be provided in order.\n" +
index 5770e8869ab2512477f54decf1255556d1f187da..13c59fc40dd07871ffaad2bcc1e861d548b138cd 100644 (file)
  */
 package org.sonar.scanner.cpd;
 
-import com.google.common.collect.Lists;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.Phase;
 import org.sonar.api.batch.fs.FilePredicates;
 import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.scanner.sensor.ProjectSensor;
 import org.sonar.duplications.block.Block;
 import org.sonar.duplications.block.BlockChunker;
 import org.sonar.duplications.java.JavaStatementBuilder;
@@ -48,7 +49,7 @@ import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
  * Special case for Java that use a dedicated block indexer.
  */
 @Phase(name = Phase.Name.POST)
-public class JavaCpdBlockIndexerSensor implements Sensor {
+public class JavaCpdBlockIndexerSensor implements ProjectSensor {
 
   private static final int BLOCK_SIZE = 10;
   private static final Logger LOG = LoggerFactory.getLogger(JavaCpdBlockIndexerSensor.class);
@@ -66,12 +67,16 @@ public class JavaCpdBlockIndexerSensor implements Sensor {
 
   @Override
   public void execute(SensorContext context) {
-    String[] cpdExclusions = context.config().getStringArray(CoreProperties.CPD_EXCLUSIONS);
     FilePredicates p = context.fileSystem().predicates();
-    List<InputFile> sourceFiles = Lists.newArrayList(context.fileSystem().inputFiles(p.and(
-      p.hasType(InputFile.Type.MAIN),
-      p.hasLanguage("java"),
-      p.doesNotMatchPathPatterns(cpdExclusions))));
+    List<InputFile> sourceFiles = StreamSupport.stream(
+      context.fileSystem().inputFiles(
+        p.and(
+          p.hasType(InputFile.Type.MAIN),
+          p.hasLanguage("java")
+        )
+      ).spliterator(), false)
+      .filter(f -> !((DefaultInputFile) f).isExcludedForDuplication())
+      .collect(Collectors.toList());
     if (sourceFiles.isEmpty()) {
       return;
     }
index 6171c2793de311d64dedfecef70acf9045b8a959..26b6ffa807952b9979e95919f78e3f1d63ab56ee 100644 (file)
@@ -109,7 +109,7 @@ import org.sonar.scanner.scan.filesystem.FileIndexer;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 import org.sonar.scanner.scan.filesystem.LanguageDetection;
 import org.sonar.scanner.scan.filesystem.MetadataGenerator;
-import org.sonar.scanner.scan.filesystem.ProjectCoverageExclusions;
+import org.sonar.scanner.scan.filesystem.ProjectCoverageAndDuplicationExclusions;
 import org.sonar.scanner.scan.filesystem.ProjectExclusionFilters;
 import org.sonar.scanner.scan.filesystem.ProjectFileIndexer;
 import org.sonar.scanner.scan.filesystem.ScannerComponentIdGenerator;
@@ -244,7 +244,7 @@ public class ProjectScanContainer extends ComponentContainer {
       ScannerProperties.class,
       new ProjectConfigurationProvider(),
 
-      ProjectCoverageExclusions.class,
+      ProjectCoverageAndDuplicationExclusions.class,
 
       // Report
       ScannerMetrics.class,
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/AbstractCoverageAndDuplicationExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/AbstractCoverageAndDuplicationExclusions.java
new file mode 100644 (file)
index 0000000..c061aa5
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.scan.filesystem;
+
+import java.util.Collection;
+import java.util.function.Function;
+import java.util.stream.Stream;
+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.utils.WildcardPattern;
+
+import static java.util.stream.Collectors.toList;
+
+@Immutable
+public abstract class AbstractCoverageAndDuplicationExclusions {
+  private static final Logger LOG = LoggerFactory.getLogger(AbstractCoverageAndDuplicationExclusions.class);
+  private final Function<DefaultInputFile, String> pathExtractor;
+  private final String[] coverageExclusionConfig;
+  private final String[] duplicationExclusionConfig;
+
+  private final Collection<WildcardPattern> coverageExclusionPatterns;
+  private final Collection<WildcardPattern> duplicationExclusionPatterns;
+
+  public AbstractCoverageAndDuplicationExclusions(Function<String, String[]> configProvider, Function<DefaultInputFile, String> pathExtractor) {
+    this.pathExtractor = pathExtractor;
+    coverageExclusionConfig = configProvider.apply(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY);
+    coverageExclusionPatterns = Stream.of(coverageExclusionConfig).map(WildcardPattern::create).collect(toList());
+    duplicationExclusionConfig = configProvider.apply(CoreProperties.CPD_EXCLUSIONS);
+    duplicationExclusionPatterns = Stream.of(duplicationExclusionConfig).map(WildcardPattern::create).collect(toList());
+  }
+
+  public String[] getCoverageExclusionConfig() {
+    return coverageExclusionConfig;
+  }
+
+  public String[] getDuplicationExclusionConfig() {
+    return duplicationExclusionConfig;
+  }
+
+  void log() {
+    if (!coverageExclusionPatterns.isEmpty()) {
+      log("Excluded sources for coverage: ", coverageExclusionPatterns);
+    }
+    if (!duplicationExclusionPatterns.isEmpty()) {
+      log("Excluded sources for duplication: ", duplicationExclusionPatterns);
+    }
+  }
+
+  public boolean isExcludedForCoverage(DefaultInputFile file) {
+    return isExcluded(file, coverageExclusionPatterns);
+  }
+
+  public boolean isExcludedForDuplication(DefaultInputFile file) {
+    return isExcluded(file, duplicationExclusionPatterns);
+  }
+
+  private boolean isExcluded(DefaultInputFile file, Collection<WildcardPattern> patterns) {
+    if (patterns.isEmpty()) {
+      return false;
+    }
+    final String path = pathExtractor.apply(file);
+    return patterns
+      .stream()
+      .anyMatch(p -> p.match(path));
+  }
+
+  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/scan/filesystem/AbstractCoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/AbstractCoverageExclusions.java
deleted file mode 100644 (file)
index 2d1abe0..0000000
+++ /dev/null
@@ -1,79 +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.scan.filesystem;
-
-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.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(Function<String, String[]> configProvider, Function<DefaultInputFile, String> pathExtractor) {
-    this.pathExtractor = pathExtractor;
-    Builder<WildcardPattern> builder = ImmutableList.builder();
-    coverageExclusionConfig = configProvider.apply(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);
-    }
-  }
-
-  public 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);
-      }
-    }
-  }
-}
index aba73c172a44d4a2837de22877ea42b49dca13f5..60798fe990b9b7f4177746352cc1d1f52e0040b6 100644 (file)
@@ -51,7 +51,7 @@ public class FileIndexer {
   private final ScanProperties properties;
   private final InputFileFilter[] filters;
   private final ProjectExclusionFilters projectExclusionFilters;
-  private final ProjectCoverageExclusions projectCoverageExclusions;
+  private final ProjectCoverageAndDuplicationExclusions projectCoverageAndDuplicationExclusions;
   private final IssueExclusionsLoader issueExclusionsLoader;
   private final MetadataGenerator metadataGenerator;
   private final DefaultInputProject project;
@@ -62,15 +62,16 @@ public class FileIndexer {
 
   private boolean warnExclusionsAlreadyLogged;
   private boolean warnCoverageExclusionsAlreadyLogged;
+  private boolean warnDuplicationExclusionsAlreadyLogged;
 
   public FileIndexer(DefaultInputProject project, ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore,
-    ProjectExclusionFilters projectExclusionFilters, ProjectCoverageExclusions projectCoverageExclusions, IssueExclusionsLoader issueExclusionsLoader,
-    MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, LanguageDetection languageDetection, AnalysisWarnings analysisWarnings, ScanProperties properties,
-    InputFileFilter[] filters) {
+                     ProjectExclusionFilters projectExclusionFilters, ProjectCoverageAndDuplicationExclusions projectCoverageAndDuplicationExclusions, IssueExclusionsLoader issueExclusionsLoader,
+                     MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, LanguageDetection languageDetection, AnalysisWarnings analysisWarnings, ScanProperties properties,
+                     InputFileFilter[] filters) {
     this.project = project;
     this.scannerComponentIdGenerator = scannerComponentIdGenerator;
     this.componentStore = componentStore;
-    this.projectCoverageExclusions = projectCoverageExclusions;
+    this.projectCoverageAndDuplicationExclusions = projectCoverageAndDuplicationExclusions;
     this.issueExclusionsLoader = issueExclusionsLoader;
     this.metadataGenerator = metadataGenerator;
     this.sensorStrategy = sensorStrategy;
@@ -82,17 +83,17 @@ public class FileIndexer {
   }
 
   public FileIndexer(DefaultInputProject project, ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore,
-    ProjectExclusionFilters projectExclusionFilters, ProjectCoverageExclusions projectCoverageExclusions, IssueExclusionsLoader issueExclusionsLoader,
-    MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, LanguageDetection languageDetection, AnalysisWarnings analysisWarnings, ScanProperties properties) {
-    this(project, scannerComponentIdGenerator, componentStore, projectExclusionFilters, projectCoverageExclusions, issueExclusionsLoader, metadataGenerator, sensorStrategy,
+                     ProjectExclusionFilters projectExclusionFilters, ProjectCoverageAndDuplicationExclusions projectCoverageAndDuplicationExclusions, IssueExclusionsLoader issueExclusionsLoader,
+                     MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, LanguageDetection languageDetection, AnalysisWarnings analysisWarnings, ScanProperties properties) {
+    this(project, scannerComponentIdGenerator, componentStore, projectExclusionFilters, projectCoverageAndDuplicationExclusions, issueExclusionsLoader, metadataGenerator, sensorStrategy,
       languageDetection,
       analysisWarnings,
       properties, new InputFileFilter[0]);
   }
 
-  public void indexFile(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageExclusions moduleCoverageExclusions, Path sourceFile,
-    InputFile.Type type, ProgressReport progressReport,
-    AtomicInteger excludedByPatternsCount)
+  public void indexFile(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, Path sourceFile,
+                        InputFile.Type type, ProgressReport progressReport,
+                        AtomicInteger excludedByPatternsCount)
     throws IOException {
     // get case of real file without resolving link
     Path realAbsoluteFile = sourceFile.toRealPath(LinkOption.NOFOLLOW_LINKS).toAbsolutePath().normalize();
@@ -136,7 +137,8 @@ public class FileIndexer {
     componentStore.put(module.key(), inputFile);
     issueExclusionsLoader.addMulticriteriaPatterns(inputFile);
     LOG.debug("'{}' indexed {}with language '{}'", projectRelativePath, type == Type.TEST ? "as test " : "", inputFile.language());
-    evaluateCoverageExclusions(moduleCoverageExclusions, inputFile);
+    evaluateCoverageExclusions(moduleCoverageAndDuplicationExclusions, inputFile);
+    evaluateDuplicationExclusions(moduleCoverageAndDuplicationExclusions, inputFile);
     if (properties.preloadFileMetadata()) {
       inputFile.checkMetadata();
     }
@@ -151,14 +153,14 @@ public class FileIndexer {
     }
   }
 
-  private void evaluateCoverageExclusions(ModuleCoverageExclusions moduleCoverageExclusions, DefaultInputFile inputFile) {
-    boolean excludedByProjectConfiguration = projectCoverageExclusions.isExcluded(inputFile);
+  private void evaluateCoverageExclusions(ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, DefaultInputFile inputFile) {
+    boolean excludedByProjectConfiguration = projectCoverageAndDuplicationExclusions.isExcludedForCoverage(inputFile);
     if (excludedByProjectConfiguration) {
       inputFile.setExcludedForCoverage(true);
       LOG.debug("File {} excluded for coverage", inputFile);
-    } else if (moduleCoverageExclusions.isExcluded(inputFile)) {
+    } else if (moduleCoverageAndDuplicationExclusions.isExcludedForCoverage(inputFile)) {
       inputFile.setExcludedForCoverage(true);
-      if (Arrays.equals(moduleCoverageExclusions.getCoverageExclusionConfig(), projectCoverageExclusions.getCoverageExclusionConfig())) {
+      if (Arrays.equals(moduleCoverageAndDuplicationExclusions.getCoverageExclusionConfig(), projectCoverageAndDuplicationExclusions.getCoverageExclusionConfig())) {
         warnOnceDeprecatedCoverageExclusion(
           "Specifying module-relative paths at project level in the property '" + CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY + "' is deprecated. " +
             "To continue matching files like '" + inputFile + "', update this property so that patterns refer to project-relative paths.");
@@ -167,6 +169,22 @@ public class FileIndexer {
     }
   }
 
+  private void evaluateDuplicationExclusions(ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, DefaultInputFile inputFile) {
+    boolean excludedByProjectConfiguration = projectCoverageAndDuplicationExclusions.isExcludedForDuplication(inputFile);
+    if (excludedByProjectConfiguration) {
+      inputFile.setExcludedForDuplication(true);
+      LOG.debug("File {} excluded for duplication", inputFile);
+    } else if (moduleCoverageAndDuplicationExclusions.isExcludedForDuplication(inputFile)) {
+      inputFile.setExcludedForDuplication(true);
+      if (Arrays.equals(moduleCoverageAndDuplicationExclusions.getDuplicationExclusionConfig(), projectCoverageAndDuplicationExclusions.getDuplicationExclusionConfig())) {
+        warnOnceDeprecatedDuplicationExclusion(
+          "Specifying module-relative paths at project level in the property '" + CoreProperties.CPD_EXCLUSIONS + "' is deprecated. " +
+            "To continue matching files like '" + inputFile + "', update this property so that patterns refer to project-relative paths.");
+      }
+      LOG.debug("File {} excluded for duplication", inputFile);
+    }
+  }
+
   private void warnOnceDeprecatedExclusion(String msg) {
     if (!warnExclusionsAlreadyLogged) {
       LOG.warn(msg);
@@ -183,6 +201,14 @@ public class FileIndexer {
     }
   }
 
+  private void warnOnceDeprecatedDuplicationExclusion(String msg) {
+    if (!warnDuplicationExclusionsAlreadyLogged) {
+      LOG.warn(msg);
+      analysisWarnings.addUnique(msg);
+      warnDuplicationExclusionsAlreadyLogged = true;
+    }
+  }
+
   private boolean accept(InputFile indexedFile) {
     // InputFileFilter extensions. Might trigger generation of metadata
     for (InputFileFilter filter : filters) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleCoverageAndDuplicationExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleCoverageAndDuplicationExclusions.java
new file mode 100644 (file)
index 0000000..6c9dd27
--- /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.scan.filesystem;
+
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.scanner.scan.ModuleConfiguration;
+
+@Immutable
+public class ModuleCoverageAndDuplicationExclusions extends AbstractCoverageAndDuplicationExclusions {
+
+  public ModuleCoverageAndDuplicationExclusions(ModuleConfiguration moduleConfiguration) {
+    super(moduleConfiguration::getStringArray, DefaultInputFile::getModuleRelativePath);
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleCoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleCoverageExclusions.java
deleted file mode 100644 (file)
index d49f5af..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.scan.filesystem;
-
-import javax.annotation.concurrent.Immutable;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.scanner.scan.ModuleConfiguration;
-
-@Immutable
-public class ModuleCoverageExclusions extends AbstractCoverageExclusions {
-
-  public ModuleCoverageExclusions(ModuleConfiguration moduleConfiguration) {
-    super(moduleConfiguration::getStringArray, DefaultInputFile::getModuleRelativePath);
-  }
-}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectCoverageAndDuplicationExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectCoverageAndDuplicationExclusions.java
new file mode 100644 (file)
index 0000000..7835903
--- /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.scan.filesystem;
+
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.scanner.scan.ProjectConfiguration;
+
+@Immutable
+public class ProjectCoverageAndDuplicationExclusions extends AbstractCoverageAndDuplicationExclusions {
+
+  public ProjectCoverageAndDuplicationExclusions(ProjectConfiguration projectConfig) {
+    super(projectConfig::getStringArray, DefaultInputFile::getProjectRelativePath);
+    log();
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectCoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectCoverageExclusions.java
deleted file mode 100644 (file)
index e100450..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.scan.filesystem;
-
-import javax.annotation.concurrent.Immutable;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.scanner.scan.ProjectConfiguration;
-
-@Immutable
-public class ProjectCoverageExclusions extends AbstractCoverageExclusions {
-
-  public ProjectCoverageExclusions(ProjectConfiguration projectConfig) {
-    super(projectConfig::getStringArray, DefaultInputFile::getProjectRelativePath);
-    log();
-  }
-}
index ece4b46784b9109a2595adbc407caeb1fcce16f4..1db64920c9ed96975e7b8db95c7513fa5b2fca1d 100644 (file)
@@ -105,9 +105,9 @@ public class ProjectFileIndexer {
     // Emulate creation of module level settings
     ModuleConfiguration moduleConfig = new ModuleConfigurationProvider().provide(globalConfig, module, projectServerSettings);
     ModuleExclusionFilters moduleExclusionFilters = new ModuleExclusionFilters(moduleConfig);
-    ModuleCoverageExclusions moduleCoverageExclusions = new ModuleCoverageExclusions(moduleConfig);
-    indexFiles(module, moduleExclusionFilters, moduleCoverageExclusions, module.getSourceDirsOrFiles(), Type.MAIN, excludedByPatternsCount);
-    indexFiles(module, moduleExclusionFilters, moduleCoverageExclusions, module.getTestDirsOrFiles(), Type.TEST, excludedByPatternsCount);
+    ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions = new ModuleCoverageAndDuplicationExclusions(moduleConfig);
+    indexFiles(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, module.getSourceDirsOrFiles(), Type.MAIN, excludedByPatternsCount);
+    indexFiles(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, module.getTestDirsOrFiles(), Type.TEST, excludedByPatternsCount);
   }
 
   private static void logPaths(String label, Path baseDir, List<Path> paths) {
@@ -139,14 +139,14 @@ public class ProjectFileIndexer {
     return count == 1 ? "file" : "files";
   }
 
-  private void indexFiles(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageExclusions moduleCoverageExclusions, List<Path> sources,
-    Type type, AtomicInteger excludedByPatternsCount) {
+  private void indexFiles(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, List<Path> sources,
+                          Type type, AtomicInteger excludedByPatternsCount) {
     try {
       for (Path dirOrFile : sources) {
         if (dirOrFile.toFile().isDirectory()) {
-          indexDirectory(module, moduleExclusionFilters, moduleCoverageExclusions, dirOrFile, type, excludedByPatternsCount);
+          indexDirectory(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, dirOrFile, type, excludedByPatternsCount);
         } else {
-          fileIndexer.indexFile(module, moduleExclusionFilters, moduleCoverageExclusions, dirOrFile, type, progressReport, excludedByPatternsCount);
+          fileIndexer.indexFile(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, dirOrFile, type, progressReport, excludedByPatternsCount);
         }
       }
     } catch (IOException e) {
@@ -154,25 +154,25 @@ public class ProjectFileIndexer {
     }
   }
 
-  private void indexDirectory(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageExclusions moduleCoverageExclusions, Path dirToIndex,
-    Type type, AtomicInteger excludedByPatternsCount)
+  private void indexDirectory(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, Path dirToIndex,
+                              Type type, AtomicInteger excludedByPatternsCount)
     throws IOException {
     Files.walkFileTree(dirToIndex.normalize(), Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
-      new IndexFileVisitor(module, moduleExclusionFilters, moduleCoverageExclusions, type, excludedByPatternsCount));
+      new IndexFileVisitor(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, type, excludedByPatternsCount));
   }
 
   private class IndexFileVisitor implements FileVisitor<Path> {
     private final DefaultInputModule module;
     private final ModuleExclusionFilters moduleExclusionFilters;
-    private final ModuleCoverageExclusions moduleCoverageExclusions;
+    private final ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions;
     private final Type type;
     private final AtomicInteger excludedByPatternsCount;
 
-    IndexFileVisitor(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageExclusions moduleCoverageExclusions, Type type,
-      AtomicInteger excludedByPatternsCount) {
+    IndexFileVisitor(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, Type type,
+                     AtomicInteger excludedByPatternsCount) {
       this.module = module;
       this.moduleExclusionFilters = moduleExclusionFilters;
-      this.moduleCoverageExclusions = moduleCoverageExclusions;
+      this.moduleCoverageAndDuplicationExclusions = moduleCoverageAndDuplicationExclusions;
       this.type = type;
       this.excludedByPatternsCount = excludedByPatternsCount;
     }
@@ -193,7 +193,7 @@ public class ProjectFileIndexer {
     @Override
     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
       if (!Files.isHidden(file)) {
-        fileIndexer.indexFile(module, moduleExclusionFilters, moduleCoverageExclusions, file, type, progressReport, excludedByPatternsCount);
+        fileIndexer.indexFile(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, file, type, progressReport, excludedByPatternsCount);
       }
       return FileVisitResult.CONTINUE;
     }
index ba2418dd2964e78b620ba77bc8a502d65d4d15ff..3e06892cf84eaab6d528ab5b1aedec1b97d1a875 100644 (file)
@@ -186,7 +186,7 @@ public class ProjectSensorContext implements SensorContext {
     if (analysisMode.isIssues()) {
       return NO_OP_NEW_CPD_TOKENS;
     }
-    return new DefaultCpdTokens(config, sensorStorage);
+    return new DefaultCpdTokens(sensorStorage);
   }
 
   @Override
index 203e5b715b0bc23f2af2f47d9ad46910cb17b086..7a0a2f21695cbd23e0cc5db3c9f3472029fc858d 100644 (file)
@@ -32,8 +32,7 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-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.TestInputFileBuilder;
 import org.sonar.api.batch.sensor.internal.SensorContextTester;
 import org.sonar.duplications.block.Block;
@@ -57,7 +56,7 @@ public class JavaCpdBlockIndexerSensorTest {
   @Captor
   private ArgumentCaptor<List<Block>> blockCaptor;
 
-  private InputFile file;
+  private DefaultInputFile file;
 
   @Before
   public void prepare() throws IOException {
@@ -75,7 +74,7 @@ public class JavaCpdBlockIndexerSensorTest {
 
   @Test
   public void testExclusions() {
-    context.settings().setProperty(CoreProperties.CPD_EXCLUSIONS, "**");
+    file.setExcludedForDuplication(true);
     new JavaCpdBlockIndexerSensor(index).execute(context);
     verifyZeroInteractions(index);
   }
index 9226bf3819965531250b7137febabec27598eb10..6b622327bccffad59de8b940de6b0e8d65853549 100644 (file)
@@ -67,7 +67,6 @@ public class CoverageMediumTest {
 
     AnalysisResult result = tester.newAnalysis()
       .properties(ImmutableMap.<String, String>builder()
-        .put("sonar.task", "scan")
         .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
         .put("sonar.projectKey", "com.foo.project")
         .put("sonar.sources", "src")
@@ -103,7 +102,6 @@ public class CoverageMediumTest {
 
     AnalysisResult result = tester.newAnalysis()
       .properties(ImmutableMap.<String, String>builder()
-        .put("sonar.task", "scan")
         .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
         .put("sonar.projectKey", "com.foo.project")
         .put("sonar.sources", "src")
@@ -141,7 +139,6 @@ public class CoverageMediumTest {
 
     AnalysisResult result = tester.newAnalysis()
       .properties(ImmutableMap.<String, String>builder()
-        .put("sonar.task", "scan")
         .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
         .put("sonar.projectKey", "com.foo.project")
         .put("sonar.sources", "src")
@@ -169,35 +166,34 @@ public class CoverageMediumTest {
     File srcDirB = new File(baseDirModuleB, "src");
     srcDirB.mkdirs();
 
-    File xooFileA = new File(srcDirA, "sample.xoo");
-    File xooUtCoverageFileA = new File(srcDirA, "sample.xoo.coverage");
+    File xooFileA = new File(srcDirA, "sampleA.xoo");
+    File xooUtCoverageFileA = new File(srcDirA, "sampleA.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");
+    File xooFileB = new File(srcDirB, "sampleB.xoo");
+    File xooUtCoverageFileB = new File(srcDirB, "sampleB.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);
 
     AnalysisResult result = tester.newAnalysis()
       .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")
+        .put("sonar.coverage.exclusions", "src/sampleA.xoo")
         .build())
       .execute();
 
-    InputFile fileA = result.inputFile("moduleA/src/sample.xoo");
+    InputFile fileA = result.inputFile("moduleA/src/sampleA.xoo");
     assertThat(result.coverageFor(fileA, 2)).isNull();
 
-    InputFile fileB = result.inputFile("moduleB/src/sample.xoo");
-    assertThat(result.coverageFor(fileB, 2)).isNull();
+    InputFile fileB = result.inputFile("moduleB/src/sampleB.xoo");
+    assertThat(result.coverageFor(fileB, 2)).isNotNull();
 
     assertThat(logTester.logs(LoggerLevel.WARN)).contains("Specifying module-relative paths at project level in the property 'sonar.coverage.exclusions' is deprecated. " +
-      "To continue matching files like 'moduleA/src/sample.xoo', update this property so that patterns refer to project-relative paths.");
+      "To continue matching files like 'moduleA/src/sampleA.xoo', update this property so that patterns refer to project-relative paths.");
   }
 
   @Test
@@ -225,7 +221,6 @@ public class CoverageMediumTest {
 
     AnalysisResult result = tester.newAnalysis()
       .properties(ImmutableMap.<String, String>builder()
-        .put("sonar.task", "scan")
         .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
         .put("sonar.projectKey", "com.foo.project")
         .put("sonar.sources", "src")
@@ -257,7 +252,6 @@ public class CoverageMediumTest {
 
     AnalysisResult result = tester.newAnalysis()
       .properties(ImmutableMap.<String, String>builder()
-        .put("sonar.task", "scan")
         .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
         .put("sonar.projectKey", "com.foo.project")
         .put("sonar.sources", "src")
@@ -302,7 +296,6 @@ public class CoverageMediumTest {
 
     AnalysisResult result = tester.newAnalysis()
       .properties(ImmutableMap.<String, String>builder()
-        .put("sonar.task", "scan")
         .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
         .put("sonar.projectKey", "com.foo.project")
         .put("sonar.modules", "module1")
index e31dfbb1b4e3e2bc2063191011d39f99c22444a2..d8900cfa569ebd20059f82591e161341b5e628ef 100644 (file)
@@ -32,6 +32,7 @@ import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.scanner.mediumtest.ScannerMediumTester;
 import org.sonar.scanner.mediumtest.AnalysisResult;
 import org.sonar.scanner.protocol.output.ScannerReport;
@@ -262,6 +263,96 @@ public class CpdMediumTest {
     assertThat(duplicationGroupsFile2).isEmpty();
   }
 
+  @Test
+  public void cross_module_duplication() throws IOException {
+
+    String duplicatedStuff = "Sample xoo\ncontent\n"
+      + "foo\nbar\ntoto\ntiti\n"
+      + "foo\nbar\ntoto\ntiti\n"
+      + "bar\ntoto\ntiti\n"
+      + "foo\nbar\ntoto\ntiti";
+
+    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, "sampleA.xoo");
+    FileUtils.write(xooFileA, duplicatedStuff, StandardCharsets.UTF_8);
+
+    File xooFileB = new File(srcDirB, "sampleB.xoo");
+    FileUtils.write(xooFileB, duplicatedStuff, StandardCharsets.UTF_8);
+
+    AnalysisResult result = tester.newAnalysis()
+      .properties(ImmutableMap.<String, String>builder()
+        .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+        .put("sonar.projectKey", "com.foo.project")
+        .put("sonar.sources", "src")
+        .put("sonar.modules", "moduleA,moduleB")
+        .put("sonar.cpd.xoo.minimumTokens", "10")
+        .build())
+      .execute();
+
+    InputFile inputFile1 = result.inputFile("moduleA/src/sampleA.xoo");
+    InputFile inputFile2 = result.inputFile("moduleB/src/sampleB.xoo");
+
+    List<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+    assertThat(duplicationGroupsFile1).isNotEmpty();
+
+    List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+    assertThat(duplicationGroupsFile2).isNotEmpty();
+  }
+
+  @Test
+  public void warn_user_for_outdated_inherited_scanner_side_exclusions_for_multi_module_project() throws IOException {
+
+    String duplicatedStuff = "Sample xoo\ncontent\n"
+      + "foo\nbar\ntoto\ntiti\n"
+      + "foo\nbar\ntoto\ntiti\n"
+      + "bar\ntoto\ntiti\n"
+      + "foo\nbar\ntoto\ntiti";
+
+    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, "sampleA.xoo");
+    FileUtils.write(xooFileA, duplicatedStuff, StandardCharsets.UTF_8);
+
+    File xooFileB = new File(srcDirB, "sampleB.xoo");
+    FileUtils.write(xooFileB, duplicatedStuff, StandardCharsets.UTF_8);
+
+    AnalysisResult result = tester.newAnalysis()
+      .properties(ImmutableMap.<String, String>builder()
+        .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+        .put("sonar.projectKey", "com.foo.project")
+        .put("sonar.sources", "src")
+        .put("sonar.modules", "moduleA,moduleB")
+        .put("sonar.cpd.xoo.minimumTokens", "10")
+        .put("sonar.cpd.exclusions", "src/sampleA.xoo")
+        .build())
+      .execute();
+
+    InputFile inputFile1 = result.inputFile("moduleA/src/sampleA.xoo");
+    InputFile inputFile2 = result.inputFile("moduleB/src/sampleB.xoo");
+
+    List<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+    assertThat(duplicationGroupsFile1).isEmpty();
+
+    List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+    assertThat(duplicationGroupsFile2).isEmpty();
+
+    assertThat(logTester.logs(LoggerLevel.WARN)).contains("Specifying module-relative paths at project level in the property 'sonar.cpd.exclusions' is deprecated. " +
+      "To continue matching files like 'moduleA/src/sampleA.xoo', update this property so that patterns refer to project-relative paths.");
+  }
+
   @Test
   public void enableCrossProjectDuplication() throws IOException {
     File srcDir = new File(baseDir, "src");
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageAndDuplicationExclusionsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleCoverageAndDuplicationExclusionsTest.java
new file mode 100644 (file)
index 0000000..63239c0
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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.CoreProperties;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.scanner.scan.ModuleConfiguration;
+import org.sonar.scanner.scan.filesystem.ModuleCoverageAndDuplicationExclusions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ModuleCoverageAndDuplicationExclusionsTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  private ModuleCoverageAndDuplicationExclusions coverageExclusions;
+  private File baseDir;
+
+  @Before
+  public void prepare() throws Exception {
+    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();
+    coverageExclusions = new ModuleCoverageAndDuplicationExclusions(mockConfig("src/org/polop/*", ""));
+    assertThat(coverageExclusions.isExcludedForCoverage(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();
+    coverageExclusions = new ModuleCoverageAndDuplicationExclusions(mockConfig("src/org/other/*", ""));
+    assertThat(coverageExclusions.isExcludedForCoverage(file)).isFalse();
+  }
+
+  private ModuleConfiguration mockConfig(String coverageExclusions, String cpdExclusions) {
+    ModuleConfiguration config = mock(ModuleConfiguration.class);
+    when(config.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY)).thenReturn(new String[] {coverageExclusions});
+    when(config.getStringArray(CoreProperties.CPD_EXCLUSIONS)).thenReturn(new String[] {cpdExclusions});
+    return config;
+  }
+}
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
deleted file mode 100644 (file)
index 7a3287d..0000000
+++ /dev/null
@@ -1,72 +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 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.scanner.scan.ModuleConfiguration;
-import org.sonar.scanner.scan.filesystem.ModuleCoverageExclusions;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ModuleCoverageExclusionsTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  private ModuleCoverageExclusions coverageExclusions;
-  private File baseDir;
-
-  @Before
-  public void prepare() throws Exception {
-    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();
-    coverageExclusions = new ModuleCoverageExclusions(mockConfig("src/org/polop/*"));
-    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();
-    coverageExclusions = new ModuleCoverageExclusions(mockConfig("src/org/other/*"));
-    assertThat(coverageExclusions.isExcluded(file)).isFalse();
-  }
-
-  private ModuleConfiguration mockConfig(String... values) {
-    ModuleConfiguration config = mock(ModuleConfiguration.class);
-    when(config.getStringArray("sonar.coverage.exclusions")).thenReturn(values);
-    return config;
-  }
-}
index 822c44b6c039d45fe5d66f35b08747a25c6617d8..df7fe533b6f3be3845a3c4f3803e02be06eec600 100644 (file)
@@ -24,10 +24,11 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.scanner.scan.ProjectConfiguration;
-import org.sonar.scanner.scan.filesystem.ProjectCoverageExclusions;
+import org.sonar.scanner.scan.filesystem.ProjectCoverageAndDuplicationExclusions;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -38,7 +39,7 @@ public class ProjectCoverageExclusionsTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-  private ProjectCoverageExclusions coverageExclusions;
+  private ProjectCoverageAndDuplicationExclusions underTest;
   private File baseDir;
 
   @Before
@@ -47,26 +48,49 @@ public class ProjectCoverageExclusionsTest {
   }
 
   @Test
-  public void shouldExcludeFileBasedOnPattern() {
+  public void shouldExcludeFileCoverageBasedOnPattern() {
     DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php"))
       .setProjectBaseDir(baseDir.toPath())
       .build();
-    coverageExclusions = new ProjectCoverageExclusions(mockConfig("moduleA/src/org/polop/*"));
-    assertThat(coverageExclusions.isExcluded(file)).isTrue();
+    underTest = new ProjectCoverageAndDuplicationExclusions(mockConfig("moduleA/src/org/polop/*", ""));
+    assertThat(underTest.isExcludedForCoverage(file)).isTrue();
+    assertThat(underTest.isExcludedForDuplication(file)).isFalse();
   }
 
   @Test
-  public void shouldNotExcludeFileBasedOnPattern() {
+  public void shouldNotExcludeFileCoverageBasedOnPattern() {
     DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php"))
       .setProjectBaseDir(baseDir.toPath())
       .build();
-    coverageExclusions = new ProjectCoverageExclusions(mockConfig("moduleA/src/org/other/*"));
-    assertThat(coverageExclusions.isExcluded(file)).isFalse();
+    underTest = new ProjectCoverageAndDuplicationExclusions(mockConfig("moduleA/src/org/other/*", ""));
+    assertThat(underTest.isExcludedForCoverage(file)).isFalse();
+    assertThat(underTest.isExcludedForDuplication(file)).isFalse();
   }
 
-  private ProjectConfiguration mockConfig(String... values) {
+  @Test
+  public void shouldExcludeFileDuplicationBasedOnPattern() {
+    DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php"))
+      .setProjectBaseDir(baseDir.toPath())
+      .build();
+    underTest = new ProjectCoverageAndDuplicationExclusions(mockConfig("", "moduleA/src/org/polop/*"));
+    assertThat(underTest.isExcludedForCoverage(file)).isFalse();
+    assertThat(underTest.isExcludedForDuplication(file)).isTrue();
+  }
+
+  @Test
+  public void shouldNotExcludeFileDuplicationBasedOnPattern() {
+    DefaultInputFile file = TestInputFileBuilder.create("foo", new File(baseDir, "moduleA"), new File(baseDir, "moduleA/src/org/polop/File.php"))
+      .setProjectBaseDir(baseDir.toPath())
+      .build();
+    underTest = new ProjectCoverageAndDuplicationExclusions(mockConfig("", "moduleA/src/org/other/*"));
+    assertThat(underTest.isExcludedForCoverage(file)).isFalse();
+    assertThat(underTest.isExcludedForDuplication(file)).isFalse();
+  }
+
+  private ProjectConfiguration mockConfig(String coverageExclusions, String cpdExclusions) {
     ProjectConfiguration config = mock(ProjectConfiguration.class);
-    when(config.getStringArray("sonar.coverage.exclusions")).thenReturn(values);
+    when(config.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY)).thenReturn(new String[] {coverageExclusions});
+    when(config.getStringArray(CoreProperties.CPD_EXCLUSIONS)).thenReturn(new String[] {cpdExclusions});
     return config;
   }