]> source.dussan.org Git - sonarqube.git/blob
952a702ee7a414e57e53b8f4757384d8c6831ed0
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.task.projectanalysis.api.posttask;
21
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;
29 import java.util.Map;
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;
56
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;
70
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");
77
78   @Rule
79   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
80   @Rule
81   public MutableQualityGateHolderRule qualityGateHolder = new MutableQualityGateHolderRule();
82   @Rule
83   public MutableQualityGateStatusHolderRule qualityGateStatusHolder = new MutableQualityGateStatusHolderRule();
84   @Rule
85   public BatchReportReaderRule reportReader = new BatchReportReaderRule();
86   @Rule
87   public LogTester logTester = new LogTester();
88
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()
93     .setType("type")
94     .setUuid("uuid")
95     .setComponent(component)
96     .setEntity(entity)
97     .build();
98   private final PostProjectAnalysisTask postProjectAnalysisTask = newPostProjectAnalysisTask("PT1");
99   private final PostProjectAnalysisTasksExecutor underTest = new PostProjectAnalysisTasksExecutor(
100     ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder,
101     reportReader, new PostProjectAnalysisTask[] {postProjectAnalysisTask});
102
103   @Before
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
112       .setBranch(branch);
113
114     reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
115   }
116
117   @Test
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);
122   }
123
124   @Test
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);
130
131     new PostProjectAnalysisTasksExecutor(
132       ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader,
133       new PostProjectAnalysisTask[] {postProjectAnalysisTask1, postProjectAnalysisTask2})
134         .finished(allStepsExecuted);
135
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();
141
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));
146
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$");
152   }
153
154   @Test
155   @UseDataProvider("booleanValues")
156   public void organization_is_null(boolean allStepsExecuted) {
157     underTest.finished(allStepsExecuted);
158
159     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
160
161     assertThat(taskContextCaptor.getValue().getProjectAnalysis().getOrganization()).isEmpty();
162   }
163
164   @Test
165   @UseDataProvider("booleanValues")
166   public void CeTask_status_depends_on_finished_method_argument_is_true_or_false(boolean allStepsExecuted) {
167     underTest.finished(allStepsExecuted);
168
169     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
170
171     assertThat(taskContextCaptor.getValue().getProjectAnalysis().getCeTask().getStatus())
172       .isEqualTo(
173         allStepsExecuted ? org.sonar.api.ce.posttask.CeTask.Status.SUCCESS : org.sonar.api.ce.posttask.CeTask.Status.FAILED);
174   }
175
176   @Test
177   public void ceTask_uuid_is_UUID_of_CeTask() {
178     underTest.finished(true);
179
180     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
181
182     assertThat(taskContextCaptor.getValue().getProjectAnalysis().getCeTask().getId())
183       .isEqualTo(ceTask.getUuid());
184   }
185
186   @Test
187   public void project_uuid_key_and_name_come_from_CeTask() {
188     underTest.finished(true);
189
190     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
191
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());
196   }
197
198   @Test
199   public void date_comes_from_AnalysisMetadataHolder() {
200     analysisMetadataHolder.setAnalysisDate(8_465_132_498L);
201     analysisMetadataHolder.setUuid(RandomStringUtils.randomAlphanumeric(40));
202
203     underTest.finished(true);
204
205     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
206
207     assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis().get().getDate())
208       .isEqualTo(new Date(analysisMetadataHolder.getAnalysisDate()));
209   }
210
211   @Test
212   public void analysisDate_and_analysisUuid_comes_from_AnalysisMetadataHolder_when_set() {
213     analysisMetadataHolder.setAnalysisDate(8465132498L);
214     analysisMetadataHolder.setUuid(RandomStringUtils.randomAlphanumeric(40));
215
216     underTest.finished(true);
217
218     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
219
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());
224   }
225
226   @Test
227   public void analysis_is_empty_when_not_set_in_AnalysisMetadataHolder() {
228     underTest.finished(false);
229
230     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
231
232     assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis()).isEmpty();
233   }
234
235   @Test
236   public void branch_comes_from_AnalysisMetadataHolder_when_set() {
237     analysisMetadataHolder.setBranch(new Branch() {
238       @Override
239       public BranchType getType() {
240         return BranchType.BRANCH;
241       }
242
243       @Override
244       public boolean isMain() {
245         return false;
246       }
247
248       @Override
249       public String getReferenceBranchUuid() {
250         throw new UnsupportedOperationException();
251       }
252
253       @Override
254       public String getName() {
255         return "feature/foo";
256       }
257
258       @Override
259       public boolean supportsCrossProjectCpd() {
260         throw new UnsupportedOperationException();
261       }
262
263       @Override
264       public String getPullRequestKey() {
265         throw new UnsupportedOperationException();
266       }
267
268       @Override
269       public String getTargetBranchName() {
270         throw new UnsupportedOperationException();
271       }
272
273       @Override
274       public String generateKey(String projectKey, @Nullable String fileOrDirPath) {
275         throw new UnsupportedOperationException();
276       }
277     });
278
279     underTest.finished(true);
280
281     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
282
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);
287   }
288
289   @Test
290   public void qualityGate_is_null_when_finished_method_argument_is_false() {
291     underTest.finished(false);
292
293     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
294
295     assertThat(taskContextCaptor.getValue().getProjectAnalysis().getQualityGate()).isNull();
296   }
297
298   @Test
299   public void qualityGate_is_populated_when_finished_method_argument_is_true() {
300     underTest.finished(true);
301
302     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
303
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);
309   }
310
311   @Test
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);
315
316     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
317
318     org.sonar.api.ce.posttask.ScannerContext scannerContext = taskContextCaptor.getValue().getProjectAnalysis().getScannerContext();
319     assertThat(scannerContext.getProperties()).containsExactly(entry("foo", "bar"));
320   }
321
322   @Test
323   @UseDataProvider("booleanValues")
324   public void logStatistics_add_fails_when_NPE_if_key_or_value_is_null(boolean allStepsExecuted) {
325     underTest.finished(allStepsExecuted);
326
327     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
328     PostProjectAnalysisTask.LogStatistics logStatistics = taskContextCaptor.getValue().getLogStatistics();
329
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");
339   }
340
341   @Test
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);
345
346     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
347     PostProjectAnalysisTask.LogStatistics logStatistics = taskContextCaptor.getValue().getLogStatistics();
348
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");
353     }
354   }
355
356   @Test
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);
360
361     verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());
362     PostProjectAnalysisTask.LogStatistics logStatistics = taskContextCaptor.getValue().getLogStatistics();
363
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");
369   }
370
371   @Test
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);
377     }
378     PostProjectAnalysisTask logStatisticsTask = mock(PostProjectAnalysisTask.class);
379     when(logStatisticsTask.getDescription()).thenReturn("PT1");
380     doAnswer(i -> {
381       PostProjectAnalysisTask.Context context = i.getArgument(0);
382       stats.forEach((k, v) -> context.getLogStatistics().add(k, v));
383       return null;
384     }).when(logStatisticsTask)
385       .finished(any(PostProjectAnalysisTask.Context.class));
386
387     new PostProjectAnalysisTasksExecutor(
388       ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader, new PostProjectAnalysisTask[] {logStatisticsTask})
389         .finished(allStepsExecuted);
390
391     verify(logStatisticsTask).finished(taskContextCaptor.capture());
392
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());
400   }
401
402   @Test
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);
409
410     doThrow(new RuntimeException("Faking a listener throws an exception"))
411       .when(postProjectAnalysisTask2)
412       .finished(any(PostProjectAnalysisTask.Context.class));
413
414     new PostProjectAnalysisTasksExecutor(
415       ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader,
416       new PostProjectAnalysisTask[] {postProjectAnalysisTask1, postProjectAnalysisTask2, postProjectAnalysisTask3})
417         .finished(allStepsExecuted);
418
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();
426
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$");
433   }
434
435   @DataProvider
436   public static Object[][] booleanValues() {
437     return new Object[][] {
438       {true},
439       {false}
440     };
441   }
442
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");
447   }
448
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));
453     return res;
454   }
455
456   private static List<PostProjectAnalysisTask.ProjectAnalysis> getAllProjectAnalyses(ArgumentCaptor<PostProjectAnalysisTask.Context> taskContextCaptor) {
457     return taskContextCaptor.getAllValues()
458       .stream()
459       .map(PostProjectAnalysisTask.Context::getProjectAnalysis)
460       .collect(toList());
461   }
462
463 }