3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.issue;
22 import com.google.common.collect.Iterators;
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.Collection;
27 import java.util.Collections;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.sonar.api.rule.RuleKey;
32 import org.sonar.api.rule.Severity;
33 import org.sonar.api.rules.RuleType;
34 import org.sonar.api.utils.Duration;
35 import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
36 import org.sonar.ce.task.projectanalysis.component.Component;
37 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
38 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
39 import org.sonar.ce.task.projectanalysis.issue.commonrule.CommonRuleEngine;
40 import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
41 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule;
42 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
43 import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
44 import org.sonar.core.issue.DefaultIssue;
45 import org.sonar.core.issue.tracking.Input;
46 import org.sonar.db.protobuf.DbIssues;
47 import org.sonar.scanner.protocol.Constants;
48 import org.sonar.scanner.protocol.output.ScannerReport;
49 import org.sonar.scanner.protocol.output.ScannerReport.IssueType;
50 import org.sonar.scanner.protocol.output.ScannerReport.TextRange;
51 import org.sonar.server.rule.CommonRuleKeys;
53 import static java.util.Arrays.asList;
54 import static java.util.Collections.emptyMap;
55 import static java.util.Collections.singletonList;
56 import static org.assertj.core.api.Assertions.assertThat;
57 import static org.mockito.ArgumentMatchers.any;
58 import static org.mockito.ArgumentMatchers.eq;
59 import static org.mockito.Mockito.mock;
60 import static org.mockito.Mockito.when;
61 import static org.sonar.api.issue.Issue.STATUS_OPEN;
62 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
64 @RunWith(DataProviderRunner.class)
65 public class TrackerRawInputFactoryTest {
67 private static final String FILE_UUID = "fake_uuid";
68 private static final String ANOTHER_FILE_UUID = "another_fake_uuid";
69 private static int FILE_REF = 2;
70 private static int NOT_IN_REPORT_FILE_REF = 3;
71 private static int ANOTHER_FILE_REF = 4;
73 private static ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF).setUuid(FILE_UUID).build();
74 private static ReportComponent ANOTHER_FILE = ReportComponent.builder(Component.Type.FILE, ANOTHER_FILE_REF).setUuid(ANOTHER_FILE_UUID).build();
75 private static ReportComponent PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1).addChildren(FILE, ANOTHER_FILE).build();
78 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
80 public BatchReportReaderRule reportReader = new BatchReportReaderRule();
82 public ActiveRulesHolderRule activeRulesHolder = new ActiveRulesHolderRule();
84 public RuleRepositoryRule ruleRepository = new RuleRepositoryRule();
86 private SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
87 private CommonRuleEngine commonRuleEngine = mock(CommonRuleEngine.class);
88 private IssueFilter issueFilter = mock(IssueFilter.class);
89 private TrackerRawInputFactory underTest = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash,
90 commonRuleEngine, issueFilter, ruleRepository, activeRulesHolder);
93 public void load_source_hash_sequences() {
94 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
95 Input<DefaultIssue> input = underTest.create(FILE);
97 assertThat(input.getLineHashSequence()).isNotNull();
98 assertThat(input.getLineHashSequence().getHashForLine(1)).isEqualTo("line");
99 assertThat(input.getLineHashSequence().getHashForLine(2)).isEmpty();
100 assertThat(input.getLineHashSequence().getHashForLine(3)).isEmpty();
102 assertThat(input.getBlockHashSequence()).isNotNull();
106 public void load_source_hash_sequences_only_on_files() {
107 Input<DefaultIssue> input = underTest.create(PROJECT);
109 assertThat(input.getLineHashSequence()).isNotNull();
110 assertThat(input.getBlockHashSequence()).isNotNull();
114 public void load_issues_from_report() {
115 RuleKey ruleKey = RuleKey.of("java", "S001");
116 markRuleAsActive(ruleKey);
117 when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);
119 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
120 ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
121 .setTextRange(TextRange.newBuilder().setStartLine(2).build())
122 .setMsg("the message")
123 .setRuleRepository(ruleKey.repository())
124 .setRuleKey(ruleKey.rule())
125 .setSeverity(Constants.Severity.BLOCKER)
127 .setQuickFixAvailable(true)
129 reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
130 Input<DefaultIssue> input = underTest.create(FILE);
132 Collection<DefaultIssue> issues = input.getIssues();
133 assertThat(issues).hasSize(1);
134 DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
136 // fields set by analysis report
137 assertThat(issue.ruleKey()).isEqualTo(ruleKey);
138 assertThat(issue.severity()).isEqualTo(Severity.BLOCKER);
139 assertThat(issue.line()).isEqualTo(2);
140 assertThat(issue.gap()).isEqualTo(3.14);
141 assertThat(issue.message()).isEqualTo("the message");
142 assertThat(issue.isQuickFixAvailable()).isTrue();
144 // fields set by compute engine
145 assertThat(issue.checksum()).isEqualTo(input.getLineHashSequence().getHashForLine(2));
146 assertThat(issue.tags()).isEmpty();
147 assertInitializedIssue(issue);
148 assertThat(issue.effort()).isNull();
152 public void set_rule_name_as_message_when_issue_message_from_report_is_empty() {
153 RuleKey ruleKey = RuleKey.of("java", "S001");
154 markRuleAsActive(ruleKey);
155 registerRule(ruleKey, "Rule 1");
156 when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);
157 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
158 ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
159 .setRuleRepository(ruleKey.repository())
160 .setRuleKey(ruleKey.rule())
163 reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
164 Input<DefaultIssue> input = underTest.create(FILE);
166 Collection<DefaultIssue> issues = input.getIssues();
167 assertThat(issues).hasSize(1);
168 DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
170 // fields set by analysis report
171 assertThat(issue.ruleKey()).isEqualTo(ruleKey);
173 // fields set by compute engine
174 assertInitializedIssue(issue);
175 assertThat(issue.message()).isEqualTo("Rule 1");
180 public void load_issues_from_report_missing_secondary_location_component() {
181 RuleKey ruleKey = RuleKey.of("java", "S001");
182 markRuleAsActive(ruleKey);
183 when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);
185 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
186 ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
187 .setTextRange(TextRange.newBuilder().setStartLine(2).build())
188 .setMsg("the message")
189 .setRuleRepository(ruleKey.repository())
190 .setRuleKey(ruleKey.rule())
191 .setSeverity(Constants.Severity.BLOCKER)
193 .addFlow(ScannerReport.Flow.newBuilder()
194 .addLocation(ScannerReport.IssueLocation.newBuilder()
195 .setComponentRef(FILE_REF)
196 .setMsg("Secondary location in same file")
197 .setTextRange(TextRange.newBuilder().setStartLine(2).build()))
198 .addLocation(ScannerReport.IssueLocation.newBuilder()
199 .setComponentRef(NOT_IN_REPORT_FILE_REF)
200 .setMsg("Secondary location in a missing file")
201 .setTextRange(TextRange.newBuilder().setStartLine(3).build()))
202 .addLocation(ScannerReport.IssueLocation.newBuilder()
203 .setComponentRef(ANOTHER_FILE_REF)
204 .setMsg("Secondary location in another file")
205 .setTextRange(TextRange.newBuilder().setStartLine(3).build()))
208 reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
209 Input<DefaultIssue> input = underTest.create(FILE);
211 Collection<DefaultIssue> issues = input.getIssues();
212 assertThat(issues).hasSize(1);
213 DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
215 DbIssues.Locations locations = issue.getLocations();
216 // fields set by analysis report
217 assertThat(locations.getFlowList()).hasSize(1);
218 assertThat(locations.getFlow(0).getLocationList()).hasSize(2);
219 // Not component id if location is in the same file
220 assertThat(locations.getFlow(0).getLocation(0).getComponentId()).isEmpty();
221 assertThat(locations.getFlow(0).getLocation(1).getComponentId()).isEqualTo(ANOTHER_FILE_UUID);
225 @UseDataProvider("ruleTypeAndStatusByIssueType")
226 public void load_external_issues_from_report(IssueType issueType, RuleType expectedRuleType, String expectedStatus) {
227 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
228 ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
229 .setTextRange(TextRange.newBuilder().setStartLine(2).build())
230 .setMsg("the message")
231 .setEngineId("eslint")
233 .setSeverity(Constants.Severity.BLOCKER)
237 reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue));
238 Input<DefaultIssue> input = underTest.create(FILE);
240 Collection<DefaultIssue> issues = input.getIssues();
241 assertThat(issues).hasSize(1);
242 DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
244 // fields set by analysis report
245 assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("external_eslint", "S001"));
246 assertThat(issue.severity()).isEqualTo(Severity.BLOCKER);
247 assertThat(issue.line()).isEqualTo(2);
248 assertThat(issue.effort()).isEqualTo(Duration.create(20L));
249 assertThat(issue.message()).isEqualTo("the message");
250 assertThat(issue.type()).isEqualTo(expectedRuleType);
252 // fields set by compute engine
253 assertThat(issue.checksum()).isEqualTo(input.getLineHashSequence().getHashForLine(2));
254 assertThat(issue.tags()).isEmpty();
255 assertInitializedExternalIssue(issue, expectedStatus);
259 public static Object[][] ruleTypeAndStatusByIssueType() {
260 return new Object[][] {
261 {IssueType.CODE_SMELL, RuleType.CODE_SMELL, STATUS_OPEN},
262 {IssueType.BUG, RuleType.BUG, STATUS_OPEN},
263 {IssueType.VULNERABILITY, RuleType.VULNERABILITY, STATUS_OPEN},
264 {IssueType.SECURITY_HOTSPOT, RuleType.SECURITY_HOTSPOT, STATUS_TO_REVIEW}
269 @UseDataProvider("ruleTypeAndStatusByIssueType")
270 public void load_external_issues_from_report_with_default_effort(IssueType issueType, RuleType expectedRuleType, String expectedStatus) {
271 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
272 ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
273 .setTextRange(TextRange.newBuilder().setStartLine(2).build())
274 .setMsg("the message")
275 .setEngineId("eslint")
277 .setSeverity(Constants.Severity.BLOCKER)
280 reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue));
281 Input<DefaultIssue> input = underTest.create(FILE);
283 Collection<DefaultIssue> issues = input.getIssues();
284 assertThat(issues).hasSize(1);
285 DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
287 // fields set by analysis report
288 assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("external_eslint", "S001"));
289 assertThat(issue.severity()).isEqualTo(Severity.BLOCKER);
290 assertThat(issue.line()).isEqualTo(2);
291 assertThat(issue.effort()).isEqualTo(Duration.create(0L));
292 assertThat(issue.message()).isEqualTo("the message");
293 assertThat(issue.type()).isEqualTo(expectedRuleType);
295 // fields set by compute engine
296 assertThat(issue.checksum()).isEqualTo(input.getLineHashSequence().getHashForLine(2));
297 assertThat(issue.tags()).isEmpty();
298 assertInitializedExternalIssue(issue, expectedStatus);
302 public void excludes_issues_on_inactive_rules() {
303 RuleKey ruleKey = RuleKey.of("java", "S001");
304 when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);
306 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
307 ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
308 .setTextRange(TextRange.newBuilder().setStartLine(2).build())
309 .setMsg("the message")
310 .setRuleRepository(ruleKey.repository())
311 .setRuleKey(ruleKey.rule())
312 .setSeverity(Constants.Severity.BLOCKER)
315 reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
316 Input<DefaultIssue> input = underTest.create(FILE);
318 Collection<DefaultIssue> issues = input.getIssues();
319 assertThat(issues).isEmpty();
323 public void filter_excludes_issues_from_report() {
324 RuleKey ruleKey = RuleKey.of("java", "S001");
325 markRuleAsActive(ruleKey);
326 when(issueFilter.accept(any(), eq(FILE))).thenReturn(false);
327 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
328 ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
329 .setTextRange(TextRange.newBuilder().setStartLine(2).build())
330 .setMsg("the message")
331 .setRuleRepository(ruleKey.repository())
332 .setRuleKey(ruleKey.rule())
333 .setSeverity(Constants.Severity.BLOCKER)
336 reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
337 Input<DefaultIssue> input = underTest.create(FILE);
339 Collection<DefaultIssue> issues = input.getIssues();
340 assertThat(issues).isEmpty();
344 public void exclude_issues_on_common_rules() {
345 RuleKey ruleKey = RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), "S001");
346 markRuleAsActive(ruleKey);
347 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
348 ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
349 .setMsg("the message")
350 .setRuleRepository(ruleKey.repository())
351 .setRuleKey(ruleKey.rule())
352 .setSeverity(Constants.Severity.BLOCKER)
354 reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
356 Input<DefaultIssue> input = underTest.create(FILE);
358 assertThat(input.getIssues()).isEmpty();
362 public void load_issues_of_compute_engine_common_rules() {
363 RuleKey ruleKey = RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), "InsufficientCoverage");
364 markRuleAsActive(ruleKey);
365 when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);
366 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
367 DefaultIssue ceIssue = new DefaultIssue()
369 .setMessage("not enough coverage")
371 when(commonRuleEngine.process(FILE)).thenReturn(singletonList(ceIssue));
373 Input<DefaultIssue> input = underTest.create(FILE);
375 assertThat(input.getIssues()).containsOnly(ceIssue);
376 assertInitializedIssue(input.getIssues().iterator().next());
380 public void filter_exclude_issues_on_common_rule() {
381 RuleKey ruleKey = RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), "InsufficientCoverage");
382 markRuleAsActive(ruleKey);
383 when(issueFilter.accept(any(), eq(FILE))).thenReturn(false);
384 when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
385 DefaultIssue ceIssue = new DefaultIssue()
387 .setMessage("not enough coverage")
389 when(commonRuleEngine.process(FILE)).thenReturn(singletonList(ceIssue));
391 Input<DefaultIssue> input = underTest.create(FILE);
393 assertThat(input.getIssues()).isEmpty();
396 private void assertInitializedIssue(DefaultIssue issue) {
397 assertInitializedExternalIssue(issue, STATUS_OPEN);
398 assertThat(issue.effort()).isNull();
399 assertThat(issue.effortInMinutes()).isNull();
402 private void assertInitializedExternalIssue(DefaultIssue issue, String expectedStatus) {
403 assertThat(issue.projectKey()).isEqualTo(PROJECT.getKey());
404 assertThat(issue.componentKey()).isEqualTo(FILE.getKey());
405 assertThat(issue.componentUuid()).isEqualTo(FILE.getUuid());
406 assertThat(issue.resolution()).isNull();
407 assertThat(issue.status()).isEqualTo(expectedStatus);
408 assertThat(issue.key()).isNull();
409 assertThat(issue.authorLogin()).isNull();
412 private void markRuleAsActive(RuleKey ruleKey) {
413 activeRulesHolder.put(new ActiveRule(ruleKey, Severity.CRITICAL, emptyMap(), 1_000L, null, "qp1"));
416 private void registerRule(RuleKey ruleKey, String name) {
417 DumbRule dumbRule = new DumbRule(ruleKey);
418 dumbRule.setName(name);
419 ruleRepository.add(dumbRule);