diff options
author | Léo Geoffroy <leo.geoffroy@sonarsource.com> | 2024-08-09 15:20:44 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-08-26 20:03:05 +0000 |
commit | f47e4cc9197bc07818510b16f3998ae1da9e8b2a (patch) | |
tree | 6b62d698038a709f1e1cdf9acbf98d6447706154 /server | |
parent | 305b3d5937ece0b9aeaaa931fc0d9a4c1e6ba52b (diff) | |
download | sonarqube-f47e4cc9197bc07818510b16f3998ae1da9e8b2a.tar.gz sonarqube-f47e4cc9197bc07818510b16f3998ae1da9e8b2a.zip |
SONAR-22727 Add new software qualities maintainability measures
Diffstat (limited to 'server')
6 files changed, 392 insertions, 183 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java index 8301ead2685..ec9221056c1 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java @@ -30,6 +30,7 @@ import org.sonar.ce.task.projectanalysis.measure.RatingMeasures; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; import org.sonar.server.measure.Rating; +import org.sonar.server.metric.SoftwareQualitiesMetrics; import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY; import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY; @@ -38,6 +39,10 @@ import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO_KEY; import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY; import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY; /** * Compute measures related to maintainability for projects and descendants : @@ -45,6 +50,9 @@ import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilde * {@link CoreMetrics#SQALE_DEBT_RATIO_KEY} * {@link CoreMetrics#SQALE_RATING_KEY} * {@link CoreMetrics#EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY} + * {@link SoftwareQualitiesMetrics#SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY} + * {@link SoftwareQualitiesMetrics#SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY} + * {@link SoftwareQualitiesMetrics#EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY} */ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<MaintainabilityMeasuresVisitor.Counter> { private final MeasureRepository measureRepository; @@ -53,11 +61,18 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main private final Metric nclocMetric; private final Metric developmentCostMetric; + // Maintainability metrics based on RuleType private final Metric maintainabilityRemediationEffortMetric; private final Metric debtRatioMetric; private final Metric maintainabilityRatingMetric; private final Metric effortToMaintainabilityRatingAMetric; + // Maintainability metrics based on Software Quality + private final Metric softwareQualityMaintainabilityRemediationEffortMetric; + private final Metric softwareQualityDebtRatioMetric; + private final Metric softwareQualityEffortToMaintainabilityRatingAMetric; + private final Metric softwareQualityMaintainabilityRatingMetric; + public MaintainabilityMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, RatingSettings ratingSettings) { super(CrawlerDepthLimit.FILE, Order.POST_ORDER, CounterFactory.INSTANCE); this.measureRepository = measureRepository; @@ -66,12 +81,18 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main // Input metrics this.nclocMetric = metricRepository.getByKey(NCLOC_KEY); this.maintainabilityRemediationEffortMetric = metricRepository.getByKey(TECHNICAL_DEBT_KEY); + this.softwareQualityMaintainabilityRemediationEffortMetric = metricRepository.getByKey(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY); // Output metrics this.developmentCostMetric = metricRepository.getByKey(DEVELOPMENT_COST_KEY); + this.debtRatioMetric = metricRepository.getByKey(SQALE_DEBT_RATIO_KEY); this.maintainabilityRatingMetric = metricRepository.getByKey(SQALE_RATING_KEY); this.effortToMaintainabilityRatingAMetric = metricRepository.getByKey(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY); + + this.softwareQualityDebtRatioMetric = metricRepository.getByKey(SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY); + this.softwareQualityMaintainabilityRatingMetric = metricRepository.getByKey(SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY); + this.softwareQualityEffortToMaintainabilityRatingAMetric = metricRepository.getByKey(EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY); } @Override @@ -99,16 +120,21 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main private void computeAndSaveMeasures(Component component, Path<Counter> path) { addDevelopmentCostMeasure(component, path.current()); - double density = computeDensity(component, path.current()); - addDebtRatioMeasure(component, density); + double density = computeDensity(maintainabilityRemediationEffortMetric, component, path.current()); + addDebtRatioMeasure(debtRatioMetric, component, density); addMaintainabilityRatingMeasure(component, density); - addEffortToMaintainabilityRatingAMeasure(component, path); + addEffortToMaintainabilityRatingAMeasure(maintainabilityRemediationEffortMetric, effortToMaintainabilityRatingAMetric, component, path); + + double densityBasedOnSoftwareQuality = computeDensity(softwareQualityMaintainabilityRemediationEffortMetric, component, path.current()); + addDebtRatioMeasure(softwareQualityDebtRatioMetric, component, densityBasedOnSoftwareQuality); + addSoftwareQualityMaintainabilityRatingMeasure(component, densityBasedOnSoftwareQuality); + addEffortToMaintainabilityRatingAMeasure(softwareQualityMaintainabilityRemediationEffortMetric, softwareQualityEffortToMaintainabilityRatingAMetric, component, path); addToParent(path); } - private double computeDensity(Component component, Counter developmentCost) { - Optional<Measure> measure = measureRepository.getRawMeasure(component, maintainabilityRemediationEffortMetric); + private double computeDensity(Metric remediationEffortMetric, Component component, Counter developmentCost) { + Optional<Measure> measure = measureRepository.getRawMeasure(component, remediationEffortMetric); double maintainabilityRemediationEffort = measure.isPresent() ? measure.get().getLongValue() : 0L; if (Double.doubleToRawLongBits(developmentCost.devCosts) != 0L) { return maintainabilityRemediationEffort / developmentCost.devCosts; @@ -121,7 +147,7 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main measureRepository.add(component, developmentCostMetric, newMeasureBuilder().create(Long.toString(developmentCost.devCosts))); } - private void addDebtRatioMeasure(Component component, double density) { + private void addDebtRatioMeasure(Metric debtRatioMetric, Component component, double density) { measureRepository.add(component, debtRatioMetric, newMeasureBuilder().create(100.0 * density, debtRatioMetric.getDecimalScale())); } @@ -130,7 +156,13 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main measureRepository.add(component, maintainabilityRatingMetric, RatingMeasures.get(rating)); } - private void addEffortToMaintainabilityRatingAMeasure(Component component, Path<Counter> path) { + private void addSoftwareQualityMaintainabilityRatingMeasure(Component component, double density) { + Rating rating = ratingSettings.getDebtRatingGrid().getAToDRatingForDensity(density); + measureRepository.add(component, softwareQualityMaintainabilityRatingMetric, RatingMeasures.get(rating)); + } + + private void addEffortToMaintainabilityRatingAMeasure(Metric maintainabilityRemediationEffortMetric, Metric effortToMaintainabilityRatingAMetric, + Component component, Path<Counter> path) { long developmentCostValue = path.current().devCosts; Optional<Measure> effortMeasure = measureRepository.getRawMeasure(component, maintainabilityRemediationEffortMetric); long effort = effortMeasure.isPresent() ? effortMeasure.get().getLongValue() : 0L; diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java index 7223d35ff01..0a59cbd50c1 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java @@ -36,6 +36,7 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; import org.sonar.ce.task.projectanalysis.source.NewLinesRepository; +import org.sonar.server.metric.SoftwareQualitiesMetrics; import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST_KEY; @@ -45,6 +46,8 @@ import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY; import static org.sonar.api.utils.KeyValueFormat.newIntegerConverter; import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY; /** * This visitor depends on {@link IntegrateIssuesVisitor} for the computation of @@ -52,6 +55,8 @@ import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilde * Compute following measure : * {@link CoreMetrics#NEW_SQALE_DEBT_RATIO_KEY} * {@link CoreMetrics#NEW_MAINTAINABILITY_RATING_KEY} + * {@link SoftwareQualitiesMetrics#NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY} + * {@link SoftwareQualitiesMetrics#NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY} */ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<NewMaintainabilityMeasuresVisitor.Counter> { private static final Logger LOG = LoggerFactory.getLogger(NewMaintainabilityMeasuresVisitor.class); @@ -67,6 +72,10 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N private final Metric newDebtRatioMetric; private final Metric newMaintainabilityRatingMetric; + private final Metric newSoftwareQualityMaintainabilityDebtRatioMetric; + private final Metric newSoftwareQualityMaintainabilityRatingMetric; + private final Metric newSoftwareQualityRemediationEffortKey; + public NewMaintainabilityMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, NewLinesRepository newLinesRepository, RatingSettings ratingSettings) { super(CrawlerDepthLimit.FILE, POST_ORDER, CounterFactory.INSTANCE); @@ -76,6 +85,7 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N // computed by NewDebtAggregator which is executed by IntegrateIssuesVisitor this.newDebtMetric = metricRepository.getByKey(NEW_TECHNICAL_DEBT_KEY); + this.newSoftwareQualityRemediationEffortKey = metricRepository.getByKey(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY); // which line is ncloc and which isn't this.nclocDataMetric = metricRepository.getByKey(NCLOC_DATA_KEY); @@ -83,6 +93,8 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N this.newDevelopmentCostMetric = metricRepository.getByKey(NEW_DEVELOPMENT_COST_KEY); this.newDebtRatioMetric = metricRepository.getByKey(NEW_SQALE_DEBT_RATIO_KEY); this.newMaintainabilityRatingMetric = metricRepository.getByKey(NEW_MAINTAINABILITY_RATING_KEY); + this.newSoftwareQualityMaintainabilityDebtRatioMetric = metricRepository.getByKey(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY); + this.newSoftwareQualityMaintainabilityRatingMetric = metricRepository.getByKey(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY); } @Override @@ -107,19 +119,26 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N if (!newLinesRepository.newLinesAvailable()) { return; } - double density = computeDensity(path.current()); - double newDebtRatio = 100.0 * density; - int newMaintainability = ratingSettings.getDebtRatingGrid().getRatingForDensity(density).getIndex(); + float newDevelopmentCost = path.current().getDevCost().getValue(); measureRepository.add(component, this.newDevelopmentCostMetric, newMeasureBuilder().create(newDevelopmentCost)); + + double density = computeDensity(path.current().getNewDebt(), path.current().getDevCost()); + double newDebtRatio = 100.0 * density; + int newMaintainabilityRating = ratingSettings.getDebtRatingGrid().getRatingForDensity(density).getIndex(); measureRepository.add(component, this.newDebtRatioMetric, newMeasureBuilder().create(newDebtRatio)); - measureRepository.add(component, this.newMaintainabilityRatingMetric, newMeasureBuilder().create(newMaintainability)); + measureRepository.add(component, this.newMaintainabilityRatingMetric, newMeasureBuilder().create(newMaintainabilityRating)); + + double densityBasedOnSoftwareQuality = computeDensity(path.current().getNewSoftwareQualityDebt(), path.current().getDevCost()); + double newSoftwareQualityDebtRatio = 100.0 * densityBasedOnSoftwareQuality; + int newSoftwareQualityMaintainabilityRating = ratingSettings.getDebtRatingGrid().getAToDRatingForDensity(densityBasedOnSoftwareQuality).getIndex(); + measureRepository.add(component, this.newSoftwareQualityMaintainabilityDebtRatioMetric, newMeasureBuilder().create(newSoftwareQualityDebtRatio)); + measureRepository.add(component, this.newSoftwareQualityMaintainabilityRatingMetric, newMeasureBuilder().create(newSoftwareQualityMaintainabilityRating)); } - private static double computeDensity(Counter counter) { - LongValue newDebt = counter.getNewDebt(); + private static double computeDensity(LongValue newDebt, LongValue devCost) { if (newDebt.isSet()) { - long developmentCost = counter.getDevCost().getValue(); + long developmentCost = devCost.getValue(); if (developmentCost != 0L) { return newDebt.getValue() / (double) developmentCost; } @@ -169,6 +188,9 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N if (hasDevCost) { long newDebt = getLongValue(measureRepository.getRawMeasure(file, this.newDebtMetric)); devCostCounter.incrementNewDebt(newDebt); + + long newSoftwareQualityDebt = getLongValue(measureRepository.getRawMeasure(file, this.newSoftwareQualityRemediationEffortKey)); + devCostCounter.incrementNewSoftwareQualityDebt(newSoftwareQualityDebt); } } @@ -192,25 +214,35 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N public static final class Counter { private final LongValue newDebt = new LongValue(); + private final LongValue newSoftwareQualityDebt = new LongValue(); private final LongValue devCost = new LongValue(); public void add(Counter counter) { this.newDebt.increment(counter.newDebt); + this.newSoftwareQualityDebt.increment(counter.newSoftwareQualityDebt); this.devCost.increment(counter.devCost); } - LongValue incrementNewDebt(long value) { - return newDebt.increment(value); + void incrementNewDebt(long value) { + newDebt.increment(value); + } + + void incrementNewSoftwareQualityDebt(long value) { + newSoftwareQualityDebt.increment(value); } - LongValue incrementDevCost(long value) { - return devCost.increment(value); + void incrementDevCost(long value) { + devCost.increment(value); } LongValue getNewDebt() { return this.newDebt; } + LongValue getNewSoftwareQualityDebt() { + return this.newSoftwareQualityDebt; + } + LongValue getDevCost() { return this.devCost; } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java index eb254a30d2d..e944aee492d 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java @@ -19,15 +19,18 @@ */ package org.sonar.ce.task.projectanalysis.qualitymodel; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.FileAttributes; import org.sonar.ce.task.projectanalysis.component.ReportComponent; import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler; -import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesRepositoryRule; import org.sonar.ce.task.projectanalysis.measure.Measure; import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule; import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule; @@ -36,6 +39,7 @@ import org.sonar.server.measure.Rating; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST; @@ -59,9 +63,18 @@ import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.toEntries; import static org.sonar.server.measure.Rating.A; import static org.sonar.server.measure.Rating.C; +import static org.sonar.server.measure.Rating.D; import static org.sonar.server.measure.Rating.E; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY; -public class MaintainabilityMeasuresVisitorTest { +class MaintainabilityMeasuresVisitorTest { static final String LANGUAGE_KEY_1 = "lKey1"; static final String LANGUAGE_KEY_2 = "lKey2"; @@ -84,29 +97,30 @@ public class MaintainabilityMeasuresVisitorTest { .build()) .build(); - @Rule + @RegisterExtension public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - @Rule + @RegisterExtension public MetricRepositoryRule metricRepository = new MetricRepositoryRule() .add(NCLOC) .add(DEVELOPMENT_COST) .add(TECHNICAL_DEBT) .add(SQALE_DEBT_RATIO) .add(SQALE_RATING) - .add(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A); + .add(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A) + .add(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT) + .add(SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO) + .add(SOFTWARE_QUALITY_MAINTAINABILITY_RATING) + .add(EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A); - @Rule + @RegisterExtension public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); - @Rule - public ComponentIssuesRepositoryRule componentIssuesRepositoryRule = new ComponentIssuesRepositoryRule(treeRootHolder); - - private RatingSettings ratingSettings = mock(RatingSettings.class); + private final RatingSettings ratingSettings = mock(RatingSettings.class); private VisitorsCrawler underTest; - @Before + @BeforeEach public void setUp() { // assumes rating configuration is consistent when(ratingSettings.getDebtRatingGrid()).thenReturn(new DebtRatingGrid(RATING_GRID)); @@ -116,7 +130,7 @@ public class MaintainabilityMeasuresVisitorTest { } @Test - public void measures_created_for_project_are_all_zero_when_they_have_no_FILE_child() { + void measures_created_for_project_are_all_zero_when_they_have_no_FILE_child() { ReportComponent root = builder(PROJECT, 1).build(); treeRootHolder.setRoot(root); @@ -127,11 +141,14 @@ public class MaintainabilityMeasuresVisitorTest { entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create("0")), entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(0d, 1)), entryOf(SQALE_RATING_KEY, createMaintainabilityRatingMeasure(A)), - entryOf(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, newMeasureBuilder().create(0L))); + entryOf(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, newMeasureBuilder().create(0L)), + entryOf(SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY, newMeasureBuilder().create(0d, 1)), + entryOf(SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, createMaintainabilityRatingMeasure(A)), + entryOf(EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY, newMeasureBuilder().create(0L))); } @Test - public void compute_development_cost() { + void compute_development_cost() { ReportComponent root = builder(PROJECT, 1).addChildren( builder(DIRECTORY, 111).addChildren( createFileComponent(LANGUAGE_KEY_1, 1111), @@ -187,36 +204,44 @@ public class MaintainabilityMeasuresVisitorTest { ncloc1211 * DEV_COST)); } - @Test - public void compute_maintainability_debt_ratio_measure() { + private static Stream<Arguments> metrics() { + return Stream.of( + arguments(TECHNICAL_DEBT_KEY, SQALE_DEBT_RATIO_KEY, SQALE_RATING_KEY, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY), + arguments(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, + EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY)); + } + + @ParameterizedTest + @MethodSource("metrics") + void compute_maintainability_debt_ratio_measure(String remediationEffortKey, String debtRatioKey) { treeRootHolder.setRoot(ROOT_PROJECT); int file1Ncloc = 10; addRawMeasure(NCLOC_KEY, FILE_1_REF, file1Ncloc); long file1MaintainabilityCost = 100L; - addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, file1MaintainabilityCost); + addRawMeasure(remediationEffortKey, FILE_1_REF, file1MaintainabilityCost); int file2Ncloc = 5; addRawMeasure(NCLOC_KEY, FILE_2_REF, file2Ncloc); long file2MaintainabilityCost = 1L; - addRawMeasure(TECHNICAL_DEBT_KEY, FILE_2_REF, file2MaintainabilityCost); + addRawMeasure(remediationEffortKey, FILE_2_REF, file2MaintainabilityCost); long directoryMaintainabilityCost = 100L; - addRawMeasure(TECHNICAL_DEBT_KEY, DIRECTORY_REF, directoryMaintainabilityCost); + addRawMeasure(remediationEffortKey, DIRECTORY_REF, directoryMaintainabilityCost); long projectMaintainabilityCost = 1000L; - addRawMeasure(TECHNICAL_DEBT_KEY, PROJECT_REF, projectMaintainabilityCost); + addRawMeasure(remediationEffortKey, PROJECT_REF, projectMaintainabilityCost); underTest.visit(ROOT_PROJECT); - verifyAddedRawMeasure(FILE_1_REF, SQALE_DEBT_RATIO_KEY, file1MaintainabilityCost * 1d / (file1Ncloc * DEV_COST) * 100); - verifyAddedRawMeasure(FILE_2_REF, SQALE_DEBT_RATIO_KEY, file2MaintainabilityCost * 1d / (file2Ncloc * DEV_COST) * 100); - verifyAddedRawMeasure(DIRECTORY_REF, SQALE_DEBT_RATIO_KEY, directoryMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST) * 100); - verifyAddedRawMeasure(PROJECT_REF, SQALE_DEBT_RATIO_KEY, projectMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST) * 100); + verifyAddedRawMeasure(FILE_1_REF, debtRatioKey, file1MaintainabilityCost * 1d / (file1Ncloc * DEV_COST) * 100); + verifyAddedRawMeasure(FILE_2_REF, debtRatioKey, file2MaintainabilityCost * 1d / (file2Ncloc * DEV_COST) * 100); + verifyAddedRawMeasure(DIRECTORY_REF, debtRatioKey, directoryMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST) * 100); + verifyAddedRawMeasure(PROJECT_REF, debtRatioKey, projectMaintainabilityCost * 1d / ((file1Ncloc + file2Ncloc) * DEV_COST) * 100); } @Test - public void compute_maintainability_rating_measure() { + void compute_maintainability_rating_measure() { treeRootHolder.setRoot(ROOT_PROJECT); addRawMeasure(NCLOC_KEY, FILE_1_REF, 10); @@ -237,58 +262,85 @@ public class MaintainabilityMeasuresVisitorTest { } @Test - public void compute_effort_to_maintainability_rating_A_measure() { + void compute_software_quality_maintainability_rating_measure() { + treeRootHolder.setRoot(ROOT_PROJECT); + + addRawMeasure(NCLOC_KEY, FILE_1_REF, 10); + addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, FILE_1_REF, 100L); + + addRawMeasure(NCLOC_KEY, FILE_2_REF, 5); + addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, FILE_2_REF, 1L); + + addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, DIRECTORY_REF, 100L); + addRawMeasure(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, PROJECT_REF, 1000L); + + underTest.visit(ROOT_PROJECT); + + verifyAddedRawMeasure(FILE_1_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, C); + verifyAddedRawMeasure(FILE_2_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, A); + verifyAddedRawMeasure(DIRECTORY_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, C); + verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, D); + } + + @ParameterizedTest + @MethodSource("metrics") + void compute_effort_to_maintainability_rating_A_measure(String remediationEffortKey, String debtRatioKey, + String ratingKey, String effortReachRatingAKey) { treeRootHolder.setRoot(ROOT_PROJECT); int file1Ncloc = 10; long file1Effort = 100L; addRawMeasure(NCLOC_KEY, FILE_1_REF, file1Ncloc); - addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, file1Effort); + addRawMeasure(remediationEffortKey, FILE_1_REF, file1Effort); int file2Ncloc = 5; long file2Effort = 20L; addRawMeasure(NCLOC_KEY, FILE_2_REF, file2Ncloc); - addRawMeasure(TECHNICAL_DEBT_KEY, FILE_2_REF, file2Effort); + addRawMeasure(remediationEffortKey, FILE_2_REF, file2Effort); long dirEffort = 120L; - addRawMeasure(TECHNICAL_DEBT_KEY, DIRECTORY_REF, dirEffort); + addRawMeasure(remediationEffortKey, DIRECTORY_REF, dirEffort); long projectEffort = 150L; - addRawMeasure(TECHNICAL_DEBT_KEY, PROJECT_REF, projectEffort); + addRawMeasure(remediationEffortKey, PROJECT_REF, projectEffort); underTest.visit(ROOT_PROJECT); - verifyAddedRawMeasure(FILE_1_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, + verifyAddedRawMeasure(FILE_1_REF, effortReachRatingAKey, (long) (file1Effort - RATING_GRID[0] * file1Ncloc * DEV_COST)); - verifyAddedRawMeasure(FILE_2_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, + verifyAddedRawMeasure(FILE_2_REF, effortReachRatingAKey, (long) (file2Effort - RATING_GRID[0] * file2Ncloc * DEV_COST)); - verifyAddedRawMeasure(DIRECTORY_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, + verifyAddedRawMeasure(DIRECTORY_REF, effortReachRatingAKey, (long) (dirEffort - RATING_GRID[0] * (file1Ncloc + file2Ncloc) * DEV_COST)); - verifyAddedRawMeasure(PROJECT_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, + verifyAddedRawMeasure(PROJECT_REF, effortReachRatingAKey, (long) (projectEffort - RATING_GRID[0] * (file1Ncloc + file2Ncloc) * DEV_COST)); } - @Test - public void compute_0_effort_to_maintainability_rating_A_when_effort_is_lower_than_dev_cost() { + @ParameterizedTest + @MethodSource("metrics") + void compute_0_effort_to_maintainability_rating_A_when_effort_is_lower_than_dev_cost(String remediationEffortKey, String debtRatioKey, + String ratingKey, String effortReachRatingAKey) { treeRootHolder.setRoot(ROOT_PROJECT); addRawMeasure(NCLOC_KEY, FILE_1_REF, 10); - addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, 2L); + addRawMeasure(remediationEffortKey, FILE_1_REF, 2L); underTest.visit(ROOT_PROJECT); - verifyAddedRawMeasure(FILE_1_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, 0L); + verifyAddedRawMeasure(FILE_1_REF, effortReachRatingAKey, 0L); } - @Test - public void effort_to_maintainability_rating_A_is_same_as_effort_when_no_dev_cost() { + @ParameterizedTest + @MethodSource("metrics") + void effort_to_maintainability_rating_A_is_same_as_effort_when_no_dev_cost(String remediationEffortKey, String debtRatioKey, + String ratingKey, String effortReachRatingAKey) { treeRootHolder.setRoot(ROOT_PROJECT); - addRawMeasure(TECHNICAL_DEBT_KEY, FILE_1_REF, 100L); + addRawMeasure(remediationEffortKey, FILE_1_REF, 100L); underTest.visit(ROOT_PROJECT); - verifyAddedRawMeasure(FILE_1_REF, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, 100); + verifyAddedRawMeasure(FILE_1_REF, effortReachRatingAKey, 100); } private void addRawMeasure(String metricKey, int componentRef, long value) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java index f29905756dd..fb1eb27c595 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java @@ -26,10 +26,14 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import org.assertj.core.data.Offset; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.sonar.api.utils.KeyValueFormat; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.FileAttributes; @@ -44,6 +48,7 @@ import org.sonar.server.measure.DebtRatingGrid; import org.sonar.server.measure.Rating; import static com.google.common.base.Preconditions.checkArgument; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA; @@ -63,10 +68,17 @@ import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilde import static org.sonar.ce.task.projectanalysis.measure.MeasureAssert.assertThat; import static org.sonar.server.measure.Rating.A; import static org.sonar.server.measure.Rating.D; +import static org.sonar.server.measure.Rating.E; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY; public class NewMaintainabilityMeasuresVisitorTest { - private static final double[] RATING_GRID = new double[]{0.1, 0.2, 0.5, 1}; + private static final double[] RATING_GRID = new double[] {0.1, 0.2, 0.5, 1}; private static final String LANGUAGE_1_KEY = "language 1 key"; private static final long DEV_COST = 30L; @@ -74,16 +86,20 @@ public class NewMaintainabilityMeasuresVisitorTest { private static final int LANGUAGE_1_FILE_REF = 11111; private static final Offset<Double> VALUE_COMPARISON_OFFSET = Offset.offset(0.01); - @Rule + @RegisterExtension public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - @Rule + @RegisterExtension public MetricRepositoryRule metricRepository = new MetricRepositoryRule() .add(NEW_TECHNICAL_DEBT) .add(NCLOC_DATA) .add(NEW_SQALE_DEBT_RATIO) .add(NEW_MAINTAINABILITY_RATING) - .add(NEW_DEVELOPMENT_COST); - @Rule + .add(NEW_DEVELOPMENT_COST) + .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT) + .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO) + .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING); + + @RegisterExtension public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); private NewLinesRepository newLinesRepository = mock(NewLinesRepository.class); @@ -91,180 +107,202 @@ public class NewMaintainabilityMeasuresVisitorTest { private VisitorsCrawler underTest; - @Before + @BeforeEach public void setUp() { when(ratingSettings.getDebtRatingGrid()).thenReturn(new DebtRatingGrid(RATING_GRID)); when(ratingSettings.getDevCost()).thenReturn(DEV_COST); underTest = new VisitorsCrawler(Arrays.asList(new NewMaintainabilityMeasuresVisitor(metricRepository, measureRepository, newLinesRepository, ratingSettings))); } - @Test - public void project_has_new_measures() { + private static Stream<Arguments> metrics() { + return Stream.of( + arguments(NEW_TECHNICAL_DEBT_KEY, NEW_SQALE_DEBT_RATIO_KEY, NEW_MAINTAINABILITY_RATING_KEY), + arguments(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY)); + } + + @ParameterizedTest + @MethodSource("metrics") + void project_has_new_measures(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(newLinesRepository.newLinesAvailable()).thenReturn(true); treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build()); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(ROOT_REF, 0); - assertNewMaintainability(ROOT_REF, A); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0); + assertNewRating(ratingKey, ROOT_REF, A); } - @Test - public void project_has_no_measure_if_new_lines_not_available() { + @ParameterizedTest + @MethodSource("metrics") + void project_has_no_measure_if_new_lines_not_available(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(newLinesRepository.newLinesAvailable()).thenReturn(false); treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build()); underTest.visit(treeRootHolder.getRoot()); - assertNoNewDebtRatioMeasure(ROOT_REF); - assertNoNewMaintainability(ROOT_REF); + assertNoNewDebtRatioMeasure(debtRatioKey, ROOT_REF); + assertNoNewMaintainability(ratingKey, ROOT_REF); } - @Test - public void file_has_no_new_debt_ratio_variation_if_new_lines_not_available() { + @ParameterizedTest + @MethodSource("metrics") + void file_has_no_new_debt_ratio_variation_if_new_lines_not_available(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(newLinesRepository.newLinesAvailable()).thenReturn(false); - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50)); + setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50)); underTest.visit(treeRootHolder.getRoot()); - assertNoNewDebtRatioMeasure(LANGUAGE_1_FILE_REF); - assertNoNewDebtRatioMeasure(ROOT_REF); + assertNoNewDebtRatioMeasure(debtRatioKey, LANGUAGE_1_FILE_REF); + assertNoNewDebtRatioMeasure(debtRatioKey, ROOT_REF); } - @Test - public void file_has_0_new_debt_ratio_if_no_line_is_new() { + @ParameterizedTest + @MethodSource("metrics") + void file_has_0_new_debt_ratio_if_no_line_is_new(String remediationEffortKey, String debtRatioKey, String ratingKey) { ReportComponent file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build(); treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) .addChildren(file) .build()); - measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50)); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, createNewDebtMeasure(50)); measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4)); setNewLines(file); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0); - assertNewDebtRatioValues(ROOT_REF, 0); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0); } - @Test - public void file_has_new_debt_ratio_if_some_lines_are_new() { + @ParameterizedTest + @MethodSource("metrics") + void file_has_new_debt_ratio_if_some_lines_are_new(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(newLinesRepository.newLinesAvailable()).thenReturn(true); - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50)); + setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 83.33); - assertNewDebtRatioValues(ROOT_REF, 83.33); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 83.33); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 83.33); } - @Test - public void new_debt_ratio_changes_with_language_cost() { + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_changes_with_language_cost(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(ratingSettings.getDevCost()).thenReturn(DEV_COST * 10); - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50)); + setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 8.33); - assertNewDebtRatioValues(ROOT_REF, 8.33); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 8.33); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 8.33); } - @Test - public void new_debt_ratio_changes_with_new_technical_debt() { - setupOneFileAloneInAProject(500, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(500)); + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_changes_with_new_technical_debt(String remediationEffortKey, String debtRatioKey, String ratingKey) { + setupOneFileAloneInAProject(remediationEffortKey, 500, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(500)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 833.33); - assertNewDebtRatioValues(ROOT_REF, 833.33); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 833.33); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 833.33); } - @Test - public void new_debt_ratio_on_non_file_level_is_based_on_new_technical_debt_of_that_level() { - setupOneFileAloneInAProject(500, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(1200)); + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_on_non_file_level_is_based_on_new_technical_debt_of_that_level(String remediationEffortKey, String debtRatioKey, String ratingKey) { + setupOneFileAloneInAProject(remediationEffortKey, 500, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(1200)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 833.33); - assertNewDebtRatioValues(ROOT_REF, 833.33); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 833.33); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 833.33); } - @Test - public void new_debt_ratio_when_file_is_unit_test() { - setupOneFileAloneInAProject(500, Flag.UT_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(1200)); + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_when_file_is_unit_test(String remediationEffortKey, String debtRatioKey, String ratingKey) { + setupOneFileAloneInAProject(remediationEffortKey, 500, Flag.UT_FILE, Flag.WITH_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(1200)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 833.33); - assertNewDebtRatioValues(ROOT_REF, 833.33); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 833.33); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 833.33); } - @Test - public void new_debt_ratio_is_0_when_file_has_no_new_lines() { + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_is_0_when_file_has_no_new_lines(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(newLinesRepository.newLinesAvailable()).thenReturn(true); - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50)); + setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0); - assertNewDebtRatioValues(ROOT_REF, 0); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0); } - @Test - public void new_debt_ratio_is_0_on_non_file_level_when_no_file_has_new_lines() { + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_is_0_on_non_file_level_when_no_file_has_new_lines(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(newLinesRepository.newLinesAvailable()).thenReturn(true); - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(200)); + setupOneFileAloneInAProject(remediationEffortKey,50, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(200)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0); - assertNewDebtRatioValues(ROOT_REF, 0); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0); } - @Test - public void new_debt_ratio_is_0_when_there_is_no_ncloc_in_file() { - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50)); + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_is_0_when_there_is_no_ncloc_in_file(String remediationEffortKey, String debtRatioKey, String ratingKey) { + setupOneFileAloneInAProject(remediationEffortKey, 50, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0); - assertNewDebtRatioValues(ROOT_REF, 0); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0); } - @Test - public void new_debt_ratio_is_0_on_non_file_level_when_one_file_has_zero_new_debt_because_of_no_changeset() { - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(200)); + @ParameterizedTest + @MethodSource("metrics") + 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) { + setupOneFileAloneInAProject(remediationEffortKey, 50, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(200)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0); - assertNewDebtRatioValues(ROOT_REF, 0); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0); } - @Test - public void new_debt_ratio_is_0_when_ncloc_measure_is_missing() { - setupOneFileAloneInAProject(50, Flag.SRC_FILE, Flag.MISSING_MEASURE_NCLOC, Flag.WITH_NEW_LINES); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50)); + @ParameterizedTest + @MethodSource("metrics") + void new_debt_ratio_is_0_when_ncloc_measure_is_missing(String remediationEffortKey, String debtRatioKey, String ratingKey) { + setupOneFileAloneInAProject(remediationEffortKey, 50, Flag.SRC_FILE, Flag.MISSING_MEASURE_NCLOC, Flag.WITH_NEW_LINES); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(50)); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0); - assertNewDebtRatioValues(ROOT_REF, 0); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 0); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 0); } - @Test - public void leaf_components_always_have_a_measure_when_at_least_one_period_exist_and_ratio_is_computed_from_current_level_new_debt() { + @ParameterizedTest + @MethodSource("metrics") + 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, + String ratingKey) { Component file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build(); treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) @@ -275,22 +313,23 @@ public class NewMaintainabilityMeasuresVisitorTest { .build()); Measure newDebtMeasure = createNewDebtMeasure(50); - measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); - measureRepository.addRawMeasure(111, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(150)); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(250)); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure); + measureRepository.addRawMeasure(111, remediationEffortKey, createNewDebtMeasure(150)); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(250)); // 4 lines file, only first one is not ncloc measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4)); // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date setNewLines(file, 3, 4); underTest.visit(treeRootHolder.getRoot()); - assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 83.33); - assertNewDebtRatioValues(111, 83.33); - assertNewDebtRatioValues(ROOT_REF, 83.33); + assertNewDebtRatioValues(debtRatioKey, LANGUAGE_1_FILE_REF, 83.33); + assertNewDebtRatioValues(debtRatioKey, 111, 83.33); + assertNewDebtRatioValues(debtRatioKey, ROOT_REF, 83.33); } - @Test - public void compute_new_maintainability_rating() { + @ParameterizedTest + @MethodSource("metrics") + void compute_new_maintainability_rating(String remediationEffortKey, String debtRatioKey, String ratingKey) { ReportComponent file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build(); treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) @@ -301,9 +340,9 @@ public class NewMaintainabilityMeasuresVisitorTest { .build()); Measure newDebtMeasure = createNewDebtMeasure(50); - measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); - measureRepository.addRawMeasure(111, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(150)); - measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(250)); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure); + measureRepository.addRawMeasure(111, remediationEffortKey, createNewDebtMeasure(150)); + measureRepository.addRawMeasure(ROOT_REF, remediationEffortKey, createNewDebtMeasure(250)); // 4 lines file, only first one is not ncloc measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4)); // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date @@ -312,13 +351,40 @@ public class NewMaintainabilityMeasuresVisitorTest { underTest.visit(treeRootHolder.getRoot()); - assertNewMaintainability(LANGUAGE_1_FILE_REF, D); - assertNewMaintainability(111, D); - assertNewMaintainability(ROOT_REF, D); + assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, D); + assertNewRating(ratingKey, 111, D); + assertNewRating(ratingKey, ROOT_REF, D); + } + + @ParameterizedTest + @MethodSource("metrics") + void compute_new_maintainability_rating_map_to_D(String remediationEffortKey, String debtRatioKey, String ratingKey) { + ReportComponent file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build(); + treeRootHolder.setRoot( + builder(PROJECT, ROOT_REF) + .addChildren( + builder(DIRECTORY, 111) + .addChildren(file) + .build()) + .build()); + + Measure newDebtMeasure = createNewDebtMeasure(400); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4)); + + setNewLines(file, 3, 4); + + underTest.visit(treeRootHolder.getRoot()); + + if (ratingKey.equals(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY)) { + assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, D); + } else if (ratingKey.equals(NEW_MAINTAINABILITY_RATING_KEY)) { + assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, E); + } } @Test - public void compute_new_development_cost() { + void compute_new_development_cost() { ReportComponent file1 = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 4)).build(); ReportComponent file2 = builder(FILE, 22_222).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 6)).build(); treeRootHolder.setRoot( @@ -346,8 +412,9 @@ public class NewMaintainabilityMeasuresVisitorTest { assertNewDevelopmentCostValues(22_222, 3 * DEV_COST); } - @Test - public void compute_new_maintainability_rating_to_A_when_no_debt() { + @ParameterizedTest + @MethodSource("metrics") + void compute_new_maintainability_rating_to_A_when_no_debt(String remediationEffortKey, String debtRatioKey, String ratingKey) { when(newLinesRepository.newLinesAvailable()).thenReturn(true); treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) @@ -360,12 +427,12 @@ public class NewMaintainabilityMeasuresVisitorTest { underTest.visit(treeRootHolder.getRoot()); - assertNewMaintainability(LANGUAGE_1_FILE_REF, A); - assertNewMaintainability(111, A); - assertNewMaintainability(ROOT_REF, A); + assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, A); + assertNewRating(ratingKey, 111, A); + assertNewRating(ratingKey, ROOT_REF, A); } - private void setupOneFileAloneInAProject(int newDebt, Flag isUnitTest, Flag withNclocLines, Flag withNewLines) { + private void setupOneFileAloneInAProject(String remediationEffortKey, int newDebt, Flag isUnitTest, Flag withNclocLines, Flag withNewLines) { checkArgument(isUnitTest == Flag.UT_FILE || isUnitTest == Flag.SRC_FILE); checkArgument(withNclocLines == Flag.WITH_NCLOC || withNclocLines == Flag.NO_NCLOC || withNclocLines == Flag.MISSING_MEASURE_NCLOC); checkArgument(withNewLines == Flag.WITH_NEW_LINES || withNewLines == Flag.NO_NEW_LINES); @@ -377,7 +444,7 @@ public class NewMaintainabilityMeasuresVisitorTest { .build()); Measure newDebtMeasure = createNewDebtMeasure(newDebt); - measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, remediationEffortKey, newDebtMeasure); if (withNclocLines == Flag.WITH_NCLOC) { // 4 lines file, only first one is not ncloc measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4)); @@ -431,25 +498,25 @@ public class NewMaintainabilityMeasuresVisitorTest { return newMeasureBuilder().create(KeyValueFormat.format(builder.build(), KeyValueFormat.newIntegerConverter(), KeyValueFormat.newIntegerConverter())); } - private void assertNoNewDebtRatioMeasure(int componentRef) { - assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SQALE_DEBT_RATIO_KEY)) + private void assertNoNewDebtRatioMeasure(String debtRatioKey, int componentRef) { + assertThat(measureRepository.getAddedRawMeasure(componentRef, debtRatioKey)) .isAbsent(); } - private void assertNewDebtRatioValues(int componentRef, double expectedValue) { - assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SQALE_DEBT_RATIO_KEY)).hasValue(expectedValue, VALUE_COMPARISON_OFFSET); + private void assertNewDebtRatioValues(String debtRatioKey, int componentRef, double expectedValue) { + assertThat(measureRepository.getAddedRawMeasure(componentRef, debtRatioKey)).hasValue(expectedValue, VALUE_COMPARISON_OFFSET); } private void assertNewDevelopmentCostValues(int componentRef, float expectedValue) { assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_DEVELOPMENT_COST_KEY)).hasValue(expectedValue, VALUE_COMPARISON_OFFSET); } - private void assertNewMaintainability(int componentRef, Rating expectedValue) { - assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_MAINTAINABILITY_RATING_KEY)).hasValue(expectedValue.getIndex()); + private void assertNewRating(String ratingKey, int componentRef, Rating expectedValue) { + assertThat(measureRepository.getAddedRawMeasure(componentRef, ratingKey)).hasValue(expectedValue.getIndex()); } - private void assertNoNewMaintainability(int componentRef) { - assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_MAINTAINABILITY_RATING_KEY)) + private void assertNoNewMaintainability(String ratingKey, int componentRef) { + assertThat(measureRepository.getAddedRawMeasure(componentRef, ratingKey)) .isAbsent(); } } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java index 247a1829f98..4bbc2b54ac7 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java @@ -78,6 +78,17 @@ public class DebtRatingGrid { .orElseThrow(() -> new IllegalArgumentException(format("Invalid value '%s'", value))); } + /** + * Computes a rating from A to D, where E is converted to D. + */ + public Rating getAToDRatingForDensity(double value) { + return ratingBounds.entrySet().stream() + .filter(e -> e.getValue().match(value)) + .map(e -> e.getKey() == E ? D : e.getKey()) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(format("Invalid value '%s'", value))); + } + public double getGradeLowerBound(Rating rating) { if (rating.getIndex() > 1) { return gridValues[rating.getIndex() - 2]; diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java index 8a312cba599..6557e108815 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java @@ -55,6 +55,21 @@ public class DebtRatingGridTest { } @Test + public void getAToDRatingForDensity_returnsValueBetweenAAndD() { + assertThat(ratingGrid.getAToDRatingForDensity(0)).isEqualTo(A); + assertThat(ratingGrid.getAToDRatingForDensity(0.05)).isEqualTo(A); + assertThat(ratingGrid.getAToDRatingForDensity(0.09999999)).isEqualTo(A); + assertThat(ratingGrid.getAToDRatingForDensity(0.1)).isEqualTo(A); + assertThat(ratingGrid.getAToDRatingForDensity(0.15)).isEqualTo(B); + assertThat(ratingGrid.getAToDRatingForDensity(0.2)).isEqualTo(B); + assertThat(ratingGrid.getAToDRatingForDensity(0.25)).isEqualTo(C); + assertThat(ratingGrid.getAToDRatingForDensity(0.5)).isEqualTo(C); + assertThat(ratingGrid.getAToDRatingForDensity(0.65)).isEqualTo(D); + assertThat(ratingGrid.getAToDRatingForDensity(1)).isEqualTo(D); + assertThat(ratingGrid.getAToDRatingForDensity(1.01)).isEqualTo(D); + } + + @Test public void density_matching_exact_grid_values() { assertThat(ratingGrid.getRatingForDensity(0.1)).isEqualTo(A); assertThat(ratingGrid.getRatingForDensity(0.2)).isEqualTo(B); |