3 * Copyright (C) 2009-2023 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.server.issue.index;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
28 import java.util.Optional;
29 import org.junit.Before;
30 import org.junit.Rule;
31 import org.junit.Test;
32 import org.mockito.ArgumentCaptor;
33 import org.sonar.api.utils.System2;
34 import org.sonar.api.utils.log.LogTester;
35 import org.sonar.api.utils.log.LoggerLevel;
36 import org.sonar.ce.queue.CeQueue;
37 import org.sonar.ce.queue.CeTaskSubmit;
38 import org.sonar.core.util.SequenceUuidFactory;
39 import org.sonar.core.util.UuidFactory;
40 import org.sonar.db.DbClient;
41 import org.sonar.db.DbTester;
42 import org.sonar.db.ce.CeActivityDto;
43 import org.sonar.db.ce.CeActivityDto.Status;
44 import org.sonar.db.ce.CeQueueDto;
45 import org.sonar.db.ce.CeTaskCharacteristicDto;
46 import org.sonar.db.component.BranchDto;
47 import org.sonar.db.component.SnapshotDto;
48 import org.sonar.db.project.ProjectDto;
50 import static org.assertj.core.api.Assertions.assertThat;
51 import static org.assertj.core.api.Assertions.assertThatCode;
52 import static org.assertj.core.api.Assertions.tuple;
53 import static org.mockito.ArgumentMatchers.anyCollection;
54 import static org.mockito.Mockito.mock;
55 import static org.mockito.Mockito.times;
56 import static org.mockito.Mockito.verify;
57 import static org.mockito.Mockito.when;
58 import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_TYPE_KEY;
59 import static org.sonar.db.ce.CeTaskTypes.BRANCH_ISSUE_SYNC;
60 import static org.sonar.db.ce.CeTaskTypes.REPORT;
61 import static org.sonar.db.component.BranchType.BRANCH;
62 import static org.sonar.db.component.BranchType.PULL_REQUEST;
63 import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
65 public class AsyncIssueIndexingImplTest {
68 public DbTester dbTester = DbTester.create(System2.INSTANCE);
70 public LogTester logTester = new LogTester();
72 private final DbClient dbClient = dbTester.getDbClient();
73 private final CeQueue ceQueue = mock(CeQueue.class);
74 private final UuidFactory uuidFactory = new SequenceUuidFactory();
76 private final AsyncIssueIndexingImpl underTest = new AsyncIssueIndexingImpl(ceQueue, dbClient);
79 public void before() {
80 when(ceQueue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(uuidFactory.create()));
84 public void triggerOnIndexCreation() {
85 BranchDto dto = new BranchDto()
86 .setBranchType(BRANCH)
88 .setUuid("branch_uuid")
89 .setProjectUuid("project_uuid");
90 dbClient.branchDao().insert(dbTester.getSession(), dto);
93 underTest.triggerOnIndexCreation();
95 Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), "branch_uuid");
96 assertThat(branch).isPresent();
97 assertThat(branch.get().isNeedIssueSync()).isTrue();
98 verify(ceQueue, times(1)).prepareSubmit();
99 verify(ceQueue, times(1)).massSubmit(anyCollection());
100 assertThat(logTester.logs(LoggerLevel.INFO))
101 .contains("1 branch found in need of issue sync.");
105 public void triggerForProject() {
106 ProjectDto projectDto = dbTester.components().insertPrivateProjectDto();
107 BranchDto dto = new BranchDto()
108 .setBranchType(BRANCH)
109 .setKey("branchName")
110 .setUuid("branch_uuid")
111 .setProjectUuid(projectDto.getUuid());
112 dbTester.components().insertProjectBranch(projectDto, dto);
114 underTest.triggerForProject(projectDto.getUuid());
116 Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), "branch_uuid");
117 assertThat(branch).isPresent();
118 assertThat(branch.get().isNeedIssueSync()).isTrue();
119 verify(ceQueue, times(2)).prepareSubmit();
120 verify(ceQueue, times(1)).massSubmit(anyCollection());
121 assertThat(logTester.logs(LoggerLevel.INFO))
122 .contains("2 branch(es) found in need of issue sync for project.");
126 public void triggerOnIndexCreation_no_branch() {
127 underTest.triggerOnIndexCreation();
129 assertThat(logTester.logs(LoggerLevel.INFO)).contains("0 branch found in need of issue sync.");
133 public void triggerForProject_no_branch() {
134 underTest.triggerForProject("some-random-uuid");
135 assertThat(logTester.logs(LoggerLevel.INFO)).contains("0 branch(es) found in need of issue sync for project.");
139 public void remove_existing_indexation_task() {
140 String reportTaskUuid = persistReportTasks();
142 CeQueueDto task = new CeQueueDto();
143 task.setUuid("uuid_2");
144 task.setTaskType(BRANCH_ISSUE_SYNC);
145 dbClient.ceQueueDao().insert(dbTester.getSession(), task);
146 CeActivityDto activityDto = new CeActivityDto(task);
147 activityDto.setStatus(Status.SUCCESS);
148 dbClient.ceActivityDao().insert(dbTester.getSession(), activityDto);
151 underTest.triggerOnIndexCreation();
153 assertCeTasks(reportTaskUuid);
154 assertThat(logTester.logs(LoggerLevel.INFO))
156 "1 pending indexation task found to be deleted...",
157 "1 completed indexation task found to be deleted...",
158 "Indexation task deletion complete.",
159 "Deleting tasks characteristics...",
160 "Tasks characteristics deletion complete.");
164 public void remove_existing_indexation_for_project_task() {
165 String reportTaskUuid = persistReportTasks();
167 ProjectDto projectDto = dbTester.components().insertPrivateProjectDto();
168 String branchUuid = "branch_uuid";
169 dbTester.components().insertProjectBranch(projectDto, b -> b.setBranchType(BRANCH).setUuid(branchUuid));
170 CeQueueDto mainBranchTask = new CeQueueDto().setUuid("uuid_2").setTaskType(BRANCH_ISSUE_SYNC)
171 .setMainComponentUuid(projectDto.getUuid()).setComponentUuid(projectDto.getUuid());
172 CeQueueDto branchTask = new CeQueueDto().setUuid("uuid_3").setTaskType(BRANCH_ISSUE_SYNC)
173 .setMainComponentUuid(projectDto.getUuid()).setComponentUuid(branchUuid);
174 dbClient.ceQueueDao().insert(dbTester.getSession(), mainBranchTask);
175 dbClient.ceQueueDao().insert(dbTester.getSession(), branchTask);
178 underTest.triggerForProject(projectDto.getUuid());
180 assertCeTasks(reportTaskUuid);
181 assertThat(logTester.logs(LoggerLevel.INFO))
183 "2 pending indexation task found to be deleted...",
184 "2 completed indexation task found to be deleted...",
185 "Indexation task deletion complete.",
186 "Deleting tasks characteristics...",
187 "Tasks characteristics deletion complete.",
188 "Tasks characteristics deletion complete.",
189 "2 branch(es) found in need of issue sync for project.");
193 public void order_by_last_analysis_date() {
194 BranchDto dto = new BranchDto()
195 .setBranchType(BRANCH)
197 .setUuid("branch_uuid1")
198 .setProjectUuid("project_uuid1");
199 dbClient.branchDao().insert(dbTester.getSession(), dto);
201 insertSnapshot("analysis_1", "project_uuid1", 1);
203 BranchDto dto2 = new BranchDto()
204 .setBranchType(BRANCH)
206 .setUuid("branch_uuid2")
207 .setProjectUuid("project_uuid2");
208 dbClient.branchDao().insert(dbTester.getSession(), dto2);
210 insertSnapshot("analysis_2", "project_uuid2", 2);
212 underTest.triggerOnIndexCreation();
214 verify(ceQueue, times(2)).prepareSubmit();
216 ArgumentCaptor<Collection<CeTaskSubmit>> captor = ArgumentCaptor.forClass(Collection.class);
218 verify(ceQueue, times(1)).massSubmit(captor.capture());
219 List<Collection<CeTaskSubmit>> captures = captor.getAllValues();
220 assertThat(captures).hasSize(1);
221 Collection<CeTaskSubmit> tasks = captures.get(0);
222 assertThat(tasks).hasSize(2);
224 .extracting(p -> p.getComponent().get().getUuid())
225 .containsExactly("branch_uuid2", "branch_uuid1");
227 assertThat(logTester.logs(LoggerLevel.INFO))
228 .contains("2 projects found in need of issue sync.");
232 public void characteristics_are_defined() {
233 BranchDto dto = new BranchDto()
234 .setBranchType(BRANCH)
236 .setUuid("branch_uuid1")
237 .setProjectUuid("project_uuid1");
238 dbClient.branchDao().insert(dbTester.getSession(), dto);
240 insertSnapshot("analysis_1", "project_uuid1", 1);
242 BranchDto dto2 = new BranchDto()
243 .setBranchType(PULL_REQUEST)
245 .setUuid("pr_uuid_1")
246 .setProjectUuid("project_uuid2");
247 dbClient.branchDao().insert(dbTester.getSession(), dto2);
249 insertSnapshot("analysis_2", "project_uuid2", 2);
251 underTest.triggerOnIndexCreation();
253 ArgumentCaptor<Collection<CeTaskSubmit>> captor = ArgumentCaptor.forClass(Collection.class);
254 verify(ceQueue, times(1)).massSubmit(captor.capture());
255 List<Collection<CeTaskSubmit>> captures = captor.getAllValues();
256 assertThat(captures).hasSize(1);
257 Collection<CeTaskSubmit> tasks = captures.get(0);
258 assertThat(tasks).hasSize(2);
261 .extracting(p -> p.getCharacteristics().get(BRANCH_TYPE_KEY),
262 p -> p.getCharacteristics().get(CeTaskCharacteristicDto.BRANCH_KEY),
263 p -> p.getCharacteristics().get(CeTaskCharacteristicDto.PULL_REQUEST))
264 .containsExactlyInAnyOrder(
265 tuple("BRANCH", "branch_1", null),
266 tuple("PULL_REQUEST", null, "pr_1"));
270 public void verify_comparator_transitivity() {
271 Map<String, SnapshotDto> map = new HashMap<>();
272 map.put("A", new SnapshotDto().setCreatedAt(1L));
273 map.put("B", new SnapshotDto().setCreatedAt(2L));
274 map.put("C", new SnapshotDto().setCreatedAt(-1L));
275 List<String> uuids = new ArrayList<>(map.keySet());
277 Comparators.verifyTransitivity(AsyncIssueIndexingImpl.compareBySnapshot(map), uuids);
281 public void trigger_with_lot_of_not_analyzed_project_should_not_raise_exception() {
282 for (int i = 0; i < 100; i++) {
283 BranchDto dto = new BranchDto()
284 .setBranchType(BRANCH)
285 .setKey("branch_" + i)
286 .setUuid("branch_uuid" + i)
287 .setProjectUuid("project_uuid" + i);
288 dbClient.branchDao().insert(dbTester.getSession(), dto);
290 insertSnapshot("analysis_" + i, "project_uuid" + i, 1);
293 for (int i = 100; i < 200; i++) {
294 BranchDto dto = new BranchDto()
295 .setBranchType(BRANCH)
296 .setKey("branch_" + i)
297 .setUuid("branch_uuid" + i)
298 .setProjectUuid("project_uuid" + i);
299 dbClient.branchDao().insert(dbTester.getSession(), dto);
303 assertThatCode(underTest::triggerOnIndexCreation).doesNotThrowAnyException();
306 private SnapshotDto insertSnapshot(String analysisUuid, String projectUuid, long createdAt) {
307 SnapshotDto snapshot = new SnapshotDto()
308 .setUuid(analysisUuid)
309 .setComponentUuid(projectUuid)
310 .setStatus(STATUS_PROCESSED)
311 .setCreatedAt(createdAt)
313 dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), snapshot);
318 private String persistReportTasks() {
319 CeQueueDto reportTask = new CeQueueDto();
320 reportTask.setUuid("uuid_1");
321 reportTask.setTaskType(REPORT);
322 dbClient.ceQueueDao().insert(dbTester.getSession(), reportTask);
324 CeActivityDto reportActivity = new CeActivityDto(reportTask);
325 reportActivity.setStatus(Status.SUCCESS);
326 dbClient.ceActivityDao().insert(dbTester.getSession(), reportActivity);
327 return reportTask.getUuid();
330 private void assertCeTasks(String reportTaskUuid) {
331 assertThat(dbClient.ceQueueDao().selectAllInAscOrder(dbTester.getSession())).extracting("uuid")
332 .containsExactly(reportTaskUuid);
333 assertThat(dbClient.ceActivityDao().selectByTaskType(dbTester.getSession(), BRANCH_ISSUE_SYNC)).isEmpty();
334 assertThat(dbClient.ceActivityDao().selectByTaskType(dbTester.getSession(), REPORT)).hasSize(1);
335 assertThat(dbClient.ceTaskCharacteristicsDao().selectByTaskUuids(dbTester.getSession(), new HashSet<>(List.of("uuid_2")))).isEmpty();