aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-batch/src
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2015-11-02 15:34:12 +0100
committerDuarte Meneses <duarte.meneses@sonarsource.com>2015-11-04 11:07:44 +0100
commit7f652d4d5eae1098e92522e9f9bb024fec000075 (patch)
treeb29dc410b74344ca5f4614f0332e6323aebc1db5 /sonar-batch/src
parent7c8ad8ca233cfa6f24a8822f9a4487fa9461a539 (diff)
downloadsonarqube-7f652d4d5eae1098e92522e9f9bb024fec000075.tar.gz
sonarqube-7f652d4d5eae1098e92522e9f9bb024fec000075.zip
SONAR-6931 Speed up issues mode by scanning only changed files
Diffstat (limited to 'sonar-batch/src')
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/DefaultProjectTree.java2
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java11
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java56
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java2
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java20
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java73
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java22
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java186
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java30
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java65
10 files changed, 373 insertions, 94 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultProjectTree.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultProjectTree.java
index b520c1bcf17..674a5db2182 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/DefaultProjectTree.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultProjectTree.java
@@ -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;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java b/sonar-batch/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java
index b4d2e3f322e..2e0d79cf9d1 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java
@@ -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) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java
index 5273748a7f4..eaaf466228e 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java
@@ -19,14 +19,20 @@
*/
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
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java
index e4e5a1602ee..29488db579d 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java
@@ -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);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java
index 24e26f5eb94..17d6f8d4271 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java
@@ -19,19 +19,25 @@
*/
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() {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
index 3164a69d10a..195f1441bb4 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
@@ -19,19 +19,14 @@
*/
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);
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java b/sonar-batch/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java
index 7ea7a4088d0..9c9b294cbaf 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java
@@ -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;
@@ -85,9 +84,29 @@ public class DefaultAnalysisModeTest {
}
@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
index 00000000000..05e68afb164
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java
@@ -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");
+ }
+ });
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java
index 016dc1c3c46..dceefcaaaac 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java
@@ -19,8 +19,13 @@
*/
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);
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java
index 4a5e04b4a8e..ea087c8ebcb 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java
@@ -19,6 +19,10 @@
*/
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"));
@@ -100,6 +115,28 @@ public class DefaultModuleFileSystemTest {
}
@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");
File buildDir = temp.newFolder("build");
@@ -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);