3 * Copyright (C) 2009-2024 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.api.posttask;
22 import com.google.common.collect.ImmutableMap;
23 import com.tngtech.java.junit.dataprovider.DataProvider;
24 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
25 import com.tngtech.java.junit.dataprovider.UseDataProvider;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.Random;
31 import javax.annotation.Nullable;
32 import org.apache.commons.lang.RandomStringUtils;
33 import org.junit.Before;
34 import org.junit.Rule;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.mockito.ArgumentCaptor;
38 import org.mockito.InOrder;
39 import org.slf4j.event.Level;
40 import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
41 import org.sonar.api.ce.posttask.Project;
42 import org.sonar.api.testfixtures.log.LogTester;
43 import org.sonar.ce.task.CeTask;
44 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
45 import org.sonar.ce.task.projectanalysis.analysis.Branch;
46 import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
47 import org.sonar.ce.task.projectanalysis.metric.Metric;
48 import org.sonar.ce.task.projectanalysis.qualitygate.Condition;
49 import org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus;
50 import org.sonar.ce.task.projectanalysis.qualitygate.MutableQualityGateHolderRule;
51 import org.sonar.ce.task.projectanalysis.qualitygate.MutableQualityGateStatusHolderRule;
52 import org.sonar.ce.task.projectanalysis.qualitygate.QualityGate;
53 import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus;
54 import org.sonar.db.component.BranchType;
55 import org.sonar.scanner.protocol.output.ScannerReport;
57 import static com.google.common.collect.ImmutableList.of;
58 import static java.util.Arrays.asList;
59 import static java.util.stream.Collectors.toList;
60 import static org.assertj.core.api.Assertions.assertThat;
61 import static org.assertj.core.api.ThrowableAssert.catchThrowable;
62 import static org.assertj.core.data.MapEntry.entry;
63 import static org.mockito.ArgumentMatchers.any;
64 import static org.mockito.Mockito.doAnswer;
65 import static org.mockito.Mockito.doThrow;
66 import static org.mockito.Mockito.inOrder;
67 import static org.mockito.Mockito.mock;
68 import static org.mockito.Mockito.verify;
69 import static org.mockito.Mockito.when;
71 @RunWith(DataProviderRunner.class)
72 public class PostProjectAnalysisTasksExecutorTest {
73 private static final String QUALITY_GATE_UUID = "98451";
74 private static final String QUALITY_GATE_NAME = "qualityGate name";
75 private static final Condition CONDITION_1 = createCondition("metric key 1");
76 private static final Condition CONDITION_2 = createCondition("metric key 2");
79 public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
81 public MutableQualityGateHolderRule qualityGateHolder = new MutableQualityGateHolderRule();
83 public MutableQualityGateStatusHolderRule qualityGateStatusHolder = new MutableQualityGateStatusHolderRule();
85 public BatchReportReaderRule reportReader = new BatchReportReaderRule();
87 public LogTester logTester = new LogTester();
89 private final ArgumentCaptor<PostProjectAnalysisTask.Context> taskContextCaptor = ArgumentCaptor.forClass(PostProjectAnalysisTask.Context.class);
90 private final CeTask.Component component = new CeTask.Component("component uuid", "component key", "component name");
91 private final CeTask.Component entity = new CeTask.Component("entity uuid", "component key", "component name");
92 private final CeTask ceTask = new CeTask.Builder()
95 .setComponent(component)
98 private final PostProjectAnalysisTask postProjectAnalysisTask = newPostProjectAnalysisTask("PT1");
99 private final PostProjectAnalysisTasksExecutor underTest = new PostProjectAnalysisTasksExecutor(
100 ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder,
101 reportReader, new PostProjectAnalysisTask[] {postProjectAnalysisTask});
104 public void setUp() {
105 qualityGateHolder.setQualityGate(new QualityGate(QUALITY_GATE_UUID, QUALITY_GATE_NAME, of(CONDITION_1, CONDITION_2)));
106 qualityGateStatusHolder.setStatus(QualityGateStatus.OK, ImmutableMap.of(
107 CONDITION_1, ConditionStatus.create(ConditionStatus.EvaluationStatus.OK, "value"),
108 CONDITION_2, ConditionStatus.NO_VALUE_STATUS));
109 Branch branch = mock(Branch.class);
110 when(branch.getType()).thenReturn(BranchType.BRANCH);
111 analysisMetadataHolder
114 reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
118 @UseDataProvider("booleanValues")
119 public void does_not_fail_when_there_is_no_PostProjectAnalysisTasksExecutor(boolean allStepsExecuted) {
120 new PostProjectAnalysisTasksExecutor(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader, null)
121 .finished(allStepsExecuted);
125 @UseDataProvider("booleanValues")
126 public void finished_calls_all_PostProjectAnalysisTask_in_order_of_the_array_and_passes_the_same_object_to_all(boolean allStepsExecuted) {
127 PostProjectAnalysisTask postProjectAnalysisTask1 = newPostProjectAnalysisTask("PT1");
128 PostProjectAnalysisTask postProjectAnalysisTask2 = newPostProjectAnalysisTask("PT2");
129 InOrder inOrder = inOrder(postProjectAnalysisTask1, postProjectAnalysisTask2);
131 new PostProjectAnalysisTasksExecutor(
132 ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader,
133 new PostProjectAnalysisTask[] {postProjectAnalysisTask1, postProjectAnalysisTask2})
134 .finished(allStepsExecuted);
136 inOrder.verify(postProjectAnalysisTask1).finished(taskContextCaptor.capture());
137 inOrder.verify(postProjectAnalysisTask1).getDescription();
138 inOrder.verify(postProjectAnalysisTask2).finished(taskContextCaptor.capture());
139 inOrder.verify(postProjectAnalysisTask2).getDescription();
140 inOrder.verifyNoMoreInteractions();
142 ArgumentCaptor<PostProjectAnalysisTask.Context> taskContextCaptor = this.taskContextCaptor;
143 List<PostProjectAnalysisTask.ProjectAnalysis> allValues = getAllProjectAnalyses(taskContextCaptor);
144 assertThat(allValues).hasSize(2);
145 assertThat(allValues.get(0)).isSameAs(allValues.get(1));
147 assertThat(logTester.logs()).hasSize(2);
148 List<String> logs = logTester.logs(Level.INFO);
149 assertThat(logs).hasSize(2);
150 assertThat(logs.get(0)).matches("^PT1 \\| status=SUCCESS \\| time=\\d+ms$");
151 assertThat(logs.get(1)).matches("^PT2 \\| status=SUCCESS \\| time=\\d+ms$");
155 @UseDataProvider("booleanValues")
156 public void organization_is_null(boolean allStepsExecuted) {
157 underTest.finished(allStepsExecuted);
159 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
161 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getOrganization()).isEmpty();
165 @UseDataProvider("booleanValues")
166 public void CeTask_status_depends_on_finished_method_argument_is_true_or_false(boolean allStepsExecuted) {
167 underTest.finished(allStepsExecuted);
169 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
171 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getCeTask().getStatus())
173 allStepsExecuted ? org.sonar.api.ce.posttask.CeTask.Status.SUCCESS : org.sonar.api.ce.posttask.CeTask.Status.FAILED);
177 public void ceTask_uuid_is_UUID_of_CeTask() {
178 underTest.finished(true);
180 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
182 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getCeTask().getId())
183 .isEqualTo(ceTask.getUuid());
187 public void project_uuid_key_and_name_come_from_CeTask() {
188 underTest.finished(true);
190 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
192 Project project = taskContextCaptor.getValue().getProjectAnalysis().getProject();
193 assertThat(project.getUuid()).isEqualTo(ceTask.getEntity().get().getUuid());
194 assertThat(project.getKey()).isEqualTo(ceTask.getEntity().get().getKey().get());
195 assertThat(project.getName()).isEqualTo(ceTask.getEntity().get().getName().get());
199 public void date_comes_from_AnalysisMetadataHolder() {
200 analysisMetadataHolder.setAnalysisDate(8_465_132_498L);
201 analysisMetadataHolder.setUuid(RandomStringUtils.randomAlphanumeric(40));
203 underTest.finished(true);
205 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
207 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis().get().getDate())
208 .isEqualTo(new Date(analysisMetadataHolder.getAnalysisDate()));
212 public void analysisDate_and_analysisUuid_comes_from_AnalysisMetadataHolder_when_set() {
213 analysisMetadataHolder.setAnalysisDate(8465132498L);
214 analysisMetadataHolder.setUuid(RandomStringUtils.randomAlphanumeric(40));
216 underTest.finished(true);
218 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
220 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis().get().getDate())
221 .isEqualTo(new Date(analysisMetadataHolder.getAnalysisDate()));
222 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis().get().getAnalysisUuid())
223 .isEqualTo(analysisMetadataHolder.getUuid());
227 public void analysis_is_empty_when_not_set_in_AnalysisMetadataHolder() {
228 underTest.finished(false);
230 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
232 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis()).isEmpty();
236 public void branch_comes_from_AnalysisMetadataHolder_when_set() {
237 analysisMetadataHolder.setBranch(new Branch() {
239 public BranchType getType() {
240 return BranchType.BRANCH;
244 public boolean isMain() {
249 public String getReferenceBranchUuid() {
250 throw new UnsupportedOperationException();
254 public String getName() {
255 return "feature/foo";
259 public boolean supportsCrossProjectCpd() {
260 throw new UnsupportedOperationException();
264 public String getPullRequestKey() {
265 throw new UnsupportedOperationException();
269 public String getTargetBranchName() {
270 throw new UnsupportedOperationException();
274 public String generateKey(String projectKey, @Nullable String fileOrDirPath) {
275 throw new UnsupportedOperationException();
279 underTest.finished(true);
281 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
283 org.sonar.api.ce.posttask.Branch branch = taskContextCaptor.getValue().getProjectAnalysis().getBranch().get();
284 assertThat(branch.isMain()).isFalse();
285 assertThat(branch.getName()).hasValue("feature/foo");
286 assertThat(branch.getType()).isEqualTo(BranchImpl.Type.BRANCH);
290 public void qualityGate_is_null_when_finished_method_argument_is_false() {
291 underTest.finished(false);
293 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
295 assertThat(taskContextCaptor.getValue().getProjectAnalysis().getQualityGate()).isNull();
299 public void qualityGate_is_populated_when_finished_method_argument_is_true() {
300 underTest.finished(true);
302 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
304 org.sonar.api.ce.posttask.QualityGate qualityGate = taskContextCaptor.getValue().getProjectAnalysis().getQualityGate();
305 assertThat(qualityGate.getStatus()).isEqualTo(org.sonar.api.ce.posttask.QualityGate.Status.OK);
306 assertThat(qualityGate.getId()).isEqualTo(QUALITY_GATE_UUID);
307 assertThat(qualityGate.getName()).isEqualTo(QUALITY_GATE_NAME);
308 assertThat(qualityGate.getConditions()).hasSize(2);
312 public void scannerContext_loads_properties_from_scanner_report() {
313 reportReader.putContextProperties(asList(ScannerReport.ContextProperty.newBuilder().setKey("foo").setValue("bar").build()));
314 underTest.finished(true);
316 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
318 org.sonar.api.ce.posttask.ScannerContext scannerContext = taskContextCaptor.getValue().getProjectAnalysis().getScannerContext();
319 assertThat(scannerContext.getProperties()).containsExactly(entry("foo", "bar"));
323 @UseDataProvider("booleanValues")
324 public void logStatistics_add_fails_when_NPE_if_key_or_value_is_null(boolean allStepsExecuted) {
325 underTest.finished(allStepsExecuted);
327 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
328 PostProjectAnalysisTask.LogStatistics logStatistics = taskContextCaptor.getValue().getLogStatistics();
330 assertThat(catchThrowable(() -> logStatistics.add(null, "foo")))
331 .isInstanceOf(NullPointerException.class)
332 .hasMessage("Statistic has null key");
333 assertThat(catchThrowable(() -> logStatistics.add(null, null)))
334 .isInstanceOf(NullPointerException.class)
335 .hasMessage("Statistic has null key");
336 assertThat(catchThrowable(() -> logStatistics.add("bar", null)))
337 .isInstanceOf(NullPointerException.class)
338 .hasMessage("Statistic with key [bar] has null value");
342 @UseDataProvider("booleanValues")
343 public void logStatistics_add_fails_with_IAE_if_key_is_time_or_status_ignoring_case(boolean allStepsExecuted) {
344 underTest.finished(allStepsExecuted);
346 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
347 PostProjectAnalysisTask.LogStatistics logStatistics = taskContextCaptor.getValue().getLogStatistics();
349 for (String reservedKey : asList("time", "TIME", "TImE", "status", "STATUS", "STaTuS")) {
350 assertThat(catchThrowable(() -> logStatistics.add(reservedKey, "foo")))
351 .isInstanceOf(IllegalArgumentException.class)
352 .hasMessage("Statistic with key [" + reservedKey + "] is not accepted");
357 @UseDataProvider("booleanValues")
358 public void logStatistics_add_fails_with_IAE_if_same_key_with_exact_case_added_twice(boolean allStepsExecuted) {
359 underTest.finished(allStepsExecuted);
361 verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
362 PostProjectAnalysisTask.LogStatistics logStatistics = taskContextCaptor.getValue().getLogStatistics();
364 String key = RandomStringUtils.randomAlphabetic(10);
365 logStatistics.add(key, new Object());
366 assertThat(catchThrowable(() -> logStatistics.add(key, "bar")))
367 .isInstanceOf(IllegalArgumentException.class)
368 .hasMessage("Statistic with key [" + key + "] is already present");
372 @UseDataProvider("booleanValues")
373 public void logStatistics_adds_statistics_to_end_of_task_log(boolean allStepsExecuted) {
374 Map<String, Object> stats = new HashMap<>();
375 for (int i = 0; i <= new Random().nextInt(10); i++) {
376 stats.put("statKey_" + i, "statVal_" + i);
378 PostProjectAnalysisTask logStatisticsTask = mock(PostProjectAnalysisTask.class);
379 when(logStatisticsTask.getDescription()).thenReturn("PT1");
381 PostProjectAnalysisTask.Context context = i.getArgument(0);
382 stats.forEach((k, v) -> context.getLogStatistics().add(k, v));
384 }).when(logStatisticsTask)
385 .finished(any(PostProjectAnalysisTask.Context.class));
387 new PostProjectAnalysisTasksExecutor(
388 ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader, new PostProjectAnalysisTask[] {logStatisticsTask})
389 .finished(allStepsExecuted);
391 verify(logStatisticsTask).finished(taskContextCaptor.capture());
393 assertThat(logTester.logs()).hasSize(1);
394 List<String> logs = logTester.logs(Level.INFO);
395 assertThat(logs).hasSize(1);
396 StringBuilder expectedLog = new StringBuilder("^PT1 ");
397 stats.forEach((k, v) -> expectedLog.append("\\| " + k + "=" + v + " "));
398 expectedLog.append("\\| status=SUCCESS \\| time=\\d+ms$");
399 assertThat(logs.get(0)).matches(expectedLog.toString());
403 @UseDataProvider("booleanValues")
404 public void finished_does_not_fail_if_listener_throws_exception_and_execute_subsequent_listeners(boolean allStepsExecuted) {
405 PostProjectAnalysisTask postProjectAnalysisTask1 = newPostProjectAnalysisTask("PT1");
406 PostProjectAnalysisTask postProjectAnalysisTask2 = newPostProjectAnalysisTask("PT2");
407 PostProjectAnalysisTask postProjectAnalysisTask3 = newPostProjectAnalysisTask("PT3");
408 InOrder inOrder = inOrder(postProjectAnalysisTask1, postProjectAnalysisTask2, postProjectAnalysisTask3);
410 doThrow(new RuntimeException("Faking a listener throws an exception"))
411 .when(postProjectAnalysisTask2)
412 .finished(any(PostProjectAnalysisTask.Context.class));
414 new PostProjectAnalysisTasksExecutor(
415 ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader,
416 new PostProjectAnalysisTask[] {postProjectAnalysisTask1, postProjectAnalysisTask2, postProjectAnalysisTask3})
417 .finished(allStepsExecuted);
419 inOrder.verify(postProjectAnalysisTask1).finished(taskContextCaptor.capture());
420 inOrder.verify(postProjectAnalysisTask1).getDescription();
421 inOrder.verify(postProjectAnalysisTask2).finished(taskContextCaptor.capture());
422 inOrder.verify(postProjectAnalysisTask2).getDescription();
423 inOrder.verify(postProjectAnalysisTask3).finished(taskContextCaptor.capture());
424 inOrder.verify(postProjectAnalysisTask3).getDescription();
425 inOrder.verifyNoMoreInteractions();
427 assertThat(logTester.logs()).hasSize(4);
428 List<String> logs = logTester.logs(Level.INFO);
429 assertThat(logs).hasSize(3);
430 assertThat(logs.get(0)).matches("^PT1 \\| status=SUCCESS \\| time=\\d+ms$");
431 assertThat(logs.get(1)).matches("^PT2 \\| status=FAILED \\| time=\\d+ms$");
432 assertThat(logs.get(2)).matches("^PT3 \\| status=SUCCESS \\| time=\\d+ms$");
436 public static Object[][] booleanValues() {
437 return new Object[][] {
443 private static Condition createCondition(String metricKey) {
444 Metric metric = mock(Metric.class);
445 when(metric.getKey()).thenReturn(metricKey);
446 return new Condition(metric, Condition.Operator.LESS_THAN.getDbValue(), "error threshold");
449 private static PostProjectAnalysisTask newPostProjectAnalysisTask(String description) {
450 PostProjectAnalysisTask res = mock(PostProjectAnalysisTask.class);
451 when(res.getDescription()).thenReturn(description);
452 doAnswer(i -> null).when(res).finished(any(PostProjectAnalysisTask.Context.class));
456 private static List<PostProjectAnalysisTask.ProjectAnalysis> getAllProjectAnalyses(ArgumentCaptor<PostProjectAnalysisTask.Context> taskContextCaptor) {
457 return taskContextCaptor.getAllValues()
459 .map(PostProjectAnalysisTask.Context::getProjectAnalysis)