]> source.dussan.org Git - sonarqube.git/blob
aa9d78dc20568b14a8be61db3fbd0a899da7be4b
[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.issue;
21
22 import com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.util.Arrays;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.function.BiConsumer;
31 import java.util.stream.Stream;
32 import org.apache.commons.lang3.ArrayUtils;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.sonar.api.rule.RuleKey;
37 import org.sonar.ce.task.projectanalysis.analysis.Analysis;
38 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
39 import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin;
40 import org.sonar.ce.task.projectanalysis.component.Component;
41 import org.sonar.ce.task.projectanalysis.filemove.AddedFileRepository;
42 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule;
43 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
44 import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository;
45 import org.sonar.ce.task.projectanalysis.scm.Changeset;
46 import org.sonar.ce.task.projectanalysis.scm.ScmInfo;
47 import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository;
48 import org.sonar.core.issue.DefaultIssue;
49 import org.sonar.db.protobuf.DbCommons.TextRange;
50 import org.sonar.db.protobuf.DbIssues;
51 import org.sonar.db.protobuf.DbIssues.Flow;
52 import org.sonar.db.protobuf.DbIssues.Location;
53 import org.sonar.db.protobuf.DbIssues.Locations.Builder;
54 import org.sonar.server.issue.IssueFieldsSetter;
55
56 import static org.assertj.core.api.Assertions.assertThatThrownBy;
57 import static org.mockito.ArgumentMatchers.any;
58 import static org.mockito.ArgumentMatchers.eq;
59 import static org.mockito.ArgumentMatchers.same;
60 import static org.mockito.Mockito.atLeastOnce;
61 import static org.mockito.Mockito.mock;
62 import static org.mockito.Mockito.never;
63 import static org.mockito.Mockito.verify;
64 import static org.mockito.Mockito.verifyNoInteractions;
65 import static org.mockito.Mockito.when;
66 import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
67
68 @RunWith(DataProviderRunner.class)
69 public class IssueCreationDateCalculatorTest {
70   private static final String COMPONENT_UUID = "ab12";
71
72   @org.junit.Rule
73   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
74
75   private ScmInfoRepository scmInfoRepository = mock(ScmInfoRepository.class);
76   private IssueFieldsSetter issueUpdater = mock(IssueFieldsSetter.class);
77   private ActiveRulesHolder activeRulesHolder = mock(ActiveRulesHolder.class);
78   private Component component = mock(Component.class);
79   private RuleKey ruleKey = RuleKey.of("reop", "rule");
80   private DefaultIssue issue = mock(DefaultIssue.class);
81   private ActiveRule activeRule = mock(ActiveRule.class);
82
83   private IssueCreationDateCalculator underTest;
84
85   private Analysis baseAnalysis = mock(Analysis.class);
86   private Map<String, ScannerPlugin> scannerPlugins = new HashMap<>();
87   private RuleRepository ruleRepository = mock(RuleRepository.class);
88   private AddedFileRepository addedFileRepository = mock(AddedFileRepository.class);
89   private QProfileStatusRepository qProfileStatusRepository = mock(QProfileStatusRepository.class);
90   private ScmInfo scmInfo;
91   private Rule rule = mock(Rule.class);
92
93   @Before
94   public void before() {
95     analysisMetadataHolder.setScannerPluginsByKey(scannerPlugins);
96     analysisMetadataHolder.setAnalysisDate(new Date());
97     when(component.getUuid()).thenReturn(COMPONENT_UUID);
98     underTest = new IssueCreationDateCalculator(analysisMetadataHolder, scmInfoRepository, issueUpdater, activeRulesHolder, ruleRepository, addedFileRepository, qProfileStatusRepository);
99
100     when(ruleRepository.findByKey(ruleKey)).thenReturn(Optional.of(rule));
101     when(activeRulesHolder.get(any(RuleKey.class))).thenReturn(Optional.empty());
102     when(activeRulesHolder.get(ruleKey)).thenReturn(Optional.of(activeRule));
103     when(activeRule.getQProfileKey()).thenReturn("qpKey");
104     when(issue.getRuleKey()).thenReturn(ruleKey);
105     when(qProfileStatusRepository.get(any())).thenReturn(Optional.of(UNCHANGED));
106   }
107
108   @Test
109   public void should_not_backdate_if_no_scm_available() {
110     previousAnalysisWas(2000L);
111     currentAnalysisIs(3000L);
112
113     makeIssueNew();
114     noScm();
115     setRuleUpdatedAt(2800L);
116
117     run();
118
119     assertNoChangeOfCreationDate();
120   }
121
122   @Test
123   @UseDataProvider("backdatingDateCases")
124   public void should_not_backdate_if_rule_and_plugin_and_base_plugin_are_old(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
125     previousAnalysisWas(2000L);
126     currentAnalysisIs(3000L);
127
128     makeIssueNew();
129     configure.accept(issue, createMockScmInfo());
130     setRuleUpdatedAt(1500L);
131     rulePlugin("customjava");
132     pluginUpdatedAt("customjava", "java", 1700L);
133     pluginUpdatedAt("java", 1700L);
134
135     run();
136
137     assertNoChangeOfCreationDate();
138   }
139
140   @Test
141   @UseDataProvider("backdatingDateCases")
142   public void should_not_backdate_if_rule_and_plugin_are_old_and_no_base_plugin(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
143     previousAnalysisWas(2000L);
144     currentAnalysisIs(3000L);
145
146     makeIssueNew();
147     configure.accept(issue, createMockScmInfo());
148     setRuleUpdatedAt(1500L);
149     rulePlugin("java");
150     pluginUpdatedAt("java", 1700L);
151
152     run();
153
154     assertNoChangeOfCreationDate();
155   }
156
157   @Test
158   @UseDataProvider("backdatingDateCases")
159   public void should_not_backdate_if_issue_existed_before(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
160     previousAnalysisWas(2000L);
161     currentAnalysisIs(3000L);
162
163     makeIssueNotNew();
164     configure.accept(issue, createMockScmInfo());
165     setRuleUpdatedAt(2800L);
166
167     run();
168
169     assertNoChangeOfCreationDate();
170   }
171
172   @Test
173   public void should_not_fail_for_issue_about_to_be_closed() {
174     previousAnalysisWas(2000L);
175     currentAnalysisIs(3000L);
176
177     makeIssueNotNew();
178     setIssueBelongToNonExistingRule();
179
180     run();
181
182     assertNoChangeOfCreationDate();
183   }
184
185   @Test
186   public void should_fail_if_rule_is_not_found() {
187     previousAnalysisWas(2000L);
188     currentAnalysisIs(3000L);
189
190     when(ruleRepository.findByKey(ruleKey)).thenReturn(Optional.empty());
191     makeIssueNew();
192
193     assertThatThrownBy(this::run)
194       .isInstanceOf(IllegalStateException.class)
195       .hasMessage("The rule with key 'reop:rule' raised an issue, but no rule with that key was found");
196   }
197
198   @Test
199   @UseDataProvider("backdatingDateCases")
200   public void should_backdate_date_if_scm_is_available_and_rule_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
201     previousAnalysisWas(2000L);
202     currentAnalysisIs(3000L);
203
204     makeIssueNew();
205     configure.accept(issue, createMockScmInfo());
206     setRuleUpdatedAt(2800L);
207
208     run();
209
210     assertChangeOfCreationDateTo(expectedDate);
211   }
212
213   @Test
214   @UseDataProvider("backdatingDateCases")
215   public void should_backdate_date_if_scm_is_available_and_rule_has_changed(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
216     previousAnalysisWas(2000L);
217     currentAnalysisIs(3000L);
218
219     makeIssueNew();
220     configure.accept(issue, createMockScmInfo());
221     setRuleUpdatedAt(2800L);
222
223     run();
224
225     assertChangeOfCreationDateTo(expectedDate);
226   }
227
228   @Test
229   @UseDataProvider("backdatingDateCases")
230   public void should_backdate_date_if_scm_is_available_and_first_analysis(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
231     currentAnalysisIsFirstAnalysis();
232     currentAnalysisIs(3000L);
233
234     makeIssueNew();
235     configure.accept(issue, createMockScmInfo());
236
237     run();
238
239     assertChangeOfCreationDateTo(expectedDate);
240   }
241
242   @Test
243   @UseDataProvider("backdatingDateCases")
244   public void should_backdate_date_if_scm_is_available_and_current_component_is_new_file(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
245     previousAnalysisWas(2000L);
246     currentAnalysisIs(3000L);
247
248     makeIssueNew();
249     configure.accept(issue, createMockScmInfo());
250     currentComponentIsNewFile();
251
252     run();
253
254     assertChangeOfCreationDateTo(expectedDate);
255   }
256
257   @Test
258   @UseDataProvider("backdatingDateAndChangedQPStatusCases")
259   public void should_backdate_if_qp_of_the_rule_which_raised_the_issue_has_changed(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate, QProfileStatusRepository.Status status) {
260     previousAnalysisWas(2000L);
261     currentAnalysisIs(3000L);
262
263     makeIssueNew();
264     configure.accept(issue, createMockScmInfo());
265     changeQualityProfile(status);
266
267     run();
268
269     assertChangeOfCreationDateTo(expectedDate);
270   }
271
272   @Test
273   @UseDataProvider("backdatingDateCases")
274   public void should_backdate_if_scm_is_available_and_plugin_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
275     previousAnalysisWas(2000L);
276     currentAnalysisIs(3000L);
277
278     makeIssueNew();
279     configure.accept(issue, createMockScmInfo());
280     setRuleUpdatedAt(1500L);
281     rulePlugin("java");
282     pluginUpdatedAt("java", 2500L);
283
284     run();
285
286     assertChangeOfCreationDateTo(expectedDate);
287   }
288
289   @Test
290   @UseDataProvider("backdatingDateCases")
291   public void should_backdate_if_scm_is_available_and_base_plugin_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
292     previousAnalysisWas(2000L);
293     currentAnalysisIs(3000L);
294
295     makeIssueNew();
296     configure.accept(issue, createMockScmInfo());
297     setRuleUpdatedAt(1500L);
298     rulePlugin("customjava");
299     pluginUpdatedAt("customjava", "java", 1000L);
300     pluginUpdatedAt("java", 2500L);
301
302     run();
303
304     assertChangeOfCreationDateTo(expectedDate);
305   }
306
307   @Test
308   @UseDataProvider("backdatingDateCases")
309   public void should_backdate_external_issues(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
310     currentAnalysisIsFirstAnalysis();
311     currentAnalysisIs(3000L);
312
313     makeIssueNew();
314     when(rule.isExternal()).thenReturn(true);
315     configure.accept(issue, createMockScmInfo());
316
317     run();
318
319     assertChangeOfCreationDateTo(expectedDate);
320     verifyNoInteractions(activeRulesHolder);
321   }
322
323   @DataProvider
324   public static Object[][] backdatingDateAndChangedQPStatusCases() {
325     return Stream.of(backdatingDateCases())
326       .flatMap(datesCases ->
327         Stream.of(QProfileStatusRepository.Status.values())
328           .filter(s -> !UNCHANGED.equals(s))
329           .map(s -> ArrayUtils.add(datesCases, s)))
330       .toArray(Object[][]::new);
331   }
332
333   @DataProvider
334   public static Object[][] backdatingDateCases() {
335     return new Object[][] {
336       {new NoIssueLocation(), 1200L},
337       {new OnlyPrimaryLocation(), 1300L},
338       {new FlowOnCurrentFileOnly(), 1900L},
339       {new FlowOnMultipleFiles(), 1700L}
340     };
341   }
342
343   private static class NoIssueLocation implements BiConsumer<DefaultIssue, ScmInfo> {
344     @Override
345     public void accept(DefaultIssue issue, ScmInfo scmInfo) {
346       setDateOfLatestScmChangeset(scmInfo, 1200L);
347     }
348   }
349
350   private static class OnlyPrimaryLocation implements BiConsumer<DefaultIssue, ScmInfo> {
351     @Override
352     public void accept(DefaultIssue issue, ScmInfo scmInfo) {
353       when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder().setTextRange(range(2, 3)).build());
354       setDateOfChangetsetAtLine(scmInfo, 2, 1200L);
355       setDateOfChangetsetAtLine(scmInfo, 3, 1300L);
356     }
357   }
358
359   private static class FlowOnCurrentFileOnly implements BiConsumer<DefaultIssue, ScmInfo> {
360     @Override
361     public void accept(DefaultIssue issue, ScmInfo scmInfo) {
362       Builder locations = DbIssues.Locations.newBuilder()
363         .setTextRange(range(2, 3))
364         .addFlow(newFlow(newLocation(4, 5)))
365         .addFlow(newFlow(newLocation(6, 7, COMPONENT_UUID), newLocation(8, 9, COMPONENT_UUID)));
366       when(issue.getLocations()).thenReturn(locations.build());
367       setDateOfChangetsetAtLine(scmInfo, 2, 1200L);
368       setDateOfChangetsetAtLine(scmInfo, 3, 1300L);
369       setDateOfChangetsetAtLine(scmInfo, 4, 1400L);
370       setDateOfChangetsetAtLine(scmInfo, 5, 1500L);
371       // some lines missing should be ok
372       setDateOfChangetsetAtLine(scmInfo, 9, 1900L);
373     }
374   }
375
376   private static class FlowOnMultipleFiles implements BiConsumer<DefaultIssue, ScmInfo> {
377     @Override
378     public void accept(DefaultIssue issue, ScmInfo scmInfo) {
379       Builder locations = DbIssues.Locations.newBuilder()
380         .setTextRange(range(2, 3))
381         .addFlow(newFlow(newLocation(4, 5)))
382         .addFlow(newFlow(newLocation(6, 7, COMPONENT_UUID), newLocation(8, 9, "another")));
383       when(issue.getLocations()).thenReturn(locations.build());
384       setDateOfChangetsetAtLine(scmInfo, 2, 1200L);
385       setDateOfChangetsetAtLine(scmInfo, 3, 1300L);
386       setDateOfChangetsetAtLine(scmInfo, 4, 1400L);
387       setDateOfChangetsetAtLine(scmInfo, 5, 1500L);
388       setDateOfChangetsetAtLine(scmInfo, 6, 1600L);
389       setDateOfChangetsetAtLine(scmInfo, 7, 1700L);
390       setDateOfChangetsetAtLine(scmInfo, 8, 1800L);
391       setDateOfChangetsetAtLine(scmInfo, 9, 1900L);
392     }
393   }
394
395   private void previousAnalysisWas(long analysisDate) {
396     analysisMetadataHolder.setBaseAnalysis(baseAnalysis);
397     when(baseAnalysis.getCreatedAt())
398       .thenReturn(analysisDate);
399   }
400
401   private void pluginUpdatedAt(String pluginKey, long updatedAt) {
402     scannerPlugins.put(pluginKey, new ScannerPlugin(pluginKey, null, updatedAt));
403   }
404
405   private void pluginUpdatedAt(String pluginKey, String basePluginKey, long updatedAt) {
406     scannerPlugins.put(pluginKey, new ScannerPlugin(pluginKey, basePluginKey, updatedAt));
407   }
408
409   private AnalysisMetadataHolderRule currentAnalysisIsFirstAnalysis() {
410     return analysisMetadataHolder.setBaseAnalysis(null);
411   }
412
413   private void currentAnalysisIs(long analysisDate) {
414     analysisMetadataHolder.setAnalysisDate(analysisDate);
415   }
416
417   private void currentComponentIsNewFile() {
418     when(component.getType()).thenReturn(Component.Type.FILE);
419     when(addedFileRepository.isAdded(component)).thenReturn(true);
420   }
421
422   private void makeIssueNew() {
423     when(issue.isNew())
424       .thenReturn(true);
425   }
426
427   private void makeIssueNotNew() {
428     when(issue.isNew())
429       .thenReturn(false);
430   }
431
432   private void changeQualityProfile(QProfileStatusRepository.Status status) {
433     when(qProfileStatusRepository.get(any())).thenReturn(Optional.of(status));
434   }
435
436   private void setIssueBelongToNonExistingRule() {
437     when(issue.getRuleKey())
438       .thenReturn(RuleKey.of("repo", "disabled"));
439   }
440
441   private void noScm() {
442     when(scmInfoRepository.getScmInfo(component))
443       .thenReturn(Optional.empty());
444   }
445
446   private static void setDateOfLatestScmChangeset(ScmInfo scmInfo, long date) {
447     Changeset changeset = Changeset.newChangesetBuilder().setDate(date).setRevision("1").build();
448     when(scmInfo.getLatestChangeset()).thenReturn(changeset);
449   }
450
451   private static void setDateOfChangetsetAtLine(ScmInfo scmInfo, int line, long date) {
452     Changeset changeset = Changeset.newChangesetBuilder().setDate(date).setRevision("1").build();
453     when(scmInfo.hasChangesetForLine(line)).thenReturn(true);
454     when(scmInfo.getChangesetForLine(line)).thenReturn(changeset);
455   }
456
457   private ScmInfo createMockScmInfo() {
458     if (scmInfo == null) {
459       scmInfo = mock(ScmInfo.class);
460       when(scmInfoRepository.getScmInfo(component))
461         .thenReturn(Optional.of(scmInfo));
462     }
463     return scmInfo;
464   }
465
466   private void setRuleUpdatedAt(long updateAt) {
467     when(activeRule.getUpdatedAt()).thenReturn(updateAt);
468   }
469
470   private void rulePlugin(String pluginKey) {
471     when(activeRule.getPluginKey()).thenReturn(pluginKey);
472   }
473
474   private static Location newLocation(int startLine, int endLine) {
475     return Location.newBuilder().setTextRange(range(startLine, endLine)).build();
476   }
477
478   private static Location newLocation(int startLine, int endLine, String componentUuid) {
479     return Location.newBuilder().setTextRange(range(startLine, endLine)).setComponentId(componentUuid).build();
480   }
481
482   private static org.sonar.db.protobuf.DbCommons.TextRange range(int startLine, int endLine) {
483     return TextRange.newBuilder().setStartLine(startLine).setEndLine(endLine).build();
484   }
485
486   private static Flow newFlow(Location... locations) {
487     Flow.Builder builder = Flow.newBuilder();
488     Arrays.stream(locations).forEach(builder::addLocation);
489     return builder.build();
490   }
491
492   private void run() {
493     underTest.beforeComponent(component);
494     underTest.onIssue(component, issue);
495     underTest.afterComponent(component);
496   }
497
498   private void assertNoChangeOfCreationDate() {
499     verify(issueUpdater, never())
500       .setCreationDate(any(), any(), any());
501   }
502
503   private void assertChangeOfCreationDateTo(long createdAt) {
504     verify(issueUpdater, atLeastOnce())
505       .setCreationDate(same(issue), eq(new Date(createdAt)), any());
506   }
507 }