]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14258 Pull requests inherit issue state from the source branch
authorMichal Duda <michal.duda@sonarsource.com>
Thu, 14 Jan 2021 14:38:52 +0000 (15:38 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 21 Jan 2021 20:30:29 +0000 (20:30 +0000)
21 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMerger.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecution.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecution.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuids.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecution.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactory.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueTrackingDelegatorTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestSourceBranchMergerTest.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/PullRequestTrackerExecutionTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ReferenceBranchTrackerExecutionTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerExecutionTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java [new file with mode: 0644]

index c3ba335000410988cd5537ffdf514845a2be9691..52637abdf53c40bac103575dae09cc01c3f64c85 100644 (file)
@@ -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,
index 7e85f5a204e689347322b3a0fc39e73c4627cb14..3739b26c6edf0c7f2761892968a04feed5ffb010 100644 (file)
@@ -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<DefaultIssue> cacheAppender = protoIssueCache.newAppender()) {
       issueVisitors.beforeComponent(component);
-      TrackingResult tracking = issueTracking.track(component);
-      fillNewOpenIssues(component, tracking.newIssues(), cacheAppender);
+      Input<DefaultIssue> 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<DefaultIssue> newIssues, CacheAppender<DefaultIssue> cacheAppender) {
+  private void fillNewOpenIssues(Component component, Stream<DefaultIssue> newIssues, Input<DefaultIssue> rawInput, CacheAppender<DefaultIssue> cacheAppender) {
     List<DefaultIssue> 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) {
index a92a0ac98eef2bde055912f58cb308d219ce1b1a..b395611cdd9fa36726f1cd72e8b6179fe7725609 100644 (file)
@@ -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()) {
index 4d5fee969ec0f8d6db41c36ad244e4f561a41ef5..266d489aa7257035e7307c26dafb4fd2ee25f58b 100644 (file)
@@ -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<DefaultIssue> rawInput) {
     if (analysisMetadataHolder.isPullRequest()) {
-      return standardResult(pullRequestTracker.track(component));
+      return standardResult(pullRequestTracker.track(component, rawInput));
     } else if (isFirstAnalysisSecondaryBranch()) {
-      Tracking<DefaultIssue, DefaultIssue> tracking = referenceBranchTracker.track(component);
+      Tracking<DefaultIssue, DefaultIssue> 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 (file)
index 0000000..40f9421
--- /dev/null
@@ -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<DefaultIssue, DefaultIssue> tracker;
+  private final IssueLifecycle issueLifecycle;
+  private final TrackerSourceBranchInputFactory sourceBranchInputFactory;
+
+  public PullRequestSourceBranchMerger(Tracker<DefaultIssue, DefaultIssue> tracker, IssueLifecycle issueLifecycle,
+    TrackerSourceBranchInputFactory sourceBranchInputFactory) {
+    this.tracker = tracker;
+    this.issueLifecycle = issueLifecycle;
+    this.sourceBranchInputFactory = sourceBranchInputFactory;
+  }
+
+  public void tryMergeIssuesFromSourceBranchOfPullRequest(Component component, Collection<DefaultIssue> newIssues, Input<DefaultIssue> rawInput) {
+    if (sourceBranchInputFactory.hasSourceBranchAnalysis()) {
+      Input<DefaultIssue> sourceBranchInput = sourceBranchInputFactory.createForSourceBranch(component);
+      DefaultTrackingInput rawPrTrackingInput = new DefaultTrackingInput(newIssues, rawInput.getLineHashSequence(), rawInput.getBlockHashSequence());
+      Tracking<DefaultIssue, DefaultIssue> prTracking = tracker.trackNonClosed(rawPrTrackingInput, sourceBranchInput);
+      for (Map.Entry<DefaultIssue, DefaultIssue> pair : prTracking.getMatchedRaws().entrySet()) {
+        issueLifecycle.copyExistingIssueFromSourceBranchToPullRequest(pair.getKey(), pair.getValue());
+      }
+    }
+  }
+}
index 257e40342edfcbeea72ac2802ecda82fe361d4f6..eb9dbfe9e3b5099ce9e12fcca5dd0e09dcbf414d 100644 (file)
@@ -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<DefaultIssue, DefaultIssue> tracker;
   private final NewLinesRepository newLinesRepository;
 
-  public PullRequestTrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, Tracker<DefaultIssue, DefaultIssue> tracker,
+  public PullRequestTrackerExecution(TrackerBaseInputFactory baseInputFactory, Tracker<DefaultIssue, DefaultIssue> tracker,
     NewLinesRepository newLinesRepository) {
     this.baseInputFactory = baseInputFactory;
-    this.rawInputFactory = rawInputFactory;
     this.tracker = tracker;
     this.newLinesRepository = newLinesRepository;
   }
 
-  public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
-    Input<DefaultIssue> rawInput = rawInputFactory.create(component);
+  public Tracking<DefaultIssue, DefaultIssue> track(Component component, Input<DefaultIssue> rawInput) {
     Input<DefaultIssue> previousAnalysisInput = baseInputFactory.create(component);
 
     // Step 1: only keep issues on changed lines
index 460bfc605aeb7413729d112ed4ea17dacb5217cc..080f3ed704c334114a0365a6ed8f536cf5436b8e 100644 (file)
@@ -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<DefaultIssue, DefaultIssue> tracker;
 
-  public ReferenceBranchTrackerExecution(TrackerRawInputFactory rawInputFactory, TrackerReferenceBranchInputFactory referenceBranchInputFactory,
+  public ReferenceBranchTrackerExecution(TrackerReferenceBranchInputFactory referenceBranchInputFactory,
     Tracker<DefaultIssue, DefaultIssue> tracker) {
-    this.rawInputFactory = rawInputFactory;
     this.referenceBranchInputFactory = referenceBranchInputFactory;
     this.tracker = tracker;
   }
 
-  public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
-    return tracker.trackNonClosed(rawInputFactory.create(component), referenceBranchInputFactory.create(component));
+  public Tracking<DefaultIssue, DefaultIssue> track(Component component, Input<DefaultIssue> 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 (file)
index 0000000..13f442a
--- /dev/null
@@ -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<String, String> 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<BranchDto> 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<ComponentDto> 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);
+  }
+}
index 0199f83ce626ac740122beef6ac3cfade5e58637..b5702e1940950a1a53a0e60f2846e2be722f0e36 100644 (file)
@@ -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<DefaultIssue, DefaultIssue> tracker;
   private final ComponentIssuesLoader componentIssuesLoader;
   private final AnalysisMetadataHolder analysisMetadataHolder;
 
-  public TrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory,
+  public TrackerExecution(TrackerBaseInputFactory baseInputFactory,
     ClosedIssuesInputFactory closedIssuesInputFactory, Tracker<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> track(Component component) {
-    Input<DefaultIssue> rawInput = rawInputFactory.create(component);
+  public Tracking<DefaultIssue, DefaultIssue> track(Component component, Input<DefaultIssue> rawInput) {
     Input<DefaultIssue> openBaseIssuesInput = baseInputFactory.create(component);
     NonClosedTracking<DefaultIssue, DefaultIssue> openIssueTracking = tracker.trackNonClosed(rawInput, openBaseIssuesInput);
     if (openIssueTracking.isComplete() || analysisMetadataHolder.isFirstAnalysis()) {
index ded92f5935ba4a1d3d38448f3fca96e91139513b..81fe3370f42709c81db8afb539e373ed92d1b205 100644 (file)
@@ -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 (file)
index 0000000..eaf528c
--- /dev/null
@@ -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<DefaultIssue> createForSourceBranch(Component component) {
+    String sourceBranchComponentUuid = sourceBranchComponentUuids.getSourceBranchComponentUuid(component.getDbKey());
+    return new SourceBranchLazyInput(component.getType(), sourceBranchComponentUuid);
+  }
+
+  private class SourceBranchLazyInput extends LazyInput<DefaultIssue> {
+    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<String> hashes = dbClient.fileSourceDao().selectLineHashes(session, sourceBranchComponentUuid);
+        if (hashes == null || hashes.isEmpty()) {
+          return EMPTY_LINE_HASH_SEQUENCE;
+        }
+        return new LineHashSequence(hashes);
+      }
+    }
+
+    @Override
+    protected List<DefaultIssue> loadIssues() {
+      if (sourceBranchComponentUuid == null) {
+        return Collections.emptyList();
+      }
+      return componentIssuesLoader.loadOpenIssuesWithChanges(sourceBranchComponentUuid);
+    }
+  }
+
+}
index 33654165338067a7373a8a1cb41946095ea83631..618e7ab7a0f4a44954f923717b1f0340502ee2e2 100644 (file)
@@ -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<DefaultIssue> 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) {
index 1960677b6da7c2a4dd8aa097a5603fb6749ac0c0..384ea59093be6214d911f01d64741d0efc86d33d 100644 (file)
@@ -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);
index cea47498535fe4ebdc89934adc21ee97e59b475c..3f89c4a572f0f1396ab236e56f10506c7b5f1967 100644 (file)
@@ -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<DefaultIssue, DefaultIssue> trackingResult;
+  @Mock
+  private Input<DefaultIssue> 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 (file)
index 0000000..33ce21f
--- /dev/null
@@ -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<DefaultIssue, DefaultIssue> tracker;
+
+  @Mock
+  private IssueLifecycle issueLifecycle;
+
+  @Mock
+  private TrackerSourceBranchInputFactory sourceBranchInputFactory;
+
+  @Mock
+  private NonClosedTracking<DefaultIssue, DefaultIssue> prTracking;
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private PullRequestSourceBranchMerger underTest;
+  private RuleDefinitionDto rule;
+  private DefaultIssue rawIssue;
+  private Input<DefaultIssue> 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<DefaultIssue> 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;
+  }
+}
index b98686c00bb5e664d9daeba41ebeb9d4bef6a962..0f0f13015b8463489388f815314dc8ae9c2eadc9 100644 (file)
@@ -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<DefaultIssue> rawIssues = new ArrayList<>();
+  private final List<DefaultIssue> baseIssues = new ArrayList<>();
 
   private PullRequestTrackerExecution underTest;
 
-  private List<DefaultIssue> rawIssues = new ArrayList<>();
-  private List<DefaultIssue> baseIssues = new ArrayList<>();
-
   @Before
   public void setUp() {
-    when(rawFactory.create(FILE)).thenReturn(createInput(rawIssues));
     when(baseFactory.create(FILE)).thenReturn(createInput(baseIssues));
 
     Tracker<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> tracking = underTest.track(FILE);
+    Tracking<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> tracking = underTest.track(FILE);
+    Tracking<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> tracking = underTest.track(FILE);
+    Tracking<DefaultIssue, DefaultIssue> 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));
   }
index 9fcdce949571d67c87b723b41d7d9955bc971f1a..bf5f5b3dde2f0c9aab4db69fba5f8e40ae4870cd 100644 (file)
@@ -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<DefaultIssue> rawInput = mock(Input.class);
     Input<DefaultIssue> mergeInput = mock(Input.class);
     NonClosedTracking<DefaultIssue, DefaultIssue> 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);
   }
 }
index 7e60b731fa05bc108336841f6a8db6f7612e3ee5..579735a4644a8950ad5316243ae1514551e4d719 100644 (file)
@@ -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<DefaultIssue, SiblingIssue> tracker = new SimpleTracker<>();
+  private final SimpleTracker<DefaultIssue, SiblingIssue> 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 (file)
index 0000000..7051aa0
--- /dev/null
@@ -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();
+  }
+}
index aa50038c3ff9888944f2fc3470bfbf2c9b7872d3..c7019f8f580feeed9dc7eee0c94af76cfb8908c5 100644 (file)
@@ -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<DefaultIssue, DefaultIssue> 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<DefaultIssue> rawInput = mock(Input.class);
-  private Input<DefaultIssue> openIssuesInput = mock(Input.class);
-  private Input<DefaultIssue> closedIssuesInput = mock(Input.class);
-  private NonClosedTracking<DefaultIssue, DefaultIssue> nonClosedTracking = mock(NonClosedTracking.class);
-  private Tracking<DefaultIssue, DefaultIssue> closedTracking = mock(Tracking.class);
+  private final Input<DefaultIssue> rawInput = mock(Input.class);
+  private final Input<DefaultIssue> openIssuesInput = mock(Input.class);
+  private final Input<DefaultIssue> closedIssuesInput = mock(Input.class);
+  private final NonClosedTracking<DefaultIssue, DefaultIssue> nonClosedTracking = mock(NonClosedTracking.class);
+  private final Tracking<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> tracking = underTest.track(component);
+    Tracking<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> tracking = underTest.track(component);
+    Tracking<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> tracking = underTest.track(component);
+    Tracking<DefaultIssue, DefaultIssue> 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<DefaultIssue, DefaultIssue> tracking = underTest.track(component);
+    Tracking<DefaultIssue, DefaultIssue> 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 (file)
index 0000000..8820b15
--- /dev/null
@@ -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<DefaultIssue> 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<DefaultIssue> 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<DefaultIssue> 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();
+  }
+}