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.

IssuePublisherTest.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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.scanner.issue;
  21. import java.io.IOException;
  22. import java.util.Collections;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import org.junit.Before;
  26. import org.junit.Rule;
  27. import org.junit.Test;
  28. import org.junit.rules.TemporaryFolder;
  29. import org.junit.runner.RunWith;
  30. import org.mockito.ArgumentCaptor;
  31. import org.mockito.junit.MockitoJUnitRunner;
  32. import org.sonar.api.batch.bootstrap.ProjectDefinition;
  33. import org.sonar.api.batch.fs.InputComponent;
  34. import org.sonar.api.batch.fs.internal.DefaultInputFile;
  35. import org.sonar.api.batch.fs.internal.DefaultInputProject;
  36. import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
  37. import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
  38. import org.sonar.api.batch.rule.internal.NewActiveRule;
  39. import org.sonar.api.batch.sensor.issue.NewIssue;
  40. import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue;
  41. import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
  42. import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation;
  43. import org.sonar.api.batch.sensor.issue.internal.DefaultMessageFormatting;
  44. import org.sonar.api.rule.RuleKey;
  45. import org.sonar.api.rule.Severity;
  46. import org.sonar.api.rules.RuleType;
  47. import org.sonar.scanner.protocol.output.ScannerReport;
  48. import org.sonar.scanner.protocol.output.ScannerReport.FlowType;
  49. import org.sonar.scanner.report.ReportPublisher;
  50. import static org.assertj.core.api.Assertions.assertThat;
  51. import static org.assertj.core.api.Assertions.tuple;
  52. import static org.mockito.ArgumentMatchers.any;
  53. import static org.mockito.ArgumentMatchers.eq;
  54. import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
  55. import static org.mockito.Mockito.mock;
  56. import static org.mockito.Mockito.verify;
  57. import static org.mockito.Mockito.verifyNoInteractions;
  58. import static org.mockito.Mockito.when;
  59. import static org.sonar.api.batch.sensor.issue.MessageFormatting.Type.CODE;
  60. @RunWith(MockitoJUnitRunner.class)
  61. public class IssuePublisherTest {
  62. static final RuleKey JAVA_RULE_KEY = RuleKey.of("java", "AvoidCycle");
  63. private static final RuleKey NOSONAR_RULE_KEY = RuleKey.of("java", "NoSonarCheck");
  64. private DefaultInputProject project;
  65. @Rule
  66. public TemporaryFolder temp = new TemporaryFolder();
  67. public IssueFilters filters = mock(IssueFilters.class);
  68. private final ActiveRulesBuilder activeRulesBuilder = new ActiveRulesBuilder();
  69. private IssuePublisher moduleIssues;
  70. private final DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php").initMetadata("Foo\nBar\nBiz\n").build();
  71. private final ReportPublisher reportPublisher = mock(ReportPublisher.class, RETURNS_DEEP_STUBS);
  72. @Before
  73. public void prepare() throws IOException {
  74. project = new DefaultInputProject(ProjectDefinition.create()
  75. .setKey("foo")
  76. .setBaseDir(temp.newFolder())
  77. .setWorkDir(temp.newFolder()));
  78. activeRulesBuilder.addRule(new NewActiveRule.Builder()
  79. .setRuleKey(JAVA_RULE_KEY)
  80. .setSeverity(Severity.INFO)
  81. .setQProfileKey("qp-1")
  82. .build());
  83. initModuleIssues();
  84. }
  85. @Test
  86. public void ignore_null_active_rule() {
  87. RuleKey INACTIVE_RULE_KEY = RuleKey.of("repo", "inactive");
  88. initModuleIssues();
  89. DefaultIssue issue = new DefaultIssue(project)
  90. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
  91. .forRule(INACTIVE_RULE_KEY);
  92. boolean added = moduleIssues.initAndAddIssue(issue);
  93. assertThat(added).isFalse();
  94. verifyNoInteractions(reportPublisher);
  95. }
  96. @Test
  97. public void ignore_null_rule_of_active_rule() {
  98. initModuleIssues();
  99. DefaultIssue issue = new DefaultIssue(project)
  100. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
  101. .forRule(JAVA_RULE_KEY);
  102. boolean added = moduleIssues.initAndAddIssue(issue);
  103. assertThat(added).isFalse();
  104. verifyNoInteractions(reportPublisher);
  105. }
  106. @Test
  107. public void add_issue_to_cache() {
  108. initModuleIssues();
  109. final String ruleDescriptionContextKey = "spring";
  110. DefaultIssue issue = new DefaultIssue(project)
  111. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
  112. .forRule(JAVA_RULE_KEY)
  113. .overrideSeverity(org.sonar.api.batch.rule.Severity.CRITICAL)
  114. .setQuickFixAvailable(true)
  115. .setRuleDescriptionContextKey(ruleDescriptionContextKey)
  116. .setCodeVariants(List.of("variant1", "variant2"));
  117. when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true);
  118. boolean added = moduleIssues.initAndAddIssue(issue);
  119. assertThat(added).isTrue();
  120. ArgumentCaptor<ScannerReport.Issue> argument = ArgumentCaptor.forClass(ScannerReport.Issue.class);
  121. verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.scannerId()), argument.capture());
  122. assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL);
  123. assertThat(argument.getValue().getQuickFixAvailable()).isTrue();
  124. assertThat(argument.getValue().getRuleDescriptionContextKey()).isEqualTo(ruleDescriptionContextKey);
  125. assertThat(argument.getValue().getCodeVariantsList()).containsExactly("variant1", "variant2");
  126. }
  127. @Test
  128. public void add_issue_flows_to_cache() {
  129. initModuleIssues();
  130. DefaultMessageFormatting messageFormatting = new DefaultMessageFormatting().start(0).end(4).type(CODE);
  131. DefaultIssue issue = new DefaultIssue(project)
  132. .at(new DefaultIssueLocation().on(file))
  133. // Flow without type
  134. .addFlow(List.of(new DefaultIssueLocation().on(file).at(file.selectLine(1)).message("Foo1", List.of(messageFormatting)),
  135. new DefaultIssueLocation().on(file).at(file.selectLine(2)).message("Foo2")))
  136. // Flow with type and description
  137. .addFlow(List.of(new DefaultIssueLocation().on(file)), NewIssue.FlowType.DATA, "description")
  138. // Flow with execution type and no description
  139. .addFlow(List.of(new DefaultIssueLocation().on(file)), NewIssue.FlowType.EXECUTION, null)
  140. .forRule(JAVA_RULE_KEY);
  141. when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true);
  142. moduleIssues.initAndAddIssue(issue);
  143. ArgumentCaptor<ScannerReport.Issue> argument = ArgumentCaptor.forClass(ScannerReport.Issue.class);
  144. verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.scannerId()), argument.capture());
  145. List<ScannerReport.Flow> writtenFlows = argument.getValue().getFlowList();
  146. assertThat(writtenFlows)
  147. .extracting(ScannerReport.Flow::getDescription, ScannerReport.Flow::getType)
  148. .containsExactly(tuple("", FlowType.UNDEFINED), tuple("description", FlowType.DATA), tuple("", FlowType.EXECUTION));
  149. assertThat(writtenFlows.get(0).getLocationCount()).isEqualTo(2);
  150. assertThat(writtenFlows.get(0).getLocationList()).containsExactly(
  151. ScannerReport.IssueLocation.newBuilder()
  152. .setComponentRef(file.scannerId())
  153. .setMsg("Foo1")
  154. .addMsgFormatting(ScannerReport.MessageFormatting.newBuilder().setStart(0).setEnd(4).setType(ScannerReport.MessageFormattingType.CODE).build())
  155. .setTextRange(ScannerReport.TextRange.newBuilder().setStartLine(1).setEndLine(1).setEndOffset(3).build())
  156. .build(),
  157. ScannerReport.IssueLocation.newBuilder()
  158. .setComponentRef(file.scannerId())
  159. .setMsg("Foo2")
  160. .setTextRange(ScannerReport.TextRange.newBuilder().setStartLine(2).setEndLine(2).setEndOffset(3).build())
  161. .build());
  162. }
  163. @Test
  164. public void add_external_issue_to_cache() {
  165. initModuleIssues();
  166. DefaultExternalIssue issue = new DefaultExternalIssue(project)
  167. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
  168. .type(RuleType.BUG)
  169. .forRule(JAVA_RULE_KEY)
  170. .severity(org.sonar.api.batch.rule.Severity.CRITICAL);
  171. moduleIssues.initAndAddExternalIssue(issue);
  172. ArgumentCaptor<ScannerReport.ExternalIssue> argument = ArgumentCaptor.forClass(ScannerReport.ExternalIssue.class);
  173. verify(reportPublisher.getWriter()).appendComponentExternalIssue(eq(file.scannerId()), argument.capture());
  174. assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL);
  175. }
  176. @Test
  177. public void use_severity_from_active_rule_if_no_severity_on_issue() {
  178. initModuleIssues();
  179. DefaultIssue issue = new DefaultIssue(project)
  180. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
  181. .forRule(JAVA_RULE_KEY);
  182. when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true);
  183. moduleIssues.initAndAddIssue(issue);
  184. ArgumentCaptor<ScannerReport.Issue> argument = ArgumentCaptor.forClass(ScannerReport.Issue.class);
  185. verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.scannerId()), argument.capture());
  186. assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.INFO);
  187. }
  188. @Test
  189. public void filter_issue() {
  190. DefaultIssue issue = new DefaultIssue(project)
  191. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message(""))
  192. .forRule(JAVA_RULE_KEY);
  193. when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(false);
  194. boolean added = moduleIssues.initAndAddIssue(issue);
  195. assertThat(added).isFalse();
  196. verifyNoInteractions(reportPublisher);
  197. }
  198. @Test
  199. public void should_ignore_lines_commented_with_nosonar() {
  200. initModuleIssues();
  201. DefaultIssue issue = new DefaultIssue(project)
  202. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message(""))
  203. .forRule(JAVA_RULE_KEY);
  204. file.noSonarAt(new HashSet<>(Collections.singletonList(3)));
  205. boolean added = moduleIssues.initAndAddIssue(issue);
  206. assertThat(added).isFalse();
  207. verifyNoInteractions(reportPublisher);
  208. }
  209. @Test
  210. public void should_accept_issues_on_no_sonar_rules() {
  211. // The "No Sonar" rule logs violations on the lines that are flagged with "NOSONAR" !!
  212. activeRulesBuilder.addRule(new NewActiveRule.Builder()
  213. .setRuleKey(NOSONAR_RULE_KEY)
  214. .setSeverity(Severity.INFO)
  215. .setQProfileKey("qp-1")
  216. .build());
  217. initModuleIssues();
  218. file.noSonarAt(new HashSet<>(Collections.singletonList(3)));
  219. DefaultIssue issue = new DefaultIssue(project)
  220. .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message(""))
  221. .forRule(NOSONAR_RULE_KEY);
  222. when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true);
  223. boolean added = moduleIssues.initAndAddIssue(issue);
  224. assertThat(added).isTrue();
  225. verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.scannerId()), any());
  226. }
  227. /**
  228. * Every rules and active rules has to be added in builders before creating IssuePublisher
  229. */
  230. private void initModuleIssues() {
  231. moduleIssues = new IssuePublisher(activeRulesBuilder.build(), filters, reportPublisher);
  232. }
  233. }