3 * Copyright (C) 2009-2019 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.qualitymodel;
22 import org.junit.Before;
23 import org.junit.Rule;
24 import org.junit.Test;
25 import org.sonar.ce.task.projectanalysis.component.Component;
26 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
27 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
28 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
29 import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler;
30 import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesRepositoryRule;
31 import org.sonar.ce.task.projectanalysis.measure.Measure;
32 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
33 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
34 import org.sonar.server.measure.DebtRatingGrid;
35 import org.sonar.server.measure.Rating;
37 import static java.util.Collections.singletonList;
38 import static org.assertj.core.api.Assertions.assertThat;
39 import static org.mockito.Mockito.mock;
40 import static org.mockito.Mockito.when;
41 import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST;
42 import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
43 import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A;
44 import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY;
45 import static org.sonar.api.measures.CoreMetrics.NCLOC;
46 import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
47 import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO;
48 import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO_KEY;
49 import static org.sonar.api.measures.CoreMetrics.SQALE_RATING;
50 import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
51 import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT;
52 import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
53 import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
54 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
55 import static org.sonar.ce.task.projectanalysis.component.Component.Type.MODULE;
56 import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
57 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
58 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
59 import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
60 import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.toEntries;
61 import static org.sonar.server.measure.Rating.A;
62 import static org.sonar.server.measure.Rating.C;
63 import static org.sonar.server.measure.Rating.E;
65 public class MaintainabilityMeasuresVisitorTest {
67 static final String LANGUAGE_KEY_1 = "lKey1";
68 static final String LANGUAGE_KEY_2 = "lKey2";
70 static final double[] RATING_GRID = new double[] {0.1, 0.2, 0.5, 1};
72 static final long DEV_COST_LANGUAGE_1 = 30;
73 static final long DEV_COST_LANGUAGE_2 = 42;
75 static final int PROJECT_REF = 1;
76 static final int MODULE_REF = 12;
77 static final int DIRECTORY_REF = 123;
78 static final int FILE_1_REF = 1231;
79 static final int FILE_2_REF = 1232;
81 static final Component ROOT_PROJECT = builder(Component.Type.PROJECT, PROJECT_REF).setKey("project")
83 builder(MODULE, MODULE_REF).setKey("module")
85 builder(DIRECTORY, DIRECTORY_REF).setKey("directory")
87 builder(FILE, FILE_1_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file1").build(),
88 builder(FILE, FILE_2_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file2").build())
94 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
97 public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
99 .add(DEVELOPMENT_COST)
101 .add(SQALE_DEBT_RATIO)
103 .add(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A);
106 public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
109 public ComponentIssuesRepositoryRule componentIssuesRepositoryRule = new ComponentIssuesRepositoryRule(treeRootHolder);
111 private RatingSettings ratingSettings = mock(RatingSettings.class);
113 private VisitorsCrawler underTest;
116 public void setUp() {
117 // assumes rating configuration is consistent
118 when(ratingSettings.getDebtRatingGrid()).thenReturn(new DebtRatingGrid(RATING_GRID));
119 when(ratingSettings.getDevCost(LANGUAGE_KEY_1)).thenReturn(DEV_COST_LANGUAGE_1);
120 when(ratingSettings.getDevCost(LANGUAGE_KEY_2)).thenReturn(DEV_COST_LANGUAGE_2);
122 underTest = new VisitorsCrawler(singletonList(new MaintainabilityMeasuresVisitor(metricRepository, measureRepository, ratingSettings)));
126 public void measures_created_for_project_are_all_zero_when_they_have_no_FILE_child() {
127 ReportComponent root = builder(PROJECT, 1).build();
128 treeRootHolder.setRoot(root);
130 underTest.visit(root);
132 assertThat(toEntries(measureRepository.getRawMeasures(root)))
134 entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create("0")),
135 entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(0d, 1)),
136 entryOf(SQALE_RATING_KEY, createMaintainabilityRatingMeasure(A)),
137 entryOf(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, newMeasureBuilder().create(0L)));
141 public void compute_development_cost() {
142 ReportComponent root = builder(PROJECT, 1)
146 builder(DIRECTORY, 111)
148 createFileComponent(LANGUAGE_KEY_1, 1111),
149 createFileComponent(LANGUAGE_KEY_2, 1112),
150 // Unit test should not be ignored
151 builder(FILE, 1113).setFileAttributes(new FileAttributes(true, LANGUAGE_KEY_1, 1)).build())
153 builder(DIRECTORY, 112)
155 createFileComponent(LANGUAGE_KEY_2, 1121))
160 builder(DIRECTORY, 121)
162 createFileComponent(LANGUAGE_KEY_1, 1211))
164 builder(DIRECTORY, 122).build())
166 builder(MODULE, 13).build())
169 treeRootHolder.setRoot(root);
172 addRawMeasure(NCLOC_KEY, 1111, ncloc1111);
175 addRawMeasure(NCLOC_KEY, 1112, ncloc1112);
178 addRawMeasure(NCLOC_KEY, 1113, ncloc1113);
180 int nclocValue1121 = 30;
181 addRawMeasure(NCLOC_KEY, 1121, nclocValue1121);
184 addRawMeasure(NCLOC_KEY, 1211, ncloc1211);
186 underTest.visit(root);
188 // verify measures on files
189 verifyAddedRawMeasure(1111, DEVELOPMENT_COST_KEY, Long.toString(ncloc1111 * DEV_COST_LANGUAGE_1));
190 verifyAddedRawMeasure(1112, DEVELOPMENT_COST_KEY, Long.toString(ncloc1112 * DEV_COST_LANGUAGE_2));
191 verifyAddedRawMeasure(1113, DEVELOPMENT_COST_KEY, Long.toString(ncloc1113 * DEV_COST_LANGUAGE_1));
192 verifyAddedRawMeasure(1121, DEVELOPMENT_COST_KEY, Long.toString(nclocValue1121 * DEV_COST_LANGUAGE_2));
193 verifyAddedRawMeasure(1211, DEVELOPMENT_COST_KEY, Long.toString(ncloc1211 * DEV_COST_LANGUAGE_1));
195 // directory has no children => no file => 0 everywhere and A rating
196 verifyAddedRawMeasure(122, DEVELOPMENT_COST_KEY, "0");
198 // directory has children => dev cost is aggregated
199 verifyAddedRawMeasure(111, DEVELOPMENT_COST_KEY, Long.toString(
200 ncloc1111 * DEV_COST_LANGUAGE_1 +
201 ncloc1112 * DEV_COST_LANGUAGE_2 +
202 ncloc1113 * DEV_COST_LANGUAGE_1));
203 verifyAddedRawMeasure(112, DEVELOPMENT_COST_KEY, Long.toString(nclocValue1121 * DEV_COST_LANGUAGE_2));
204 verifyAddedRawMeasure(121, DEVELOPMENT_COST_KEY, Long.toString(ncloc1211 * DEV_COST_LANGUAGE_1));
206 // just for fun, we didn't define any debt on module => they must all have rating A
207 verifyAddedRawMeasure(11, DEVELOPMENT_COST_KEY, Long.toString(
208 ncloc1111 * DEV_COST_LANGUAGE_1 +
209 ncloc1112 * DEV_COST_LANGUAGE_2 +
210 ncloc1113 * DEV_COST_LANGUAGE_1 +
211 nclocValue1121 * DEV_COST_LANGUAGE_2));
212 verifyAddedRawMeasure(12, DEVELOPMENT_COST_KEY, Long.toString(ncloc1211 * DEV_COST_LANGUAGE_1));
213 verifyAddedRawMeasure(13, DEVELOPMENT_COST_KEY, "0");
214 verifyAddedRawMeasure(1, DEVELOPMENT_COST_KEY, Long.toString(
215 ncloc1111 * DEV_COST_LANGUAGE_1 +
216 ncloc1112 * DEV_COST_LANGUAGE_2 +
217 ncloc1113 * DEV_COST_LANGUAGE_1 +
218 nclocValue1121 * DEV_COST_LANGUAGE_2 +
219 ncloc1211 * DEV_COST_LANGUAGE_1));
223 public void compute_maintainability_debt_ratio_measure() {
224 treeRootHolder.setRoot(ROOT_PROJECT);
227 addRawMeasure(NCLOC_KEY, FILE_1_REF, file1Ncloc);
228 long file1MaintainabilityCost = 100L;
229 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, file1MaintainabilityCost);
232 addRawMeasure(NCLOC_KEY, FILE_2_REF, file2Ncloc);
233 long file2MaintainabilityCost = 1L;
234 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_2_REF, file2MaintainabilityCost);
236 long directoryMaintainabilityCost = 100L;
237 addRawMeasure(TECHNICAL_DEBT_KEY, DIRECTORY_REF, directoryMaintainabilityCost);
239 long moduleMaintainabilityCost = 100L;
240 addRawMeasure(TECHNICAL_DEBT_KEY, MODULE_REF, moduleMaintainabilityCost);
242 long projectMaintainabilityCost = 1000L;
243 addRawMeasure(TECHNICAL_DEBT_KEY, PROJECT_REF, projectMaintainabilityCost);
245 underTest.visit(ROOT_PROJECT);
247 verifyAddedRawMeasure(FILE_1_REF, SQALE_DEBT_RATIO_KEY, file1MaintainabilityCost * 1d / (file1Ncloc * DEV_COST_LANGUAGE_1) * 100);
248 verifyAddedRawMeasure(FILE_2_REF, SQALE_DEBT_RATIO_KEY, file2MaintainabilityCost * 1d / (file2Ncloc * DEV_COST_LANGUAGE_1) * 100);
249 verifyAddedRawMeasure(DIRECTORY_REF, SQALE_DEBT_RATIO_KEY, directoryMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST_LANGUAGE_1) * 100);
250 verifyAddedRawMeasure(MODULE_REF, SQALE_DEBT_RATIO_KEY, moduleMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST_LANGUAGE_1) * 100);
251 verifyAddedRawMeasure(PROJECT_REF, SQALE_DEBT_RATIO_KEY, projectMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST_LANGUAGE_1) * 100);
255 public void compute_maintainability_rating_measure() {
256 treeRootHolder.setRoot(ROOT_PROJECT);
258 addRawMeasure(NCLOC_KEY, FILE_1_REF, 10);
259 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, 100L);
261 addRawMeasure(NCLOC_KEY, FILE_2_REF, 5);
262 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_2_REF, 1L);
264 addRawMeasure(TECHNICAL_DEBT_KEY, DIRECTORY_REF, 100L);
265 addRawMeasure(TECHNICAL_DEBT_KEY, MODULE_REF, 100L);
266 addRawMeasure(TECHNICAL_DEBT_KEY, PROJECT_REF, 1000L);
268 underTest.visit(ROOT_PROJECT);
270 verifyAddedRawMeasure(FILE_1_REF, SQALE_RATING_KEY, C);
271 verifyAddedRawMeasure(FILE_2_REF, SQALE_RATING_KEY, A);
272 verifyAddedRawMeasure(DIRECTORY_REF, SQALE_RATING_KEY, C);
273 verifyAddedRawMeasure(MODULE_REF, SQALE_RATING_KEY, C);
274 verifyAddedRawMeasure(PROJECT_REF, SQALE_RATING_KEY, E);
278 public void compute_effort_to_maintainability_rating_A_measure() {
279 treeRootHolder.setRoot(ROOT_PROJECT);
282 long file1Effort = 100L;
283 addRawMeasure(NCLOC_KEY, FILE_1_REF, file1Ncloc);
284 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, file1Effort);
287 long file2Effort = 20L;
288 addRawMeasure(NCLOC_KEY, FILE_2_REF, file2Ncloc);
289 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_2_REF, file2Effort);
291 long dirEffort = 120L;
292 addRawMeasure(TECHNICAL_DEBT_KEY, DIRECTORY_REF, dirEffort);
294 long moduleEffort = 120L;
295 addRawMeasure(TECHNICAL_DEBT_KEY, MODULE_REF, moduleEffort);
297 long projectEffort = 150L;
298 addRawMeasure(TECHNICAL_DEBT_KEY, PROJECT_REF, projectEffort);
300 underTest.visit(ROOT_PROJECT);
302 verifyAddedRawMeasure(FILE_1_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY,
303 (long) (file1Effort - RATING_GRID[0] * file1Ncloc * DEV_COST_LANGUAGE_1));
304 verifyAddedRawMeasure(FILE_2_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY,
305 (long) (file2Effort - RATING_GRID[0] * file2Ncloc * DEV_COST_LANGUAGE_1));
306 verifyAddedRawMeasure(DIRECTORY_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY,
307 (long) (dirEffort - RATING_GRID[0] * (file1Ncloc + file2Ncloc) * DEV_COST_LANGUAGE_1));
308 verifyAddedRawMeasure(MODULE_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY,
309 (long) (moduleEffort - RATING_GRID[0] * (file1Ncloc + file2Ncloc) * DEV_COST_LANGUAGE_1));
310 verifyAddedRawMeasure(PROJECT_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY,
311 (long) (projectEffort - RATING_GRID[0] * (file1Ncloc + file2Ncloc) * DEV_COST_LANGUAGE_1));
315 public void compute_0_effort_to_maintainability_rating_A_when_effort_is_lower_than_dev_cost() {
316 treeRootHolder.setRoot(ROOT_PROJECT);
318 addRawMeasure(NCLOC_KEY, FILE_1_REF, 10);
319 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, 2L);
321 underTest.visit(ROOT_PROJECT);
323 verifyAddedRawMeasure(FILE_1_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, 0L);
327 public void effort_to_maintainability_rating_A_is_same_as_effort_when_no_dev_cost() {
328 treeRootHolder.setRoot(ROOT_PROJECT);
330 addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, 100L);
332 underTest.visit(ROOT_PROJECT);
334 verifyAddedRawMeasure(FILE_1_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, 100);
337 private void addRawMeasure(String metricKey, int componentRef, long value) {
338 measureRepository.addRawMeasure(componentRef, metricKey, newMeasureBuilder().create(value));
341 private void addRawMeasure(String metricKey, int componentRef, int value) {
342 measureRepository.addRawMeasure(componentRef, metricKey, newMeasureBuilder().create(value));
345 private void verifyAddedRawMeasure(int componentRef, String metricKey, long value) {
346 assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(value)));
349 private void verifyAddedRawMeasure(int componentRef, String metricKey, double value) {
350 assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(value, 1)));
353 private void verifyAddedRawMeasure(int componentRef, String metricKey, Rating rating) {
354 assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(rating.getIndex(), rating.name())));
357 private void verifyAddedRawMeasure(int componentRef, String metricKey, String value) {
358 assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(value)));
361 private static ReportComponent createFileComponent(String languageKey1, int fileRef) {
362 return builder(FILE, fileRef).setFileAttributes(new FileAttributes(false, languageKey1, 1)).build();
365 private static Measure createMaintainabilityRatingMeasure(Rating rating) {
366 return newMeasureBuilder().create(rating.getIndex(), rating.name());