]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9837 Detect files changed in the current branch with SCM
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 19 Sep 2017 15:25:57 +0000 (17:25 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 28 Sep 2017 07:14:43 +0000 (09:14 +0200)
15 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/scm/ScmBranchProvider.java [new file with mode: 0644]
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/main/java/org/sonar/scanner/scan/filesystem/MetadataGenerator.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetection.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetectionFactory.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmConfiguration.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/MetadataGeneratorTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionFactoryTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java [new file with mode: 0644]

index 917440ecb774923b969b90b4da2c8d1081a97906..6def80e8ea63ded021dd833a45ca18f51a186c5e 100644 (file)
@@ -137,10 +137,6 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile
     return indexedFile.getProjectRelativePath();
   }
 
-  /**
-   * @deprecated since 6.6
-   */
-  @Deprecated
   @Override
   public String absolutePath() {
     return indexedFile.absolutePath();
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmBranchProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmBranchProvider.java
new file mode 100644 (file)
index 0000000..5984182
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.api.batch.scm;
+
+import java.nio.file.Path;
+import java.util.Collection;
+import javax.annotation.Nullable;
+import org.sonar.api.ExtensionPoint;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.ScannerSide;
+
+/**
+ * A {@link ScmProvider} with the capability of finding out which files were changed in a branch.
+ * @since 6.6
+ */
+@ScannerSide
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+@ExtensionPoint
+public abstract class ScmBranchProvider extends ScmProvider {
+
+  /**
+   * Return absolute path of files changed in the current branch, compared to the provided target branch.
+   * @return null if SCM provider was not able to compute the list of files.
+   */
+  @Nullable
+  public Collection<Path> branchChangedFiles(String targetBranchName, Path rootBaseDir) {
+    return null;
+  }
+}
index b9c7d7792578e8f1949422484ed8aeeea8e5c32e..14e403f2a01e3d15c3ec734029f65a27b4bbe9ef 100644 (file)
@@ -119,7 +119,6 @@ public class ModuleScanContainer extends ComponentContainer {
       ExclusionFilters.class,
       new MetadataGeneratorProvider(),
       FileMetadata.class,
-      StatusDetectionFactory.class,
       LanguageDetection.class,
       FileIndexer.class,
       InputFileBuilder.class,
index 7deb3166880efa1bf99343d8029b33eef3530090..fc34c441582d3b0fa6665b00487a5cea3c949a28 100644 (file)
@@ -93,9 +93,11 @@ import org.sonar.scanner.scan.branch.BranchType;
 import org.sonar.scanner.scan.branch.ProjectBranchesProvider;
 import org.sonar.scanner.scan.filesystem.BatchIdGenerator;
 import org.sonar.scanner.scan.filesystem.InputComponentStoreProvider;
+import org.sonar.scanner.scan.filesystem.StatusDetectionFactory;
 import org.sonar.scanner.scan.measure.DefaultMetricFinder;
 import org.sonar.scanner.scan.measure.DeprecatedMetricFinder;
 import org.sonar.scanner.scan.measure.MeasureCache;
+import org.sonar.scanner.scm.ScmChangedFilesProvider;
 import org.sonar.scanner.storage.Storages;
 
 public class ProjectScanContainer extends ComponentContainer {
@@ -157,6 +159,8 @@ public class ProjectScanContainer extends ComponentContainer {
       new InputModuleHierarchyProvider(),
       DefaultComponentTree.class,
       BatchIdGenerator.class,
+      new ScmChangedFilesProvider(),
+      StatusDetectionFactory.class,
 
       // rules
       new ActiveRulesProvider(),
index f0bfe220d08ec6338b45cde8018b14d975c7f2f6..3ad3c9960ad38a4d2cadf94b2e4eb8b23e13926f 100644 (file)
@@ -62,15 +62,15 @@ class MetadataGenerator {
       if (charsetDetector.run()) {
         charset = charsetDetector.charset();
       } else {
-        LOG.debug("Failed to detect a valid charset for file '{}'. Using default charset.", inputFile.relativePath());
+        LOG.debug("Failed to detect a valid charset for file '{}'. Using default charset.", inputFile);
         charset = defaultEncoding;
       }
       InputStream is = charsetDetector.inputStream();
       inputFile.setCharset(charset);
       Metadata metadata = fileMetadata.readMetadata(is, charset, inputFile.absolutePath(), exclusionsScanner.createCharHandlerFor(inputFile.key()));
       inputFile.setMetadata(metadata);
-      inputFile.setStatus(statusDetection.status(inputModule.definition().getKeyWithBranch(), inputFile.relativePath(), metadata.hash()));
-      LOG.debug("'{}' generated metadata {} with charset '{}'", inputFile.relativePath(), inputFile.type() == Type.TEST ? "as test " : "", charset);
+      inputFile.setStatus(statusDetection.status(inputModule.definition().getKeyWithBranch(), inputFile, metadata.hash()));
+      LOG.debug("'{}' generated metadata {} with charset '{}'", inputFile, inputFile.type() == Type.TEST ? "as test " : "", charset);
     } catch (Exception e) {
       throw new IllegalStateException(e);
     }
index 79f3576e67bc145d7ffd2c8e88bc4e392f3c63af..b91a8165734aee185c2f697cfeda64526dd0bdbe 100644 (file)
 package org.sonar.scanner.scan.filesystem;
 
 import javax.annotation.concurrent.Immutable;
-
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.scanner.repository.FileData;
 import org.sonar.scanner.repository.ProjectRepositories;
+import org.sonar.scanner.scm.ScmChangedFiles;
 
 @Immutable
 class StatusDetection {
 
   private final ProjectRepositories projectRepositories;
+  private final ScmChangedFiles scmChangedFiles;
 
-  StatusDetection(ProjectRepositories projectSettings) {
+  StatusDetection(ProjectRepositories projectSettings, ScmChangedFiles scmChangedFiles) {
     this.projectRepositories = projectSettings;
+    this.scmChangedFiles = scmChangedFiles;
   }
 
-  InputFile.Status status(String projectKeyWithBranch, String relativePath, String hash) {
-    FileData fileDataPerPath = projectRepositories.fileData(projectKeyWithBranch, relativePath);
+  InputFile.Status status(String projectKeyWithBranch, DefaultInputFile inputFile, String hash) {
+    FileData fileDataPerPath = projectRepositories.fileData(projectKeyWithBranch, inputFile.relativePath());
     if (fileDataPerPath == null) {
       return InputFile.Status.ADDED;
     }
@@ -47,6 +50,9 @@ class StatusDetection {
     if (StringUtils.isEmpty(previousHash)) {
       return InputFile.Status.ADDED;
     }
+    if (!scmChangedFiles.confirmChanged(inputFile.path())) {
+      return InputFile.Status.SAME;
+    }
     return InputFile.Status.CHANGED;
   }
 }
index e4eabe778d4048a722393d32004c39bd02fcc15b..b1cdf614e6ba6df6a8a21eafd2f60310277c752a 100644 (file)
@@ -21,17 +21,20 @@ package org.sonar.scanner.scan.filesystem;
 
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.scanner.repository.ProjectRepositories;
+import org.sonar.scanner.scm.ScmChangedFiles;
 
 @ScannerSide
 public class StatusDetectionFactory {
 
   private final ProjectRepositories projectReferentials;
+  private final ScmChangedFiles scmChangedFiles;
 
-  public StatusDetectionFactory(ProjectRepositories projectReferentials) {
+  public StatusDetectionFactory(ProjectRepositories projectReferentials, ScmChangedFiles scmChangedFiles) {
     this.projectReferentials = projectReferentials;
+    this.scmChangedFiles = scmChangedFiles;
   }
 
   StatusDetection create() {
-    return new StatusDetection(projectReferentials);
+    return new StatusDetection(projectReferentials, scmChangedFiles);
   }
 }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java
new file mode 100644 (file)
index 0000000..d4dcdd9
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.scm;
+
+import java.nio.file.Path;
+import java.util.Collection;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+public class ScmChangedFiles {
+  @Nullable
+  private final Collection<Path> fileCollection;
+
+  public ScmChangedFiles(@Nullable Collection<Path> changedFiles) {
+    this.fileCollection = changedFiles;
+  }
+
+  public boolean verifyChanged(Path file) {
+    return fileCollection == null || fileCollection.contains(file);
+  }
+
+  Collection<Path> get() {
+    return fileCollection;
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java
new file mode 100644 (file)
index 0000000..60b87a1
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.scm;
+
+import java.nio.file.Path;
+import java.util.Collection;
+import javax.annotation.CheckForNull;
+import org.picocontainer.annotations.Nullable;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.api.batch.scm.ScmBranchProvider;
+import org.sonar.api.batch.scm.ScmProvider;
+import org.sonar.scanner.scan.branch.BranchConfiguration;
+
+public class ScmChangedFilesProvider extends ProviderAdapter {
+  private static final Logger LOG = LoggerFactory.getLogger(ScmChangedFilesProvider.class);
+
+  private ScmChangedFiles scmBranchChangedFiles;
+
+  /*
+   * ScmConfiguration is not available in issues mode
+   */
+  public ScmChangedFiles provide(@Nullable ScmConfiguration scmConfiguration, BranchConfiguration branchConfiguration, InputModuleHierarchy inputModuleHierarchy) {
+    if (scmBranchChangedFiles == null) {
+      if (scmConfiguration == null) {
+        scmBranchChangedFiles = new ScmChangedFiles(null);
+      } else {
+        Path rootBaseDir = inputModuleHierarchy.root().getBaseDir();
+        Collection<Path> changedFiles = loadChangedFilesIfNeeded(scmConfiguration, branchConfiguration, rootBaseDir);
+        scmBranchChangedFiles = new ScmChangedFiles(changedFiles);
+      }
+    }
+    return scmBranchChangedFiles;
+  }
+
+  @CheckForNull
+  private static Collection<Path> loadChangedFilesIfNeeded(ScmConfiguration scmConfiguration, BranchConfiguration branchConfiguration, Path rootBaseDir) {
+    if (branchConfiguration.isShortLivingBranch()) {
+      ScmProvider scmProvider = scmConfiguration.provider();
+      if (scmProvider != null && (scmProvider instanceof ScmBranchProvider)) {
+        ScmBranchProvider scmBranchProvider = (ScmBranchProvider) scmProvider;
+        Collection<Path> changedFiles = scmBranchProvider.branchChangedFiles(branchConfiguration.branchTarget(), rootBaseDir);
+        if (changedFiles != null) {
+          LOG.debug("SCM reported {} files changed in the branch", changedFiles.size());
+          return changedFiles;
+        }
+      }
+
+      LOG.debug("SCM information about changed files in the branch is not available");
+    }
+    return null;
+  }
+
+}
index ef380a0c74e90c05f0ab660ef8f7ff55f3f00250..f34abc0eba67c97feb7f3bc6cf16c512cccf09b8 100644 (file)
  */
 package org.sonar.scanner.scm;
 
-import com.google.common.base.Joiner;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
 import org.apache.commons.lang.StringUtils;
 import org.picocontainer.Startable;
 import org.sonar.api.CoreProperties;
@@ -51,7 +52,7 @@ import org.sonar.api.utils.log.Loggers;
 })
 @InstantiationStrategy(InstantiationStrategy.PER_BATCH)
 @ScannerSide
-public final class ScmConfiguration implements Startable {
+public class ScmConfiguration implements Startable {
   private static final Logger LOG = Loggers.get(ScmConfiguration.class);
 
   public static final String FORCE_RELOAD_KEY = "sonar.scm.forceReloadAll";
@@ -103,7 +104,8 @@ public final class ScmConfiguration implements Startable {
     if (providerPerKey.containsKey(forcedProviderKey)) {
       this.provider = providerPerKey.get(forcedProviderKey);
     } else {
-      String supportedProviders = providerPerKey.isEmpty() ? "No SCM provider installed" : ("Supported SCM providers are " + Joiner.on(",").join(providerPerKey.keySet()));
+      String supportedProviders = providerPerKey.isEmpty() ? "No SCM provider installed"
+        : ("Supported SCM providers are " + providerPerKey.keySet().stream().collect(Collectors.joining(",")));
       throw new IllegalArgumentException("SCM provider was set to \"" + forcedProviderKey + "\" but no SCM provider found for this key. " + supportedProviders);
     }
   }
@@ -132,6 +134,7 @@ public final class ScmConfiguration implements Startable {
     }
   }
 
+  @CheckForNull
   public ScmProvider provider() {
     return provider;
   }
index 27fa5362f54839c3af525629b71164c82f9a4f5a..2dd1961963ccf15a65ee971e661c906b3f964817 100644 (file)
@@ -140,11 +140,10 @@ public class MetadataGeneratorTest {
     FileUtils.write(srcFile.toFile(), "single line");
 
     // status
-    when(statusDetection.status("foo", "src/main/java/foo/Bar.java", "6c1d64c0b3555892fe7273e954f6fb5a"))
+    DefaultInputFile inputFile = createInputFileWithMetadata(baseDir, "src/main/java/foo/Bar.java");
+    when(statusDetection.status("foo", inputFile, "6c1d64c0b3555892fe7273e954f6fb5a"))
       .thenReturn(InputFile.Status.ADDED);
 
-    InputFile inputFile = createInputFileWithMetadata(baseDir, "src/main/java/foo/Bar.java");
-
     assertThat(inputFile.type()).isEqualTo(InputFile.Type.MAIN);
     assertThat(inputFile.file()).isEqualTo(srcFile.toFile());
     assertThat(inputFile.absolutePath()).isEqualTo(PathUtils.sanitize(srcFile.toAbsolutePath().toString()));
index 160580763f0d23d739b1426c2dfa6c3de3cf2717..259b5e5a6533607938750435f5518ba43b0d4594 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.scanner.scan.filesystem;
 
 import org.junit.Test;
 import org.sonar.scanner.repository.ProjectRepositories;
+import org.sonar.scanner.scm.ScmChangedFiles;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -28,7 +29,7 @@ import static org.mockito.Mockito.mock;
 public class StatusDetectionFactoryTest {
   @Test
   public void testCreate() throws Exception {
-    StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectRepositories.class));
+    StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectRepositories.class), mock(ScmChangedFiles.class));
     StatusDetection detection = factory.create();
     assertThat(detection).isNotNull();
   }
index a295fdc08be0c40f2d83bae9ec2c6b13bc5113a4..bcf70eb65d7a4e54e583d0fb4dd8746e7bec8a23 100644 (file)
@@ -22,23 +22,47 @@ package org.sonar.scanner.scan.filesystem;
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableTable;
 import com.google.common.collect.Table;
+import java.nio.file.Paths;
+import java.util.Collections;
 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.scanner.repository.FileData;
 import org.sonar.scanner.repository.ProjectRepositories;
+import org.sonar.scanner.scm.ScmChangedFiles;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class StatusDetectionTest {
   @Test
   public void detect_status() {
-    Table<String, String, String> t = ImmutableTable.of();
-    ProjectRepositories ref = new ProjectRepositories(t, createTable(), null);
-    StatusDetection statusDetection = new StatusDetection(ref);
+    ProjectRepositories ref = new ProjectRepositories(ImmutableTable.of(), createTable(), null);
+    ScmChangedFiles changedFiles = new ScmChangedFiles(null);
+    StatusDetection statusDetection = new StatusDetection(ref, changedFiles);
 
-    assertThat(statusDetection.status("foo", "src/Foo.java", "ABCDE")).isEqualTo(InputFile.Status.SAME);
-    assertThat(statusDetection.status("foo", "src/Foo.java", "XXXXX")).isEqualTo(InputFile.Status.CHANGED);
-    assertThat(statusDetection.status("foo", "src/Other.java", "QWERT")).isEqualTo(InputFile.Status.ADDED);
+    assertThat(statusDetection.status("foo", createFile("src/Foo.java"), "ABCDE")).isEqualTo(InputFile.Status.SAME);
+    assertThat(statusDetection.status("foo", createFile("src/Foo.java"), "XXXXX")).isEqualTo(InputFile.Status.CHANGED);
+    assertThat(statusDetection.status("foo", createFile("src/Other.java"), "QWERT")).isEqualTo(InputFile.Status.ADDED);
+  }
+
+  @Test
+  public void detect_status_branches_exclude() {
+    ProjectRepositories ref = new ProjectRepositories(ImmutableTable.of(), createTable(), null);
+    ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.emptyList());
+    StatusDetection statusDetection = new StatusDetection(ref, changedFiles);
+
+    // normally changed
+    assertThat(statusDetection.status("foo", createFile("src/Foo.java"), "XXXXX")).isEqualTo(InputFile.Status.SAME);
+  }
+
+  @Test
+  public void detect_status_branches_confirm() {
+    ProjectRepositories ref = new ProjectRepositories(ImmutableTable.of(), createTable(), null);
+    ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.singletonList(Paths.get("module", "src", "Foo.java")));
+    StatusDetection statusDetection = new StatusDetection(ref, changedFiles);
+
+    assertThat(statusDetection.status("foo", createFile("src/Foo.java"), "XXXXX")).isEqualTo(InputFile.Status.CHANGED);
   }
 
   private static Table<String, String, FileData> createTable() {
@@ -49,4 +73,8 @@ public class StatusDetectionTest {
 
     return t;
   }
+
+  private static DefaultInputFile createFile(String relativePath) {
+    return new TestInputFileBuilder("module", relativePath).build();
+  }
 }
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java
new file mode 100644 (file)
index 0000000..6777aaa
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.scm;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.api.batch.scm.ScmBranchProvider;
+import org.sonar.api.batch.scm.ScmProvider;
+import org.sonar.scanner.scan.branch.BranchConfiguration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class ScmChangedFilesProviderTest {
+  @Mock
+  private ScmConfiguration scmConfiguration;
+  @Mock
+  private BranchConfiguration branchConfiguration;
+  @Mock
+  private InputModuleHierarchy inputModuleHierarchy;
+  @Mock
+  private ScmBranchProvider scmProvider;
+
+  private Path rootBaseDir = Paths.get("root");
+  private ScmChangedFilesProvider provider;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    DefaultInputModule root = mock(DefaultInputModule.class);
+    when(root.getBaseDir()).thenReturn(rootBaseDir);
+    when(inputModuleHierarchy.root()).thenReturn(root);
+    provider = new ScmChangedFilesProvider();
+  }
+
+  @Test
+  public void testNoScmProvider() {
+    when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+    ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
+
+    assertThat(scmChangedFiles.get()).isNull();
+    verify(scmConfiguration).provider();
+  }
+
+  @Test
+  public void testProviderDoesntSupport() {
+    when(branchConfiguration.branchTarget()).thenReturn("target");
+    when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+    when(scmConfiguration.provider()).thenReturn(scmProvider);
+    when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(null);
+    ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
+
+    assertThat(scmChangedFiles.get()).isNull();
+    verify(scmProvider).branchChangedFiles("target", rootBaseDir);
+  }
+
+  @Test
+  public void testNoOpInNonShortLivedBranch() {
+    when(branchConfiguration.isShortLivingBranch()).thenReturn(false);
+    ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
+
+    assertThat(scmChangedFiles.get()).isNull();
+    verifyZeroInteractions(scmConfiguration);
+  }
+
+  @Test
+  public void testLegacyScmProvider() {
+    ScmProvider legacy = mock(ScmProvider.class);
+    when(scmConfiguration.provider()).thenReturn(legacy);
+    when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+
+    ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
+
+    assertThat(scmChangedFiles.get()).isNull();
+    verify(scmConfiguration).provider();
+    verifyZeroInteractions(legacy);
+  }
+
+  @Test
+  public void testReturnChangedFiles() {
+    when(branchConfiguration.branchTarget()).thenReturn("target");
+    when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+    when(scmConfiguration.provider()).thenReturn(scmProvider);
+    when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(Collections.singletonList(Paths.get("changedFile")));
+    ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
+
+    assertThat(scmChangedFiles.get()).containsOnly(Paths.get("changedFile"));
+    verify(scmProvider).branchChangedFiles("target", rootBaseDir);
+  }
+
+  @Test
+  public void testCacheObject() {
+    provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
+    provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
+    verify(branchConfiguration).isShortLivingBranch();
+  }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java
new file mode 100644 (file)
index 0000000..ec1ce40
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.scm;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ScmChangedFilesTest {
+  private ScmChangedFiles scmChangedFiles;
+
+  @Test
+  public void testGetter() {
+    Collection<Path> files = Collections.singletonList(Paths.get("files"));
+    scmChangedFiles = new ScmChangedFiles(files);
+    assertThat(scmChangedFiles.get()).containsOnly(Paths.get("files"));
+  }
+
+  @Test
+  public void testNullable() {
+    scmChangedFiles = new ScmChangedFiles(null);
+    assertThat(scmChangedFiles.get()).isNull();
+    assertThat(scmChangedFiles.verifyChanged(Paths.get("files2"))).isTrue();
+  }
+
+  @Test
+  public void testConfirm() {
+    Collection<Path> files = Collections.singletonList(Paths.get("files"));
+    scmChangedFiles = new ScmChangedFiles(files);
+    assertThat(scmChangedFiles.verifyChanged(Paths.get("files"))).isTrue();
+    assertThat(scmChangedFiles.verifyChanged(Paths.get("files2"))).isFalse();
+  }
+}