diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2015-11-02 15:34:12 +0100 |
---|---|---|
committer | Duarte Meneses <duarte.meneses@sonarsource.com> | 2015-11-04 11:07:44 +0100 |
commit | 7f652d4d5eae1098e92522e9f9bb024fec000075 (patch) | |
tree | b29dc410b74344ca5f4614f0332e6323aebc1db5 /sonar-batch/src | |
parent | 7c8ad8ca233cfa6f24a8822f9a4487fa9461a539 (diff) | |
download | sonarqube-7f652d4d5eae1098e92522e9f9bb024fec000075.tar.gz sonarqube-7f652d4d5eae1098e92522e9f9bb024fec000075.zip |
SONAR-6931 Speed up issues mode by scanning only changed files
Diffstat (limited to 'sonar-batch/src')
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); |