123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- /*
- * SonarQube
- * Copyright (C) 2009-2021 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
- package org.sonar.ce.task.projectanalysis.issue;
-
- import com.tngtech.java.junit.dataprovider.DataProvider;
- import com.tngtech.java.junit.dataprovider.DataProviderRunner;
- import com.tngtech.java.junit.dataprovider.UseDataProvider;
- import java.util.Arrays;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Optional;
- import java.util.function.BiConsumer;
- import java.util.stream.Stream;
- import org.apache.commons.lang.ArrayUtils;
- import org.junit.Before;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.sonar.api.rule.RuleKey;
- import org.sonar.ce.task.projectanalysis.analysis.Analysis;
- import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
- import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin;
- import org.sonar.ce.task.projectanalysis.component.Component;
- import org.sonar.ce.task.projectanalysis.filemove.AddedFileRepository;
- import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule;
- import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
- import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository;
- import org.sonar.ce.task.projectanalysis.scm.Changeset;
- import org.sonar.ce.task.projectanalysis.scm.ScmInfo;
- import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository;
- import org.sonar.core.issue.DefaultIssue;
- import org.sonar.db.protobuf.DbCommons.TextRange;
- import org.sonar.db.protobuf.DbIssues;
- import org.sonar.db.protobuf.DbIssues.Flow;
- import org.sonar.db.protobuf.DbIssues.Location;
- import org.sonar.db.protobuf.DbIssues.Locations.Builder;
- import org.sonar.server.issue.IssueFieldsSetter;
-
- import static org.assertj.core.api.Assertions.assertThatThrownBy;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.ArgumentMatchers.eq;
- import static org.mockito.ArgumentMatchers.same;
- import static org.mockito.Mockito.atLeastOnce;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.never;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.verifyZeroInteractions;
- import static org.mockito.Mockito.when;
- import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
-
- @RunWith(DataProviderRunner.class)
- public class IssueCreationDateCalculatorTest {
- private static final String COMPONENT_UUID = "ab12";
-
- @org.junit.Rule
- public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
- private ScmInfoRepository scmInfoRepository = mock(ScmInfoRepository.class);
- private IssueFieldsSetter issueUpdater = mock(IssueFieldsSetter.class);
- private ActiveRulesHolder activeRulesHolder = mock(ActiveRulesHolder.class);
- private Component component = mock(Component.class);
- private RuleKey ruleKey = RuleKey.of("reop", "rule");
- private DefaultIssue issue = mock(DefaultIssue.class);
- private ActiveRule activeRule = mock(ActiveRule.class);
-
- private IssueCreationDateCalculator underTest;
-
- private Analysis baseAnalysis = mock(Analysis.class);
- private Map<String, ScannerPlugin> scannerPlugins = new HashMap<>();
- private RuleRepository ruleRepository = mock(RuleRepository.class);
- private AddedFileRepository addedFileRepository = mock(AddedFileRepository.class);
- private QProfileStatusRepository qProfileStatusRepository = mock(QProfileStatusRepository.class);
- private ScmInfo scmInfo;
- private Rule rule = mock(Rule.class);
-
- @Before
- public void before() {
- analysisMetadataHolder.setScannerPluginsByKey(scannerPlugins);
- analysisMetadataHolder.setAnalysisDate(new Date());
- when(component.getUuid()).thenReturn(COMPONENT_UUID);
- underTest = new IssueCreationDateCalculator(analysisMetadataHolder, scmInfoRepository, issueUpdater, activeRulesHolder, ruleRepository, addedFileRepository, qProfileStatusRepository);
-
- when(ruleRepository.findByKey(ruleKey)).thenReturn(Optional.of(rule));
- when(activeRulesHolder.get(any(RuleKey.class))).thenReturn(Optional.empty());
- when(activeRulesHolder.get(ruleKey)).thenReturn(Optional.of(activeRule));
- when(activeRule.getQProfileKey()).thenReturn("qpKey");
- when(issue.getRuleKey()).thenReturn(ruleKey);
- when(qProfileStatusRepository.get(any())).thenReturn(Optional.of(UNCHANGED));
- }
-
- @Test
- public void should_not_backdate_if_no_scm_available() {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- noScm();
- setRuleUpdatedAt(2800L);
-
- run();
-
- assertNoChangeOfCreationDate();
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_not_backdate_if_rule_and_plugin_and_base_plugin_are_old(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- setRuleUpdatedAt(1500L);
- rulePlugin("customjava");
- pluginUpdatedAt("customjava", "java", 1700L);
- pluginUpdatedAt("java", 1700L);
-
- run();
-
- assertNoChangeOfCreationDate();
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_not_backdate_if_rule_and_plugin_are_old_and_no_base_plugin(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- setRuleUpdatedAt(1500L);
- rulePlugin("java");
- pluginUpdatedAt("java", 1700L);
-
- run();
-
- assertNoChangeOfCreationDate();
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_not_backdate_if_issue_existed_before(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNotNew();
- configure.accept(issue, createMockScmInfo());
- setRuleUpdatedAt(2800L);
-
- run();
-
- assertNoChangeOfCreationDate();
- }
-
- @Test
- public void should_not_fail_for_issue_about_to_be_closed() {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNotNew();
- setIssueBelongToNonExistingRule();
-
- run();
-
- assertNoChangeOfCreationDate();
- }
-
- @Test
- public void should_fail_if_rule_is_not_found() {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- when(ruleRepository.findByKey(ruleKey)).thenReturn(Optional.empty());
- makeIssueNew();
-
- assertThatThrownBy(this::run)
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("The rule with key 'reop:rule' raised an issue, but no rule with that key was found");
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_backdate_date_if_scm_is_available_and_rule_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- setRuleUpdatedAt(2800L);
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_backdate_date_if_scm_is_available_and_rule_has_changed(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- setRuleUpdatedAt(2800L);
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_backdate_date_if_scm_is_available_and_first_analysis(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- currentAnalysisIsFirstAnalysis();
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_backdate_date_if_scm_is_available_and_current_component_is_new_file(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- currentComponentIsNewFile();
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- }
-
- @Test
- @UseDataProvider("backdatingDateAndChangedQPStatusCases")
- public void should_backdate_if_qp_of_the_rule_which_raised_the_issue_has_changed(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate, QProfileStatusRepository.Status status) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- changeQualityProfile(status);
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_backdate_if_scm_is_available_and_plugin_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- setRuleUpdatedAt(1500L);
- rulePlugin("java");
- pluginUpdatedAt("java", 2500L);
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_backdate_if_scm_is_available_and_base_plugin_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- previousAnalysisWas(2000L);
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- configure.accept(issue, createMockScmInfo());
- setRuleUpdatedAt(1500L);
- rulePlugin("customjava");
- pluginUpdatedAt("customjava", "java", 1000L);
- pluginUpdatedAt("java", 2500L);
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- }
-
- @Test
- @UseDataProvider("backdatingDateCases")
- public void should_backdate_external_issues(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
- currentAnalysisIsFirstAnalysis();
- currentAnalysisIs(3000L);
-
- makeIssueNew();
- when(rule.isExternal()).thenReturn(true);
- configure.accept(issue, createMockScmInfo());
-
- run();
-
- assertChangeOfCreationDateTo(expectedDate);
- verifyZeroInteractions(activeRulesHolder);
- }
-
- @DataProvider
- public static Object[][] backdatingDateAndChangedQPStatusCases() {
- return Stream.of(backdatingDateCases())
- .flatMap(datesCases ->
- Stream.of(QProfileStatusRepository.Status.values())
- .filter(s -> !UNCHANGED.equals(s))
- .map(s -> ArrayUtils.add(datesCases, s)))
- .toArray(Object[][]::new);
- }
-
- @DataProvider
- public static Object[][] backdatingDateCases() {
- return new Object[][] {
- {new NoIssueLocation(), 1200L},
- {new OnlyPrimaryLocation(), 1300L},
- {new FlowOnCurrentFileOnly(), 1900L},
- {new FlowOnMultipleFiles(), 1700L}
- };
- }
-
- private static class NoIssueLocation implements BiConsumer<DefaultIssue, ScmInfo> {
- @Override
- public void accept(DefaultIssue issue, ScmInfo scmInfo) {
- setDateOfLatestScmChangeset(scmInfo, 1200L);
- }
- }
-
- private static class OnlyPrimaryLocation implements BiConsumer<DefaultIssue, ScmInfo> {
- @Override
- public void accept(DefaultIssue issue, ScmInfo scmInfo) {
- when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder().setTextRange(range(2, 3)).build());
- setDateOfChangetsetAtLine(scmInfo, 2, 1200L);
- setDateOfChangetsetAtLine(scmInfo, 3, 1300L);
- }
- }
-
- private static class FlowOnCurrentFileOnly implements BiConsumer<DefaultIssue, ScmInfo> {
- @Override
- public void accept(DefaultIssue issue, ScmInfo scmInfo) {
- Builder locations = DbIssues.Locations.newBuilder()
- .setTextRange(range(2, 3))
- .addFlow(newFlow(newLocation(4, 5)))
- .addFlow(newFlow(newLocation(6, 7, COMPONENT_UUID), newLocation(8, 9, COMPONENT_UUID)));
- when(issue.getLocations()).thenReturn(locations.build());
- setDateOfChangetsetAtLine(scmInfo, 2, 1200L);
- setDateOfChangetsetAtLine(scmInfo, 3, 1300L);
- setDateOfChangetsetAtLine(scmInfo, 4, 1400L);
- setDateOfChangetsetAtLine(scmInfo, 5, 1500L);
- // some lines missing should be ok
- setDateOfChangetsetAtLine(scmInfo, 9, 1900L);
- }
- }
-
- private static class FlowOnMultipleFiles implements BiConsumer<DefaultIssue, ScmInfo> {
- @Override
- public void accept(DefaultIssue issue, ScmInfo scmInfo) {
- Builder locations = DbIssues.Locations.newBuilder()
- .setTextRange(range(2, 3))
- .addFlow(newFlow(newLocation(4, 5)))
- .addFlow(newFlow(newLocation(6, 7, COMPONENT_UUID), newLocation(8, 9, "another")));
- when(issue.getLocations()).thenReturn(locations.build());
- setDateOfChangetsetAtLine(scmInfo, 2, 1200L);
- setDateOfChangetsetAtLine(scmInfo, 3, 1300L);
- setDateOfChangetsetAtLine(scmInfo, 4, 1400L);
- setDateOfChangetsetAtLine(scmInfo, 5, 1500L);
- setDateOfChangetsetAtLine(scmInfo, 6, 1600L);
- setDateOfChangetsetAtLine(scmInfo, 7, 1700L);
- setDateOfChangetsetAtLine(scmInfo, 8, 1800L);
- setDateOfChangetsetAtLine(scmInfo, 9, 1900L);
- }
- }
-
- private void previousAnalysisWas(long analysisDate) {
- analysisMetadataHolder.setBaseAnalysis(baseAnalysis);
- when(baseAnalysis.getCreatedAt())
- .thenReturn(analysisDate);
- }
-
- private void pluginUpdatedAt(String pluginKey, long updatedAt) {
- scannerPlugins.put(pluginKey, new ScannerPlugin(pluginKey, null, updatedAt));
- }
-
- private void pluginUpdatedAt(String pluginKey, String basePluginKey, long updatedAt) {
- scannerPlugins.put(pluginKey, new ScannerPlugin(pluginKey, basePluginKey, updatedAt));
- }
-
- private AnalysisMetadataHolderRule currentAnalysisIsFirstAnalysis() {
- return analysisMetadataHolder.setBaseAnalysis(null);
- }
-
- private void currentAnalysisIs(long analysisDate) {
- analysisMetadataHolder.setAnalysisDate(analysisDate);
- }
-
- private void currentComponentIsNewFile() {
- when(component.getType()).thenReturn(Component.Type.FILE);
- when(addedFileRepository.isAdded(component)).thenReturn(true);
- }
-
- private void makeIssueNew() {
- when(issue.isNew())
- .thenReturn(true);
- }
-
- private void makeIssueNotNew() {
- when(issue.isNew())
- .thenReturn(false);
- }
-
- private void changeQualityProfile(QProfileStatusRepository.Status status) {
- when(qProfileStatusRepository.get(any())).thenReturn(Optional.of(status));
- }
-
- private void setIssueBelongToNonExistingRule() {
- when(issue.getRuleKey())
- .thenReturn(RuleKey.of("repo", "disabled"));
- }
-
- private void noScm() {
- when(scmInfoRepository.getScmInfo(component))
- .thenReturn(Optional.empty());
- }
-
- private static void setDateOfLatestScmChangeset(ScmInfo scmInfo, long date) {
- Changeset changeset = Changeset.newChangesetBuilder().setDate(date).setRevision("1").build();
- when(scmInfo.getLatestChangeset()).thenReturn(changeset);
- }
-
- private static void setDateOfChangetsetAtLine(ScmInfo scmInfo, int line, long date) {
- Changeset changeset = Changeset.newChangesetBuilder().setDate(date).setRevision("1").build();
- when(scmInfo.hasChangesetForLine(line)).thenReturn(true);
- when(scmInfo.getChangesetForLine(line)).thenReturn(changeset);
- }
-
- private ScmInfo createMockScmInfo() {
- if (scmInfo == null) {
- scmInfo = mock(ScmInfo.class);
- when(scmInfoRepository.getScmInfo(component))
- .thenReturn(Optional.of(scmInfo));
- }
- return scmInfo;
- }
-
- private void setRuleUpdatedAt(long updateAt) {
- when(activeRule.getUpdatedAt()).thenReturn(updateAt);
- }
-
- private void rulePlugin(String pluginKey) {
- when(activeRule.getPluginKey()).thenReturn(pluginKey);
- }
-
- private static Location newLocation(int startLine, int endLine) {
- return Location.newBuilder().setTextRange(range(startLine, endLine)).build();
- }
-
- private static Location newLocation(int startLine, int endLine, String componentUuid) {
- return Location.newBuilder().setTextRange(range(startLine, endLine)).setComponentId(componentUuid).build();
- }
-
- private static org.sonar.db.protobuf.DbCommons.TextRange range(int startLine, int endLine) {
- return TextRange.newBuilder().setStartLine(startLine).setEndLine(endLine).build();
- }
-
- private static Flow newFlow(Location... locations) {
- Flow.Builder builder = Flow.newBuilder();
- Arrays.stream(locations).forEach(builder::addLocation);
- return builder.build();
- }
-
- private void run() {
- underTest.beforeComponent(component);
- underTest.onIssue(component, issue);
- underTest.afterComponent(component);
- }
-
- private void assertNoChangeOfCreationDate() {
- verify(issueUpdater, never())
- .setCreationDate(any(), any(), any());
- }
-
- private void assertChangeOfCreationDateTo(long createdAt) {
- verify(issueUpdater, atLeastOnce())
- .setCreationDate(same(issue), eq(new Date(createdAt)), any());
- }
- }
|