From 891ed0cbda954290bbe2ce13e605506c9eb01353 Mon Sep 17 00:00:00 2001 From: Michal Duda Date: Thu, 14 Jan 2021 15:38:52 +0100 Subject: [PATCH] SONAR-14258 Pull requests inherit issue state from the source branch --- ...ProjectAnalysisTaskContainerPopulator.java | 6 + .../issue/IntegrateIssuesVisitor.java | 24 ++- .../projectanalysis/issue/IssueLifecycle.java | 10 +- .../issue/IssueTrackingDelegator.java | 9 +- .../issue/PullRequestSourceBranchMerger.java | 52 +++++++ .../issue/PullRequestTrackerExecution.java | 7 +- .../ReferenceBranchTrackerExecution.java | 9 +- .../issue/SourceBranchComponentUuids.java | 87 +++++++++++ .../issue/TrackerExecution.java | 7 +- .../issue/TrackerRawInputFactory.java | 2 +- .../TrackerSourceBranchInputFactory.java | 89 +++++++++++ .../issue/IntegrateIssuesVisitorTest.java | 46 +++--- .../issue/IssueLifecycleTest.java | 95 ++++++++++-- .../issue/IssueTrackingDelegatorTest.java | 35 +++-- .../PullRequestSourceBranchMergerTest.java | 144 ++++++++++++++++++ .../PullRequestTrackerExecutionTest.java | 19 +-- .../ReferenceBranchTrackerExecutionTest.java | 7 +- .../issue/SiblingsIssueMergerTest.java | 27 ++-- .../issue/SourceBranchComponentUuidsTest.java | 121 +++++++++++++++ .../issue/TrackerExecutionTest.java | 26 ++-- .../TrackerSourceBranchInputFactoryTest.java | 111 ++++++++++++++ 21 files changed, 813 insertions(+), 120 deletions(-) create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMerger.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuids.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactory.java create mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMergerTest.java create mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java create mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index c3ba3350004..52637abdf53 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -70,6 +70,7 @@ import org.sonar.ce.task.projectanalysis.issue.LoadComponentUuidsHavingOpenIssue import org.sonar.ce.task.projectanalysis.issue.MovedIssueVisitor; import org.sonar.ce.task.projectanalysis.issue.NewEffortAggregator; import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache; +import org.sonar.ce.task.projectanalysis.issue.PullRequestSourceBranchMerger; import org.sonar.ce.task.projectanalysis.issue.PullRequestTrackerExecution; import org.sonar.ce.task.projectanalysis.issue.ReferenceBranchTrackerExecution; import org.sonar.ce.task.projectanalysis.issue.RemoveProcessedComponentsVisitor; @@ -79,10 +80,12 @@ import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUser; import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUserLoader; import org.sonar.ce.task.projectanalysis.issue.SiblingsIssueMerger; import org.sonar.ce.task.projectanalysis.issue.SiblingsIssuesLoader; +import org.sonar.ce.task.projectanalysis.issue.SourceBranchComponentUuids; import org.sonar.ce.task.projectanalysis.issue.TrackerBaseInputFactory; import org.sonar.ce.task.projectanalysis.issue.TrackerExecution; import org.sonar.ce.task.projectanalysis.issue.TrackerRawInputFactory; import org.sonar.ce.task.projectanalysis.issue.TrackerReferenceBranchInputFactory; +import org.sonar.ce.task.projectanalysis.issue.TrackerSourceBranchInputFactory; import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver; import org.sonar.ce.task.projectanalysis.issue.commonrule.BranchCoverageRule; import org.sonar.ce.task.projectanalysis.issue.commonrule.CommentDensityRule; @@ -275,10 +278,13 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop TrackerBaseInputFactory.class, TrackerRawInputFactory.class, TrackerReferenceBranchInputFactory.class, + TrackerSourceBranchInputFactory.class, + SourceBranchComponentUuids.class, ClosedIssuesInputFactory.class, Tracker.class, TrackerExecution.class, PullRequestTrackerExecution.class, + PullRequestSourceBranchMerger.class, ReferenceBranchTrackerExecution.class, ComponentIssuesLoader.class, BaseIssuesLoader.class, 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 7e85f5a204e..3739b26c6ed 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 @@ -28,6 +28,7 @@ import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter; import org.sonar.ce.task.projectanalysis.util.cache.DiskCache.CacheAppender; import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; import org.sonar.core.util.stream.MoreCollectors; import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; @@ -35,29 +36,41 @@ import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter { private final ProtoIssueCache protoIssueCache; + private final TrackerRawInputFactory rawInputFactory; private final IssueLifecycle issueLifecycle; private final IssueVisitors issueVisitors; private final IssueTrackingDelegator issueTracking; private final SiblingsIssueMerger issueStatusCopier; private final ReferenceBranchComponentUuids referenceBranchComponentUuids; + private final PullRequestSourceBranchMerger pullRequestSourceBranchMerger; - public IntegrateIssuesVisitor(ProtoIssueCache protoIssueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors, IssueTrackingDelegator issueTracking, - SiblingsIssueMerger issueStatusCopier, ReferenceBranchComponentUuids referenceBranchComponentUuids) { + public IntegrateIssuesVisitor( + ProtoIssueCache protoIssueCache, + TrackerRawInputFactory rawInputFactory, + IssueLifecycle issueLifecycle, + IssueVisitors issueVisitors, + IssueTrackingDelegator issueTracking, + SiblingsIssueMerger issueStatusCopier, + ReferenceBranchComponentUuids referenceBranchComponentUuids, + PullRequestSourceBranchMerger pullRequestSourceBranchMerger) { super(CrawlerDepthLimit.FILE, POST_ORDER); this.protoIssueCache = protoIssueCache; + this.rawInputFactory = rawInputFactory; this.issueLifecycle = issueLifecycle; this.issueVisitors = issueVisitors; this.issueTracking = issueTracking; this.issueStatusCopier = issueStatusCopier; this.referenceBranchComponentUuids = referenceBranchComponentUuids; + this.pullRequestSourceBranchMerger = pullRequestSourceBranchMerger; } @Override public void visitAny(Component component) { try (CacheAppender cacheAppender = protoIssueCache.newAppender()) { issueVisitors.beforeComponent(component); - TrackingResult tracking = issueTracking.track(component); - fillNewOpenIssues(component, tracking.newIssues(), cacheAppender); + Input rawInput = rawInputFactory.create(component); + TrackingResult tracking = issueTracking.track(component, rawInput); + fillNewOpenIssues(component, tracking.newIssues(), rawInput, cacheAppender); fillExistingOpenIssues(component, tracking.issuesToMerge(), cacheAppender); closeIssues(component, tracking.issuesToClose(), cacheAppender); copyIssues(component, tracking.issuesToCopy(), cacheAppender); @@ -67,7 +80,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter { } } - private void fillNewOpenIssues(Component component, Stream newIssues, CacheAppender cacheAppender) { + private void fillNewOpenIssues(Component component, Stream newIssues, Input rawInput, CacheAppender cacheAppender) { List newIssuesList = newIssues .peek(issueLifecycle::initNewOpenIssue) .collect(MoreCollectors.toList()); @@ -76,6 +89,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter { return; } + pullRequestSourceBranchMerger.tryMergeIssuesFromSourceBranchOfPullRequest(component, newIssuesList, rawInput); issueStatusCopier.tryMerge(component, newIssuesList); for (DefaultIssue issue : newIssuesList) { 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 a92a0ac98ee..b395611cdd9 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 @@ -109,7 +109,15 @@ public class IssueLifecycle { raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_BRANCH, from, to); } - private void copyAttributesOfIssueFromAnotherBranch(DefaultIssue to, DefaultIssue from) { + public void copyExistingIssueFromSourceBranchToPullRequest(DefaultIssue raw, DefaultIssue base) { + Preconditions.checkState(analysisMetadataHolder.isPullRequest(), "This operation should be done only on pull request analysis"); + copyAttributesOfIssueFromAnotherBranch(raw, base); + String from = analysisMetadataHolder.getBranch().getName(); + String to = "#" + analysisMetadataHolder.getPullRequestKey(); + raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_BRANCH, from, to); + } + + public void copyAttributesOfIssueFromAnotherBranch(DefaultIssue to, DefaultIssue from) { to.setCopied(true); copyFields(to, from); if (from.manualSeverity()) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegator.java index 4d5fee969ec..266d489aa72 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegator.java @@ -22,6 +22,7 @@ package org.sonar.ce.task.projectanalysis.issue; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; import org.sonar.core.issue.tracking.Tracking; import static java.util.Collections.emptyMap; @@ -41,14 +42,14 @@ public class IssueTrackingDelegator { this.analysisMetadataHolder = analysisMetadataHolder; } - public TrackingResult track(Component component) { + public TrackingResult track(Component component, Input rawInput) { if (analysisMetadataHolder.isPullRequest()) { - return standardResult(pullRequestTracker.track(component)); + return standardResult(pullRequestTracker.track(component, rawInput)); } else if (isFirstAnalysisSecondaryBranch()) { - Tracking tracking = referenceBranchTracker.track(component); + Tracking tracking = referenceBranchTracker.track(component, rawInput); return new TrackingResult(tracking.getMatchedRaws(), emptyMap(), empty(), tracking.getUnmatchedRaws()); } else { - return standardResult(tracker.track(component)); + return standardResult(tracker.track(component, rawInput)); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMerger.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMerger.java new file mode 100644 index 00000000000..40f9421b867 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMerger.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.Collection; +import java.util.Map; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; +import org.sonar.core.issue.tracking.Tracker; +import org.sonar.core.issue.tracking.Tracking; + +public class PullRequestSourceBranchMerger { + private final Tracker tracker; + private final IssueLifecycle issueLifecycle; + private final TrackerSourceBranchInputFactory sourceBranchInputFactory; + + public PullRequestSourceBranchMerger(Tracker tracker, IssueLifecycle issueLifecycle, + TrackerSourceBranchInputFactory sourceBranchInputFactory) { + this.tracker = tracker; + this.issueLifecycle = issueLifecycle; + this.sourceBranchInputFactory = sourceBranchInputFactory; + } + + public void tryMergeIssuesFromSourceBranchOfPullRequest(Component component, Collection newIssues, Input rawInput) { + if (sourceBranchInputFactory.hasSourceBranchAnalysis()) { + Input sourceBranchInput = sourceBranchInputFactory.createForSourceBranch(component); + DefaultTrackingInput rawPrTrackingInput = new DefaultTrackingInput(newIssues, rawInput.getLineHashSequence(), rawInput.getBlockHashSequence()); + Tracking prTracking = tracker.trackNonClosed(rawPrTrackingInput, sourceBranchInput); + for (Map.Entry pair : prTracking.getMatchedRaws().entrySet()) { + issueLifecycle.copyExistingIssueFromSourceBranchToPullRequest(pair.getKey(), pair.getValue()); + } + } + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecution.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecution.java index 257e40342ed..eb9dbfe9e3b 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecution.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecution.java @@ -34,20 +34,17 @@ import org.sonar.core.issue.tracking.Tracking; public class PullRequestTrackerExecution { private final TrackerBaseInputFactory baseInputFactory; - private final TrackerRawInputFactory rawInputFactory; private final Tracker tracker; private final NewLinesRepository newLinesRepository; - public PullRequestTrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, Tracker tracker, + public PullRequestTrackerExecution(TrackerBaseInputFactory baseInputFactory, Tracker tracker, NewLinesRepository newLinesRepository) { this.baseInputFactory = baseInputFactory; - this.rawInputFactory = rawInputFactory; this.tracker = tracker; this.newLinesRepository = newLinesRepository; } - public Tracking track(Component component) { - Input rawInput = rawInputFactory.create(component); + public Tracking track(Component component, Input rawInput) { Input previousAnalysisInput = baseInputFactory.create(component); // Step 1: only keep issues on changed lines diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecution.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecution.java index 460bfc605ae..080f3ed704c 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecution.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecution.java @@ -21,22 +21,21 @@ package org.sonar.ce.task.projectanalysis.issue; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; import org.sonar.core.issue.tracking.Tracker; import org.sonar.core.issue.tracking.Tracking; public class ReferenceBranchTrackerExecution { - private final TrackerRawInputFactory rawInputFactory; private final TrackerReferenceBranchInputFactory referenceBranchInputFactory; private final Tracker tracker; - public ReferenceBranchTrackerExecution(TrackerRawInputFactory rawInputFactory, TrackerReferenceBranchInputFactory referenceBranchInputFactory, + public ReferenceBranchTrackerExecution(TrackerReferenceBranchInputFactory referenceBranchInputFactory, Tracker tracker) { - this.rawInputFactory = rawInputFactory; this.referenceBranchInputFactory = referenceBranchInputFactory; this.tracker = tracker; } - public Tracking track(Component component) { - return tracker.trackNonClosed(rawInputFactory.create(component), referenceBranchInputFactory.create(component)); + public Tracking track(Component component, Input rawInput) { + return tracker.trackNonClosed(rawInput, referenceBranchInputFactory.create(component)); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuids.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuids.java new file mode 100644 index 00000000000..13f442a4d74 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuids.java @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.annotation.CheckForNull; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.ComponentDto; + +import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey; + +/** + * Cache a map between component keys and uuids in the source branch of a pull request + */ +public class SourceBranchComponentUuids { + private final AnalysisMetadataHolder analysisMetadataHolder; + private final DbClient dbClient; + private Map sourceBranchComponentsUuidsByKey; + private boolean hasSourceBranchAnalysis; + + public SourceBranchComponentUuids(AnalysisMetadataHolder analysisMetadataHolder, DbClient dbClient) { + this.analysisMetadataHolder = analysisMetadataHolder; + this.dbClient = dbClient; + } + + private void lazyInit() { + if (sourceBranchComponentsUuidsByKey == null) { + sourceBranchComponentsUuidsByKey = new HashMap<>(); + + if (analysisMetadataHolder.isPullRequest()) { + try (DbSession dbSession = dbClient.openSession(false)) { + initForSourceBranch(dbSession); + } + } else { + hasSourceBranchAnalysis = false; + } + } + } + + private void initForSourceBranch(DbSession dbSession) { + Optional branchDtoOpt = dbClient.branchDao().selectByBranchKey(dbSession, analysisMetadataHolder.getProject().getUuid(), + analysisMetadataHolder.getBranch().getName()); + String sourceBranchUuid = branchDtoOpt.map(BranchDto::getUuid).orElse(null); + hasSourceBranchAnalysis = sourceBranchUuid != null && dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, sourceBranchUuid).isPresent(); + if (hasSourceBranchAnalysis) { + List targetComponents = dbClient.componentDao().selectByProjectUuid(sourceBranchUuid, dbSession); + for (ComponentDto dto : targetComponents) { + sourceBranchComponentsUuidsByKey.put(dto.getKey(), dto.uuid()); + } + } + } + + public boolean hasSourceBranchAnalysis() { + lazyInit(); + return hasSourceBranchAnalysis; + } + + @CheckForNull + public String getSourceBranchComponentUuid(String dbKey) { + lazyInit(); + String cleanComponentKey = removeBranchAndPullRequestFromKey(dbKey); + return sourceBranchComponentsUuidsByKey.get(cleanComponentKey); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecution.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecution.java index 0199f83ce62..b5702e19409 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecution.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecution.java @@ -33,25 +33,22 @@ import org.sonar.core.util.stream.MoreCollectors; public class TrackerExecution { private final TrackerBaseInputFactory baseInputFactory; - private final TrackerRawInputFactory rawInputFactory; private final ClosedIssuesInputFactory closedIssuesInputFactory; private final Tracker tracker; private final ComponentIssuesLoader componentIssuesLoader; private final AnalysisMetadataHolder analysisMetadataHolder; - public TrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, + public TrackerExecution(TrackerBaseInputFactory baseInputFactory, ClosedIssuesInputFactory closedIssuesInputFactory, Tracker tracker, ComponentIssuesLoader componentIssuesLoader, AnalysisMetadataHolder analysisMetadataHolder) { this.baseInputFactory = baseInputFactory; - this.rawInputFactory = rawInputFactory; this.closedIssuesInputFactory = closedIssuesInputFactory; this.tracker = tracker; this.componentIssuesLoader = componentIssuesLoader; this.analysisMetadataHolder = analysisMetadataHolder; } - public Tracking track(Component component) { - Input rawInput = rawInputFactory.create(component); + public Tracking track(Component component, Input rawInput) { Input openBaseIssuesInput = baseInputFactory.create(component); NonClosedTracking openIssueTracking = tracker.trackNonClosed(rawInput, openBaseIssuesInput); if (openIssueTracking.isComplete() || analysisMetadataHolder.isFirstAnalysis()) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java index ded92f5935b..81fe3370f42 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java @@ -54,7 +54,7 @@ import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW; public class TrackerRawInputFactory { - private static final long DEFAULT_EXTERNAL_ISSUE_EFFORT = 0l; + private static final long DEFAULT_EXTERNAL_ISSUE_EFFORT = 0L; private final TreeRootHolder treeRootHolder; private final BatchReportReader reportReader; private final CommonRuleEngine commonRuleEngine; diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactory.java new file mode 100644 index 00000000000..eaf528ca3e7 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactory.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.Collections; +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; +import org.sonar.core.issue.tracking.LazyInput; +import org.sonar.core.issue.tracking.LineHashSequence; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; + +public class TrackerSourceBranchInputFactory { + private static final LineHashSequence EMPTY_LINE_HASH_SEQUENCE = new LineHashSequence(Collections.emptyList()); + + private final ComponentIssuesLoader componentIssuesLoader; + private final DbClient dbClient; + private final SourceBranchComponentUuids sourceBranchComponentUuids; + + public TrackerSourceBranchInputFactory(ComponentIssuesLoader componentIssuesLoader, SourceBranchComponentUuids sourceBranchComponentUuids, + DbClient dbClient) { + this.componentIssuesLoader = componentIssuesLoader; + this.sourceBranchComponentUuids = sourceBranchComponentUuids; + this.dbClient = dbClient; + } + + public boolean hasSourceBranchAnalysis() { + return sourceBranchComponentUuids.hasSourceBranchAnalysis(); + } + + public Input createForSourceBranch(Component component) { + String sourceBranchComponentUuid = sourceBranchComponentUuids.getSourceBranchComponentUuid(component.getDbKey()); + return new SourceBranchLazyInput(component.getType(), sourceBranchComponentUuid); + } + + private class SourceBranchLazyInput extends LazyInput { + private final Component.Type type; + private final String sourceBranchComponentUuid; + + private SourceBranchLazyInput(Component.Type type, @Nullable String sourceBranchComponentUuid) { + this.type = type; + this.sourceBranchComponentUuid = sourceBranchComponentUuid; + } + + @Override + protected LineHashSequence loadLineHashSequence() { + if (sourceBranchComponentUuid == null || type != Component.Type.FILE) { + return EMPTY_LINE_HASH_SEQUENCE; + } + + try (DbSession session = dbClient.openSession(false)) { + List hashes = dbClient.fileSourceDao().selectLineHashes(session, sourceBranchComponentUuid); + if (hashes == null || hashes.isEmpty()) { + return EMPTY_LINE_HASH_SEQUENCE; + } + return new LineHashSequence(hashes); + } + } + + @Override + protected List loadIssues() { + if (sourceBranchComponentUuid == null) { + return Collections.emptyList(); + } + return componentIssuesLoader.loadOpenIssuesWithChanges(sourceBranchComponentUuid); + } + } + +} 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 33654165338..618e7ab7a0f 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 @@ -69,7 +69,6 @@ import org.sonar.server.issue.IssueFieldsSetter; import org.sonar.server.issue.workflow.IssueWorkflow; import static com.google.common.collect.Lists.newArrayList; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -116,28 +115,28 @@ public class IntegrateIssuesVisitorTest { @Rule public SourceLinesRepositoryRule fileSourceRepository = new SourceLinesRepositoryRule(); - private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class); - private IssueFilter issueFilter = mock(IssueFilter.class); - private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class); - private IssueChangeContext issueChangeContext = mock(IssueChangeContext.class); - private IssueLifecycle issueLifecycle = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, mock(IssueWorkflow.class), new IssueFieldsSetter(), + private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class); + private final IssueFilter issueFilter = mock(IssueFilter.class); + private final MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class); + private final IssueChangeContext issueChangeContext = mock(IssueChangeContext.class); + private final IssueLifecycle issueLifecycle = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, mock(IssueWorkflow.class), new IssueFieldsSetter(), mock(DebtCalculator.class), ruleRepositoryRule); - private IssueVisitor issueVisitor = mock(IssueVisitor.class); - private ReferenceBranchComponentUuids mergeBranchComponentsUuids = mock(ReferenceBranchComponentUuids.class); - private SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class); - private ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class); - private SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class); - private NewLinesRepository newLinesRepository = mock(NewLinesRepository.class); + private final IssueVisitor issueVisitor = mock(IssueVisitor.class); + private final ReferenceBranchComponentUuids mergeBranchComponentsUuids = mock(ReferenceBranchComponentUuids.class); + private final SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class); + private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class); + private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class); + private final NewLinesRepository newLinesRepository = mock(NewLinesRepository.class); private ArgumentCaptor defaultIssueCaptor; - private ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(), + private final ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(), System2.INSTANCE); private IssueTrackingDelegator trackingDelegator; private TrackerExecution tracker; private PullRequestTrackerExecution prBranchTracker; private ReferenceBranchTrackerExecution mergeBranchTracker; - private ActiveRulesHolder activeRulesHolder = new AlwaysActiveRulesHolderImpl(); + private final ActiveRulesHolder activeRulesHolder = new AlwaysActiveRulesHolderImpl(); private ProtoIssueCache protoIssueCache; private TypeAwareVisitor underTest; @@ -156,15 +155,16 @@ public class IntegrateIssuesVisitorTest { new IssueFieldsSetter(), mock(ComponentsWithUnprocessedIssues.class)); TrackerReferenceBranchInputFactory mergeInputFactory = new TrackerReferenceBranchInputFactory(issuesLoader, mergeBranchComponentsUuids, dbClient); ClosedIssuesInputFactory closedIssuesInputFactory = new ClosedIssuesInputFactory(issuesLoader, dbClient, movedFilesRepository); - tracker = new TrackerExecution(baseInputFactory, rawInputFactory, closedIssuesInputFactory, new Tracker<>(), issuesLoader, analysisMetadataHolder); - prBranchTracker = new PullRequestTrackerExecution(baseInputFactory, rawInputFactory, new Tracker<>(), newLinesRepository); - mergeBranchTracker = new ReferenceBranchTrackerExecution(rawInputFactory, mergeInputFactory, new Tracker<>()); + tracker = new TrackerExecution(baseInputFactory, closedIssuesInputFactory, new Tracker<>(), issuesLoader, analysisMetadataHolder); + prBranchTracker = new PullRequestTrackerExecution(baseInputFactory, new Tracker<>(), newLinesRepository); + mergeBranchTracker = new ReferenceBranchTrackerExecution(mergeInputFactory, new Tracker<>()); trackingDelegator = new IssueTrackingDelegator(prBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder); treeRootHolder.setRoot(PROJECT); protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE); when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true); when(issueChangeContext.date()).thenReturn(new Date()); - underTest = new IntegrateIssuesVisitor(protoIssueCache, issueLifecycle, issueVisitors, trackingDelegator, issueStatusCopier, referenceBranchComponentUuids); + underTest = new IntegrateIssuesVisitor(protoIssueCache, rawInputFactory, issueLifecycle, issueVisitors, trackingDelegator, issueStatusCopier, referenceBranchComponentUuids, + mock(PullRequestSourceBranchMerger.class)); } @Test @@ -177,7 +177,7 @@ public class IntegrateIssuesVisitorTest { .setRuleKey("S001") .setSeverity(Constants.Severity.BLOCKER) .build(); - reportReader.putIssues(FILE_REF, asList(reportIssue)); + reportReader.putIssues(FILE_REF, singletonList(reportIssue)); fileSourceRepository.addLine(FILE_REF, "line1"); underTest.visitAny(FILE); @@ -199,7 +199,7 @@ public class IntegrateIssuesVisitorTest { .setRuleKey(ruleKey.rule()) .setSeverity(Constants.Severity.BLOCKER) .build(); - reportReader.putIssues(FILE_REF, asList(reportIssue)); + reportReader.putIssues(FILE_REF, singletonList(reportIssue)); fileSourceRepository.addLine(FILE_REF, "line1"); underTest.visitAny(FILE); @@ -224,7 +224,7 @@ public class IntegrateIssuesVisitorTest { .setRuleKey(ruleKey.rule()) .setSeverity(Constants.Severity.BLOCKER) .build(); - reportReader.putIssues(FILE_REF, asList(reportIssue)); + reportReader.putIssues(FILE_REF, singletonList(reportIssue)); fileSourceRepository.addLine(FILE_REF, "line1"); underTest.visitAny(FILE); @@ -244,7 +244,7 @@ public class IntegrateIssuesVisitorTest { .setRuleKey("S001") .setSeverity(Constants.Severity.BLOCKER) .build(); - reportReader.putIssues(FILE_REF, asList(reportIssue)); + reportReader.putIssues(FILE_REF, singletonList(reportIssue)); fileSourceRepository.addLine(FILE_REF, "line1"); underTest.visitAny(FILE); @@ -311,7 +311,7 @@ public class IntegrateIssuesVisitorTest { assertThat(issues.get(0).isNew()).isFalse(); assertThat(issues.get(0).isCopied()).isTrue(); assertThat(issues.get(0).changes()).hasSize(1); - assertThat(issues.get(0).changes().get(0).diffs()).contains(entry(IssueFieldsSetter.FROM_BRANCH, new FieldDiffs.Diff("master", null))); + assertThat(issues.get(0).changes().get(0).diffs()).contains(entry(IssueFieldsSetter.FROM_BRANCH, new FieldDiffs.Diff<>("master", null))); } private void addBaseIssue(RuleKey ruleKey) { 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 1960677b6da..384ea59093b 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 @@ -31,6 +31,7 @@ import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.issue.FieldDiffs; import org.sonar.core.issue.IssueChangeContext; +import org.sonar.db.component.BranchType; import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbIssues; import org.sonar.server.issue.IssueFieldsSetter; @@ -38,12 +39,13 @@ import org.sonar.server.issue.workflow.IssueWorkflow; import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.groups.Tuple.tuple; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; @@ -59,18 +61,18 @@ public class IssueLifecycleTest { private static final Date DEFAULT_DATE = new Date(); private static final Duration DEFAULT_DURATION = Duration.create(10); - private DumbRule rule = new DumbRule(XOO_X1); + private final DumbRule rule = new DumbRule(XOO_X1); @Rule public RuleRepositoryRule ruleRepository = new RuleRepositoryRule().add(rule); @Rule public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); - private IssueChangeContext issueChangeContext = IssueChangeContext.createUser(DEFAULT_DATE, "default_user_uuid"); - private IssueWorkflow workflow = mock(IssueWorkflow.class); - private IssueFieldsSetter updater = mock(IssueFieldsSetter.class); - private DebtCalculator debtCalculator = mock(DebtCalculator.class); - private IssueLifecycle underTest = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, workflow, updater, debtCalculator, ruleRepository); + private final IssueChangeContext issueChangeContext = IssueChangeContext.createUser(DEFAULT_DATE, "default_user_uuid"); + private final IssueWorkflow workflow = mock(IssueWorkflow.class); + private final IssueFieldsSetter updater = mock(IssueFieldsSetter.class); + private final DebtCalculator debtCalculator = mock(DebtCalculator.class); + private final IssueLifecycle underTest = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, workflow, updater, debtCalculator, ruleRepository); @Test public void initNewOpenIssue() { @@ -161,6 +163,79 @@ public class IssueLifecycleTest { assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).newValue()).isEqualTo("master"); } + @Test + public void copyExistingIssuesFromSourceBranchOfPullRequest() { + String pullRequestKey = "1"; + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + when(branch.getName()).thenReturn("sourceBranch-1"); + when(branch.getPullRequestKey()).thenReturn(pullRequestKey); + analysisMetadataHolder.setBranch(branch); + analysisMetadataHolder.setPullRequestKey(pullRequestKey); + DefaultIssue raw = new DefaultIssue() + .setKey("raw"); + DefaultIssue fromShort = new DefaultIssue() + .setKey("short"); + fromShort.setResolution("resolution"); + fromShort.setStatus("status"); + + Date commentDate = new Date(); + fromShort.addComment(new DefaultIssueComment() + .setIssueKey("short") + .setCreatedAt(commentDate) + .setUserUuid("user_uuid") + .setMarkdownText("A comment")); + + Date diffDate = new Date(); + // file diff alone + fromShort.addChange(new FieldDiffs() + .setCreationDate(diffDate) + .setIssueKey("short") + .setUserUuid("user_uuid") + .setDiff("file", "uuidA1", "uuidB1")); + // file diff with another field + fromShort.addChange(new FieldDiffs() + .setCreationDate(diffDate) + .setIssueKey("short") + .setUserUuid("user_uuid") + .setDiff("severity", "MINOR", "MAJOR") + .setDiff("file", "uuidA2", "uuidB2")); + + underTest.copyExistingIssueFromSourceBranchToPullRequest(raw, fromShort); + + assertThat(raw.resolution()).isEqualTo("resolution"); + assertThat(raw.status()).isEqualTo("status"); + assertThat(raw.defaultIssueComments()) + .extracting(DefaultIssueComment::issueKey, DefaultIssueComment::createdAt, DefaultIssueComment::userUuid, DefaultIssueComment::markdownText) + .containsOnly(tuple("raw", commentDate, "user_uuid", "A comment")); + assertThat(raw.changes()).hasSize(2); + assertThat(raw.changes().get(0).creationDate()).isEqualTo(diffDate); + assertThat(raw.changes().get(0).userUuid()).isEqualTo("user_uuid"); + assertThat(raw.changes().get(0).issueKey()).isEqualTo("raw"); + assertThat(raw.changes().get(0).diffs()).containsOnlyKeys("severity"); + assertThat(raw.changes().get(1).userUuid()).isEqualTo("default_user_uuid"); + assertThat(raw.changes().get(1).diffs()).containsOnlyKeys(IssueFieldsSetter.FROM_BRANCH); + assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).oldValue()).isEqualTo("sourceBranch-1"); + assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).newValue()).isEqualTo("#1"); + } + + @Test + public void copyExistingIssuesFromSourceBranchOfPullRequest_only_works_for_pull_requests() { + DefaultIssue raw = new DefaultIssue() + .setKey("raw"); + DefaultIssue from = new DefaultIssue() + .setKey("short"); + from.setResolution("resolution"); + from.setStatus("status"); + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.BRANCH); + analysisMetadataHolder.setBranch(branch); + + assertThatThrownBy(() -> underTest.copyExistingIssueFromSourceBranchToPullRequest(raw, from)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("This operation should be done only on pull request analysis"); + } + @Test public void copiedIssue() { DefaultIssue raw = new DefaultIssue() @@ -222,7 +297,7 @@ public class IssueLifecycleTest { assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_BRANCH).oldValue()).isEqualTo("master"); assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_BRANCH).newValue()).isEqualTo("release-2.x"); - verifyZeroInteractions(updater); + verifyNoInteractions(updater); } @Test @@ -290,9 +365,9 @@ public class IssueLifecycleTest { assertThat(raw.isChanged()).isFalse(); assertThat(raw.changes()).hasSize(2); assertThat(raw.changes().get(0).diffs()) - .containsOnly(entry("foo", new FieldDiffs.Diff("bar", "donut"))); + .containsOnly(entry("foo", new FieldDiffs.Diff<>("bar", "donut"))); assertThat(raw.changes().get(1).diffs()) - .containsOnly(entry("file", new FieldDiffs.Diff("A", "B"))); + .containsOnly(entry("file", new FieldDiffs.Diff<>("A", "B"))); verify(updater).setPastSeverity(raw, BLOCKER, issueChangeContext); verify(updater).setPastLine(raw, 10); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegatorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegatorTest.java index cea47498535..3f89c4a572f 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegatorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegatorTest.java @@ -27,12 +27,13 @@ import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.analysis.Branch; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; import org.sonar.core.issue.tracking.Tracking; import org.sonar.db.component.BranchType; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; public class IssueTrackingDelegatorTest { @@ -48,6 +49,8 @@ public class IssueTrackingDelegatorTest { private Component component; @Mock private Tracking trackingResult; + @Mock + private Input rawInput; private IssueTrackingDelegator underTest; @@ -55,20 +58,20 @@ public class IssueTrackingDelegatorTest { public void setUp() { MockitoAnnotations.initMocks(this); underTest = new IssueTrackingDelegator(prBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder); - when(tracker.track(component)).thenReturn(trackingResult); - when(mergeBranchTracker.track(component)).thenReturn(trackingResult); - when(prBranchTracker.track(component)).thenReturn(trackingResult); + when(tracker.track(component, rawInput)).thenReturn(trackingResult); + when(mergeBranchTracker.track(component, rawInput)).thenReturn(trackingResult); + when(prBranchTracker.track(component, rawInput)).thenReturn(trackingResult); } @Test public void delegate_regular_tracker() { when(analysisMetadataHolder.getBranch()).thenReturn(mock(Branch.class)); - underTest.track(component); + underTest.track(component, rawInput); - verify(tracker).track(component); - verifyZeroInteractions(prBranchTracker); - verifyZeroInteractions(mergeBranchTracker); + verify(tracker).track(component, rawInput); + verifyNoInteractions(prBranchTracker); + verifyNoInteractions(mergeBranchTracker); } @Test @@ -79,11 +82,11 @@ public class IssueTrackingDelegatorTest { when(analysisMetadataHolder.getBranch()).thenReturn(branch); when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true); - underTest.track(component); + underTest.track(component, rawInput); - verify(mergeBranchTracker).track(component); - verifyZeroInteractions(tracker); - verifyZeroInteractions(prBranchTracker); + verify(mergeBranchTracker).track(component, rawInput); + verifyNoInteractions(tracker); + verifyNoInteractions(prBranchTracker); } @@ -94,10 +97,10 @@ public class IssueTrackingDelegatorTest { when(analysisMetadataHolder.getBranch()).thenReturn(mock(Branch.class)); when(analysisMetadataHolder.isPullRequest()).thenReturn(true); - underTest.track(component); + underTest.track(component, rawInput); - verify(prBranchTracker).track(component); - verifyZeroInteractions(tracker); - verifyZeroInteractions(mergeBranchTracker); + verify(prBranchTracker).track(component, rawInput); + verifyNoInteractions(tracker); + verifyNoInteractions(mergeBranchTracker); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMergerTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMergerTest.java new file mode 100644 index 00000000000..33ce21f012f --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMergerTest.java @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.Date; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.issue.Issue; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.BlockHashSequence; +import org.sonar.core.issue.tracking.Input; +import org.sonar.core.issue.tracking.LineHashSequence; +import org.sonar.core.issue.tracking.NonClosedTracking; +import org.sonar.core.issue.tracking.Tracker; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.rule.RuleDefinitionDto; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; +import static org.sonar.db.component.ComponentTesting.newFileDto; + +public class PullRequestSourceBranchMergerTest { + private static final String PROJECT_KEY = "project"; + private static final String PROJECT_UUID = "projectUuid"; + private static final int FILE_1_REF = 12341; + private static final String FILE_1_KEY = "fileKey"; + private static final String FILE_1_UUID = "fileUuid"; + private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder( + org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF) + .setKey(FILE_1_KEY) + .setUuid(FILE_1_UUID) + .build(); + + @Mock + private Tracker tracker; + + @Mock + private IssueLifecycle issueLifecycle; + + @Mock + private TrackerSourceBranchInputFactory sourceBranchInputFactory; + + @Mock + private NonClosedTracking prTracking; + + @Rule + public DbTester db = DbTester.create(); + + private PullRequestSourceBranchMerger underTest; + private RuleDefinitionDto rule; + private DefaultIssue rawIssue; + private Input rawIssuesInput; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + underTest = new PullRequestSourceBranchMerger( + tracker, + issueLifecycle, + sourceBranchInputFactory); + + ComponentDto projectDto = db.components().insertPublicProject(p -> p.setDbKey(PROJECT_KEY).setUuid(PROJECT_UUID)); + ComponentDto branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1") + .setBranchType(BranchType.PULL_REQUEST) + .setMergeBranchUuid(projectDto.uuid())); + ComponentDto branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2") + .setBranchType(BranchType.PULL_REQUEST) + .setMergeBranchUuid(projectDto.uuid())); + ComponentDto branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3") + .setBranchType(BranchType.PULL_REQUEST) + .setMergeBranchUuid(projectDto.uuid())); + db.components().insertComponent(newFileDto(branch1Dto).setDbKey(FILE_1_KEY + ":PULL_REQUEST:myBranch1")); + db.components().insertComponent(newFileDto(branch2Dto).setDbKey(FILE_1_KEY + ":PULL_REQUEST:myBranch2")); + db.components().insertComponent(newFileDto(branch3Dto).setDbKey(FILE_1_KEY + ":PULL_REQUEST:myBranch3")); + rule = db.rules().insert(); + rawIssue = createIssue("issue1", rule.getKey(), Issue.STATUS_OPEN, new Date()); + rawIssuesInput = new DefaultTrackingInput(singletonList(rawIssue), mock(LineHashSequence.class), mock(BlockHashSequence.class)); + } + + @Test + public void tryMergeIssuesFromSourceBranchOfPullRequest_does_nothing_if_source_branch_was_not_analyzed() { + when(sourceBranchInputFactory.hasSourceBranchAnalysis()).thenReturn(false); + + underTest.tryMergeIssuesFromSourceBranchOfPullRequest(FILE_1, rawIssuesInput.getIssues(), rawIssuesInput); + + verifyNoInteractions(issueLifecycle); + } + + @Test + public void tryMergeIssuesFromSourceBranchOfPullRequest_merges_issue_state_from_source_branch_into_pull_request() { + DefaultIssue sourceBranchIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_CONFIRMED, new Date()); + Input sourceBranchInput = new DefaultTrackingInput(singletonList(sourceBranchIssue), mock(LineHashSequence.class), mock(BlockHashSequence.class)); + when(sourceBranchInputFactory.hasSourceBranchAnalysis()).thenReturn(true); + when(sourceBranchInputFactory.createForSourceBranch(any())).thenReturn(sourceBranchInput); + when(tracker.trackNonClosed(any(), any())).thenReturn(prTracking); + when(prTracking.getMatchedRaws()).thenReturn(singletonMap(rawIssue, sourceBranchIssue)); + + underTest.tryMergeIssuesFromSourceBranchOfPullRequest(FILE_1, rawIssuesInput.getIssues(), rawIssuesInput); + + verify(issueLifecycle).copyExistingIssueFromSourceBranchToPullRequest(rawIssue, sourceBranchIssue); + } + + private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, Date creationDate) { + DefaultIssue issue = new DefaultIssue(); + issue.setKey(key); + issue.setRuleKey(ruleKey); + issue.setMessage("msg"); + issue.setLine(1); + issue.setStatus(status); + issue.setResolution(null); + issue.setCreationDate(creationDate); + issue.setChecksum("checksum"); + return issue; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecutionTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecutionTest.java index b98686c00bb..0f0f13015b8 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecutionTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecutionTest.java @@ -56,22 +56,19 @@ public class PullRequestTrackerExecutionTest { .setUuid(FILE_UUID) .build(); - private TrackerRawInputFactory rawFactory = mock(TrackerRawInputFactory.class); - private TrackerBaseInputFactory baseFactory = mock(TrackerBaseInputFactory.class); - private NewLinesRepository newLinesRepository = mock(NewLinesRepository.class); + private final TrackerBaseInputFactory baseFactory = mock(TrackerBaseInputFactory.class); + private final NewLinesRepository newLinesRepository = mock(NewLinesRepository.class); + private final List rawIssues = new ArrayList<>(); + private final List baseIssues = new ArrayList<>(); private PullRequestTrackerExecution underTest; - private List rawIssues = new ArrayList<>(); - private List baseIssues = new ArrayList<>(); - @Before public void setUp() { - when(rawFactory.create(FILE)).thenReturn(createInput(rawIssues)); when(baseFactory.create(FILE)).thenReturn(createInput(baseIssues)); Tracker tracker = new Tracker<>(); - underTest = new PullRequestTrackerExecution(baseFactory, rawFactory, tracker, newLinesRepository); + underTest = new PullRequestTrackerExecution(baseFactory, tracker, newLinesRepository); } @Test @@ -86,7 +83,7 @@ public class PullRequestTrackerExecutionTest { when(newLinesRepository.getNewLines(FILE)).thenReturn(Optional.of(new HashSet<>(Arrays.asList(1, 3)))); - Tracking tracking = underTest.track(FILE); + Tracking tracking = underTest.track(FILE, createInput(rawIssues)); assertThat(tracking.getUnmatchedBases()).isEmpty(); assertThat(tracking.getMatchedRaws()).isEmpty(); @@ -122,7 +119,7 @@ public class PullRequestTrackerExecutionTest { when(newLinesRepository.getNewLines(FILE)).thenReturn(Optional.of(new HashSet<>(Arrays.asList(7, 10)))); - Tracking tracking = underTest.track(FILE); + Tracking tracking = underTest.track(FILE, createInput(rawIssues)); assertThat(tracking.getUnmatchedBases()).isEmpty(); assertThat(tracking.getMatchedRaws()).isEmpty(); @@ -142,7 +139,7 @@ public class PullRequestTrackerExecutionTest { baseIssues.add(rawIssues.get(0)); - Tracking tracking = underTest.track(FILE); + Tracking tracking = underTest.track(FILE, createInput(rawIssues)); assertThat(tracking.getMatchedRaws()).isEqualTo(Collections.singletonMap(rawIssues.get(0), rawIssues.get(0))); assertThat(tracking.getUnmatchedRaws()).containsOnly(rawIssues.get(2)); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecutionTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecutionTest.java index 9fcdce94957..bf5f5b3dde2 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecutionTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecutionTest.java @@ -34,8 +34,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ReferenceBranchTrackerExecutionTest { - @Mock - private TrackerRawInputFactory rawInputFactory; @Mock private TrackerReferenceBranchInputFactory mergeInputFactory; @Mock @@ -48,7 +46,7 @@ public class ReferenceBranchTrackerExecutionTest { @Before public void before() { MockitoAnnotations.initMocks(this); - underTest = new ReferenceBranchTrackerExecution(rawInputFactory, mergeInputFactory, tracker); + underTest = new ReferenceBranchTrackerExecution(mergeInputFactory, tracker); } @Test @@ -56,10 +54,9 @@ public class ReferenceBranchTrackerExecutionTest { Input rawInput = mock(Input.class); Input mergeInput = mock(Input.class); NonClosedTracking result = mock(NonClosedTracking.class); - when(rawInputFactory.create(component)).thenReturn(rawInput); when(mergeInputFactory.create(component)).thenReturn(mergeInput); when(tracker.trackNonClosed(rawInput, mergeInput)).thenReturn(result); - assertThat(underTest.track(component)).isEqualTo(result); + assertThat(underTest.track(component, rawInput)).isEqualTo(result); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java index 7e60b731fa0..579735a4644 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java @@ -23,7 +23,6 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Date; -import javax.annotation.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -53,7 +52,7 @@ import org.sonar.db.user.UserDto; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; import static org.sonar.db.component.ComponentTesting.newFileDto; @@ -86,11 +85,11 @@ public class SiblingsIssueMergerTest { private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder( org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF) - .setKey(FILE_1_KEY) - .setUuid(FILE_1_UUID) - .build(); + .setKey(FILE_1_KEY) + .setUuid(FILE_1_UUID) + .build(); - private SimpleTracker tracker = new SimpleTracker<>(); + private final SimpleTracker tracker = new SimpleTracker<>(); private SiblingsIssueMerger copier; private ComponentDto fileOnBranch1Dto; private ComponentDto fileOnBranch2Dto; @@ -129,10 +128,10 @@ public class SiblingsIssueMergerTest { @Test public void do_nothing_if_no_match() { - DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, null, new Date()); + DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, new Date()); copier.tryMerge(FILE_1, Collections.singleton(i)); - verifyZeroInteractions(issueLifecycle); + verifyNoInteractions(issueLifecycle); } @Test @@ -140,13 +139,13 @@ public class SiblingsIssueMergerTest { db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum")); copier.tryMerge(FILE_1, Collections.emptyList()); - verifyZeroInteractions(issueLifecycle); + verifyNoInteractions(issueLifecycle); } @Test public void merge_confirmed_issues() { db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum")); - DefaultIssue newIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_OPEN, null, new Date()); + DefaultIssue newIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_OPEN, new Date()); copier.tryMerge(FILE_1, Collections.singleton(newIssue)); @@ -165,7 +164,7 @@ public class SiblingsIssueMergerTest { .setIssueUpdateDate(Date.from(now.plus(1, ChronoUnit.SECONDS)))); db.issues().insert(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum") .setIssueUpdateDate(Date.from(now))); - DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date()); + DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, new Date()); copier.tryMerge(FILE_1, Collections.singleton(newIssue)); @@ -182,7 +181,7 @@ public class SiblingsIssueMergerTest { .insert(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum")); db.issues().insertComment(issue, user, "A comment 2"); db.issues().insertFieldDiffs(issue, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date())); - DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date()); + DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, new Date()); copier.tryMerge(FILE_1, Collections.singleton(newIssue)); @@ -194,14 +193,14 @@ public class SiblingsIssueMergerTest { assertThat(issueToMerge.getValue().changes()).isNotEmpty(); } - private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, @Nullable String resolution, Date creationDate) { + private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, Date creationDate) { DefaultIssue issue = new DefaultIssue(); issue.setKey(key); issue.setRuleKey(ruleKey); issue.setMessage("msg"); issue.setLine(1); issue.setStatus(status); - issue.setResolution(resolution); + issue.setResolution(null); issue.setCreationDate(creationDate); issue.setChecksum("checksum"); return issue; diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java new file mode 100644 index 00000000000..7051aa09003 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java @@ -0,0 +1,121 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.analysis.Branch; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.protobuf.DbProjectBranches; +import org.sonar.server.project.Project; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.component.SnapshotTesting.newAnalysis; + +public class SourceBranchComponentUuidsTest { + + private static final String BRANCH_KEY = "branch1"; + private static final String PR_KEY = "pr1"; + + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + + @Rule + public DbTester db = DbTester.create(); + + private SourceBranchComponentUuids underTest; + private final Branch branch = mock(Branch.class); + private ComponentDto branch1; + private ComponentDto branch1File; + private ComponentDto pr1File; + + @Before + public void setup() { + underTest = new SourceBranchComponentUuids(analysisMetadataHolder, db.getDbClient()); + Project project = mock(Project.class); + analysisMetadataHolder.setProject(project); + analysisMetadataHolder.setBranch(branch); + + ComponentDto projectDto = db.components().insertPublicProject(); + when(project.getUuid()).thenReturn(projectDto.uuid()); + branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey(BRANCH_KEY)); + ComponentDto pr1branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(PR_KEY) + .setBranchType(BranchType.PULL_REQUEST) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch(BRANCH_KEY).build()) + .setMergeBranchUuid(projectDto.getMainBranchProjectUuid())); + branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File"); + pr1File = ComponentTesting.newFileDto(pr1branch, null, "file").setUuid("file1"); + db.components().insertComponents(branch1File, pr1File); + } + + @Test + public void should_support_db_key_when_looking_for_source_branch_component() { + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + when(branch.getName()).thenReturn(BRANCH_KEY); + when(branch.getPullRequestKey()).thenReturn(PR_KEY); + db.components().insertSnapshot(newAnalysis(branch1)); + + assertThat(underTest.getSourceBranchComponentUuid(pr1File.getDbKey())).isEqualTo(branch1File.uuid()); + assertThat(underTest.hasSourceBranchAnalysis()).isTrue(); + } + + @Test + public void should_support_key_when_looking_for_source_branch_component() { + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + when(branch.getName()).thenReturn(BRANCH_KEY); + when(branch.getPullRequestKey()).thenReturn(PR_KEY); + db.components().insertSnapshot(newAnalysis(branch1)); + + assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid()); + } + + @Test + public void return_null_if_file_doesnt_exist() { + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + when(branch.getName()).thenReturn(BRANCH_KEY); + when(branch.getPullRequestKey()).thenReturn(PR_KEY); + db.components().insertSnapshot(newAnalysis(branch1)); + + assertThat(underTest.getSourceBranchComponentUuid("doesnt exist")).isNull(); + } + + @Test + public void skip_init_if_not_a_pull_request() { + when(branch.getType()).thenReturn(BranchType.BRANCH); + when(branch.getName()).thenReturn(BRANCH_KEY); + + assertThat(underTest.getSourceBranchComponentUuid(pr1File.getDbKey())).isNull(); + } + + @Test + public void skip_init_if_no_source_branch_analysis() { + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + when(branch.getName()).thenReturn(BRANCH_KEY); + + assertThat(underTest.getSourceBranchComponentUuid(pr1File.getDbKey())).isNull(); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecutionTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecutionTest.java index aa50038c3ff..c7019f8f580 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecutionTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecutionTest.java @@ -45,25 +45,24 @@ import static org.mockito.Mockito.when; import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; public class TrackerExecutionTest { - private final TrackerRawInputFactory rawInputFactory = mock(TrackerRawInputFactory.class); private final TrackerBaseInputFactory baseInputFactory = mock(TrackerBaseInputFactory.class); private final ClosedIssuesInputFactory closedIssuesInputFactory = mock(ClosedIssuesInputFactory.class); private final Tracker tracker = mock(Tracker.class); private final ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class); private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class); - private TrackerExecution underTest = new TrackerExecution(baseInputFactory, rawInputFactory, closedIssuesInputFactory, tracker, componentIssuesLoader, analysisMetadataHolder); + private final TrackerExecution underTest = new TrackerExecution(baseInputFactory, closedIssuesInputFactory, tracker, componentIssuesLoader, + analysisMetadataHolder); - private Input rawInput = mock(Input.class); - private Input openIssuesInput = mock(Input.class); - private Input closedIssuesInput = mock(Input.class); - private NonClosedTracking nonClosedTracking = mock(NonClosedTracking.class); - private Tracking closedTracking = mock(Tracking.class); + private final Input rawInput = mock(Input.class); + private final Input openIssuesInput = mock(Input.class); + private final Input closedIssuesInput = mock(Input.class); + private final NonClosedTracking nonClosedTracking = mock(NonClosedTracking.class); + private final Tracking closedTracking = mock(Tracking.class); @Test public void track_tracks_only_nonClosed_issues_if_tracking_returns_complete_from_Tracker() { ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).build(); - when(rawInputFactory.create(component)).thenReturn(rawInput); when(baseInputFactory.create(component)).thenReturn(openIssuesInput); when(closedIssuesInputFactory.create(any())).thenThrow(new IllegalStateException("closedIssuesInputFactory should not be called")); when(nonClosedTracking.isComplete()).thenReturn(true); @@ -71,7 +70,7 @@ public class TrackerExecutionTest { when(tracker.trackNonClosed(rawInput, openIssuesInput)).thenReturn(nonClosedTracking); when(tracker.trackClosed(any(), any())).thenThrow(new IllegalStateException("trackClosed should not be called")); - Tracking tracking = underTest.track(component); + Tracking tracking = underTest.track(component, rawInput); assertThat(tracking).isSameAs(nonClosedTracking); verify(tracker).trackNonClosed(rawInput, openIssuesInput); @@ -81,7 +80,6 @@ public class TrackerExecutionTest { @Test public void track_does_not_track_nonClosed_issues_if_tracking_returns_incomplete_but_this_is_first_analysis() { ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).build(); - when(rawInputFactory.create(component)).thenReturn(rawInput); when(baseInputFactory.create(component)).thenReturn(openIssuesInput); when(closedIssuesInputFactory.create(any())).thenThrow(new IllegalStateException("closedIssuesInputFactory should not be called")); when(nonClosedTracking.isComplete()).thenReturn(false); @@ -89,7 +87,7 @@ public class TrackerExecutionTest { when(tracker.trackNonClosed(rawInput, openIssuesInput)).thenReturn(nonClosedTracking); when(tracker.trackClosed(any(), any())).thenThrow(new IllegalStateException("trackClosed should not be called")); - Tracking tracking = underTest.track(component); + Tracking tracking = underTest.track(component, rawInput); assertThat(tracking).isSameAs(nonClosedTracking); verify(tracker).trackNonClosed(rawInput, openIssuesInput); @@ -99,7 +97,6 @@ public class TrackerExecutionTest { @Test public void track_tracks_nonClosed_issues_and_then_closedOnes_if_tracking_returns_incomplete() { ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).build(); - when(rawInputFactory.create(component)).thenReturn(rawInput); when(baseInputFactory.create(component)).thenReturn(openIssuesInput); when(closedIssuesInputFactory.create(component)).thenReturn(closedIssuesInput); when(nonClosedTracking.isComplete()).thenReturn(false); @@ -107,7 +104,7 @@ public class TrackerExecutionTest { when(tracker.trackNonClosed(rawInput, openIssuesInput)).thenReturn(nonClosedTracking); when(tracker.trackClosed(nonClosedTracking, closedIssuesInput)).thenReturn(closedTracking); - Tracking tracking = underTest.track(component); + Tracking tracking = underTest.track(component, rawInput); assertThat(tracking).isSameAs(closedTracking); verify(tracker).trackNonClosed(rawInput, openIssuesInput); @@ -118,7 +115,6 @@ public class TrackerExecutionTest { @Test public void track_loadChanges_on_matched_closed_issues() { ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).build(); - when(rawInputFactory.create(component)).thenReturn(rawInput); when(baseInputFactory.create(component)).thenReturn(openIssuesInput); when(closedIssuesInputFactory.create(component)).thenReturn(closedIssuesInput); when(nonClosedTracking.isComplete()).thenReturn(false); @@ -134,7 +130,7 @@ public class TrackerExecutionTest { Collections.shuffle(mappedBaseIssues); when(closedTracking.getMatchedRaws()).thenReturn(mappedBaseIssues.stream().collect(uniqueIndex(i -> new DefaultIssue().setKey("raw_for_" + i.key()), i -> i))); - Tracking tracking = underTest.track(component); + Tracking tracking = underTest.track(component, rawInput); assertThat(tracking).isSameAs(closedTracking); verify(tracker).trackNonClosed(rawInput, openIssuesInput); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java new file mode 100644 index 00000000000..8820b1531a5 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.Collections; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TrackerSourceBranchInputFactoryTest { + private final static String COMPONENT_KEY = "file1"; + private final static String COMPONENT_UUID = "uuid1"; + + @Rule + public DbTester db = DbTester.create(); + + private final ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class); + private final SourceBranchComponentUuids sourceBranchComponentUuids = mock(SourceBranchComponentUuids.class); + private TrackerSourceBranchInputFactory underTest; + + @Before + public void setUp() { + underTest = new TrackerSourceBranchInputFactory(componentIssuesLoader, sourceBranchComponentUuids, db.getDbClient()); + } + + @Test + public void gets_issues_and_hashes_in_matching_component() { + DefaultIssue issue1 = new DefaultIssue(); + when(sourceBranchComponentUuids.getSourceBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID); + when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1)); + ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID); + db.fileSources().insertFileSource(fileDto, 3); + + Component component = mock(Component.class); + when(component.getDbKey()).thenReturn(COMPONENT_KEY); + when(component.getType()).thenReturn(Component.Type.FILE); + Input input = underTest.createForSourceBranch(component); + + assertThat(input.getIssues()).containsOnly(issue1); + assertThat(input.getLineHashSequence().length()).isEqualTo(3); + } + + @Test + public void get_issues_without_line_hashes() { + DefaultIssue issue1 = new DefaultIssue(); + when(sourceBranchComponentUuids.getSourceBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID); + when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1)); + ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID); + db.fileSources().insertFileSource(fileDto, 0); + + Component component = mock(Component.class); + when(component.getDbKey()).thenReturn(COMPONENT_KEY); + when(component.getType()).thenReturn(Component.Type.FILE); + Input input = underTest.createForSourceBranch(component); + + assertThat(input.getIssues()).containsOnly(issue1); + assertThat(input.getLineHashSequence().length()).isZero(); + } + + @Test + public void gets_nothing_when_there_is_no_matching_component() { + Component component = mock(Component.class); + when(component.getDbKey()).thenReturn(COMPONENT_KEY); + when(component.getType()).thenReturn(Component.Type.FILE); + Input input = underTest.createForSourceBranch(component); + + assertThat(input.getIssues()).isEmpty(); + assertThat(input.getLineHashSequence().length()).isZero(); + } + + @Test + public void hasSourceBranchAnalysis_returns_true_if_source_branch_of_pr_was_analysed() { + when(sourceBranchComponentUuids.hasSourceBranchAnalysis()).thenReturn(true); + + assertThat(underTest.hasSourceBranchAnalysis()).isTrue(); + } + + @Test + public void hasSourceBranchAnalysis_returns_false_if_no_source_branch_analysis() { + when(sourceBranchComponentUuids.hasSourceBranchAnalysis()).thenReturn(false); + + assertThat(underTest.hasSourceBranchAnalysis()).isFalse(); + } +} -- 2.39.5