You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IssueCreationDateCalculatorTest.java 17KB

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