]> source.dussan.org Git - sonarqube.git/blob
fb1eb27c5959ad8e9b84e757d183d33b2c581f7f
[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 com.google.common.collect.ImmutableMap;
23 import com.google.common.collect.ImmutableSet;
24 import com.google.common.collect.Ordering;
25 import java.util.Arrays;
26 import java.util.HashSet;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.stream.Stream;
30 import org.assertj.core.data.Offset;
31 import org.junit.jupiter.api.BeforeEach;
32 import org.junit.jupiter.api.Test;
33 import org.junit.jupiter.api.extension.RegisterExtension;
34 import org.junit.jupiter.params.ParameterizedTest;
35 import org.junit.jupiter.params.provider.Arguments;
36 import org.junit.jupiter.params.provider.MethodSource;
37 import org.sonar.api.utils.KeyValueFormat;
38 import org.sonar.ce.task.projectanalysis.component.Component;
39 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
40 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
41 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
42 import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler;
43 import org.sonar.ce.task.projectanalysis.measure.Measure;
44 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
45 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
46 import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
47 import org.sonar.server.measure.DebtRatingGrid;
48 import org.sonar.server.measure.Rating;
49
50 import static com.google.common.base.Preconditions.checkArgument;
51 import static org.junit.jupiter.params.provider.Arguments.arguments;
52 import static org.mockito.Mockito.mock;
53 import static org.mockito.Mockito.when;
54 import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA;
55 import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY;
56 import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST;
57 import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST_KEY;
58 import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING;
59 import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
60 import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO;
61 import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY;
62 import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT;
63 import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
64 import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
65 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
66 import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
67 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
68 import static org.sonar.ce.task.projectanalysis.measure.MeasureAssert.assertThat;
69 import static org.sonar.server.measure.Rating.A;
70 import static org.sonar.server.measure.Rating.D;
71 import static org.sonar.server.measure.Rating.E;
72 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO;
73 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY;
74 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING;
75 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
76 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT;
77 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY;
78
79 public class NewMaintainabilityMeasuresVisitorTest {
80
81   private static final double[] RATING_GRID = new double[] {0.1, 0.2, 0.5, 1};
82
83   private static final String LANGUAGE_1_KEY = "language 1 key";
84   private static final long DEV_COST = 30L;
85   private static final int ROOT_REF = 1;
86   private static final int LANGUAGE_1_FILE_REF = 11111;
87   private static final Offset<Double> VALUE_COMPARISON_OFFSET = Offset.offset(0.01);
88
89   @RegisterExtension
90   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
91   @RegisterExtension
92   public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
93     .add(NEW_TECHNICAL_DEBT)
94     .add(NCLOC_DATA)
95     .add(NEW_SQALE_DEBT_RATIO)
96     .add(NEW_MAINTAINABILITY_RATING)
97     .add(NEW_DEVELOPMENT_COST)
98     .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT)
99     .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO)
100     .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING);
101
102   @RegisterExtension
103   public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
104
105   private NewLinesRepository newLinesRepository = mock(NewLinesRepository.class);
106   private RatingSettings ratingSettings = mock(RatingSettings.class);
107
108   private VisitorsCrawler underTest;
109
110   @BeforeEach
111   public void setUp() {
112     when(ratingSettings.getDebtRatingGrid()).thenReturn(new DebtRatingGrid(RATING_GRID));
113     when(ratingSettings.getDevCost()).thenReturn(DEV_COST);
114     underTest = new VisitorsCrawler(Arrays.asList(new NewMaintainabilityMeasuresVisitor(metricRepository, measureRepository, newLinesRepository, ratingSettings)));
115   }
116
117   private static Stream<Arguments> metrics() {
118     return Stream.of(
119       arguments(NEW_TECHNICAL_DEBT_KEY, NEW_SQALE_DEBT_RATIO_KEY, NEW_MAINTAINABILITY_RATING_KEY),
120       arguments(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY));
121   }
122
123   @ParameterizedTest
124   @MethodSource("metrics")
125   void project_has_new_measures(String remediationEffortKey, String debtRatioKey, String ratingKey) {
126     when(newLinesRepository.newLinesAvailable()).thenReturn(true);
127     treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build());
128
129     underTest.visit(treeRootHolder.getRoot());
130
131     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0);
132     assertNewRating(ratingKey, ROOT_REF, A);
133   }
134
135   @ParameterizedTest
136   @MethodSource("metrics")
137   void project_has_no_measure_if_new_lines_not_available(String remediationEffortKey, String debtRatioKey, String ratingKey) {
138     when(newLinesRepository.newLinesAvailable()).thenReturn(false);
139     treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build());
140
141     underTest.visit(treeRootHolder.getRoot());
142
143     assertNoNewDebtRatioMeasure(debtRatioKey, ROOT_REF);
144     assertNoNewMaintainability(ratingKey, ROOT_REF);
145   }
146
147   @ParameterizedTest
148   @MethodSource("metrics")
149   void file_has_no_new_debt_ratio_variation_if_new_lines_not_available(String remediationEffortKey, String debtRatioKey, String ratingKey) {
150     when(newLinesRepository.newLinesAvailable()).thenReturn(false);
151     setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES);
152     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50));
153
154     underTest.visit(treeRootHolder.getRoot());
155
156     assertNoNewDebtRatioMeasure(debtRatioKey, LANGUAGE_1_FILE_REF);
157     assertNoNewDebtRatioMeasure(debtRatioKey, ROOT_REF);
158   }
159
160   @ParameterizedTest
161   @MethodSource("metrics")
162   void file_has_0_new_debt_ratio_if_no_line_is_new(String remediationEffortKey, String debtRatioKey, String ratingKey) {
163     ReportComponent file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build();
164     treeRootHolder.setRoot(
165       builder(PROJECT, ROOT_REF)
166         .addChildren(file)
167         .build());
168     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, createNewDebtMeasure(50));
169     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
170     setNewLines(file);
171
172     underTest.visit(treeRootHolder.getRoot());
173
174     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0);
175     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0);
176   }
177
178   @ParameterizedTest
179   @MethodSource("metrics")
180   void file_has_new_debt_ratio_if_some_lines_are_new(String remediationEffortKey, String debtRatioKey, String ratingKey) {
181     when(newLinesRepository.newLinesAvailable()).thenReturn(true);
182     setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES);
183     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50));
184
185     underTest.visit(treeRootHolder.getRoot());
186
187     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 83.33);
188     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 83.33);
189   }
190
191   @ParameterizedTest
192   @MethodSource("metrics")
193   void new_debt_ratio_changes_with_language_cost(String remediationEffortKey, String debtRatioKey, String ratingKey) {
194     when(ratingSettings.getDevCost()).thenReturn(DEV_COST * 10);
195     setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES);
196     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50));
197
198     underTest.visit(treeRootHolder.getRoot());
199
200     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 8.33);
201     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 8.33);
202   }
203
204   @ParameterizedTest
205   @MethodSource("metrics")
206   void new_debt_ratio_changes_with_new_technical_debt(String remediationEffortKey, String debtRatioKey, String ratingKey) {
207     setupOneFileAloneInAProject(remediationEffortKey, 500, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES);
208     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(500));
209
210     underTest.visit(treeRootHolder.getRoot());
211
212     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 833.33);
213     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 833.33);
214   }
215
216   @ParameterizedTest
217   @MethodSource("metrics")
218   void new_debt_ratio_on_non_file_level_is_based_on_new_technical_debt_of_that_level(String remediationEffortKey, String debtRatioKey, String ratingKey) {
219     setupOneFileAloneInAProject(remediationEffortKey, 500, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES);
220     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(1200));
221
222     underTest.visit(treeRootHolder.getRoot());
223
224     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 833.33);
225     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 833.33);
226   }
227
228   @ParameterizedTest
229   @MethodSource("metrics")
230   void new_debt_ratio_when_file_is_unit_test(String remediationEffortKey, String debtRatioKey, String ratingKey) {
231     setupOneFileAloneInAProject(remediationEffortKey, 500, Flag.UT_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES);
232     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(1200));
233
234     underTest.visit(treeRootHolder.getRoot());
235
236     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 833.33);
237     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 833.33);
238   }
239
240   @ParameterizedTest
241   @MethodSource("metrics")
242   void new_debt_ratio_is_0_when_file_has_no_new_lines(String remediationEffortKey, String debtRatioKey, String ratingKey) {
243     when(newLinesRepository.newLinesAvailable()).thenReturn(true);
244     setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES);
245     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50));
246
247     underTest.visit(treeRootHolder.getRoot());
248
249     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0);
250     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0);
251   }
252
253   @ParameterizedTest
254   @MethodSource("metrics")
255   void new_debt_ratio_is_0_on_non_file_level_when_no_file_has_new_lines(String remediationEffortKey, String debtRatioKey, String ratingKey) {
256     when(newLinesRepository.newLinesAvailable()).thenReturn(true);
257     setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES);
258     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(200));
259
260     underTest.visit(treeRootHolder.getRoot());
261
262     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0);
263     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0);
264   }
265
266   @ParameterizedTest
267   @MethodSource("metrics")
268   void new_debt_ratio_is_0_when_there_is_no_ncloc_in_file(String remediationEffortKey, String debtRatioKey, String ratingKey) {
269     setupOneFileAloneInAProject(remediationEffortKey, 50, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_NEW_LINES);
270     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50));
271
272     underTest.visit(treeRootHolder.getRoot());
273
274     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0);
275     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0);
276   }
277
278   @ParameterizedTest
279   @MethodSource("metrics")
280   void new_debt_ratio_is_0_on_non_file_level_when_one_file_has_zero_new_debt_because_of_no_changeset(String remediationEffortKey, String debtRatioKey, String ratingKey) {
281     setupOneFileAloneInAProject(remediationEffortKey, 50, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_NEW_LINES);
282     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(200));
283
284     underTest.visit(treeRootHolder.getRoot());
285
286     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0);
287     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0);
288   }
289
290   @ParameterizedTest
291   @MethodSource("metrics")
292   void new_debt_ratio_is_0_when_ncloc_measure_is_missing(String remediationEffortKey, String debtRatioKey, String ratingKey) {
293     setupOneFileAloneInAProject(remediationEffortKey, 50, Flag.SRC_FILE, Flag.MISSING_MEASURE_NCLOC, Flag.WITH_NEW_LINES);
294     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50));
295
296     underTest.visit(treeRootHolder.getRoot());
297
298     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0);
299     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0);
300   }
301
302   @ParameterizedTest
303   @MethodSource("metrics")
304   void leaf_components_always_have_a_measure_when_at_least_one_period_exist_and_ratio_is_computed_from_current_level_new_debt(String remediationEffortKey, String debtRatioKey,
305     String ratingKey) {
306     Component file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build();
307     treeRootHolder.setRoot(
308       builder(PROJECT, ROOT_REF)
309         .addChildren(
310           builder(DIRECTORY, 111)
311             .addChildren(file)
312             .build())
313         .build());
314
315     Measure newDebtMeasure = createNewDebtMeasure(50);
316     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure);
317     measureRepository.addRawMeasure(111, remediationEffortKey, createNewDebtMeasure(150));
318     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(250));
319     // 4 lines file, only first one is not ncloc
320     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
321     // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date
322     setNewLines(file, 3, 4);
323     underTest.visit(treeRootHolder.getRoot());
324
325     assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 83.33);
326     assertNewDebtRatioValues(debtRatioKey, 111, 83.33);
327     assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 83.33);
328   }
329
330   @ParameterizedTest
331   @MethodSource("metrics")
332   void compute_new_maintainability_rating(String remediationEffortKey, String debtRatioKey, String ratingKey) {
333     ReportComponent file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build();
334     treeRootHolder.setRoot(
335       builder(PROJECT, ROOT_REF)
336         .addChildren(
337           builder(DIRECTORY, 111)
338             .addChildren(file)
339             .build())
340         .build());
341
342     Measure newDebtMeasure = createNewDebtMeasure(50);
343     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure);
344     measureRepository.addRawMeasure(111, remediationEffortKey, createNewDebtMeasure(150));
345     measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(250));
346     // 4 lines file, only first one is not ncloc
347     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
348     // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date
349
350     setNewLines(file, 3, 4);
351
352     underTest.visit(treeRootHolder.getRoot());
353
354     assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, D);
355     assertNewRating(ratingKey, 111, D);
356     assertNewRating(ratingKey, ROOT_REF, D);
357   }
358
359   @ParameterizedTest
360   @MethodSource("metrics")
361   void compute_new_maintainability_rating_map_to_D(String remediationEffortKey, String debtRatioKey, String ratingKey) {
362     ReportComponent file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build();
363     treeRootHolder.setRoot(
364       builder(PROJECT, ROOT_REF)
365         .addChildren(
366           builder(DIRECTORY, 111)
367             .addChildren(file)
368             .build())
369         .build());
370
371     Measure newDebtMeasure = createNewDebtMeasure(400);
372     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure);
373     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
374
375     setNewLines(file, 3, 4);
376
377     underTest.visit(treeRootHolder.getRoot());
378
379     if (ratingKey.equals(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY)) {
380       assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, D);
381     } else if (ratingKey.equals(NEW_MAINTAINABILITY_RATING_KEY)) {
382       assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, E);
383     }
384   }
385
386   @Test
387   void compute_new_development_cost() {
388     ReportComponent file1 = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 4)).build();
389     ReportComponent file2 = builder(FILE, 22_222).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 6)).build();
390     treeRootHolder.setRoot(
391       builder(PROJECT, ROOT_REF)
392         .addChildren(
393           builder(DIRECTORY, 111)
394             .addChildren(file1, file2)
395             .build())
396         .build());
397
398     // 4 lines file, only first one is not ncloc
399     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
400     // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date
401     setNewLines(file1, 3, 4);
402
403     // 6 lines file, only last one is not ncloc
404     measureRepository.addRawMeasure(22_222, NCLOC_DATA_KEY, createNclocDataMeasure(1, 2, 3, 4, 5));
405     // first 2 lines are before all snapshots, 4 last lines are after PERIOD 2's snapshot date
406     setNewLines(file2, 3, 4, 5, 6);
407
408     underTest.visit(treeRootHolder.getRoot());
409
410     assertNewDevelopmentCostValues(ROOT_REF, 5 * DEV_COST);
411     assertNewDevelopmentCostValues(LANGUAGE_1_FILE_REF, 2 * DEV_COST);
412     assertNewDevelopmentCostValues(22_222, 3 * DEV_COST);
413   }
414
415   @ParameterizedTest
416   @MethodSource("metrics")
417   void compute_new_maintainability_rating_to_A_when_no_debt(String remediationEffortKey, String debtRatioKey, String ratingKey) {
418     when(newLinesRepository.newLinesAvailable()).thenReturn(true);
419     treeRootHolder.setRoot(
420       builder(PROJECT, ROOT_REF)
421         .addChildren(
422           builder(DIRECTORY, 111)
423             .addChildren(
424               builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build())
425             .build())
426         .build());
427
428     underTest.visit(treeRootHolder.getRoot());
429
430     assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, A);
431     assertNewRating(ratingKey, 111, A);
432     assertNewRating(ratingKey, ROOT_REF, A);
433   }
434
435   private void setupOneFileAloneInAProject(String remediationEffortKey, int newDebt, Flag isUnitTest, Flag withNclocLines, Flag withNewLines) {
436     checkArgument(isUnitTest == Flag.UT_FILE || isUnitTest == Flag.SRC_FILE);
437     checkArgument(withNclocLines == Flag.WITH_NCLOC || withNclocLines == Flag.NO_NCLOC || withNclocLines == Flag.MISSING_MEASURE_NCLOC);
438     checkArgument(withNewLines == Flag.WITH_NEW_LINES || withNewLines == Flag.NO_NEW_LINES);
439
440     Component file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(isUnitTest == Flag.UT_FILE, LANGUAGE_1_KEY, 1)).build();
441     treeRootHolder.setRoot(
442       builder(PROJECT, ROOT_REF)
443         .addChildren(file)
444         .build());
445
446     Measure newDebtMeasure = createNewDebtMeasure(newDebt);
447     measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure);
448     if (withNclocLines == Flag.WITH_NCLOC) {
449       // 4 lines file, only first one is not ncloc
450       measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
451     } else if (withNclocLines == Flag.NO_NCLOC) {
452       // 4 lines file, none of which is ncloc
453       measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNoNclocDataMeasure(4));
454     }
455     if (withNewLines == Flag.WITH_NEW_LINES) {
456       // 2 last lines are new
457       setNewLines(file, 3, 4);
458     }
459   }
460
461   private void setNewLines(Component component, int... lineNumbers) {
462     HashSet<Integer> newLines = new HashSet<>();
463     for (int i : lineNumbers) {
464       newLines.add(i);
465     }
466     when(newLinesRepository.newLinesAvailable()).thenReturn(true);
467     when(newLinesRepository.getNewLines(component)).thenReturn(Optional.of(newLines));
468
469   }
470
471   private enum Flag {
472     UT_FILE, SRC_FILE, NO_NEW_LINES, WITH_NEW_LINES, WITH_NCLOC, NO_NCLOC, MISSING_MEASURE_NCLOC
473   }
474
475   public static ReportComponent.Builder builder(Component.Type type, int ref) {
476     return ReportComponent.builder(type, ref).setKey(String.valueOf(ref));
477   }
478
479   private Measure createNewDebtMeasure(long value) {
480     return newMeasureBuilder().create(value);
481   }
482
483   private static Measure createNclocDataMeasure(Integer... nclocLines) {
484     Set<Integer> nclocLinesSet = ImmutableSet.copyOf(nclocLines);
485     int max = Ordering.<Integer>natural().max(nclocLinesSet);
486     ImmutableMap.Builder<Integer, Integer> builder = ImmutableMap.builder();
487     for (int i = 1; i <= max; i++) {
488       builder.put(i, nclocLinesSet.contains(i) ? 1 : 0);
489     }
490     return newMeasureBuilder().create(KeyValueFormat.format(builder.build(), KeyValueFormat.newIntegerConverter(), KeyValueFormat.newIntegerConverter()));
491   }
492
493   private static Measure createNoNclocDataMeasure(int lineCount) {
494     ImmutableMap.Builder<Integer, Integer> builder = ImmutableMap.builder();
495     for (int i = 1; i <= lineCount; i++) {
496       builder.put(i, 0);
497     }
498     return newMeasureBuilder().create(KeyValueFormat.format(builder.build(), KeyValueFormat.newIntegerConverter(), KeyValueFormat.newIntegerConverter()));
499   }
500
501   private void assertNoNewDebtRatioMeasure(String debtRatioKey, int componentRef) {
502     assertThat(measureRepository.getAddedRawMeasure(componentRef, debtRatioKey))
503       .isAbsent();
504   }
505
506   private void assertNewDebtRatioValues(String debtRatioKey, int componentRef, double expectedValue) {
507     assertThat(measureRepository.getAddedRawMeasure(componentRef, debtRatioKey)).hasValue(expectedValue, VALUE_COMPARISON_OFFSET);
508   }
509
510   private void assertNewDevelopmentCostValues(int componentRef, float expectedValue) {
511     assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_DEVELOPMENT_COST_KEY)).hasValue(expectedValue, VALUE_COMPARISON_OFFSET);
512   }
513
514   private void assertNewRating(String ratingKey, int componentRef, Rating expectedValue) {
515     assertThat(measureRepository.getAddedRawMeasure(componentRef, ratingKey)).hasValue(expectedValue.getIndex());
516   }
517
518   private void assertNoNewMaintainability(String ratingKey, int componentRef) {
519     assertThat(measureRepository.getAddedRawMeasure(componentRef, ratingKey))
520       .isAbsent();
521   }
522 }