]> source.dussan.org Git - sonarqube.git/blob
e944aee492da27e7c035dca73c6d024fc83f38cf
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.ce.task.projectanalysis.qualitymodel;
21
22 import java.util.stream.Stream;
23 import org.junit.jupiter.api.BeforeEach;
24 import org.junit.jupiter.api.Test;
25 import org.junit.jupiter.api.extension.RegisterExtension;
26 import org.junit.jupiter.params.ParameterizedTest;
27 import org.junit.jupiter.params.provider.Arguments;
28 import org.junit.jupiter.params.provider.MethodSource;
29 import org.sonar.ce.task.projectanalysis.component.Component;
30 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
31 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
32 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
33 import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler;
34 import org.sonar.ce.task.projectanalysis.measure.Measure;
35 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
36 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
37 import org.sonar.server.measure.DebtRatingGrid;
38 import org.sonar.server.measure.Rating;
39
40 import static java.util.Collections.singletonList;
41 import static org.assertj.core.api.Assertions.assertThat;
42 import static org.junit.jupiter.params.provider.Arguments.arguments;
43 import static org.mockito.Mockito.mock;
44 import static org.mockito.Mockito.when;
45 import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST;
46 import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
47 import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A;
48 import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY;
49 import static org.sonar.api.measures.CoreMetrics.NCLOC;
50 import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
51 import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO;
52 import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO_KEY;
53 import static org.sonar.api.measures.CoreMetrics.SQALE_RATING;
54 import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
55 import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT;
56 import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
57 import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
58 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
59 import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
60 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
61 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
62 import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
63 import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.toEntries;
64 import static org.sonar.server.measure.Rating.A;
65 import static org.sonar.server.measure.Rating.C;
66 import static org.sonar.server.measure.Rating.D;
67 import static org.sonar.server.measure.Rating.E;
68 import static org.sonar.server.metric.SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A;
69 import static org.sonar.server.metric.SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY;
70 import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO;
71 import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY;
72 import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING;
73 import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
74 import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT;
75 import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY;
76
77 class MaintainabilityMeasuresVisitorTest {
78
79   static final String LANGUAGE_KEY_1 = "lKey1";
80   static final String LANGUAGE_KEY_2 = "lKey2";
81
82   static final double[] RATING_GRID = new double[] {0.1, 0.2, 0.5, 1};
83
84   static final long DEV_COST = 30;
85
86   static final int PROJECT_REF = 1;
87   static final int DIRECTORY_REF = 123;
88   static final int FILE_1_REF = 1231;
89   static final int FILE_2_REF = 1232;
90
91   static final Component ROOT_PROJECT = builder(Component.Type.PROJECT, PROJECT_REF).setKey("project")
92     .addChildren(
93       builder(DIRECTORY, DIRECTORY_REF).setKey("directory")
94         .addChildren(
95           builder(FILE, FILE_1_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file1").build(),
96           builder(FILE, FILE_2_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file2").build())
97         .build())
98     .build();
99
100   @RegisterExtension
101   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
102
103   @RegisterExtension
104   public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
105     .add(NCLOC)
106     .add(DEVELOPMENT_COST)
107     .add(TECHNICAL_DEBT)
108     .add(SQALE_DEBT_RATIO)
109     .add(SQALE_RATING)
110     .add(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A)
111     .add(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT)
112     .add(SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO)
113     .add(SOFTWARE_QUALITY_MAINTAINABILITY_RATING)
114     .add(EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A);
115
116   @RegisterExtension
117   public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
118
119   private final RatingSettings ratingSettings = mock(RatingSettings.class);
120
121   private VisitorsCrawler underTest;
122
123   @BeforeEach
124   public void setUp() {
125     // assumes rating configuration is consistent
126     when(ratingSettings.getDebtRatingGrid()).thenReturn(new DebtRatingGrid(RATING_GRID));
127     when(ratingSettings.getDevCost()).thenReturn(DEV_COST);
128
129     underTest = new VisitorsCrawler(singletonList(new MaintainabilityMeasuresVisitor(metricRepository, measureRepository, ratingSettings)));
130   }
131
132   @Test
133   void measures_created_for_project_are_all_zero_when_they_have_no_FILE_child() {
134     ReportComponent root = builder(PROJECT, 1).build();
135     treeRootHolder.setRoot(root);
136
137     underTest.visit(root);
138
139     assertThat(measureRepository.getRawMeasures(root).entrySet().stream().map(e -> entryOf(e.getKey(), e.getValue())))
140       .containsOnly(
141         entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create("0")),
142         entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(0d, 1)),
143         entryOf(SQALE_RATING_KEY, createMaintainabilityRatingMeasure(A)),
144         entryOf(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, newMeasureBuilder().create(0L)),
145         entryOf(SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY, newMeasureBuilder().create(0d, 1)),
146         entryOf(SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, createMaintainabilityRatingMeasure(A)),
147         entryOf(EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY, newMeasureBuilder().create(0L)));
148   }
149
150   @Test
151   void compute_development_cost() {
152     ReportComponent root = builder(PROJECT, 1).addChildren(
153       builder(DIRECTORY, 111).addChildren(
154         createFileComponent(LANGUAGE_KEY_1, 1111),
155         createFileComponent(LANGUAGE_KEY_2, 1112),
156         // Unit test should not be ignored
157         builder(FILE, 1113).setFileAttributes(new FileAttributes(true, LANGUAGE_KEY_1, 1)).build())
158         .build(),
159       builder(DIRECTORY, 112).addChildren(
160         createFileComponent(LANGUAGE_KEY_2, 1121))
161         .build(),
162       builder(DIRECTORY, 121).addChildren(
163         createFileComponent(LANGUAGE_KEY_1, 1211))
164         .build(),
165       builder(DIRECTORY, 122).build())
166       .build();
167
168     treeRootHolder.setRoot(root);
169
170     int ncloc1112 = 12;
171     addRawMeasure(NCLOC_KEY, 1112, ncloc1112);
172
173     int ncloc1113 = 15;
174     addRawMeasure(NCLOC_KEY, 1113, ncloc1113);
175
176     int nclocValue1121 = 30;
177     addRawMeasure(NCLOC_KEY, 1121, nclocValue1121);
178
179     int ncloc1211 = 20;
180     addRawMeasure(NCLOC_KEY, 1211, ncloc1211);
181
182     underTest.visit(root);
183
184     // verify measures on files
185     verifyAddedRawMeasure(1112, DEVELOPMENT_COST_KEY, Long.toString(ncloc1112 * DEV_COST));
186     verifyAddedRawMeasure(1113, DEVELOPMENT_COST_KEY, Long.toString(ncloc1113 * DEV_COST));
187     verifyAddedRawMeasure(1121, DEVELOPMENT_COST_KEY, Long.toString(nclocValue1121 * DEV_COST));
188     verifyAddedRawMeasure(1211, DEVELOPMENT_COST_KEY, Long.toString(ncloc1211 * DEV_COST));
189
190     // directory has no children => no file => 0 everywhere and A rating
191     verifyAddedRawMeasure(122, DEVELOPMENT_COST_KEY, "0");
192
193     // directory has children => dev cost is aggregated
194     verifyAddedRawMeasure(111, DEVELOPMENT_COST_KEY, Long.toString(
195       ncloc1112 * DEV_COST +
196         ncloc1113 * DEV_COST));
197     verifyAddedRawMeasure(112, DEVELOPMENT_COST_KEY, Long.toString(nclocValue1121 * DEV_COST));
198     verifyAddedRawMeasure(121, DEVELOPMENT_COST_KEY, Long.toString(ncloc1211 * DEV_COST));
199
200     verifyAddedRawMeasure(1, DEVELOPMENT_COST_KEY, Long.toString(
201       ncloc1112 * DEV_COST +
202         ncloc1113 * DEV_COST +
203         nclocValue1121 * DEV_COST +
204         ncloc1211 * DEV_COST));
205   }
206
207   private static Stream<Arguments> metrics() {
208     return Stream.of(
209       arguments(TECHNICAL_DEBT_KEY, SQALE_DEBT_RATIO_KEY, SQALE_RATING_KEY, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY),
210       arguments(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY,
211         EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY));
212   }
213
214   @ParameterizedTest
215   @MethodSource("metrics")
216   void compute_maintainability_debt_ratio_measure(String remediationEffortKey, String debtRatioKey) {
217     treeRootHolder.setRoot(ROOT_PROJECT);
218
219     int file1Ncloc = 10;
220     addRawMeasure(NCLOC_KEY, FILE_1_REF, file1Ncloc);
221     long file1MaintainabilityCost = 100L;
222     addRawMeasure(remediationEffortKey, FILE_1_REF, file1MaintainabilityCost);
223
224     int file2Ncloc = 5;
225     addRawMeasure(NCLOC_KEY, FILE_2_REF, file2Ncloc);
226     long file2MaintainabilityCost = 1L;
227     addRawMeasure(remediationEffortKey, FILE_2_REF, file2MaintainabilityCost);
228
229     long directoryMaintainabilityCost = 100L;
230     addRawMeasure(remediationEffortKey, DIRECTORY_REF, directoryMaintainabilityCost);
231
232     long projectMaintainabilityCost = 1000L;
233     addRawMeasure(remediationEffortKey, PROJECT_REF, projectMaintainabilityCost);
234
235     underTest.visit(ROOT_PROJECT);
236
237     verifyAddedRawMeasure(FILE_1_REF, debtRatioKey, file1MaintainabilityCost * 1d / (file1Ncloc * DEV_COST) * 100);
238     verifyAddedRawMeasure(FILE_2_REF, debtRatioKey, file2MaintainabilityCost * 1d / (file2Ncloc * DEV_COST) * 100);
239     verifyAddedRawMeasure(DIRECTORY_REF, debtRatioKey, directoryMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST) * 100);
240     verifyAddedRawMeasure(PROJECT_REF, debtRatioKey, projectMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST) * 100);
241   }
242
243   @Test
244   void compute_maintainability_rating_measure() {
245     treeRootHolder.setRoot(ROOT_PROJECT);
246
247     addRawMeasure(NCLOC_KEY, FILE_1_REF, 10);
248     addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, 100L);
249
250     addRawMeasure(NCLOC_KEY, FILE_2_REF, 5);
251     addRawMeasure(TECHNICAL_DEBT_KEY, FILE_2_REF, 1L);
252
253     addRawMeasure(TECHNICAL_DEBT_KEY, DIRECTORY_REF, 100L);
254     addRawMeasure(TECHNICAL_DEBT_KEY, PROJECT_REF, 1000L);
255
256     underTest.visit(ROOT_PROJECT);
257
258     verifyAddedRawMeasure(FILE_1_REF, SQALE_RATING_KEY, C);
259     verifyAddedRawMeasure(FILE_2_REF, SQALE_RATING_KEY, A);
260     verifyAddedRawMeasure(DIRECTORY_REF, SQALE_RATING_KEY, C);
261     verifyAddedRawMeasure(PROJECT_REF, SQALE_RATING_KEY, E);
262   }
263
264   @Test
265   void compute_software_quality_maintainability_rating_measure() {
266     treeRootHolder.setRoot(ROOT_PROJECT);
267
268     addRawMeasure(NCLOC_KEY, FILE_1_REF, 10);
269     addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, FILE_1_REF, 100L);
270
271     addRawMeasure(NCLOC_KEY, FILE_2_REF, 5);
272     addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, FILE_2_REF, 1L);
273
274     addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, DIRECTORY_REF, 100L);
275     addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, PROJECT_REF, 1000L);
276
277     underTest.visit(ROOT_PROJECT);
278
279     verifyAddedRawMeasure(FILE_1_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, C);
280     verifyAddedRawMeasure(FILE_2_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, A);
281     verifyAddedRawMeasure(DIRECTORY_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, C);
282     verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, D);
283   }
284
285   @ParameterizedTest
286   @MethodSource("metrics")
287   void compute_effort_to_maintainability_rating_A_measure(String remediationEffortKey, String debtRatioKey,
288     String ratingKey, String effortReachRatingAKey) {
289     treeRootHolder.setRoot(ROOT_PROJECT);
290
291     int file1Ncloc = 10;
292     long file1Effort = 100L;
293     addRawMeasure(NCLOC_KEY, FILE_1_REF, file1Ncloc);
294     addRawMeasure(remediationEffortKey, FILE_1_REF, file1Effort);
295
296     int file2Ncloc = 5;
297     long file2Effort = 20L;
298     addRawMeasure(NCLOC_KEY, FILE_2_REF, file2Ncloc);
299     addRawMeasure(remediationEffortKey, FILE_2_REF, file2Effort);
300
301     long dirEffort = 120L;
302     addRawMeasure(remediationEffortKey, DIRECTORY_REF, dirEffort);
303
304     long projectEffort = 150L;
305     addRawMeasure(remediationEffortKey, PROJECT_REF, projectEffort);
306
307     underTest.visit(ROOT_PROJECT);
308
309     verifyAddedRawMeasure(FILE_1_REF, effortReachRatingAKey,
310       (long) (file1Effort - RATING_GRID[0] * file1Ncloc * DEV_COST));
311     verifyAddedRawMeasure(FILE_2_REF, effortReachRatingAKey,
312       (long) (file2Effort - RATING_GRID[0] * file2Ncloc * DEV_COST));
313     verifyAddedRawMeasure(DIRECTORY_REF, effortReachRatingAKey,
314       (long) (dirEffort - RATING_GRID[0] * (file1Ncloc + file2Ncloc) * DEV_COST));
315     verifyAddedRawMeasure(PROJECT_REF, effortReachRatingAKey,
316       (long) (projectEffort - RATING_GRID[0] * (file1Ncloc + file2Ncloc) * DEV_COST));
317   }
318
319   @ParameterizedTest
320   @MethodSource("metrics")
321   void compute_0_effort_to_maintainability_rating_A_when_effort_is_lower_than_dev_cost(String remediationEffortKey, String debtRatioKey,
322     String ratingKey, String effortReachRatingAKey) {
323     treeRootHolder.setRoot(ROOT_PROJECT);
324
325     addRawMeasure(NCLOC_KEY, FILE_1_REF, 10);
326     addRawMeasure(remediationEffortKey, FILE_1_REF, 2L);
327
328     underTest.visit(ROOT_PROJECT);
329
330     verifyAddedRawMeasure(FILE_1_REF, effortReachRatingAKey, 0L);
331   }
332
333   @ParameterizedTest
334   @MethodSource("metrics")
335   void effort_to_maintainability_rating_A_is_same_as_effort_when_no_dev_cost(String remediationEffortKey, String debtRatioKey,
336     String ratingKey, String effortReachRatingAKey) {
337     treeRootHolder.setRoot(ROOT_PROJECT);
338
339     addRawMeasure(remediationEffortKey, FILE_1_REF, 100L);
340
341     underTest.visit(ROOT_PROJECT);
342
343     verifyAddedRawMeasure(FILE_1_REF, effortReachRatingAKey, 100);
344   }
345
346   private void addRawMeasure(String metricKey, int componentRef, long value) {
347     measureRepository.addRawMeasure(componentRef, metricKey, newMeasureBuilder().create(value));
348   }
349
350   private void addRawMeasure(String metricKey, int componentRef, int value) {
351     measureRepository.addRawMeasure(componentRef, metricKey, newMeasureBuilder().create(value));
352   }
353
354   private void verifyAddedRawMeasure(int componentRef, String metricKey, long value) {
355     assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(value)));
356   }
357
358   private void verifyAddedRawMeasure(int componentRef, String metricKey, double value) {
359     assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(value, 1)));
360   }
361
362   private void verifyAddedRawMeasure(int componentRef, String metricKey, Rating rating) {
363     assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(rating.getIndex(), rating.name())));
364   }
365
366   private void verifyAddedRawMeasure(int componentRef, String metricKey, String value) {
367     assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).contains(entryOf(metricKey, newMeasureBuilder().create(value)));
368   }
369
370   private static ReportComponent createFileComponent(String languageKey1, int fileRef) {
371     return builder(FILE, fileRef).setFileAttributes(new FileAttributes(false, languageKey1, 1)).build();
372   }
373
374   private static Measure createMaintainabilityRatingMeasure(Rating rating) {
375     return newMeasureBuilder().create(rating.getIndex(), rating.name());
376   }
377
378 }