]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6931 Speed up issues mode by scanning only changed files 612/head
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 2 Nov 2015 14:34:12 +0000 (15:34 +0100)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Wed, 4 Nov 2015 10:07:44 +0000 (11:07 +0100)
13 files changed:
it/it-tests/src/test/java/it/analysis/IssuesModeTest.java
it/it-tests/src/test/java/util/ItUtils.java
sonar-batch/src/main/java/org/sonar/batch/DefaultProjectTree.java
sonar-batch/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
sonar-batch/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java
sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultFileSystem.java

index 1c60a13c84be4cda020f41ec7acca4127ac6fb12..8706d9d6df5ee9b1f103e6a559bf6e00a3a33f84 100644 (file)
@@ -5,6 +5,8 @@
  */
 package it.analysis;
 
+import org.apache.commons.io.FileUtils;
+import org.sonar.wsclient.issue.IssueClient;
 import com.google.common.collect.Maps;
 import com.sonar.orchestrator.Orchestrator;
 import com.sonar.orchestrator.build.BuildFailureException;
@@ -15,7 +17,10 @@ import com.sonar.orchestrator.config.FileSystem;
 import com.sonar.orchestrator.locator.FileLocation;
 import com.sonar.orchestrator.version.Version;
 import it.Category3Suite;
+
+import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -24,6 +29,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+
 import org.apache.commons.lang.ObjectUtils;
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
@@ -41,7 +47,6 @@ import org.sonar.wsclient.services.Resource;
 import org.sonar.wsclient.services.ResourceQuery;
 import org.sonar.wsclient.user.UserParameters;
 import util.ItUtils;
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 
@@ -89,7 +94,7 @@ public class IssuesModeTest {
   public void project_key_with_slash() throws IOException {
     restoreProfile("one-issue-per-line.xml");
     setDefaultQualityProfile("xoo", "one-issue-per-line");
-    
+
     SonarRunner runner = configureRunner("shared/xoo-sample");
     runner.setProjectKey("sample/project");
     runner.setProperty("sonar.analysis.mode", "issues");
@@ -97,6 +102,99 @@ public class IssuesModeTest {
     assertThat(ItUtils.countIssuesInJsonReport(result, true)).isEqualTo(17);
   }
 
+  // SONAR-6931
+  @Test
+  public void only_scan_changed_files_qps() throws IOException {
+    restoreProfile("one-issue-per-line.xml");
+    orchestrator.getServer().provisionProject("sample", "xoo-sample");
+    orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line");
+
+    SonarRunner runner = configureRunner("shared/xoo-sample", "sonar.verbose", "true");
+    BuildResult result = orchestrator.executeBuild(runner);
+    List<Issue> serverIssues = ItUtils.getAllServerIssues(orchestrator);
+    for (Issue i : serverIssues) {
+      assertThat(i.status()).isEqualTo("OPEN");
+    }
+    assertThat(serverIssues).hasSize(17);
+    
+    // change quality profile
+    restoreProfile("with-many-rules.xml");
+    orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "with-many-rules");
+    
+    // do it again, scanning nothing (all files should be unchanged)
+    runner = configureRunnerIssues("shared/xoo-sample",
+      "sonar.verbose", "true",
+      "sonar.scanChangedFilesOnly", "true");
+    result = orchestrator.executeBuild(runner);
+    assertThat(result.getLogs()).contains("Scanning only changed files");
+    assertThat(result.getLogs()).contains("'One Issue Per Line' skipped because there is no related file in current project");
+    ItUtils.assertIssuesInJsonReport(result, 0, 0, 17);
+  }
+  
+  // SONAR-6931
+  @Test
+  public void only_scan_changed_files_transitions() throws IOException {
+    restoreProfile("one-issue-per-line.xml");
+    orchestrator.getServer().provisionProject("sample", "xoo-sample");
+    orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line");
+
+    SonarRunner runner = configureRunner("shared/xoo-sample", "sonar.verbose", "true");
+    BuildResult result = orchestrator.executeBuild(runner);
+    List<Issue> serverIssues = ItUtils.getAllServerIssues(orchestrator);
+    for (Issue i : serverIssues) {
+      assertThat(i.status()).isEqualTo("OPEN");
+    }
+    assertThat(serverIssues).hasSize(17);
+    
+    // resolve 2 issues
+    IssueClient issueClient = orchestrator.getServer().wsClient("admin", "admin").issueClient();
+    issueClient.doTransition(serverIssues.get(0).key(), "wontfix");
+    issueClient.doTransition(serverIssues.get(1).key(), "wontfix");
+
+    // do it again, scanning nothing (all files should be unchanged)
+    runner = configureRunnerIssues("shared/xoo-sample",
+      "sonar.verbose", "true",
+      "sonar.scanChangedFilesOnly", "true");
+    result = orchestrator.executeBuild(runner);
+    assertThat(result.getLogs()).contains("Scanning only changed files");
+    assertThat(result.getLogs()).contains("'One Issue Per Line' skipped because there is no related file in current project");
+    ItUtils.assertIssuesInJsonReport(result, 0, 0, 15);
+  }
+  
+  // SONAR-6931
+  @Test
+  public void only_scan_changed_files_on_change() throws IOException {
+    restoreProfile("one-issue-per-line.xml");
+    orchestrator.getServer().provisionProject("sample", "xoo-sample");
+    orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line");
+
+    SonarRunner runner = configureRunner("shared/xoo-sample", "sonar.verbose", "true");
+    BuildResult result = orchestrator.executeBuild(runner);
+    
+    // change QP
+    restoreProfile("with-many-rules.xml");
+    orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "with-many-rules");
+    
+    // now change file hash in a temporary location
+    File tmpProjectDir = temp.newFolder();
+    FileUtils.copyDirectory(ItUtils.projectDir("shared/xoo-sample"), tmpProjectDir);
+    File srcFile = new File(tmpProjectDir, "src/main/xoo/sample/Sample.xoo");
+    FileUtils.write(srcFile, "\n", StandardCharsets.UTF_8, true);
+
+    // scan again, with different QP
+    runner = SonarRunner.create(tmpProjectDir,
+      "sonar.working.directory", temp.newFolder().getAbsolutePath(),
+      "sonar.analysis.mode", "issues",
+      "sonar.report.export.path", "sonar-report.json",
+      "sonar.userHome", temp.newFolder().getAbsolutePath(),
+      "sonar.verbose", "true",
+      "sonar.scanChangedFilesOnly", "true");
+    result = orchestrator.executeBuild(runner);
+    assertThat(result.getLogs()).contains("Scanning only changed files");
+    assertThat(result.getLogs()).doesNotContain("'One Issue Per Line' skipped because there is no related file in current project");
+    ItUtils.assertIssuesInJsonReport(result, 3, 0, 17);
+  }
+  
   @Test
   public void non_associated_mode() throws IOException {
     restoreProfile("one-issue-per-line.xml");
index 4bf8e8cf624d9d887639e6862db4a9091a259728..7ecb5fc0f61de0d8615e8dbfa86cb51e2bcb4a25 100644 (file)
@@ -4,6 +4,10 @@ package util;/*
  * mailto:contact AT sonarsource DOT com
  */
 
+import org.sonar.wsclient.issue.Issue;
+
+import org.sonar.wsclient.issue.IssueQuery;
+import org.sonar.wsclient.issue.IssueClient;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMap;
@@ -12,14 +16,18 @@ import com.sonar.orchestrator.Orchestrator;
 import com.sonar.orchestrator.build.BuildResult;
 import com.sonar.orchestrator.build.SonarRunner;
 import com.sonar.orchestrator.locator.FileLocation;
+
 import java.io.File;
 import java.io.IOException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
 import javax.annotation.Nullable;
+
 import org.apache.commons.io.FileUtils;
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
@@ -27,7 +35,6 @@ import org.json.simple.JSONValue;
 import org.sonar.wsclient.base.HttpException;
 import org.sonar.wsclient.services.PropertyDeleteQuery;
 import org.sonar.wsclient.services.PropertyUpdateQuery;
-
 import static com.google.common.collect.FluentIterable.from;
 import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -63,6 +70,12 @@ public class ItUtils {
     throw new IllegalStateException("XOO plugin is not built");
   }
 
+  public static List<Issue> getAllServerIssues(Orchestrator orchestrator) {
+    IssueClient issueClient = orchestrator.getServer().wsClient().issueClient();
+    return issueClient.find(IssueQuery.create()).list();
+
+  }
+
   /**
    * Locate the directory of sample project
    *
@@ -137,6 +150,29 @@ public class ItUtils {
     return count;
   }
 
+  public static void assertIssuesInJsonReport(BuildResult result, int newIssues, int resolvedIssues, int existingIssues) {
+    JSONObject obj = getJSONReport(result);
+    JSONArray issues = (JSONArray) obj.get("issues");
+    int countNew = 0;
+    int countResolved = 0;
+    int countExisting = 0;
+
+    for (Object issue : issues) {
+      JSONObject jsonIssue = (JSONObject) issue;
+
+      if ((Boolean) jsonIssue.get("isNew")) {
+        countNew++;
+      } else if (jsonIssue.get("resolution") != null) {
+        countResolved++;
+      } else {
+        countExisting++;
+      }
+    }
+    assertThat(countNew).isEqualTo(newIssues);
+    assertThat(countResolved).isEqualTo(resolvedIssues);
+    assertThat(countExisting).isEqualTo(existingIssues);
+  }
+
   public static SonarRunner runVerboseProjectAnalysis(Orchestrator orchestrator, String projectRelativePath, String... properties) {
     return runProjectAnalysis(orchestrator, projectRelativePath, true, properties);
   }
@@ -164,7 +200,7 @@ public class ItUtils {
     }
   }
 
-  public static void resetPeriods(Orchestrator orchestrator){
+  public static void resetPeriods(Orchestrator orchestrator) {
     setServerProperty(orchestrator, "sonar.timemachine.period1", null);
     setServerProperty(orchestrator, "sonar.timemachine.period2", null);
     setServerProperty(orchestrator, "sonar.timemachine.period3", null);
@@ -204,4 +240,4 @@ public class ItUtils {
     }
   }
 
- }
+}
index b520c1bcf17d2abfef041f1962b318b177cd652b..674a5db21828b85f12a4befa448cda1e17b274e4 100644 (file)
@@ -33,7 +33,7 @@ import org.sonar.batch.scan.ImmutableProjectReactor;
 public class DefaultProjectTree implements Startable {
 
   private final ProjectConfigurator configurator;
-  private ImmutableProjectReactor projectReactor;
+  private final ImmutableProjectReactor projectReactor;
 
   private List<Project> projects;
   private Map<ProjectDefinition, Project> projectsByDef;
index b4d2e3f322e0c58cbe5fbe07f07831612033eb9b..2e0d79cf9d11e87c0a897fc9d3be4e763f14d595 100644 (file)
@@ -36,9 +36,11 @@ import java.util.Map;
 public class DefaultAnalysisMode extends AbstractAnalysisMode implements AnalysisMode {
 
   private static final Logger LOG = LoggerFactory.getLogger(DefaultAnalysisMode.class);
+  private static final String KEY_ONLY_ANALYZE_CHANGED = "sonar.scanChangedFilesOnly";
 
   private boolean mediumTestMode;
   private boolean notAssociated;
+  private boolean onlyChanged;
 
   public DefaultAnalysisMode(GlobalProperties globalProps, AnalysisProperties props) {
     init(globalProps.properties(), props.properties());
@@ -52,6 +54,10 @@ public class DefaultAnalysisMode extends AbstractAnalysisMode implements Analysi
     return notAssociated;
   }
 
+  public boolean onlyAnalyzeChanged() {
+    return onlyChanged;
+  }
+
   private void init(Map<String, String> globalProps, Map<String, String> analysisProps) {
     // make sure analysis is consistent with global properties
     boolean globalPreview = isIssues(globalProps);
@@ -70,6 +76,8 @@ public class DefaultAnalysisMode extends AbstractAnalysisMode implements Analysi
     issues = CoreProperties.ANALYSIS_MODE_ISSUES.equals(mode) || CoreProperties.ANALYSIS_MODE_PREVIEW.equals(mode);
     mediumTestMode = "true".equals(getPropertyWithFallback(analysisProps, globalProps, FakePluginInstaller.MEDIUM_TEST_ENABLED));
     notAssociated = issues && rootProjectKeyMissing(analysisProps);
+    String onlyChangedStr = getPropertyWithFallback(analysisProps, globalProps, KEY_ONLY_ANALYZE_CHANGED);
+    onlyChanged = issues && "true".equals(onlyChangedStr);
   }
 
   public void printMode() {
@@ -86,6 +94,9 @@ public class DefaultAnalysisMode extends AbstractAnalysisMode implements Analysi
     if (notAssociated) {
       LOG.info("Local analysis");
     }
+    if (onlyChanged) {
+      LOG.info("Scanning only changed files");
+    }
   }
 
   private static String getPropertyWithFallback(Map<String, String> props1, Map<String, String> props2, String key) {
index 5273748a7f41673bf3b0e33d4e48ab86ddca6925..eaaf466228ef0b9119bf1437d013fd3ad3d5350e 100644 (file)
  */
 package org.sonar.batch.issue.tracking;
 
+import org.sonar.api.batch.fs.InputFile.Status;
+
+import org.sonar.batch.analysis.DefaultAnalysisMode;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
+
 import javax.annotation.CheckForNull;
+
 import org.sonar.api.batch.BatchSide;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.rule.ActiveRule;
@@ -54,15 +60,18 @@ public class LocalIssueTracking {
   private final ActiveRules activeRules;
   private final ServerIssueRepository serverIssueRepository;
   private final Date analysisDate;
+  private final DefaultAnalysisMode mode;
 
   private boolean hasServerAnalysis;
 
   public LocalIssueTracking(BatchComponentCache resourceCache, IssueTracking tracking, ServerLineHashesLoader lastLineHashes, IssueUpdater updater,
-    ActiveRules activeRules, ServerIssueRepository serverIssueRepository, ProjectRepositories projectRepositories, ReportPublisher reportPublisher) {
+    ActiveRules activeRules, ServerIssueRepository serverIssueRepository, ProjectRepositories projectRepositories, ReportPublisher reportPublisher,
+    DefaultAnalysisMode mode) {
     this.tracking = tracking;
     this.lastLineHashes = lastLineHashes;
     this.updater = updater;
     this.serverIssueRepository = serverIssueRepository;
+    this.mode = mode;
     this.analysisDate = ((Project) resourceCache.getRoot().resource()).getAnalysisDate();
     this.changeContext = IssueChangeContext.createScan(analysisDate);
     this.activeRules = activeRules;
@@ -78,18 +87,23 @@ public class LocalIssueTracking {
   public List<DefaultIssue> trackIssues(BatchComponent component, Set<BatchReport.Issue> rawIssues) {
     List<DefaultIssue> trackedIssues = Lists.newArrayList();
     if (hasServerAnalysis) {
-
       // all the issues that are not closed in db before starting this module scan, including manual issues
       Collection<ServerIssue> serverIssues = loadServerIssues(component);
 
-      SourceHashHolder sourceHashHolder = loadSourceHashes(component);
+      if (shouldCopyServerIssues(component)) {
+        // raw issues should be empty, we just need to deal with server issues (SONAR-6931)
+        copyServerIssues(serverIssues, trackedIssues);
+      } else {
 
-      IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, serverIssues, rawIssues);
+        SourceHashHolder sourceHashHolder = loadSourceHashes(component);
 
-      // unmatched from server = issues that have been resolved + issues on disabled/removed rules + manual issues
-      addUnmatchedFromServer(trackingResult.unmatched(), sourceHashHolder, trackedIssues);
+        IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, serverIssues, rawIssues);
 
-      mergeMatched(component, trackingResult, trackedIssues, rawIssues);
+        // unmatched from server = issues that have been resolved + issues on disabled/removed rules + manual issues
+        addUnmatchedFromServer(trackingResult.unmatched(), sourceHashHolder, trackedIssues);
+
+        mergeMatched(component, trackingResult, trackedIssues, rawIssues);
+      }
     }
 
     if (hasServerAnalysis && ResourceUtils.isRootProject(component.resource())) {
@@ -100,8 +114,33 @@ public class LocalIssueTracking {
     return trackedIssues;
   }
 
+  private boolean shouldCopyServerIssues(BatchComponent component) {
+    if (mode.onlyAnalyzeChanged() && component.isFile()) {
+      DefaultInputFile inputFile = (DefaultInputFile) component.inputComponent();
+      if (inputFile.status() == Status.SAME) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void copyServerIssues(Collection<ServerIssue> serverIssues, List<DefaultIssue> trackedIssues) {
+    for (ServerIssue serverIssue : serverIssues) {
+      org.sonar.batch.protocol.input.BatchInput.ServerIssue unmatchedPreviousIssue = ((ServerIssueFromWs) serverIssue).getDto();
+      DefaultIssue unmatched = toUnmatchedIssue(unmatchedPreviousIssue);
+
+      ActiveRule activeRule = activeRules.find(unmatched.ruleKey());
+      unmatched.setNew(false);
+
+      boolean isRemovedRule = activeRule == null;
+      unmatched.setBeingClosed(isRemovedRule);
+      unmatched.setOnDisabledRule(isRemovedRule);
+      trackedIssues.add(unmatched);
+    }
+  }
+
   private DefaultIssue toTracked(BatchComponent component, BatchReport.Issue rawIssue) {
-    DefaultIssue trackedIssue = new org.sonar.core.issue.DefaultIssueBuilder()
+    return new org.sonar.core.issue.DefaultIssueBuilder()
       .componentKey(component.key())
       .projectKey("unused")
       .ruleKey(RuleKey.of(rawIssue.getRuleRepository(), rawIssue.getRuleKey()))
@@ -110,7 +149,6 @@ public class LocalIssueTracking {
       .message(rawIssue.hasMsg() ? rawIssue.getMsg() : null)
       .severity(rawIssue.getSeverity().name())
       .build();
-    return trackedIssue;
   }
 
   @CheckForNull
index e4e5a1602ee1843ed7bec9db1b7a9e83d4f3473b..29488db579db8de011178289eed9f641a522b315 100644 (file)
@@ -53,7 +53,7 @@ public class ComponentIndexer {
   public void execute(DefaultModuleFileSystem fs) {
     module.setBaseDir(fs.baseDir());
 
-    for (InputFile inputFile : fs.inputFiles(fs.predicates().all())) {
+    for (InputFile inputFile : fs.inputFiles()) {
       String languageKey = inputFile.language();
       boolean unitTest = InputFile.Type.TEST == inputFile.type();
       Resource sonarFile = File.create(inputFile.relativePath(), languages.get(languageKey), unitTest);
index 24e26f5eb9470c43f7f8eeffb0cadb0c1e902884..17d6f8d4271b7c9e7db0406a51d74648e3a78b5b 100644 (file)
  */
 package org.sonar.batch.scan.filesystem;
 
+import org.sonar.api.batch.fs.InputFile.Status;
+
+import org.sonar.batch.analysis.DefaultAnalysisMode;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
+
 import java.io.File;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.fs.FilePredicate;
@@ -59,7 +65,7 @@ public class DefaultModuleFileSystem extends DefaultFileSystem implements Module
   private boolean initialized;
 
   public DefaultModuleFileSystem(ModuleInputFileCache moduleInputFileCache, Project project,
-    Settings settings, FileIndexer indexer, ModuleFileSystemInitializer initializer, ComponentIndexer componentIndexer) {
+    Settings settings, FileIndexer indexer, ModuleFileSystemInitializer initializer, ComponentIndexer componentIndexer, DefaultAnalysisMode mode) {
     super(initializer.baseDir(), moduleInputFileCache);
     this.componentIndexer = componentIndexer;
     this.moduleKey = project.getKey();
@@ -70,11 +76,16 @@ public class DefaultModuleFileSystem extends DefaultFileSystem implements Module
     this.sourceDirsOrFiles = initializer.sources();
     this.testDirsOrFiles = initializer.tests();
     this.binaryDirs = initializer.binaryDirs();
+
+    // filter the files that sensors have access to (SONAR-6931)
+    if (mode.onlyAnalyzeChanged()) {
+      setDefaultPredicate(predicates.not(predicates.hasStatus(Status.SAME)));
+    }
   }
 
   @VisibleForTesting
   public DefaultModuleFileSystem(Project project,
-    Settings settings, FileIndexer indexer, ModuleFileSystemInitializer initializer, ComponentIndexer componentIndexer) {
+    Settings settings, FileIndexer indexer, ModuleFileSystemInitializer initializer, ComponentIndexer componentIndexer, DefaultAnalysisMode mode) {
     super(initializer.baseDir().toPath());
     this.componentIndexer = componentIndexer;
     this.moduleKey = project.getKey();
@@ -85,6 +96,11 @@ public class DefaultModuleFileSystem extends DefaultFileSystem implements Module
     this.sourceDirsOrFiles = initializer.sources();
     this.testDirsOrFiles = initializer.tests();
     this.binaryDirs = initializer.binaryDirs();
+    
+    // filter the files sensors have access to
+    if (mode.onlyAnalyzeChanged()) {
+      setDefaultPredicate(predicates.not(predicates.hasStatus(Status.SAME)));
+    }
   }
 
   public boolean isInitialized() {
index 3164a69d10af3616b72477641af4ff2fcc38ebbe..195f1441bb4a670f9f5b902546f6d1b001b5316f 100644 (file)
  */
 package org.sonar.batch.scan.filesystem;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import javax.annotation.CheckForNull;
+import com.google.common.collect.Table;
+import com.google.common.collect.TreeBasedTable;
 import org.sonar.api.batch.BatchSide;
 import org.sonar.api.batch.fs.InputDir;
 import org.sonar.api.batch.fs.InputFile;
 
+import javax.annotation.CheckForNull;
+
 /**
  * Cache of all files and dirs. This cache is shared amongst all project modules. Inclusion and
  * exclusion patterns are already applied.
@@ -39,91 +34,59 @@ import org.sonar.api.batch.fs.InputFile;
 @BatchSide
 public class InputPathCache {
 
-  private final Map<String, SortedMap<String, InputFile>> inputFileCache = new LinkedHashMap<>();
-  private final Map<String, SortedMap<String, InputDir>> inputDirCache = new LinkedHashMap<>();
+  private final Table<String, String, InputFile> inputFileCache = TreeBasedTable.create();
+  private final Table<String, String, InputDir> inputDirCache = TreeBasedTable.create();
 
   public Iterable<InputFile> allFiles() {
-    return Iterables.concat(Iterables.transform(inputFileCache.values(), new Function<Map<String, InputFile>, Collection<InputFile>>() {
-      @Override
-      public Collection<InputFile> apply(Map<String, InputFile> input) {
-        return input.values();
-      }
-    }));
+    return inputFileCache.values();
   }
 
   public Iterable<InputDir> allDirs() {
-    return Iterables.concat(Iterables.transform(inputDirCache.values(), new Function<Map<String, InputDir>, Collection<InputDir>>() {
-      @Override
-      public Collection<InputDir> apply(Map<String, InputDir> input) {
-        return input.values();
-      }
-    }));
+    return inputDirCache.values();
   }
 
   public Iterable<InputFile> filesByModule(String moduleKey) {
-    if (inputFileCache.containsKey(moduleKey)) {
-      return inputFileCache.get(moduleKey).values();
-    }
-    return Collections.emptyList();
+    return inputFileCache.row(moduleKey).values();
   }
 
   public Iterable<InputDir> dirsByModule(String moduleKey) {
-    if (inputDirCache.containsKey(moduleKey)) {
-      return inputDirCache.get(moduleKey).values();
-    }
-    return Collections.emptyList();
+    return inputDirCache.row(moduleKey).values();
   }
 
   public InputPathCache removeModule(String moduleKey) {
-    inputFileCache.remove(moduleKey);
-    inputDirCache.remove(moduleKey);
+    inputFileCache.row(moduleKey).clear();
+    inputDirCache.row(moduleKey).clear();
     return this;
   }
 
   public InputPathCache remove(String moduleKey, InputFile inputFile) {
-    if (inputFileCache.containsKey(moduleKey)) {
-      inputFileCache.get(moduleKey).remove(inputFile.relativePath());
-    }
+    inputFileCache.remove(moduleKey, inputFile.relativePath());
     return this;
   }
 
   public InputPathCache remove(String moduleKey, InputDir inputDir) {
-    if (inputDirCache.containsKey(moduleKey)) {
-      inputDirCache.get(moduleKey).remove(inputDir.relativePath());
-    }
+    inputDirCache.remove(moduleKey, inputDir.relativePath());
     return this;
   }
 
   public InputPathCache put(String moduleKey, InputFile inputFile) {
-    if (!inputFileCache.containsKey(moduleKey)) {
-      inputFileCache.put(moduleKey, new TreeMap<String, InputFile>());
-    }
-    inputFileCache.get(moduleKey).put(inputFile.relativePath(), inputFile);
+    inputFileCache.put(moduleKey, inputFile.relativePath(), inputFile);
     return this;
   }
 
   public InputPathCache put(String moduleKey, InputDir inputDir) {
-    if (!inputDirCache.containsKey(moduleKey)) {
-      inputDirCache.put(moduleKey, new TreeMap<String, InputDir>());
-    }
-    inputDirCache.get(moduleKey).put(inputDir.relativePath(), inputDir);
+    inputDirCache.put(moduleKey, inputDir.relativePath(), inputDir);
     return this;
   }
 
   @CheckForNull
   public InputFile getFile(String moduleKey, String relativePath) {
-    if (inputFileCache.containsKey(moduleKey)) {
-      return inputFileCache.get(moduleKey).get(relativePath);
-    }
-    return null;
+    return inputFileCache.get(moduleKey, relativePath);
   }
 
   @CheckForNull
   public InputDir getDir(String moduleKey, String relativePath) {
-    if (inputDirCache.containsKey(moduleKey)) {
-      return inputDirCache.get(moduleKey).get(relativePath);
-    }
-    return null;
+    return inputDirCache.get(moduleKey, relativePath);
   }
 
 }
index 7ea7a4088d01ef4561d44da35724d074f142cdb0..9c9b294cbafd9d96308f4a8d89bcfba51c642318 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.batch.analysis;
 
 import org.junit.Rule;
-
 import org.junit.rules.ExpectedException;
 import org.sonar.batch.analysis.DefaultAnalysisMode;
 import org.sonar.batch.analysis.AnalysisProperties;
@@ -84,10 +83,30 @@ public class DefaultAnalysisModeTest {
     assertThat(mode.isPreview()).isFalse();
   }
 
+  @Test
+  public void only_scan_changed() {
+    Map<String, String> props = new HashMap<>();
+    props.put(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES);
+    GlobalProperties globalProps = new GlobalProperties(props);
+
+    props.put("sonar.scanChangedFilesOnly", "true");
+    AnalysisProperties analysisProps = new AnalysisProperties(props);
+
+    DefaultAnalysisMode mode = new DefaultAnalysisMode(globalProps, analysisProps);
+    assertThat(mode.onlyAnalyzeChanged()).isTrue();
+
+    props.put(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_PUBLISH);
+    analysisProps = new AnalysisProperties(props);
+
+    mode = new DefaultAnalysisMode(globalProps, analysisProps);
+    assertThat(mode.onlyAnalyzeChanged()).isFalse();
+  }
+
   @Test
   public void default_publish_mode() {
     DefaultAnalysisMode mode = createMode(null);
     assertThat(mode.isPublish()).isTrue();
+    assertThat(mode.onlyAnalyzeChanged()).isFalse();
   }
 
   @Test
@@ -95,6 +114,7 @@ public class DefaultAnalysisModeTest {
     DefaultAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE_ISSUES);
 
     assertThat(mode.isIssues()).isTrue();
+    assertThat(mode.onlyAnalyzeChanged()).isFalse();
   }
 
   private static DefaultAnalysisMode createMode(@Nullable String mode) {
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java
new file mode 100644 (file)
index 0000000..05e68af
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.mediumtest.issuesmode;
+
+import org.assertj.core.api.Condition;
+
+import com.google.common.io.Resources;
+import org.sonar.batch.repository.FileData;
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.FileFilterUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.sonar.api.CoreProperties;
+import org.sonar.batch.mediumtest.BatchMediumTester;
+import org.sonar.batch.protocol.Constants.Severity;
+import org.sonar.batch.protocol.input.BatchInput.ServerIssue;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.log.LogTester;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.batch.mediumtest.TaskResult;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ScanOnlyChangedTest {
+  @org.junit.Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @org.junit.Rule
+  public LogTester logTester = new LogTester();
+
+  private static SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
+
+  private BatchMediumTester tester;
+
+  private static Long date(String date) {
+    try {
+      return sdf.parse(date).getTime();
+    } catch (ParseException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Before
+  public void prepare() throws IOException {
+    String filePath = "xources/hello/HelloJava.xoo";
+    String md5sum = DigestUtils.md5Hex(FileUtils.readFileToString(new File(
+      Resources.getResource("mediumtest/xoo/sample/" + filePath).getPath())));
+
+    tester = BatchMediumTester.builder()
+      .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES))
+      .registerPlugin("xoo", new XooPlugin())
+      .addDefaultQProfile("xoo", "Sonar Way")
+      .addRules(new XooRulesDefinition())
+      .addRule("manual:MyManualIssue", "manual", "MyManualIssue", "My manual issue")
+      .addRule("manual:MyManualIssueDup", "manual", "MyManualIssue", "My manual issue")
+      .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", null, "xoo")
+      .addActiveRule("xoo", "OneIssueOnDirPerFile", null, "OneIssueOnDirPerFile", "MAJOR", null, "xoo")
+      .addActiveRule("xoo", "OneIssuePerModule", null, "OneIssuePerModule", "MAJOR", null, "xoo")
+      .addActiveRule("manual", "MyManualIssue", null, "My manual issue", "MAJOR", null, null)
+      // this will cause the file to have status==SAME
+      .addFileData("sample", filePath, new FileData(md5sum, null))
+      .setPreviousAnalysisDate(new Date())
+      // Existing issue that is copied
+      .mockServerIssue(ServerIssue.newBuilder().setKey("xyz")
+        .setModuleKey("sample")
+        .setMsg("One issue per Line copied")
+        .setPath("xources/hello/HelloJava.xoo")
+        .setRuleRepository("xoo")
+        .setRuleKey("OneIssuePerLine")
+        .setLine(1)
+        .setSeverity(Severity.MAJOR)
+        .setCreationDate(date("14/03/2004"))
+        .setChecksum(DigestUtils.md5Hex("packagehello;"))
+        .setStatus("OPEN")
+        .build())
+      // Existing issue on project that is still detected
+      .mockServerIssue(ServerIssue.newBuilder().setKey("resolved-on-project")
+        .setModuleKey("sample")
+        .setRuleRepository("xoo")
+        .setRuleKey("OneIssuePerModule")
+        .setSeverity(Severity.CRITICAL)
+        .setCreationDate(date("14/03/2004"))
+        .setStatus("OPEN")
+        .build())
+      // Manual issue
+      .mockServerIssue(ServerIssue.newBuilder().setKey("manual")
+        .setModuleKey("sample")
+        .setPath("xources/hello/HelloJava.xoo")
+        .setRuleRepository("manual")
+        .setRuleKey("MyManualIssue")
+        .setLine(1)
+        .setSeverity(Severity.MAJOR)
+        .setCreationDate(date("14/03/2004"))
+        .setChecksum(DigestUtils.md5Hex("packagehello;"))
+        .setStatus("OPEN")
+        .build())
+      .build();
+    tester.start();
+  }
+
+  @After
+  public void stop() {
+    tester.stop();
+  }
+
+  private File copyProject(String path) throws Exception {
+    File projectDir = temp.newFolder();
+    File originalProjectDir = new File(IssueModeAndReportsMediumTest.class.getResource(path).toURI());
+    FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar")));
+    return projectDir;
+  }
+
+  @Test
+  public void testIssueTrackingChangedFiles() throws Exception {
+    File projectDir = copyProject("/mediumtest/xoo/sample");
+
+    TaskResult result = tester
+      .newScanTask(new File(projectDir, "sonar-project.properties"))
+      .property("sonar.scanChangedFilesOnly", "true")
+      .start();
+
+    int newIssues = 0;
+    int openIssues = 0;
+    int resolvedIssue = 0;
+    for (Issue issue : result.trackedIssues()) {
+      System.out
+        .println(issue.message() + " " + issue.key() + " " + issue.ruleKey() + " " + issue.isNew() + " " + issue.resolution() + " " + issue.componentKey() + " " + issue.line());
+      if (issue.isNew()) {
+        newIssues++;
+      } else if (issue.resolution() != null) {
+        resolvedIssue++;
+      } else {
+        openIssues++;
+      }
+    }
+    /*
+     * We have:
+     * 6 new issues per line (open) in helloscala.xoo
+     * 2 new issues per file in helloscala.xoo / ClassOneTest.xoo
+     * 1 server issue (open, not new) copied from server in HelloJava.xoo (this file is unchanged)
+     * 1 manual issue (open, not new) in HelloJava.xoo
+     * 1 existing issue on the project (open, not new)
+     */
+    System.out.println("new: " + newIssues + " open: " + openIssues + " resolved " + resolvedIssue);
+    assertThat(newIssues).isEqualTo(8);
+    assertThat(openIssues).isEqualTo(3);
+    assertThat(resolvedIssue).isEqualTo(0);
+
+    // should only have server issues (HelloJava.xoo should not have been analyzed)
+    assertThat(result.trackedIssues()).haveExactly(2, new Condition<Issue>() {
+      @Override
+      public boolean matches(Issue value) {
+        return value.componentKey().endsWith("HelloJava.xoo");
+      }
+    });
+  }
+
+}
index 016dc1c3c463d354799e89f10f1f938aab4450de..dceefcaaaacf9b418b9156d504e3a7005d4323e9 100644 (file)
  */
 package org.sonar.batch.scan.filesystem;
 
+import org.sonar.api.batch.fs.InputFile.Status;
+
+import org.sonar.batch.analysis.DefaultAnalysisMode;
+
 import java.io.File;
 import java.io.IOException;
+
 import org.apache.commons.io.FileUtils;
 import org.junit.Before;
 import org.junit.Rule;
@@ -40,7 +45,6 @@ import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Resource;
 import org.sonar.batch.index.BatchComponent;
 import org.sonar.batch.index.BatchComponentCache;
-
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.mock;
@@ -57,6 +61,7 @@ public class ComponentIndexerTest {
   private AbstractLanguage cobolLanguage;
   private Project project;
   private ModuleFileSystemInitializer initializer;
+  private DefaultAnalysisMode mode;
 
   @Before
   public void prepare() throws IOException {
@@ -65,6 +70,7 @@ public class ComponentIndexerTest {
     sonarIndex = mock(SonarIndex.class);
     project = new Project("myProject");
     initializer = mock(ModuleFileSystemInitializer.class);
+    mode = mock(DefaultAnalysisMode.class);
     when(initializer.baseDir()).thenReturn(baseDir);
     when(initializer.workingDir()).thenReturn(temp.newFolder());
     cobolLanguage = new AbstractLanguage("cobol") {
@@ -79,10 +85,11 @@ public class ComponentIndexerTest {
   public void should_index_java_files() throws IOException {
     Languages languages = new Languages(Java.INSTANCE);
     ComponentIndexer indexer = createIndexer(languages);
-    DefaultModuleFileSystem fs = new DefaultModuleFileSystem(project, null, mock(FileIndexer.class), initializer, indexer);
-    fs.add(newInputFile("src/main/java/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false));
-    fs.add(newInputFile("src/main/java2/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false));
-    fs.add(newInputFile("src/test/java/foo/bar/FooTest.java", "", "foo/bar/FooTest.java", "java", true));
+    DefaultModuleFileSystem fs = new DefaultModuleFileSystem(project, null, mock(FileIndexer.class), initializer, indexer, mode);
+    fs.add(newInputFile("src/main/java/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false, Status.ADDED));
+    fs.add(newInputFile("src/main/java2/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false, Status.ADDED));
+    // should index even if filter is applied
+    fs.add(newInputFile("src/test/java/foo/bar/FooTest.java", "", "foo/bar/FooTest.java", "java", true, Status.SAME));
 
     fs.index();
 
@@ -110,10 +117,10 @@ public class ComponentIndexerTest {
   public void should_index_cobol_files() throws IOException {
     Languages languages = new Languages(cobolLanguage);
     ComponentIndexer indexer = createIndexer(languages);
-    DefaultModuleFileSystem fs = new DefaultModuleFileSystem(project, null, mock(FileIndexer.class), initializer, indexer);
-    fs.add(newInputFile("src/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false));
-    fs.add(newInputFile("src2/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false));
-    fs.add(newInputFile("src/test/foo/bar/FooTest.cbl", "", "foo/bar/FooTest.cbl", "cobol", true));
+    DefaultModuleFileSystem fs = new DefaultModuleFileSystem(project, null, mock(FileIndexer.class), initializer, indexer, mode);
+    fs.add(newInputFile("src/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false, Status.ADDED));
+    fs.add(newInputFile("src2/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false, Status.ADDED));
+    fs.add(newInputFile("src/test/foo/bar/FooTest.cbl", "", "foo/bar/FooTest.cbl", "cobol", true, Status.ADDED));
 
     fs.index();
 
@@ -122,12 +129,13 @@ public class ComponentIndexerTest {
     verify(sonarIndex).index(org.sonar.api.resources.File.create("/src/test/foo/bar/FooTest.cbl", cobolLanguage, true));
   }
 
-  private DefaultInputFile newInputFile(String path, String content, String sourceRelativePath, String languageKey, boolean unitTest) throws IOException {
+  private DefaultInputFile newInputFile(String path, String content, String sourceRelativePath, String languageKey, boolean unitTest, InputFile.Status status) throws IOException {
     File file = new File(baseDir, path);
     FileUtils.write(file, content);
     return new DefaultInputFile("foo", path)
       .setLanguage(languageKey)
-      .setType(unitTest ? InputFile.Type.TEST : InputFile.Type.MAIN);
+      .setType(unitTest ? InputFile.Type.TEST : InputFile.Type.MAIN)
+      .setStatus(status);
   }
 
 }
index 4a5e04b4a8e8e0dd1359bf4d2a5c1fa0bb7f9297..ea087c8ebcb2bb823e29dd040d0ab97fea2755dd 100644 (file)
  */
 package org.sonar.batch.scan.filesystem;
 
+import org.sonar.api.batch.fs.InputFile.Status;
+
+import org.junit.Before;
+import org.sonar.batch.analysis.DefaultAnalysisMode;
 import com.google.common.collect.Lists;
 import org.junit.Rule;
 import org.junit.Test;
@@ -50,22 +54,33 @@ public class DefaultModuleFileSystemTest {
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  Settings settings = new Settings();
-  FileIndexer fileIndexer = mock(FileIndexer.class);
-  ModuleFileSystemInitializer initializer = mock(ModuleFileSystemInitializer.class, Mockito.RETURNS_DEEP_STUBS);
-  ComponentIndexer componentIndexer = mock(ComponentIndexer.class);
-  ModuleInputFileCache moduleInputFileCache = mock(ModuleInputFileCache.class);
+  private Settings settings;
+  private FileIndexer fileIndexer;
+  private ModuleFileSystemInitializer initializer;
+  private ComponentIndexer componentIndexer;
+  private ModuleInputFileCache moduleInputFileCache;
+  private DefaultAnalysisMode mode;
+
+  @Before
+  public void setUp() {
+    settings = new Settings();
+    fileIndexer = mock(FileIndexer.class);
+    initializer = mock(ModuleFileSystemInitializer.class, Mockito.RETURNS_DEEP_STUBS);
+    componentIndexer = mock(ComponentIndexer.class);
+    moduleInputFileCache = mock(ModuleInputFileCache.class);
+    mode = mock(DefaultAnalysisMode.class);
+  }
 
   @Test
   public void test_equals_and_hashCode() throws Exception {
     DefaultModuleFileSystem foo1 = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("foo"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
     DefaultModuleFileSystem foo2 = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("foo"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
     DefaultModuleFileSystem bar = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("bar"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("bar"), settings, fileIndexer, initializer, componentIndexer, mode);
     DefaultModuleFileSystem branch = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("bar", "branch", "My project"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("bar", "branch", "My project"), settings, fileIndexer, initializer, componentIndexer, mode);
 
     assertThat(foo1.moduleKey()).isEqualTo("foo");
     assertThat(branch.moduleKey()).isEqualTo("bar:branch");
@@ -80,7 +95,7 @@ public class DefaultModuleFileSystemTest {
   @Test
   public void default_source_encoding() {
     DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("foo"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
 
     assertThat(fs.sourceCharset()).isEqualTo(Charset.defaultCharset());
     assertThat(fs.isDefaultJvmEncoding()).isTrue();
@@ -90,7 +105,7 @@ public class DefaultModuleFileSystemTest {
   public void source_encoding_is_set() {
     settings.setProperty(CoreProperties.ENCODING_PROPERTY, "Cp1124");
     DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("foo"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
 
     assertThat(fs.encoding()).isEqualTo(Charset.forName("Cp1124"));
     assertThat(fs.sourceCharset()).isEqualTo(Charset.forName("Cp1124"));
@@ -99,6 +114,28 @@ public class DefaultModuleFileSystemTest {
     assertThat(fs.isDefaultJvmEncoding()).isFalse();
   }
 
+  @Test
+  public void default_predicate_scan_only_changed() throws IOException {
+    when(mode.onlyAnalyzeChanged()).thenReturn(true);
+
+    DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache,
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
+
+    File baseDir = temp.newFile();
+    InputFile mainInput = new DefaultInputFile("foo", "Main.java").setModuleBaseDir(baseDir.toPath()).setType(InputFile.Type.MAIN);
+    InputFile testInput = new DefaultInputFile("foo", "Test.java").setModuleBaseDir(baseDir.toPath()).setType(InputFile.Type.TEST);
+    InputFile mainSameInput = new DefaultInputFile("foo", "MainSame.java").setModuleBaseDir(baseDir.toPath())
+      .setType(InputFile.Type.TEST).setStatus(Status.SAME);
+    when(moduleInputFileCache.inputFiles()).thenReturn(Lists.newArrayList(mainInput, testInput, mainSameInput));
+
+    fs.index();
+    Iterable<InputFile> inputFiles = fs.inputFiles(fs.predicates().all());
+    assertThat(inputFiles).containsOnly(mainInput, testInput);
+
+    Iterable<InputFile> allInputFiles = fs.inputFiles();
+    assertThat(allInputFiles).containsOnly(mainInput, mainSameInput, testInput);
+  }
+
   @Test
   public void test_dirs() throws IOException {
     File basedir = temp.newFolder("base");
@@ -120,7 +157,7 @@ public class DefaultModuleFileSystemTest {
     when(initializer.tests()).thenReturn(Arrays.asList(javaTest, additionalTest));
 
     DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("foo"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
 
     assertThat(fs.baseDir().getCanonicalPath()).isEqualTo(basedir.getCanonicalPath());
     assertThat(fs.workDir().getCanonicalPath()).isEqualTo(workingDir.getCanonicalPath());
@@ -133,7 +170,7 @@ public class DefaultModuleFileSystemTest {
   @Test
   public void should_search_input_files() throws Exception {
     DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("foo"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
 
     File baseDir = temp.newFile();
     InputFile mainInput = new DefaultInputFile("foo", "Main.java").setModuleBaseDir(baseDir.toPath()).setType(InputFile.Type.MAIN);
@@ -151,7 +188,7 @@ public class DefaultModuleFileSystemTest {
   @Test
   public void should_index() {
     DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache,
-      new Project("foo"), settings, fileIndexer, initializer, componentIndexer);
+      new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode);
 
     verifyZeroInteractions(fileIndexer);
 
index 2876fdfa9bab142950b2aa48ed041cae21d26bdd..b53c2d6bdbdee18cda2dd8621972c67a1f306bcf 100644 (file)
@@ -54,7 +54,8 @@ public class DefaultFileSystem implements FileSystem {
   private final Path baseDir;
   private Path workDir;
   private Charset encoding;
-  private final FilePredicates predicates;
+  protected final FilePredicates predicates;
+  private FilePredicate defaultPredicate;
 
   /**
    * Only for testing
@@ -104,7 +105,12 @@ public class DefaultFileSystem implements FileSystem {
     this.workDir = d.getAbsoluteFile().toPath().normalize();
     return this;
   }
-
+  
+  public DefaultFileSystem setDefaultPredicate(@Nullable FilePredicate predicate) {
+    this.defaultPredicate = predicate;
+    return this;
+  }
+  
   @Override
   public File workDir() {
     return workDir.toFile();
@@ -135,11 +141,24 @@ public class DefaultFileSystem implements FileSystem {
     throw new IllegalArgumentException(sb.toString());
 
   }
+  
+  /**
+   * Bypass default predicate to get all files/dirs indexed.
+   * Default predicate is used when some files/dirs should not be processed by sensors.
+   */
+  public Iterable<InputFile> inputFiles() {
+    doPreloadFiles();
+    return OptimizedFilePredicateAdapter.create(predicates.all()).get(cache);
+  }
 
   @Override
   public Iterable<InputFile> inputFiles(FilePredicate predicate) {
     doPreloadFiles();
-    return OptimizedFilePredicateAdapter.create(predicate).get(cache);
+    FilePredicate combinedPredicate = predicate;
+    if(defaultPredicate != null) {
+      combinedPredicate = predicates().and(defaultPredicate, predicate);
+    }
+    return OptimizedFilePredicateAdapter.create(combinedPredicate).get(cache);
   }
 
   @Override