diff options
17 files changed, 393 insertions, 13 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java index 76545a33367..1992fabf8f8 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java @@ -20,6 +20,7 @@ package org.sonar.ce.task.projectanalysis.component; public interface ComponentUuidFactory { + /** * Get UUID from database if it exists, otherwise generate a new one. */ diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java index 732f278d20a..5ffc8b1c360 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java @@ -30,7 +30,6 @@ import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Scopes; -import org.sonar.core.component.ComponentKeys; import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -105,7 +104,7 @@ public class ComponentUuidFactoryWithMigration implements ComponentUuidFactory { private static Map<String, String> loadModulePathsByUuid(DbClient dbClient, DbSession dbSession, String rootKey, Map<String, String> pathByModuleKey) { List<ComponentDto> moduleDtos = dbClient.componentDao() - .selectModulesFromProjectKey(dbSession, rootKey, false).stream() + .selectProjectAndModulesFromProjectKey(dbSession, rootKey, false).stream() .filter(c -> Qualifiers.MODULE.equals(c.qualifier())) .collect(Collectors.toList()); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java index 6426ce28fb4..b137dcf409f 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java @@ -19,11 +19,11 @@ */ package org.sonar.ce.task.projectanalysis.issue; +import java.util.Collections; import java.util.List; import javax.annotation.CheckForNull; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.core.issue.DefaultIssue; -import org.sonar.ce.task.projectanalysis.component.Component; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -45,6 +45,10 @@ public class ComponentIssuesRepositoryImpl implements MutableComponentIssuesRepo @Override public List<DefaultIssue> getIssues(Component component) { + if (component.getType() == Component.Type.DIRECTORY) { + // No issues on directories + return Collections.emptyList(); + } checkState(this.component != null && this.issues != null, "Issues have not been initialized"); checkArgument(component.equals(this.component), "Only issues from component '%s' are available, but wanted component is '%s'.", diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java index 213b747ac96..bf04ad5d4a0 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java @@ -61,7 +61,6 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter { public void visitAny(Component component) { try (DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender()) { issueVisitors.beforeComponent(component); - TrackingResult tracking = issueTracking.track(component); fillNewOpenIssues(component, tracking.newIssues(), cacheAppender); fillExistingOpenIssues(component, tracking.issuesToMerge(), cacheAppender); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java index b4d8c344fa2..e8f046e8f06 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java @@ -147,6 +147,10 @@ public class IssueLifecycle { Preconditions.checkArgument(raw.isFromExternalRuleEngine() != (raw.type() == null), "At this stage issue type should be set for and only for external issues"); raw.setKey(base.key()); raw.setNew(false); + if (base.isChanged()) { + // In case issue was moved from module or folder to the root project + raw.setChanged(true); + } setType(raw); copyFields(raw, base); base.changes().forEach(raw::addChange); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java new file mode 100644 index 00000000000..958c6ad3864 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.issue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.resources.Qualifiers; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReportModulesPath; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.issue.IssueFieldsSetter; + +import static org.apache.commons.lang.StringUtils.trimToEmpty; + +class ProjectTrackerBaseLazyInput extends BaseInputFactory.BaseLazyInput { + + private AnalysisMetadataHolder analysisMetadataHolder; + private ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues; + private DbClient dbClient; + private IssueFieldsSetter issueUpdater; + private ComponentIssuesLoader issuesLoader; + private ReportModulesPath reportModulesPath; + + ProjectTrackerBaseLazyInput(AnalysisMetadataHolder analysisMetadataHolder, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, DbClient dbClient, + IssueFieldsSetter issueUpdater, ComponentIssuesLoader issuesLoader, ReportModulesPath reportModulesPath, Component component) { + super(dbClient, component, null); + this.analysisMetadataHolder = analysisMetadataHolder; + this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues; + this.dbClient = dbClient; + this.issueUpdater = issueUpdater; + this.issuesLoader = issuesLoader; + this.reportModulesPath = reportModulesPath; + } + + @Override + protected List<DefaultIssue> loadIssues() { + List<DefaultIssue> result = new ArrayList<>(); + try (DbSession dbSession = dbClient.openSession(false)) { + Set<String> dirOrModulesUuidsWithIssues = dbClient.issueDao().selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(dbSession, component.getUuid()); + if (!dirOrModulesUuidsWithIssues.isEmpty()) { + Map<String, String> pathByModuleKey = reportModulesPath.get(); + // Migrate issues that were previously on modules or directories to the root project + Map<String, ComponentDto> modulesByUuid = dbClient.componentDao().selectProjectAndModulesFromProjectKey(dbSession, component.getDbKey(), true) + .stream().collect(Collectors.toMap(ComponentDto::uuid, Function.identity())); + List<ComponentDto> dirOrModulesWithIssues = dbClient.componentDao().selectByUuids(dbSession, dirOrModulesUuidsWithIssues); + dirOrModulesWithIssues.forEach(c -> { + List<DefaultIssue> issuesOnModuleOrDir = issuesLoader.loadOpenIssues(c.uuid()); + String moduleOrDirProjectRelativePath = c.qualifier().equals(Qualifiers.MODULE) ? buildModuleProjectRelativePath(pathByModuleKey, c) + : buildDirectoryProjectRelativePath(pathByModuleKey, c, modulesByUuid.get(c.moduleUuid())); + result.addAll(migrateIssuesToTheRoot(issuesOnModuleOrDir, moduleOrDirProjectRelativePath)); + componentsWithUnprocessedIssues.remove(c.uuid()); + }); + } + result.addAll(issuesLoader.loadOpenIssues(effectiveUuid)); + return result; + } + } + + private static String buildDirectoryProjectRelativePath(Map<String, String> pathByModuleKey, ComponentDto c, ComponentDto parentModule) { + String moduleProjectRelativePath = buildModuleProjectRelativePath(pathByModuleKey, parentModule); + return Stream.of(moduleProjectRelativePath, c.path()) + .map(StringUtils::trimToNull) + .filter(s -> s != null && !"/".equals(s)) + .collect(Collectors.joining("/")); + } + + private static String buildModuleProjectRelativePath(Map<String, String> pathByModuleKey, ComponentDto parentModule) { + return Optional.ofNullable(pathByModuleKey.get(parentModule.getKey())) + // If module is not in the scanner report, we can't guess the path accurately. Fallback on what we have in DB + .orElse(trimToEmpty(parentModule.path())); + } + + private Collection<? extends DefaultIssue> migrateIssuesToTheRoot(List<DefaultIssue> issuesOnModule, String modulePath) { + for (DefaultIssue i : issuesOnModule) { + // changes the issue's component uuid, add a change and set issue as changed to enforce it is persisted to DB + IssueChangeContext context = IssueChangeContext.createUser(new Date(analysisMetadataHolder.getAnalysisDate()), null); + if (StringUtils.isNotBlank(modulePath)) { + issueUpdater.setMessage(i, "[" + modulePath + "] " + i.getMessage(), context); + } + issueUpdater.setIssueMoved(i, component.getUuid(), context); + // other fields (such as module, modulePath, componentKey) are read-only and set/reset for consistency only + i.setComponentKey(component.getKey()); + i.setModuleUuid(null); + i.setModuleUuidPath(null); + } + return issuesOnModule; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java index 757711cc3bb..844854ff0e4 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java @@ -19,14 +19,18 @@ */ package org.sonar.ce.task.projectanalysis.issue; +import java.util.Collections; import java.util.List; import javax.annotation.Nullable; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReportModulesPath; import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository; import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository.OriginalFile; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.tracking.Input; import org.sonar.db.DbClient; +import org.sonar.server.issue.IssueFieldsSetter; /** * Factory of {@link Input} of base data for issue tracking. Data are lazy-loaded. @@ -36,20 +40,35 @@ public class TrackerBaseInputFactory extends BaseInputFactory { private final ComponentIssuesLoader issuesLoader; private final DbClient dbClient; private final MovedFilesRepository movedFilesRepository; + private final ReportModulesPath reportModulesPath; + private final AnalysisMetadataHolder analysisMetadataHolder; + private final IssueFieldsSetter issueUpdater; + private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues; - public TrackerBaseInputFactory(ComponentIssuesLoader issuesLoader, DbClient dbClient, MovedFilesRepository movedFilesRepository) { + public TrackerBaseInputFactory(ComponentIssuesLoader issuesLoader, DbClient dbClient, MovedFilesRepository movedFilesRepository, ReportModulesPath reportModulesPath, + AnalysisMetadataHolder analysisMetadataHolder, IssueFieldsSetter issueUpdater, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues) { this.issuesLoader = issuesLoader; this.dbClient = dbClient; this.movedFilesRepository = movedFilesRepository; + this.reportModulesPath = reportModulesPath; + this.analysisMetadataHolder = analysisMetadataHolder; + this.issueUpdater = issueUpdater; + this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues; } public Input<DefaultIssue> create(Component component) { - return new TrackerBaseLazyInput(dbClient, component, movedFilesRepository.getOriginalFile(component).orNull()); + if (component.getType() == Component.Type.PROJECT) { + return new ProjectTrackerBaseLazyInput(analysisMetadataHolder, componentsWithUnprocessedIssues, dbClient, issueUpdater, issuesLoader, reportModulesPath, component); + } else if (component.getType() == Component.Type.DIRECTORY) { + // Folders have no issues + return new EmptyTrackerBaseLazyInput(dbClient, component); + } + return new FileTrackerBaseLazyInput(dbClient, component, movedFilesRepository.getOriginalFile(component).orNull()); } - private class TrackerBaseLazyInput extends BaseLazyInput { + private class FileTrackerBaseLazyInput extends BaseLazyInput { - private TrackerBaseLazyInput(DbClient dbClient, Component component, @Nullable OriginalFile originalFile) { + private FileTrackerBaseLazyInput(DbClient dbClient, Component component, @Nullable OriginalFile originalFile) { super(dbClient, component, originalFile); } @@ -57,5 +76,20 @@ public class TrackerBaseInputFactory extends BaseInputFactory { protected List<DefaultIssue> loadIssues() { return issuesLoader.loadOpenIssues(effectiveUuid); } + + } + + private class EmptyTrackerBaseLazyInput extends BaseLazyInput { + + private EmptyTrackerBaseLazyInput(DbClient dbClient, Component component) { + super(dbClient, component, null); + } + + @Override + protected List<DefaultIssue> loadIssues() { + return Collections.emptyList(); + } + } + } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java index c6d3083a1f4..85437b81742 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java @@ -49,6 +49,11 @@ public class ComponentIssuesRepositoryImplTest { } @Test + public void no_issues_on_dir() { + assertThat(sut.getIssues(builder(Component.Type.DIRECTORY, 1).build())).isEmpty(); + } + + @Test public void set_empty_issues() { sut.setIssues(FILE_1, Collections.emptyList()); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java index deda0b2abaa..a1600f1a4c8 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java @@ -38,6 +38,7 @@ import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.MergeBranchComponentUuids; import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.ReportModulesPath; import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitor; import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository; @@ -61,6 +62,7 @@ import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleTesting; import org.sonar.scanner.protocol.Constants; import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.server.issue.IssueFieldsSetter; import static com.google.common.collect.Lists.newArrayList; import static java.util.Arrays.asList; @@ -141,7 +143,7 @@ public class IntegrateIssuesVisitorTest { DbClient dbClient = dbTester.getDbClient(); TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash, new CommonRuleEngineImpl(), issueFilter, ruleRepositoryRule, activeRulesHolder, issueRelocationToRoot); - TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository); + TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository, mock(ReportModulesPath.class), analysisMetadataHolder, new IssueFieldsSetter(), mock(ComponentsWithUnprocessedIssues.class)); TrackerMergeBranchInputFactory mergeInputFactory = new TrackerMergeBranchInputFactory(issuesLoader, mergeBranchComponentsUuids, dbClient); ClosedIssuesInputFactory closedIssuesInputFactory = new ClosedIssuesInputFactory(issuesLoader, dbClient, movedFilesRepository); tracker = new TrackerExecution(baseInputFactory, rawInputFactory, closedIssuesInputFactory, new Tracker<>(), issuesLoader, analysisMetadataHolder); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java index 2436989451c..12a3d68b91a 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java @@ -395,6 +395,23 @@ public class IssueLifecycleTest { } @Test + public void mergeExistingOpenIssue_with_base_changed() { + DefaultIssue raw = new DefaultIssue() + .setNew(true) + .setKey("RAW_KEY") + .setRuleKey(XOO_X1); + DefaultIssue base = new DefaultIssue() + .setChanged(true) + .setKey("BASE_KEY") + .setResolution(RESOLUTION_FALSE_POSITIVE) + .setStatus(STATUS_RESOLVED); + + underTest.mergeExistingOpenIssue(raw, base); + + assertThat(raw.isChanged()).isTrue(); + } + + @Test public void mergeExistingOpenIssue_with_attributes() { DefaultIssue raw = new DefaultIssue() .setNew(true) diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java new file mode 100644 index 00000000000..9980407c94d --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java @@ -0,0 +1,164 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.issue; + +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.Date; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.ReportModulesPath; +import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.issue.IssueDto; +import org.sonar.db.rule.RuleDefinitionDto; +import org.sonar.server.issue.IssueFieldsSetter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.utils.DateUtils.parseDate; +import static org.sonar.db.component.ComponentTesting.newDirectory; +import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.component.ComponentTesting.newModuleDto; + +public class ProjectTrackerBaseLazyInputTest { + + private static final Date ANALYSIS_DATE = parseDate("2016-06-01"); + + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(ANALYSIS_DATE); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public ActiveRulesHolderRule activeRulesHolderRule = new ActiveRulesHolderRule(); + @Rule + public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule(); + + private DbClient dbClient = dbTester.getDbClient(); + private ProjectTrackerBaseLazyInput underTest; + private RuleDefinitionDto rule; + private ComponentDto rootProjectDto; + private ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(), + System2.INSTANCE); + private ReportModulesPath reportModulesPath; + + @Before + public void prepare() { + rule = dbTester.rules().insert(); + ruleRepositoryRule.add(rule.getKey()); + rootProjectDto = dbTester.components().insertMainBranch(); + ReportComponent rootProject = ReportComponent.builder(Component.Type.FILE, 1) + .setKey(rootProjectDto.getDbKey()) + .setUuid(rootProjectDto.uuid()).build(); + reportModulesPath = mock(ReportModulesPath.class); + underTest = new ProjectTrackerBaseLazyInput(analysisMetadataHolder, mock(ComponentsWithUnprocessedIssues.class), dbClient, new IssueFieldsSetter(), issuesLoader, + reportModulesPath, rootProject); + } + + @Test + public void return_only_open_project_issues_if_no_modules_and_folders() { + ComponentDto file = dbTester.components().insertComponent(newFileDto(rootProjectDto)); + IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null)); + IssueDto closedIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("CLOSED").setResolution("FIXED")); + IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null)); + + assertThat(underTest.loadIssues()).extracting(DefaultIssue::key).containsOnly(openIssueOnProject.getKey()); + } + + @Test + public void migrate_and_return_folder_issues_on_root_project() { + when(reportModulesPath.get()).thenReturn(Collections.emptyMap()); + ComponentDto folder = dbTester.components().insertComponent(newDirectory(rootProjectDto, "src")); + ComponentDto file = dbTester.components().insertComponent(newFileDto(rootProjectDto)); + IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null)); + IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null)); + IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null)); + + assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage) + .containsExactlyInAnyOrder( + tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()), + tuple(openIssueOnDir.getKey(), "[src] Issue on dir")); + + } + + @Test + public void migrate_and_return_module_and_folder_issues_on_module() { + ComponentDto module = dbTester.components().insertComponent(newModuleDto(rootProjectDto).setPath("moduleAInDb")); + when(reportModulesPath.get()).thenReturn(ImmutableMap.of(module.getDbKey(), "moduleAInReport")); + ComponentDto folder = dbTester.components().insertComponent(newDirectory(module, "src")); + ComponentDto file = dbTester.components().insertComponent(newFileDto(module)); + IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null)); + IssueDto openIssueOnModule = dbTester.issues().insert(rule, rootProjectDto, module, i -> i.setStatus("OPEN").setMessage("Issue on module").setResolution(null)); + IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null)); + IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null)); + + assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage) + .containsExactlyInAnyOrder( + tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()), + tuple(openIssueOnModule.getKey(), "[moduleAInReport] Issue on module"), + tuple(openIssueOnDir.getKey(), "[moduleAInReport/src] Issue on dir")); + + } + + @Test + public void use_db_path_if_module_missing_in_report() { + ComponentDto module = dbTester.components().insertComponent(newModuleDto(rootProjectDto).setPath("moduleAInDb")); + when(reportModulesPath.get()).thenReturn(Collections.emptyMap()); + ComponentDto folder = dbTester.components().insertComponent(newDirectory(module, "src")); + IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null)); + IssueDto openIssueOnModule = dbTester.issues().insert(rule, rootProjectDto, module, i -> i.setStatus("OPEN").setMessage("Issue on module").setResolution(null)); + IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null)); + + assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage) + .containsExactlyInAnyOrder( + tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()), + tuple(openIssueOnModule.getKey(), "[moduleAInDb] Issue on module"), + tuple(openIssueOnDir.getKey(), "[moduleAInDb/src] Issue on dir")); + + } + + @Test + public void empty_path_if_module_missing_in_report_and_db_and_for_slash_folder () { + ComponentDto module = dbTester.components().insertComponent(newModuleDto(rootProjectDto).setPath(null)); + when(reportModulesPath.get()).thenReturn(Collections.emptyMap()); + ComponentDto folder = dbTester.components().insertComponent(newDirectory(module, "/")); + IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null)); + IssueDto openIssueOnModule = dbTester.issues().insert(rule, rootProjectDto, module, i -> i.setStatus("OPEN").setMessage("Issue on module").setResolution(null)); + IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null)); + + assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage) + .containsExactlyInAnyOrder( + tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()), + tuple(openIssueOnModule.getKey(), "Issue on module"), + tuple(openIssueOnDir.getKey(), "Issue on dir")); + + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java index 556dc5088d0..f9562921b33 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java @@ -22,13 +22,19 @@ package org.sonar.ce.task.projectanalysis.issue; import com.google.common.base.Optional; import org.junit.Before; import org.junit.Test; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.ReportModulesPath; import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.source.FileSourceDao; +import org.sonar.server.issue.IssueFieldsSetter; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -37,7 +43,12 @@ import static org.mockito.Mockito.when; public class TrackerBaseInputFactoryTest { private static final String FILE_UUID = "uuid"; + private static final String DIR_UUID = "dir"; private static final ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid(FILE_UUID).build(); + private static final ReportComponent FOLDER = ReportComponent.builder(Component.Type.DIRECTORY, 2).setUuid(DIR_UUID).build(); + + @org.junit.Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); private ComponentIssuesLoader issuesLoader = mock(ComponentIssuesLoader.class); private DbClient dbClient = mock(DbClient.class); @@ -46,7 +57,8 @@ public class TrackerBaseInputFactoryTest { private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class); - private TrackerBaseInputFactory underTest = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository); + private TrackerBaseInputFactory underTest = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository, mock(ReportModulesPath.class), analysisMetadataHolder, + new IssueFieldsSetter(), mock(ComponentsWithUnprocessedIssues.class)); @Before public void setUp() throws Exception { @@ -64,6 +76,12 @@ public class TrackerBaseInputFactoryTest { } @Test + public void create_returns_empty_Input_for_folders() { + Input<DefaultIssue> input = underTest.create(FOLDER); + assertThat(input.getIssues()).isEmpty(); + } + + @Test public void create_returns_Input_which_retrieves_lines_hashes_of_original_file_of_component_when_it_has_one() { String originalUuid = "original uuid"; diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java index fe2f3941ab3..aed5fc1138a 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java @@ -183,7 +183,7 @@ public class ComponentDao implements Dao { return mapper(session).selectUuidsByKeyFromProjectKey(projectKey); } - public List<ComponentDto> selectModulesFromProjectKey(DbSession session, String projectKey, boolean excludeDisabled) { + public List<ComponentDto> selectProjectAndModulesFromProjectKey(DbSession session, String projectKey, boolean excludeDisabled) { return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT, excludeDisabled); } @@ -192,7 +192,7 @@ public class ComponentDao implements Dao { } public List<ComponentDto> selectEnabledModulesFromProjectKey(DbSession session, String projectKey) { - return selectModulesFromProjectKey(session, projectKey, true); + return selectProjectAndModulesFromProjectKey(session, projectKey, true); } public List<ComponentDto> selectByKeys(DbSession session, Collection<String> keys) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java index 4d2e46185e9..60da3909145 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java @@ -61,6 +61,10 @@ public class IssueDao implements Dao { return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid); } + public Set<String> selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(DbSession session, String projectUuid) { + return mapper(session).selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(projectUuid); + } + public List<IssueDto> selectNonClosedByComponentUuidExcludingExternalsAndSecurityHotspots(DbSession dbSession, String componentUuid) { return mapper(dbSession).selectNonClosedByComponentUuidExcludingExternals(componentUuid); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java index df441a1b778..e399267440d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java @@ -32,6 +32,8 @@ public interface IssueMapper { Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid); + Set<String> selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid); + List<IssueDto> selectByKeys(List<String> keys); List<IssueDto> selectByKeysIfNotUpdatedAt(@Param("keys") List<String> keys, @Param("updatedAt") long updatedAt); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml index 9592c6831ce..cf9958a1e50 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml @@ -242,6 +242,13 @@ i.kee, ic.issue_change_creation_date desc </select> + <select id="selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid" parameterType="string" resultType="string"> + select distinct(i.component_uuid) + from issues i + inner join projects p on p.uuid=i.component_uuid + where i.project_uuid=#{projectUuid,jdbcType=VARCHAR} and i.status <> 'CLOSED' and (p.qualifier = 'DIR' OR p.qualifier = 'BRC') + </select> + <select id="selectComponentUuidsOfOpenIssuesForProjectUuid" parameterType="string" resultType="string"> select distinct(i.component_uuid) from issues i diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java index c9ac75f8e75..adcd48e6003 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java @@ -20,8 +20,9 @@ package org.sonar.scanner.repository.settings; import java.util.Map; +import javax.annotation.Nullable; @FunctionalInterface public interface SettingsLoader { - Map<String, String> load(String componentKey); + Map<String, String> load(@Nullable String componentKey); } |