]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7782 Compute Maintainability Rating on New Code
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 30 Sep 2016 11:15:03 +0000 (13:15 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 3 Oct 2016 16:19:37 +0000 (18:19 +0200)
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java
sonar-core/src/main/resources/org/sonar/l10n/core.properties
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java

index 16328ee9f434e813825ff7e12979a3614d4e38a2..c33e94ebe849c750e21a409cccb098cebeec8fc7 100644 (file)
@@ -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<NewMaintainabilityMeasuresVisitor.Counter> {
   private static final Logger LOG = Loggers.get(NewMaintainabilityMeasuresVisitor.class);
@@ -61,25 +66,31 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N
   private final MeasureRepository measureRepository;
   private final PeriodsHolder periodsHolder;
   private final RatingSettings ratingSettings;
+  private final RatingGrid ratingGrid;
 
   private final Metric newDebtMetric;
   private final Metric nclocDataMetric;
+
   private final Metric newDebtRatioMetric;
+  private final Metric newMaintainabilityRatingMetric;
 
   public NewMaintainabilityMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, ScmInfoRepository scmInfoRepository,
-                                           PeriodsHolder periodsHolder, RatingSettings ratingSettings) {
+    PeriodsHolder periodsHolder, RatingSettings ratingSettings) {
     super(CrawlerDepthLimit.FILE, POST_ORDER, CounterFactory.INSTANCE);
     this.measureRepository = measureRepository;
     this.scmInfoRepository = scmInfoRepository;
     this.periodsHolder = periodsHolder;
     this.ratingSettings = ratingSettings;
+    this.ratingGrid = ratingSettings.getRatingGrid();
 
     // computed by NewDebtAggregator which is executed by IntegrateIssuesVisitor
-    this.newDebtMetric = metricRepository.getByKey(CoreMetrics.NEW_TECHNICAL_DEBT_KEY);
+    this.newDebtMetric = metricRepository.getByKey(NEW_TECHNICAL_DEBT_KEY);
     // which line is ncloc and which isn't
-    this.nclocDataMetric = metricRepository.getByKey(CoreMetrics.NCLOC_DATA_KEY);
-    // output metric
-    this.newDebtRatioMetric = metricRepository.getByKey(CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY);
+    this.nclocDataMetric = metricRepository.getByKey(NCLOC_DATA_KEY);
+
+    // output metrics
+    this.newDebtRatioMetric = metricRepository.getByKey(NEW_SQALE_DEBT_RATIO_KEY);
+    this.newMaintainabilityRatingMetric = metricRepository.getByKey(NEW_MAINTAINABILITY_RATING_KEY);
   }
 
   @Override
@@ -107,14 +118,18 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N
   }
 
   private void computeAndSaveNewDebtRatioMeasure(Component component, Path<Counter> 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());
     }
   }
 
index 015df327fa4389bd59e2175d4939f6c2f1fd2b10..df9964c4c539a4b71a4a48ad62980761822805fe 100644 (file)
@@ -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.<ComponentVisitor>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),
index 538d6ed97bf297c7971bc229f87844840f09acb7..0b173769ef39364d8963aec0fd5936bd933a9994 100644 (file)
@@ -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
index 3be47d98602c9bd23d3a428866cc63eb925d68ee..2ba6e188ee52007c585384e7209e130ffa255a99 100644 (file)
@@ -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<Integer> 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
    */