3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.ce.task.projectanalysis.issue;
22 import java.time.Instant;
23 import java.time.temporal.ChronoUnit;
24 import java.util.Collections;
25 import java.util.Date;
26 import javax.annotation.Nullable;
27 import org.junit.Before;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.mockito.ArgumentCaptor;
31 import org.mockito.Mock;
32 import org.mockito.MockitoAnnotations;
33 import org.sonar.api.config.internal.MapSettings;
34 import org.sonar.api.issue.Issue;
35 import org.sonar.api.rule.RuleKey;
36 import org.sonar.api.utils.System2;
37 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
38 import org.sonar.ce.task.projectanalysis.analysis.Branch;
39 import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues;
40 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
41 import org.sonar.core.issue.DefaultIssue;
42 import org.sonar.core.issue.FieldDiffs;
43 import org.sonar.core.issue.tracking.SimpleTracker;
44 import org.sonar.db.DbClient;
45 import org.sonar.db.DbTester;
46 import org.sonar.db.component.BranchType;
47 import org.sonar.db.component.ComponentDto;
48 import org.sonar.db.component.KeyType;
49 import org.sonar.db.issue.IssueDto;
50 import org.sonar.db.issue.IssueTesting;
51 import org.sonar.db.rule.RuleDefinitionDto;
52 import org.sonar.db.user.UserDto;
54 import static org.assertj.core.api.Assertions.assertThat;
55 import static org.mockito.ArgumentMatchers.eq;
56 import static org.mockito.Mockito.verify;
57 import static org.mockito.Mockito.verifyZeroInteractions;
58 import static org.mockito.Mockito.when;
59 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
60 import static org.sonar.db.component.ComponentTesting.newFileDto;
62 public class SiblingsIssueMergerTest {
64 private IssueLifecycle issueLifecycle;
67 private Branch branch;
70 public DbTester db = DbTester.create();
73 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
74 .setRoot(builder(org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT, PROJECT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
79 public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();
81 private static final String PROJECT_KEY = "project";
82 private static final int PROJECT_REF = 1;
83 private static final String PROJECT_UUID = "projectUuid";
84 private static final int FILE_1_REF = 12341;
85 private static final String FILE_1_KEY = "fileKey";
86 private static final String FILE_1_UUID = "fileUuid";
88 private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder(
89 org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF)
94 private SimpleTracker<DefaultIssue, SiblingIssue> tracker = new SimpleTracker<>();
95 private SiblingsIssueMerger copier;
96 private ComponentDto fileOnBranch1Dto;
97 private ComponentDto fileOnBranch2Dto;
98 private ComponentDto fileOnBranch3Dto;
99 private ComponentDto projectDto;
100 private ComponentDto branch1Dto;
101 private ComponentDto branch2Dto;
102 private ComponentDto branch3Dto;
103 private RuleDefinitionDto rule;
106 public void setUp() {
107 MockitoAnnotations.initMocks(this);
108 DbClient dbClient = db.getDbClient();
109 ComponentIssuesLoader componentIssuesLoader = new ComponentIssuesLoader(dbClient, null, null, new MapSettings().asConfig(), System2.INSTANCE);
110 copier = new SiblingsIssueMerger(new SiblingsIssuesLoader(new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, dbClient), dbClient, componentIssuesLoader),
113 projectDto = db.components().insertMainBranch(p -> p.setDbKey(PROJECT_KEY).setUuid(PROJECT_UUID));
114 branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1")
115 .setBranchType(BranchType.SHORT)
116 .setMergeBranchUuid(projectDto.uuid()));
117 branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2")
118 .setBranchType(BranchType.SHORT)
119 .setMergeBranchUuid(projectDto.uuid()));
120 branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3")
121 .setBranchType(BranchType.SHORT)
122 .setMergeBranchUuid(projectDto.uuid()));
123 fileOnBranch1Dto = db.components().insertComponent(newFileDto(branch1Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch1"));
124 fileOnBranch2Dto = db.components().insertComponent(newFileDto(branch2Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch2"));
125 fileOnBranch3Dto = db.components().insertComponent(newFileDto(branch3Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch3"));
126 rule = db.rules().insert();
127 when(branch.getMergeBranchUuid()).thenReturn(projectDto.uuid());
128 metadataHolder.setBranch(branch);
132 public void do_nothing_if_no_match() {
133 DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, null, new Date());
134 copier.tryMerge(FILE_1, Collections.singleton(i));
136 verifyZeroInteractions(issueLifecycle);
140 public void do_nothing_if_no_new_issue() {
141 db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
142 copier.tryMerge(FILE_1, Collections.emptyList());
144 verifyZeroInteractions(issueLifecycle);
148 public void merge_confirmed_issues() {
149 db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
150 DefaultIssue newIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
152 copier.tryMerge(FILE_1, Collections.singleton(newIssue));
154 ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
155 verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranchOrPr(eq(newIssue), issueToMerge.capture(), eq(KeyType.BRANCH), eq("myBranch1"));
157 assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
161 public void prefer_more_recently_updated_issues() {
162 Instant now = Instant.now();
163 db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum")
164 .setIssueUpdateDate(Date.from(now.plus(2, ChronoUnit.SECONDS))));
165 db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
166 .setIssueUpdateDate(Date.from(now.plus(1, ChronoUnit.SECONDS))));
167 db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
168 .setIssueUpdateDate(Date.from(now)));
169 DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
171 copier.tryMerge(FILE_1, Collections.singleton(newIssue));
173 ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
174 verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranchOrPr(eq(newIssue), issueToMerge.capture(), eq(KeyType.BRANCH), eq("myBranch1"));
176 assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
180 public void lazy_load_changes() {
181 UserDto user = db.users().insertUser();
182 IssueDto issue = db.issues()
183 .insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
184 db.issues().insertComment(issue, user, "A comment 2");
185 db.issues().insertFieldDiffs(issue, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date()));
186 DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
188 copier.tryMerge(FILE_1, Collections.singleton(newIssue));
190 ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
191 verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranchOrPr(eq(newIssue), issueToMerge.capture(), eq(KeyType.BRANCH), eq("myBranch2"));
193 assertThat(issueToMerge.getValue().key()).isEqualTo("issue");
194 assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty();
195 assertThat(issueToMerge.getValue().changes()).isNotEmpty();
198 private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, @Nullable String resolution, Date creationDate) {
199 DefaultIssue issue = new DefaultIssue();
201 issue.setRuleKey(ruleKey);
202 issue.setMessage("msg");
204 issue.setStatus(status);
205 issue.setResolution(resolution);
206 issue.setCreationDate(creationDate);
207 issue.setChecksum("checksum");