From bc430dbb92b7b25b3c640625948a37a5a2fa5c16 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 30 Sep 2016 13:15:03 +0200 Subject: [PATCH] SONAR-7782 Compute Maintainability Rating on New Code --- .../NewMaintainabilityMeasuresVisitor.java | 35 +++++--- ...NewMaintainabilityMeasuresVisitorTest.java | 87 +++++++++++++++---- .../resources/org/sonar/l10n/core.properties | 2 + .../org/sonar/api/measures/CoreMetrics.java | 19 ++++ 4 files changed, 114 insertions(+), 29 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java index 16328ee9f43..c33e94ebe84 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java @@ -42,6 +42,10 @@ import org.sonar.server.computation.task.projectanalysis.scm.Changeset; import org.sonar.server.computation.task.projectanalysis.scm.ScmInfo; import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepository; +import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY; import static org.sonar.api.utils.KeyValueFormat.newIntegerConverter; import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder; @@ -53,6 +57,7 @@ import static org.sonar.server.computation.task.projectanalysis.measure.MeasureV * * Compute following measure : * {@link CoreMetrics#NEW_SQALE_DEBT_RATIO_KEY} + * {@link CoreMetrics#NEW_MAINTAINABILITY_RATING_KEY} */ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter { private static final Logger LOG = Loggers.get(NewMaintainabilityMeasuresVisitor.class); @@ -61,25 +66,31 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter path) { - MeasureVariations.Builder builder = newMeasureVariationsBuilder(); + MeasureVariations.Builder newDebtRatio = newMeasureVariationsBuilder(); + MeasureVariations.Builder newMaintainability = newMeasureVariationsBuilder(); for (Period period : periodsHolder.getPeriods()) { double density = computeDensity(path.current(), period); - builder.setVariation(period, 100.0 * density); + newDebtRatio.setVariation(period, 100.0 * density); + newMaintainability.setVariation(period, ratingGrid.getRatingForDensity(density).getIndex()); + } + if (!newDebtRatio.isEmpty()) { + measureRepository.add(component, this.newDebtRatioMetric, newMeasureBuilder().setVariations(newDebtRatio.build()).createNoValue()); } - if (!builder.isEmpty()) { - Measure measure = newMeasureBuilder().setVariations(builder.build()).createNoValue(); - measureRepository.add(component, this.newDebtRatioMetric, measure); + if (!newMaintainability.isEmpty()) { + measureRepository.add(component, this.newMaintainabilityRatingMetric, newMeasureBuilder().setVariations(newMaintainability.build()).createNoValue()); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java index 015df327fa4..df9964c4c53 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java @@ -25,11 +25,11 @@ import com.google.common.collect.Ordering; import java.util.Arrays; import java.util.Set; import org.assertj.core.data.Offset; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.KeyValueFormat; import org.sonar.server.computation.task.projectanalysis.component.Component; -import org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor; import org.sonar.server.computation.task.projectanalysis.component.FileAttributes; import org.sonar.server.computation.task.projectanalysis.component.ReportComponent; import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderRule; @@ -40,6 +40,7 @@ import org.sonar.server.computation.task.projectanalysis.measure.MeasureVariatio import org.sonar.server.computation.task.projectanalysis.metric.MetricRepositoryRule; import org.sonar.server.computation.task.projectanalysis.period.Period; import org.sonar.server.computation.task.projectanalysis.period.PeriodsHolderRule; +import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating; import org.sonar.server.computation.task.projectanalysis.scm.Changeset; import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepositoryRule; @@ -48,6 +49,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA; import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING; +import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO; import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT; @@ -58,8 +61,13 @@ import static org.sonar.server.computation.task.projectanalysis.component.Compon import static org.sonar.server.computation.task.projectanalysis.component.Component.Type.PROJECT; import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder; import static org.sonar.server.computation.task.projectanalysis.measure.MeasureAssert.assertThat; +import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A; +import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D; public class NewMaintainabilityMeasuresVisitorTest { + + 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 LANGUAGE_1_DEV_COST = 30l; private static final long PERIOD_2_SNAPSHOT_DATE = 12323l; @@ -78,7 +86,8 @@ public class NewMaintainabilityMeasuresVisitorTest { public MetricRepositoryRule metricRepository = new MetricRepositoryRule() .add(NEW_TECHNICAL_DEBT) .add(NCLOC_DATA) - .add(NEW_SQALE_DEBT_RATIO); + .add(NEW_SQALE_DEBT_RATIO) + .add(NEW_MAINTAINABILITY_RATING); @Rule public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); @Rule @@ -86,8 +95,14 @@ public class NewMaintainabilityMeasuresVisitorTest { private RatingSettings ratingSettings = mock(RatingSettings.class); - private VisitorsCrawler underTest = new VisitorsCrawler(Arrays.asList(new NewMaintainabilityMeasuresVisitor(metricRepository, measureRepository, scmInfoRepository, - periodsHolder, ratingSettings))); + private VisitorsCrawler underTest; + + @Before + public void setUp() throws Exception { + when(ratingSettings.getRatingGrid()).thenReturn(new RatingGrid(RATING_GRID)); + underTest = new VisitorsCrawler(Arrays.asList(new NewMaintainabilityMeasuresVisitor(metricRepository, measureRepository, scmInfoRepository, + periodsHolder, ratingSettings))); + } @Test public void project_has_new_debt_ratio_variation_for_each_defined_period() { @@ -128,10 +143,8 @@ public class NewMaintainabilityMeasuresVisitorTest { treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) .addChildren( - builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build() - ) - .build() - ); + builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build()) + .build()); measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12)); measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4)); scmInfoRepository.setScmInfo(LANGUAGE_1_FILE_REF, createChangesets(PERIOD_2_SNAPSHOT_DATE - 100, 4)); @@ -283,11 +296,10 @@ public class NewMaintainabilityMeasuresVisitorTest { .addChildren( builder(DIRECTORY, 111) .addChildren( - builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build() - ).build() - ).build() - ).build() - ); + builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build()) + .build()) + .build()) + .build()); Measure newDebtMeasure = createNewDebtMeasure(50, 12); measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); @@ -327,8 +339,7 @@ public class NewMaintainabilityMeasuresVisitorTest { treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) .addChildren(builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build()) - .build() - ); + .build()); Measure newDebtMeasure = newMeasureBuilder().setVariations(new MeasureVariations(500d, 500d, 500d, 120d, 120d)).createNoValue(); measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); @@ -350,6 +361,40 @@ public class NewMaintainabilityMeasuresVisitorTest { .hasVariation5(0d, VARIATION_COMPARISON_OFFSET); } + @Test + public void compute_new_maintainability_rating() throws Exception { + setTwoPeriods(); + when(ratingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); + treeRootHolder.setRoot( + builder(PROJECT, ROOT_REF) + .addChildren( + builder(MODULE, 11) + .addChildren( + builder(DIRECTORY, 111) + .addChildren( + builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build()) + .build()) + .build()) + .build()); + + Measure newDebtMeasure = createNewDebtMeasure(50, 12); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); + measureRepository.addRawMeasure(111, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(150, 112)); + measureRepository.addRawMeasure(11, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(200, 112)); + measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(250, 212)); + // 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 + scmInfoRepository.setScmInfo(LANGUAGE_1_FILE_REF, createChangesets(PERIOD_2_SNAPSHOT_DATE - 100, 2, PERIOD_2_SNAPSHOT_DATE + 100, 2)); + + underTest.visit(treeRootHolder.getRoot()); + + assertNewMaintainability(LANGUAGE_1_FILE_REF, D, A); + assertNewMaintainability(111, D, A); + assertNewMaintainability(11, D, A); + assertNewMaintainability(ROOT_REF, D, A); + } + private void setupOneFileAloneInAProject(int newDebtPeriod2, int newDebtPeriod4, Flag isUnitTest, Flag withNclocLines, Flag withChangeSets) { checkArgument(isUnitTest == Flag.UT_FILE || isUnitTest == Flag.SRC_FILE); checkArgument(withNclocLines == Flag.WITH_NCLOC || withNclocLines == Flag.NO_NCLOC || withNclocLines == Flag.MISSING_MEASURE_NCLOC); @@ -358,10 +403,8 @@ public class NewMaintainabilityMeasuresVisitorTest { treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) .addChildren( - builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(isUnitTest == Flag.UT_FILE, LANGUAGE_1_KEY)).build() - ) - .build() - ); + builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(isUnitTest == Flag.UT_FILE, LANGUAGE_1_KEY)).build()) + .build()); Measure newDebtMeasure = createNewDebtMeasure(newDebtPeriod2, newDebtPeriod4); measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); @@ -448,6 +491,12 @@ public class NewMaintainabilityMeasuresVisitorTest { .hasVariation4(expectedPeriod4Value, VARIATION_COMPARISON_OFFSET); } + private void assertNewMaintainability(int componentRef, Rating expectedPeriod2Value, Rating expectedPeriod4Value) { + assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_MAINTAINABILITY_RATING_KEY)) + .hasVariation2(expectedPeriod2Value.getIndex()) + .hasVariation4(expectedPeriod4Value.getIndex()); + } + private void setTwoPeriods() { periodsHolder.setPeriods( new Period(2, SOME_PERIOD_MODE, null, PERIOD_2_SNAPSHOT_DATE, SOME_ANALYSIS_UUID), diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 538d6ed97bf..0b173769ef3 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -392,6 +392,8 @@ metric.new_lines_to_cover.description=Lines to cover on new code metric.new_lines_to_cover.name=Lines to Cover on New Code metric.new_line_coverage.description=Line coverage of added/changed code metric.new_line_coverage.name=Line Coverage on New Code +metric.new_maintainability_rating.description=Maintainability rating on new code +metric.new_maintainability_rating.name=Maintainability Rating on New Code metric.new_major_violations.description=New Major issues metric.new_major_violations.name=New Major Issues metric.new_minor_violations.description=New Minor issues diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index 3be47d98602..2ba6e188ee5 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -2168,6 +2168,25 @@ public final class CoreMetrics { .setWorstValue(5.0) .create(); + /** + * @since 6.2 + */ + public static final String NEW_MAINTAINABILITY_RATING_KEY = "new_maintainability_rating"; + + /** + * @since 6.2 + */ + public static final Metric NEW_MAINTAINABILITY_RATING = new Metric.Builder(NEW_MAINTAINABILITY_RATING_KEY, "Maintainability Rating on New Code", Metric.ValueType.RATING) + .setDescription("Maintainability rating on new code") + .setDomain(DOMAIN_MAINTAINABILITY) + .setDirection(Metric.DIRECTION_WORST) + .setDeleteHistoricalData(true) + .setOptimizedBestValue(true) + .setQualitative(true) + .setBestValue(1.0) + .setWorstValue(5.0) + .create(); + /** * @since 4.5 */ -- 2.39.5