3 * Copyright (C) 2009-2024 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 org.junit.Test;
23 import org.sonar.api.rules.RuleType;
24 import org.sonar.api.utils.Duration;
25 import org.sonar.ce.task.projectanalysis.analysis.Branch;
26 import org.sonar.ce.task.projectanalysis.component.Component;
27 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
28 import org.sonar.ce.task.projectanalysis.measure.Measure;
29 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
30 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
31 import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
32 import org.sonar.core.issue.DefaultIssue;
33 import org.sonar.core.util.UuidFactoryFast;
34 import org.sonar.db.component.BranchType;
36 import static org.assertj.core.api.Assertions.assertThat;
37 import static org.mockito.ArgumentMatchers.any;
38 import static org.mockito.ArgumentMatchers.eq;
39 import static org.mockito.Mockito.mock;
40 import static org.mockito.Mockito.when;
41 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
42 import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT;
43 import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY;
44 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT;
45 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT_KEY;
46 import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT;
47 import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
48 import static org.sonar.api.rules.RuleType.BUG;
49 import static org.sonar.api.rules.RuleType.CODE_SMELL;
50 import static org.sonar.api.rules.RuleType.VULNERABILITY;
52 public class NewEffortAggregatorTest {
53 private static final Component FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid("FILE").build();
54 private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 2).addChildren(FILE).build();
57 public PeriodHolderRule periodsHolder = new PeriodHolderRule();
59 public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
60 .add(NEW_TECHNICAL_DEBT)
61 .add(NEW_RELIABILITY_REMEDIATION_EFFORT)
62 .add(NEW_SECURITY_REMEDIATION_EFFORT);
64 public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create();
65 private final NewIssueClassifier newIssueClassifier = mock(NewIssueClassifier.class);
66 private final NewEffortAggregator underTest = new NewEffortAggregator(metricRepository, measureRepository, newIssueClassifier);
69 public void sum_new_maintainability_effort_of_issues() {
70 when(newIssueClassifier.isEnabled()).thenReturn(true);
71 when(newIssueClassifier.isNew(any(), any())).thenReturn(true);
72 DefaultIssue unresolved1 = newCodeSmellIssue(10L);
73 DefaultIssue old1 = oldCodeSmellIssue(100L);
74 DefaultIssue unresolved2 = newCodeSmellIssue(30L);
75 DefaultIssue old2 = oldCodeSmellIssue(300L);
76 DefaultIssue unresolvedWithoutDebt = newCodeSmellIssueWithoutEffort();
77 DefaultIssue resolved = newCodeSmellIssue(50L).setResolution(RESOLUTION_FIXED);
79 underTest.beforeComponent(FILE);
80 underTest.onIssue(FILE, unresolved1);
81 underTest.onIssue(FILE, old1);
82 underTest.onIssue(FILE, unresolved2);
83 underTest.onIssue(FILE, old2);
84 underTest.onIssue(FILE, unresolvedWithoutDebt);
85 underTest.onIssue(FILE, resolved);
86 underTest.afterComponent(FILE);
88 assertValue(FILE, NEW_TECHNICAL_DEBT_KEY, 10 + 30);
92 public void new_maintainability_effort_is_only_computed_using_code_smell_issues() {
93 when(newIssueClassifier.isEnabled()).thenReturn(true);
94 when(newIssueClassifier.isNew(any(), any())).thenReturn(true);
95 DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
96 DefaultIssue oldSmellIssue = oldCodeSmellIssue(100);
97 // Issues of type BUG and VULNERABILITY should be ignored
98 DefaultIssue bugIssue = newBugIssue(15);
99 DefaultIssue oldBugIssue = oldBugIssue(150);
100 DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
101 DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
103 underTest.beforeComponent(FILE);
104 underTest.onIssue(FILE, codeSmellIssue);
105 underTest.onIssue(FILE, oldSmellIssue);
106 underTest.onIssue(FILE, bugIssue);
107 underTest.onIssue(FILE, oldBugIssue);
108 underTest.onIssue(FILE, vulnerabilityIssue);
109 underTest.onIssue(FILE, oldVulnerabilityIssue);
110 underTest.afterComponent(FILE);
112 // Only effort of CODE SMELL issue is used
113 assertValue(FILE, NEW_TECHNICAL_DEBT_KEY, 10);
117 public void sum_new_reliability_effort_of_issues() {
118 when(newIssueClassifier.isEnabled()).thenReturn(true);
119 when(newIssueClassifier.isNew(any(), any())).thenReturn(true);
120 DefaultIssue unresolved1 = newBugIssue(10L);
121 DefaultIssue old1 = oldBugIssue(100L);
122 DefaultIssue unresolved2 = newBugIssue(30L);
124 DefaultIssue old2 = oldBugIssue(300L);
125 DefaultIssue unresolvedWithoutDebt = newBugIssueWithoutEffort();
126 DefaultIssue resolved = newBugIssue(50L).setResolution(RESOLUTION_FIXED);
128 underTest.beforeComponent(FILE);
129 underTest.onIssue(FILE, unresolved1);
130 underTest.onIssue(FILE, old1);
131 underTest.onIssue(FILE, unresolved2);
132 underTest.onIssue(FILE, old2);
133 underTest.onIssue(FILE, unresolvedWithoutDebt);
134 underTest.onIssue(FILE, resolved);
135 underTest.afterComponent(FILE);
137 assertValue(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 10 + 30);
141 public void new_reliability_effort_is_only_computed_using_bug_issues() {
142 when(newIssueClassifier.isEnabled()).thenReturn(true);
143 when(newIssueClassifier.isNew(any(), any())).thenReturn(true);
144 DefaultIssue bugIssue = newBugIssue(15);
145 DefaultIssue oldBugIssue = oldBugIssue(150);
146 // Issues of type CODE SMELL and VULNERABILITY should be ignored
147 DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
148 DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100);
149 DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
150 DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
152 underTest.beforeComponent(FILE);
153 underTest.onIssue(FILE, bugIssue);
154 underTest.onIssue(FILE, oldBugIssue);
155 underTest.onIssue(FILE, codeSmellIssue);
156 underTest.onIssue(FILE, oldCodeSmellIssue);
157 underTest.onIssue(FILE, vulnerabilityIssue);
158 underTest.onIssue(FILE, oldVulnerabilityIssue);
159 underTest.afterComponent(FILE);
161 // Only effort of BUG issue is used
162 assertValue(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 15);
166 public void sum_new_vulnerability_effort_of_issues() {
167 when(newIssueClassifier.isEnabled()).thenReturn(true);
168 DefaultIssue unresolved1 = newVulnerabilityIssue(10L);
169 DefaultIssue old1 = oldVulnerabilityIssue(100L);
170 DefaultIssue unresolved2 = newVulnerabilityIssue(30L);
171 DefaultIssue old2 = oldVulnerabilityIssue(300L);
172 DefaultIssue unresolvedWithoutDebt = newVulnerabilityIssueWithoutEffort();
173 DefaultIssue resolved = newVulnerabilityIssue(50L).setResolution(RESOLUTION_FIXED);
174 DefaultIssue oldResolved = oldVulnerabilityIssue(500L).setResolution(RESOLUTION_FIXED);
176 underTest.beforeComponent(FILE);
177 underTest.onIssue(FILE, unresolved1);
178 underTest.onIssue(FILE, old1);
179 underTest.onIssue(FILE, unresolved2);
180 underTest.onIssue(FILE, old2);
181 underTest.onIssue(FILE, unresolvedWithoutDebt);
182 underTest.onIssue(FILE, resolved);
183 underTest.onIssue(FILE, oldResolved);
184 underTest.afterComponent(FILE);
186 assertValue(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 10 + 30);
190 public void new_security_effort_is_only_computed_using_vulnerability_issues() {
191 when(newIssueClassifier.isEnabled()).thenReturn(true);
192 when(newIssueClassifier.isNew(any(), any())).thenReturn(true);
193 DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
194 DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
195 // Issues of type CODE SMELL and BUG should be ignored
196 DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
197 DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100);
198 DefaultIssue bugIssue = newBugIssue(15);
199 DefaultIssue oldBugIssue = oldBugIssue(150);
201 underTest.beforeComponent(FILE);
202 underTest.onIssue(FILE, codeSmellIssue);
203 underTest.onIssue(FILE, oldCodeSmellIssue);
204 underTest.onIssue(FILE, bugIssue);
205 underTest.onIssue(FILE, oldBugIssue);
206 underTest.onIssue(FILE, vulnerabilityIssue);
207 underTest.onIssue(FILE, oldVulnerabilityIssue);
208 underTest.afterComponent(FILE);
210 // Only effort of VULNERABILITY issue is used
211 assertValue(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 12);
215 public void aggregate_new_characteristic_measures_of_children() {
216 when(newIssueClassifier.isEnabled()).thenReturn(true);
217 when(newIssueClassifier.isNew(any(), any())).thenReturn(true);
219 DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
220 DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100);
221 DefaultIssue bugIssue = newBugIssue(8);
222 DefaultIssue oldBugIssue = oldBugIssue(80);
223 DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
224 DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
226 DefaultIssue codeSmellProjectIssue = newCodeSmellIssue(30);
227 DefaultIssue oldCodeSmellProjectIssue = oldCodeSmellIssue(300);
228 DefaultIssue bugProjectIssue = newBugIssue(28);
229 DefaultIssue oldBugProjectIssue = oldBugIssue(280);
230 DefaultIssue vulnerabilityProjectIssue = newVulnerabilityIssue(32);
231 DefaultIssue oldVulnerabilityProjectIssue = oldVulnerabilityIssue(320);
233 underTest.beforeComponent(FILE);
234 underTest.onIssue(FILE, codeSmellIssue);
235 underTest.onIssue(FILE, oldCodeSmellIssue);
236 underTest.onIssue(FILE, bugIssue);
237 underTest.onIssue(FILE, oldBugIssue);
238 underTest.onIssue(FILE, vulnerabilityIssue);
239 underTest.onIssue(FILE, oldVulnerabilityIssue);
240 underTest.afterComponent(FILE);
241 underTest.beforeComponent(PROJECT);
242 underTest.onIssue(PROJECT, codeSmellProjectIssue);
243 underTest.onIssue(PROJECT, oldCodeSmellProjectIssue);
244 underTest.onIssue(PROJECT, bugProjectIssue);
245 underTest.onIssue(PROJECT, oldBugProjectIssue);
246 underTest.onIssue(PROJECT, vulnerabilityProjectIssue);
247 underTest.onIssue(PROJECT, oldVulnerabilityProjectIssue);
248 underTest.afterComponent(PROJECT);
250 assertValue(PROJECT, NEW_TECHNICAL_DEBT_KEY, 10 + 30);
251 assertValue(PROJECT, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 8 + 28);
252 assertValue(PROJECT, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 12 + 32);
256 public void no_measures_if_no_periods() {
257 when(newIssueClassifier.isEnabled()).thenReturn(false);
258 Branch branch = mock(Branch.class);
259 when(branch.getType()).thenReturn(BranchType.BRANCH);
260 periodsHolder.setPeriod(null);
261 DefaultIssue unresolved = newCodeSmellIssue(10);
263 underTest.beforeComponent(FILE);
264 underTest.onIssue(FILE, unresolved);
265 underTest.afterComponent(FILE);
267 assertThat(measureRepository.getRawMeasures(FILE)).isEmpty();
271 public void should_have_empty_measures_if_no_issues() {
272 when(newIssueClassifier.isEnabled()).thenReturn(true);
273 when(newIssueClassifier.isNew(any(), any())).thenReturn(true);
275 underTest.beforeComponent(FILE);
276 underTest.afterComponent(FILE);
278 assertValue(FILE, NEW_TECHNICAL_DEBT_KEY, 0);
279 assertValue(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 0);
280 assertValue(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 0);
283 private void assertValue(Component component, String metricKey, int value) {
284 Measure newMeasure = measureRepository.getRawMeasure(component, metricRepository.getByKey(metricKey)).get();
285 assertThat(newMeasure.getLongValue()).isEqualTo(value);
288 private DefaultIssue newCodeSmellIssue(long effort) {
289 return createIssue(CODE_SMELL, effort, true);
292 private DefaultIssue oldCodeSmellIssue(long effort) {
293 return createIssue(CODE_SMELL, effort, false);
296 private DefaultIssue newBugIssue(long effort) {
297 return createIssue(BUG, effort, true);
300 private DefaultIssue oldBugIssue(long effort) {
301 return createIssue(BUG, effort, false);
304 private DefaultIssue newVulnerabilityIssue(long effort) {
305 return createIssue(VULNERABILITY, effort, true);
308 private DefaultIssue oldVulnerabilityIssue(long effort) {
309 return createIssue(VULNERABILITY, effort, false);
312 private DefaultIssue newCodeSmellIssueWithoutEffort() {
313 DefaultIssue defaultIssue = new DefaultIssue()
314 .setKey(UuidFactoryFast.getInstance().create())
315 .setType(CODE_SMELL);
316 when(newIssueClassifier.isNew(any(), eq(defaultIssue))).thenReturn(true);
320 private DefaultIssue createIssue(RuleType type, long effort, boolean isNew) {
321 DefaultIssue defaultIssue = new DefaultIssue()
322 .setKey(UuidFactoryFast.getInstance().create())
323 .setEffort(Duration.create(effort))
325 when(newIssueClassifier.isNew(any(), eq(defaultIssue))).thenReturn(isNew);
329 private static DefaultIssue newBugIssueWithoutEffort() {
330 return new DefaultIssue().setType(BUG);
333 private static DefaultIssue newVulnerabilityIssueWithoutEffort() {
334 return new DefaultIssue().setType(VULNERABILITY);