3 * Copyright (C) 2009-2023 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 java.util.Arrays;
23 import java.util.List;
25 import javax.annotation.Nullable;
26 import org.assertj.core.data.MapEntry;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.sonar.api.rules.RuleType;
30 import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
31 import org.sonar.ce.task.projectanalysis.component.Component;
32 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
33 import org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry;
34 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
35 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
36 import org.sonar.core.issue.DefaultIssue;
37 import org.sonar.db.rule.RuleTesting;
39 import static java.util.Arrays.stream;
40 import static org.assertj.core.api.Assertions.assertThat;
41 import static org.assertj.core.api.Assertions.entry;
42 import static org.mockito.ArgumentMatchers.any;
43 import static org.mockito.ArgumentMatchers.eq;
44 import static org.mockito.Mockito.mock;
45 import static org.mockito.Mockito.when;
46 import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
47 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
48 import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
49 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
50 import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
51 import static org.sonar.api.issue.Issue.STATUS_OPEN;
52 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
53 import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS;
54 import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY;
55 import static org.sonar.api.measures.CoreMetrics.BUGS;
56 import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
57 import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS;
58 import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
59 import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES;
60 import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY;
61 import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS;
62 import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY;
63 import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES;
64 import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY;
65 import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS;
66 import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS;
67 import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY;
68 import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS;
69 import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS;
70 import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY;
71 import static org.sonar.api.measures.CoreMetrics.NEW_BUGS;
72 import static org.sonar.api.measures.CoreMetrics.NEW_BUGS_KEY;
73 import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS;
74 import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS_KEY;
75 import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS;
76 import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY;
77 import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS;
78 import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS;
79 import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY;
80 import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS;
81 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS;
82 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY;
83 import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS;
84 import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY;
85 import static org.sonar.api.measures.CoreMetrics.NEW_VULNERABILITIES;
86 import static org.sonar.api.measures.CoreMetrics.NEW_VULNERABILITIES_KEY;
87 import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES;
88 import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY;
89 import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES;
90 import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS;
91 import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
92 import static org.sonar.api.measures.CoreMetrics.VIOLATIONS;
93 import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
94 import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES;
95 import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
96 import static org.sonar.api.measures.CoreMetrics.WONT_FIX_ISSUES;
97 import static org.sonar.api.measures.CoreMetrics.WONT_FIX_ISSUES_KEY;
98 import static org.sonar.api.rule.Severity.BLOCKER;
99 import static org.sonar.api.rule.Severity.CRITICAL;
100 import static org.sonar.api.rule.Severity.MAJOR;
101 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
102 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
103 import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
105 public class IssueCounterTest {
107 private static final Component FILE1 = builder(Component.Type.FILE, 1).build();
108 private static final Component FILE2 = builder(Component.Type.FILE, 2).build();
109 private static final Component FILE3 = builder(Component.Type.FILE, 3).build();
110 private static final Component PROJECT = builder(Component.Type.PROJECT, 4).addChildren(FILE1, FILE2, FILE3).build();
113 public BatchReportReaderRule reportReader = new BatchReportReaderRule();
116 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
119 public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
122 .add(REOPENED_ISSUES)
123 .add(CONFIRMED_ISSUES)
124 .add(BLOCKER_VIOLATIONS)
125 .add(CRITICAL_VIOLATIONS)
126 .add(MAJOR_VIOLATIONS)
127 .add(MINOR_VIOLATIONS)
128 .add(INFO_VIOLATIONS)
130 .add(NEW_BLOCKER_VIOLATIONS)
131 .add(NEW_CRITICAL_VIOLATIONS)
132 .add(NEW_MAJOR_VIOLATIONS)
133 .add(NEW_MINOR_VIOLATIONS)
134 .add(NEW_INFO_VIOLATIONS)
135 .add(FALSE_POSITIVE_ISSUES)
136 .add(WONT_FIX_ISSUES)
139 .add(VULNERABILITIES)
140 .add(SECURITY_HOTSPOTS)
141 .add(NEW_CODE_SMELLS)
143 .add(NEW_VULNERABILITIES)
144 .add(NEW_SECURITY_HOTSPOTS);
147 public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
148 private NewIssueClassifier newIssueClassifier = mock(NewIssueClassifier.class);
149 private IssueCounter underTest = new IssueCounter(metricRepository, measureRepository, newIssueClassifier);
152 public void count_issues_by_status() {
153 // bottom-up traversal -> from files to project
154 underTest.beforeComponent(FILE1);
155 underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
156 underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
157 underTest.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR));
158 underTest.afterComponent(FILE1);
160 underTest.beforeComponent(FILE2);
161 underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
162 underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
163 underTest.afterComponent(FILE2);
165 underTest.beforeComponent(FILE3);
166 // Security hotspot should be ignored
167 underTest.onIssue(FILE3, createSecurityHotspot().setStatus(STATUS_OPEN));
168 underTest.afterComponent(FILE3);
170 underTest.beforeComponent(PROJECT);
171 underTest.afterComponent(PROJECT);
173 assertMeasures(FILE1, entry(VIOLATIONS_KEY, 1), entry(OPEN_ISSUES_KEY, 1), entry(CONFIRMED_ISSUES_KEY, 0));
174 assertMeasures(FILE2, entry(VIOLATIONS_KEY, 2), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 2));
175 assertMeasures(FILE3, entry(VIOLATIONS_KEY, 0));
176 assertMeasures(PROJECT, entry(VIOLATIONS_KEY, 3), entry(OPEN_ISSUES_KEY, 1), entry(CONFIRMED_ISSUES_KEY, 2));
180 public void count_issues_by_resolution() {
181 // bottom-up traversal -> from files to project
182 underTest.beforeComponent(FILE1);
183 underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
184 underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
185 underTest.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR));
186 underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_CLOSED, MAJOR));
187 underTest.afterComponent(FILE1);
189 underTest.beforeComponent(FILE2);
190 underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
191 underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
192 underTest.onIssue(FILE2, createIssue(RESOLUTION_WONT_FIX, STATUS_CLOSED, MAJOR));
193 underTest.afterComponent(FILE2);
195 underTest.beforeComponent(FILE3);
196 // Security hotspot should be ignored
197 underTest.onIssue(FILE3, createSecurityHotspot().setResolution(RESOLUTION_WONT_FIX));
198 underTest.afterComponent(FILE3);
200 underTest.beforeComponent(PROJECT);
201 underTest.afterComponent(PROJECT);
203 assertMeasures(FILE1, entry(VIOLATIONS_KEY, 1), entry(FALSE_POSITIVE_ISSUES_KEY, 1), entry(WONT_FIX_ISSUES_KEY, 1));
204 assertMeasures(FILE2, entry(VIOLATIONS_KEY, 2), entry(FALSE_POSITIVE_ISSUES_KEY, 0), entry(WONT_FIX_ISSUES_KEY, 1));
205 assertMeasures(FILE3, entry(VIOLATIONS_KEY, 0));
206 assertMeasures(PROJECT, entry(VIOLATIONS_KEY, 3), entry(FALSE_POSITIVE_ISSUES_KEY, 1), entry(WONT_FIX_ISSUES_KEY, 2));
210 public void count_unresolved_issues_by_severity() {
211 // bottom-up traversal -> from files to project
212 underTest.beforeComponent(FILE1);
213 underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
214 // this resolved issue is ignored
215 underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
216 underTest.afterComponent(FILE1);
218 underTest.beforeComponent(FILE2);
219 underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
220 underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
221 underTest.afterComponent(FILE2);
223 underTest.beforeComponent(PROJECT);
224 // Security hotspot should be ignored
225 underTest.onIssue(FILE3, createSecurityHotspot().setSeverity(MAJOR));
226 underTest.afterComponent(PROJECT);
228 assertMeasures(FILE1, entry(BLOCKER_VIOLATIONS_KEY, 1), entry(CRITICAL_VIOLATIONS_KEY, 0), entry(MAJOR_VIOLATIONS_KEY, 0));
229 assertMeasures(FILE2, entry(BLOCKER_VIOLATIONS_KEY, 1), entry(CRITICAL_VIOLATIONS_KEY, 0), entry(MAJOR_VIOLATIONS_KEY, 1));
230 assertMeasures(PROJECT, entry(BLOCKER_VIOLATIONS_KEY, 2), entry(CRITICAL_VIOLATIONS_KEY, 0), entry(MAJOR_VIOLATIONS_KEY, 1));
234 public void count_unresolved_issues_by_type() {
235 // bottom-up traversal -> from files to project
236 // file1 : one open code smell, one closed code smell (which will be excluded from metric)
237 underTest.beforeComponent(FILE1);
238 underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.CODE_SMELL));
239 underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(RuleType.CODE_SMELL));
240 underTest.afterComponent(FILE1);
243 underTest.beforeComponent(FILE2);
244 underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER).setType(RuleType.BUG));
245 underTest.afterComponent(FILE2);
247 // file3 : one unresolved security hotspot
248 underTest.beforeComponent(FILE3);
249 underTest.onIssue(FILE3, createSecurityHotspot());
250 underTest.onIssue(FILE3, createSecurityHotspot().setResolution(RESOLUTION_WONT_FIX).setStatus(STATUS_CLOSED));
251 underTest.afterComponent(FILE3);
253 underTest.beforeComponent(PROJECT);
254 underTest.afterComponent(PROJECT);
256 assertMeasures(FILE1, entry(CODE_SMELLS_KEY, 1), entry(BUGS_KEY, 0), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 0));
257 assertMeasures(FILE2, entry(CODE_SMELLS_KEY, 0), entry(BUGS_KEY, 1), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 0));
258 assertMeasures(FILE3, entry(CODE_SMELLS_KEY, 0), entry(BUGS_KEY, 0), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 1));
259 assertMeasures(PROJECT, entry(CODE_SMELLS_KEY, 1), entry(BUGS_KEY, 1), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 1));
263 public void count_new_issues() {
264 when(newIssueClassifier.isEnabled()).thenReturn(true);
266 underTest.beforeComponent(FILE1);
267 // created before -> existing issues (so ignored)
268 underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.CODE_SMELL));
269 underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.BUG));
271 // created after -> 4 new issues but 1 is closed
272 underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(RuleType.CODE_SMELL));
273 underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(RuleType.BUG));
274 underTest.onIssue(FILE1, createNewIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(RuleType.BUG));
275 underTest.onIssue(FILE1, createNewSecurityHotspot());
276 underTest.onIssue(FILE1, createNewSecurityHotspot().setResolution(RESOLUTION_WONT_FIX).setStatus(STATUS_CLOSED));
277 underTest.afterComponent(FILE1);
279 underTest.beforeComponent(FILE2);
280 underTest.afterComponent(FILE2);
282 underTest.beforeComponent(PROJECT);
283 underTest.afterComponent(PROJECT);
285 assertValues(FILE1, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
286 entry(NEW_CODE_SMELLS_KEY, 1), entry(NEW_BUGS_KEY, 1), entry(NEW_VULNERABILITIES_KEY, 0), entry(NEW_SECURITY_HOTSPOTS_KEY, 1));
287 assertValues(PROJECT, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
288 entry(NEW_CODE_SMELLS_KEY, 1), entry(NEW_BUGS_KEY, 1), entry(NEW_VULNERABILITIES_KEY, 0), entry(NEW_SECURITY_HOTSPOTS_KEY, 1));
292 public void exclude_hotspots_from_issue_counts() {
293 // bottom-up traversal -> from files to project
294 underTest.beforeComponent(FILE1);
295 underTest.onIssue(FILE1, createSecurityHotspot());
296 underTest.onIssue(FILE1, createSecurityHotspot());
297 underTest.afterComponent(FILE1);
299 underTest.beforeComponent(FILE2);
300 underTest.onIssue(FILE2, createSecurityHotspot());
301 underTest.afterComponent(FILE2);
303 underTest.beforeComponent(FILE3);
304 underTest.afterComponent(FILE3);
306 underTest.beforeComponent(PROJECT);
307 underTest.afterComponent(PROJECT);
309 assertMeasures(FILE1, entry(VIOLATIONS_KEY, 0), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 0));
310 assertMeasures(FILE2, entry(VIOLATIONS_KEY, 0), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 0));
311 assertMeasures(FILE3, entry(VIOLATIONS_KEY, 0));
312 assertMeasures(PROJECT, entry(VIOLATIONS_KEY, 0), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 0));
316 public void exclude_new_hotspots_from_issue_counts() {
317 when(newIssueClassifier.isEnabled()).thenReturn(true);
319 underTest.beforeComponent(FILE1);
320 // created before -> existing issues (so ignored)
321 underTest.onIssue(FILE1, createSecurityHotspot());
322 underTest.onIssue(FILE1, createSecurityHotspot());
324 // created after, but closed
325 underTest.onIssue(FILE1, createNewSecurityHotspot().setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX));
327 for (String severity : Arrays.asList(CRITICAL, BLOCKER, MAJOR)) {
328 DefaultIssue issue = createNewSecurityHotspot();
329 issue.setSeverity(severity);
330 underTest.onIssue(FILE1, issue);
332 underTest.afterComponent(FILE1);
334 underTest.beforeComponent(FILE2);
335 underTest.afterComponent(FILE2);
337 underTest.beforeComponent(PROJECT);
338 underTest.afterComponent(PROJECT);
340 assertValues(FILE1, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
341 entry(NEW_VULNERABILITIES_KEY, 0));
342 assertValues(PROJECT, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
343 entry(NEW_VULNERABILITIES_KEY, 0));
347 private final void assertValues(Component componentRef, MapEntry<String, Integer>... entries) {
348 assertThat(measureRepository.getRawMeasures(componentRef).entrySet()
350 .map(e -> entry(e.getKey(), e.getValue().getIntValue())))
355 private final void assertMeasures(Component componentRef, Map.Entry<String, Integer>... entries) {
356 List<MeasureRepoEntry> expected = stream(entries)
357 .map(e -> entryOf(e.getKey(), newMeasureBuilder().create(e.getValue())))
360 assertThat(measureRepository.getRawMeasures(componentRef).entrySet().stream().map(e -> entryOf(e.getKey(), e.getValue())))
361 .containsAll(expected);
364 private DefaultIssue createNewIssue(@Nullable String resolution, String status, String severity) {
365 return createNewIssue(resolution, status, severity, RuleType.CODE_SMELL);
368 private DefaultIssue createNewIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) {
369 DefaultIssue issue = createIssue(resolution, status, severity, ruleType);
370 when(newIssueClassifier.isNew(any(), eq(issue))).thenReturn(true);
374 private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) {
375 return createIssue(resolution, status, severity, RuleType.CODE_SMELL);
378 private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) {
379 return new DefaultIssue()
380 .setResolution(resolution).setStatus(status)
381 .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
385 private static DefaultIssue createSecurityHotspot() {
386 return createIssue(null, STATUS_OPEN, "MAJOR", RuleType.SECURITY_HOTSPOT);
389 private DefaultIssue createNewSecurityHotspot() {
390 return createNewIssue(null, STATUS_OPEN, "MAJOR", RuleType.SECURITY_HOTSPOT);