aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-server
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2015-06-25 18:40:56 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2015-06-26 15:10:27 +0200
commit94597925e396c970293202b23f55871e5e64e47a (patch)
tree4944413f0a7455b7ab0c6c464cd866bf5cedb993 /server/sonar-server
parent01992f79d99fab567f780b8e2594f4ef73b50f5b (diff)
downloadsonarqube-94597925e396c970293202b23f55871e5e64e47a.tar.gz
sonarqube-94597925e396c970293202b23f55871e5e64e47a.zip
SONAR-6664 compute Sqale metrics in CE
Diffstat (limited to 'server/sonar-server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingGrid.java78
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingSettings.java151
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/sqale/package-info.java24
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/SqaleMeasuresStep.java191
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingGridTest.java63
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingSettingsTest.java120
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/SqaleMeasuresStepTest.java248
9 files changed, 878 insertions, 0 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
index 53fbde2c698..2c7e62a7c88 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
@@ -56,6 +56,7 @@ import org.sonar.server.computation.period.PeriodsHolderImpl;
import org.sonar.server.computation.qualitygate.EvaluationResultTextConverterImpl;
import org.sonar.server.computation.qualitygate.QualityGateHolderImpl;
import org.sonar.server.computation.qualitygate.QualityGateServiceImpl;
+import org.sonar.server.computation.sqale.SqaleRatingSettings;
import org.sonar.server.computation.step.ComputationStep;
import org.sonar.server.computation.step.ComputationSteps;
import org.sonar.server.view.index.ViewIndex;
@@ -133,6 +134,7 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
PeriodsHolderImpl.class,
QualityGateHolderImpl.class,
DebtModelHolderImpl.class,
+ SqaleRatingSettings.class,
BatchReportReaderImpl.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingGrid.java b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingGrid.java
new file mode 100644
index 00000000000..c180f924b4b
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingGrid.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.sqale;
+
+import java.util.Arrays;
+import org.sonar.api.utils.MessageException;
+
+public class SqaleRatingGrid {
+
+ private final double[] gridValues;
+
+ public SqaleRatingGrid(double[] gridValues) {
+ this.gridValues = Arrays.copyOf(gridValues, gridValues.length);
+ }
+
+ public int getRatingForDensity(double density) {
+ for (SqaleRating sqaleRating : SqaleRating.values()) {
+ double lowerBound = getGradeLowerBound(sqaleRating);
+ if (density >= lowerBound) {
+ return sqaleRating.getIndex();
+ }
+ }
+ throw MessageException.of("The SQALE density value should be between 0 and " + Double.MAX_VALUE + " and got " + density);
+ }
+
+ private double getGradeLowerBound(SqaleRating rating) {
+ if (rating.getIndex() > 1) {
+ return gridValues[rating.getIndex() - 2];
+ }
+ return 0;
+ }
+
+ public enum SqaleRating {
+ E(5),
+ D(4),
+ C(3),
+ B(2),
+ A(1);
+
+ private final int index;
+
+ SqaleRating(int index) {
+ this.index = index;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public static SqaleRating createForIndex(int index) {
+ for (SqaleRating rating : values()) {
+ if (rating.getIndex() == index) {
+ return rating;
+ }
+ }
+ throw new IllegalArgumentException("A SQALE rating must be in the range [1..5].");
+ }
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingSettings.java b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingSettings.java
new file mode 100644
index 00000000000..53add370c68
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleRatingSettings.java
@@ -0,0 +1,151 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.sqale;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.config.Settings;
+
+import static java.lang.String.format;
+import static org.sonar.api.CoreProperties.DEVELOPMENT_COST;
+import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS;
+import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY;
+import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY;
+import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY;
+import static org.sonar.api.CoreProperties.RATING_GRID;
+import static org.sonar.api.CoreProperties.RATING_GRID_DEF_VALUES;
+import static org.sonar.api.CoreProperties.SIZE_METRIC;
+
+public class SqaleRatingSettings {
+
+ private final Settings settings;
+ private final Map<String, LanguageSpecificConfiguration> languageSpecificConfigurationByLanguageKey;
+
+ public SqaleRatingSettings(Settings settings) {
+ this.settings = settings;
+ this.languageSpecificConfigurationByLanguageKey = buildLanguageSpecificConfigurationByLanguageKey(settings);
+ }
+
+ private static Map<String, LanguageSpecificConfiguration> buildLanguageSpecificConfigurationByLanguageKey(Settings settings) {
+ ImmutableMap.Builder<String, LanguageSpecificConfiguration> builder = ImmutableMap.builder();
+ String[] languageConfigIndexes = settings.getStringArray(LANGUAGE_SPECIFIC_PARAMETERS);
+ for (String languageConfigIndex : languageConfigIndexes) {
+ String languagePropertyKey = LANGUAGE_SPECIFIC_PARAMETERS + "." + languageConfigIndex + "." + LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY;
+ builder.put(settings.getString(languagePropertyKey), LanguageSpecificConfiguration.create(settings, languageConfigIndex));
+ }
+ return builder.build();
+ }
+
+ public double[] getRatingGrid() {
+ try {
+ String[] ratingGrades = settings.getStringArray(RATING_GRID);
+ double[] grid = new double[4];
+ for (int i = 0; i < 4; i++) {
+ grid[i] = Double.parseDouble(ratingGrades[i]);
+ }
+ return grid;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("The SQALE rating grid is incorrect. Expected something similar to '"
+ + RATING_GRID_DEF_VALUES + "' and got '"
+ + settings.getString(RATING_GRID) + "'", e);
+ }
+ }
+
+ public long getDevCost(@Nullable String languageKey) {
+ if (languageKey != null) {
+ try {
+ LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+ if (languageSpecificConfig != null && languageSpecificConfig.getManDays() != null) {
+ return Long.parseLong(languageSpecificConfig.getManDays());
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(format("The manDays for language %s is not a valid long number", languageKey), e);
+ }
+ }
+
+ return getDefaultDevelopmentCost();
+ }
+
+ private long getDefaultDevelopmentCost() {
+ try {
+ return Long.parseLong(settings.getString(DEVELOPMENT_COST));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("The value of the SQALE property '" + DEVELOPMENT_COST
+ + "' is incorrect. Expected long but got '" + settings.getString(DEVELOPMENT_COST) + "'", e);
+ }
+ }
+
+ public String getSizeMetricKey(@Nullable String languageKey) {
+ if (languageKey == null) {
+ return settings.getString(SIZE_METRIC);
+ }
+
+ LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+ if (languageSpecificConfig != null && languageSpecificConfig.getMetricKey() != null) {
+ return languageSpecificConfig.getMetricKey();
+ }
+ return settings.getString(SIZE_METRIC);
+ }
+
+ @CheckForNull
+ private LanguageSpecificConfiguration getSpecificParametersForLanguage(String languageKey) {
+ return languageSpecificConfigurationByLanguageKey.get(languageKey);
+ }
+
+ @Immutable
+ private static class LanguageSpecificConfiguration {
+ private final String language;
+ private final String manDays;
+ private final String metricKey;
+
+ private LanguageSpecificConfiguration(String language, String manDays, String metricKey) {
+ this.language = language;
+ this.manDays = manDays;
+ this.metricKey = metricKey;
+ }
+
+ static LanguageSpecificConfiguration create(Settings settings, String configurationId) {
+
+ String configurationPrefix = LANGUAGE_SPECIFIC_PARAMETERS + "." + configurationId + ".";
+
+ String language = settings.getString(configurationPrefix + LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY);
+ String manDays = settings.getString(configurationPrefix + LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY);
+ String metric = settings.getString(configurationPrefix + LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY);
+
+ return new LanguageSpecificConfiguration(language, manDays, metric);
+ }
+
+ String getLanguage() {
+ return language;
+ }
+
+ String getManDays() {
+ return manDays;
+ }
+
+ String getMetricKey() {
+ return metricKey;
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/package-info.java
new file mode 100644
index 00000000000..c0e838d6e48
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.computation.sqale;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
index 7f0f37154b1..d4e6d6fa0ff 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
@@ -56,6 +56,7 @@ public class ComputationSteps {
// data computation
CustomMeasuresCopyStep.class,
ComputeIssueMeasuresStep.class,
+ SqaleMeasuresStep.class,
// Must be executed after computation of all measures
FillMeasuresWithVariationsStep.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SqaleMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SqaleMeasuresStep.java
new file mode 100644
index 00000000000..be4effc782d
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SqaleMeasuresStep.java
@@ -0,0 +1,191 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.step;
+
+import com.google.common.base.Optional;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.PathAwareVisitor;
+import org.sonar.server.computation.component.TreeRootHolder;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepository;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricRepository;
+import org.sonar.server.computation.sqale.SqaleRatingGrid;
+import org.sonar.server.computation.sqale.SqaleRatingSettings;
+
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+
+public class SqaleMeasuresStep implements ComputationStep {
+ private final TreeRootHolder treeRootHolder;
+ private final MetricRepository metricRepository;
+ private final MeasureRepository measureRepository;
+ private final SqaleRatingSettings sqaleRatingSettings;
+
+ public SqaleMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
+ SqaleRatingSettings sqaleRatingSettings) {
+ this.treeRootHolder = treeRootHolder;
+ this.metricRepository = metricRepository;
+ this.measureRepository = measureRepository;
+ this.sqaleRatingSettings = sqaleRatingSettings;
+ }
+
+ @Override
+ public void execute() {
+ new SqualeMeasuresVisitor().visit(treeRootHolder.getRoot());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Compute Sqale related measures";
+ }
+
+ private class SqualeMeasuresVisitor extends PathAwareVisitor<DevelopmentCost> {
+ private final Metric developmentCostMetric;
+ private final Metric technicalDebtMetric;
+ private final Metric debtRatioMetric;
+ private final Metric sqaleRatingMetric;
+
+ public SqualeMeasuresVisitor() {
+ super(Component.Type.FILE, Order.POST_ORDER, new SimpleStackElementFactory<DevelopmentCost>() {
+ @Override
+ public DevelopmentCost createForAny(Component component) {
+ return new DevelopmentCost();
+ }
+ });
+
+ this.developmentCostMetric = metricRepository.getByKey(CoreMetrics.DEVELOPMENT_COST_KEY);
+ this.technicalDebtMetric = metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY);
+ this.debtRatioMetric = metricRepository.getByKey(CoreMetrics.SQALE_DEBT_RATIO_KEY);
+ this.sqaleRatingMetric = metricRepository.getByKey(CoreMetrics.SQALE_RATING_KEY);
+ }
+
+ @Override
+ public void visitProject(Component project, Path<DevelopmentCost> path) {
+ computeAndSaveMeasures(project, path);
+ }
+
+ @Override
+ public void visitDirectory(Component directory, Path<DevelopmentCost> path) {
+ computeAndSaveMeasures(directory, path);
+ }
+
+ @Override
+ protected void visitModule(Component module, Path<DevelopmentCost> path) {
+ computeAndSaveMeasures(module, path);
+ }
+
+ @Override
+ public void visitFile(Component file, Path<DevelopmentCost> path) {
+ if (!file.getFileAttributes().isUnitTest()) {
+ long developmentCosts = computeDevelopmentCost(file);
+ path.current().add(developmentCosts);
+ computeAndSaveMeasures(file, path);
+ }
+ }
+
+ private void computeAndSaveMeasures(Component component, Path<DevelopmentCost> path) {
+ saveDevelopmentCostMeasure(component, path.current());
+
+ double density = computeDensity(component, path.current());
+ saveDebtRatioMeasure(component, density);
+ saveSqaleRatingMeasure(component, density);
+
+ increaseParentDevelopmentCost(path);
+ }
+
+ private void saveDevelopmentCostMeasure(Component component, DevelopmentCost developmentCost) {
+ // the value of this measure is stored as a string because it can exceed the size limit of number storage on some DB
+ measureRepository.add(component, developmentCostMetric, newMeasureBuilder().create(Long.toString(developmentCost.getValue())));
+ }
+
+ private double computeDensity(Component component, DevelopmentCost developmentCost) {
+ double debt = getLongValue(measureRepository.getRawMeasure(component, technicalDebtMetric));
+ if (Double.doubleToRawLongBits(developmentCost.getValue()) != 0L) {
+ return debt / (double) developmentCost.getValue();
+ }
+ return 0d;
+ }
+
+ private void saveDebtRatioMeasure(Component component, double density) {
+ measureRepository.add(component, debtRatioMetric, newMeasureBuilder().create(100.0 * density));
+ }
+
+ private void saveSqaleRatingMeasure(Component component, double density) {
+ SqaleRatingGrid ratingGrid = new SqaleRatingGrid(sqaleRatingSettings.getRatingGrid());
+ int rating = ratingGrid.getRatingForDensity(density);
+ String ratingLetter = toRatingLetter(rating);
+ measureRepository.add(component, sqaleRatingMetric, newMeasureBuilder().create(rating, ratingLetter));
+ }
+
+ private void increaseParentDevelopmentCost(Path<DevelopmentCost> path) {
+ if (!path.isRoot()) {
+ // increase parent's developmentCost with our own
+ path.parent().add(path.current().getValue());
+ }
+ }
+ }
+
+ private long computeDevelopmentCost(Component file) {
+ String languageKey = file.getFileAttributes().getLanguageKey();
+ String sizeMetricKey = sqaleRatingSettings.getSizeMetricKey(languageKey);
+ Metric sizeMetric = metricRepository.getByKey(sizeMetricKey);
+ return getLongValue(measureRepository.getRawMeasure(file, sizeMetric)) * sqaleRatingSettings.getDevCost(languageKey);
+ }
+
+ private static long getLongValue(Optional<Measure> measure) {
+ if (!measure.isPresent()) {
+ return 0L;
+ }
+ return getLongValue(measure.get());
+ }
+
+ private static long getLongValue(Measure measure) {
+ switch (measure.getValueType()) {
+ case INT:
+ return measure.getIntValue();
+ case LONG:
+ return measure.getLongValue();
+ case DOUBLE:
+ return new Double(measure.getDoubleValue()).longValue();
+ default:
+ return 0L;
+ }
+ }
+
+ private static String toRatingLetter(int rating) {
+ return SqaleRatingGrid.SqaleRating.createForIndex(rating).name();
+ }
+
+ /**
+ * A wrapper class around a long which can be increased and represents the development cost of a Component
+ */
+ private static final class DevelopmentCost {
+ private long value = 0;
+
+ public void add(long developmentCosts) {
+ this.value += developmentCosts;
+ }
+
+ public long getValue() {
+ return value;
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingGridTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingGridTest.java
new file mode 100644
index 00000000000..e292eb63c2f
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingGridTest.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.sqale;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SqaleRatingGridTest {
+
+ private SqaleRatingGrid ratingGrid;
+
+ @Rule
+ public ExpectedException throwable = ExpectedException.none();
+
+ @Before
+ public void setUp() {
+ double[] gridValues = new double[] {0.1, 0.2, 0.5, 1};
+ ratingGrid = new SqaleRatingGrid(gridValues);
+ }
+
+ @Test
+ public void return_rating_matching_density() {
+ assertThat(ratingGrid.getRatingForDensity(0)).isEqualTo(1);
+ assertThat(ratingGrid.getRatingForDensity(0.05)).isEqualTo(1);
+ assertThat(ratingGrid.getRatingForDensity(0.09999999)).isEqualTo(1);
+ assertThat(ratingGrid.getRatingForDensity(0.1)).isEqualTo(2);
+ assertThat(ratingGrid.getRatingForDensity(0.15)).isEqualTo(2);
+ assertThat(ratingGrid.getRatingForDensity(0.2)).isEqualTo(3);
+ assertThat(ratingGrid.getRatingForDensity(0.25)).isEqualTo(3);
+ assertThat(ratingGrid.getRatingForDensity(0.5)).isEqualTo(4);
+ assertThat(ratingGrid.getRatingForDensity(0.65)).isEqualTo(4);
+ assertThat(ratingGrid.getRatingForDensity(1)).isEqualTo(5);
+ assertThat(ratingGrid.getRatingForDensity(1.01)).isEqualTo(5);
+ }
+
+ @Test
+ public void fail_on_invalid_density() {
+ throwable.expect(RuntimeException.class);
+
+ ratingGrid.getRatingForDensity(-1);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingSettingsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingSettingsTest.java
new file mode 100644
index 00000000000..a48753620fb
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleRatingSettingsTest.java
@@ -0,0 +1,120 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.sqale;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.CoreMetrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SqaleRatingSettingsTest {
+
+ private Settings settings;
+
+ @Rule
+ public ExpectedException throwable = ExpectedException.none();
+
+ @Before
+ public void setUp() {
+ settings = new Settings();
+ }
+
+ @Test
+ public void load_rating_grid() {
+ settings.setProperty(CoreProperties.RATING_GRID, "1,3.4,8,50");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ double[] grid = configurationLoader.getRatingGrid();
+ assertThat(grid).hasSize(4);
+ assertThat(grid[0]).isEqualTo(1.0);
+ assertThat(grid[1]).isEqualTo(3.4);
+ assertThat(grid[2]).isEqualTo(8.0);
+ assertThat(grid[3]).isEqualTo(50.0);
+ }
+
+ @Test
+ public void load_work_units_for_language() {
+ settings.setProperty(CoreProperties.DEVELOPMENT_COST, "50");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ assertThat(configurationLoader.getDevCost("defaultLanguage")).isEqualTo(50L);
+ }
+
+ @Test
+ public void load_size_metric_for_language() {
+ settings.setProperty(CoreProperties.SIZE_METRIC, "complexity");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ assertThat(configurationLoader.getSizeMetricKey("defaultLanguage")).isEqualTo("complexity");
+ }
+
+ @Test
+ public void load_overridden_values_for_language() {
+
+ String aLanguage = "aLanguage";
+ String anotherLanguage = "anotherLanguage";
+
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS, "0,1");
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY, aLanguage);
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY, "30");
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY, CoreMetrics.NCLOC_KEY);
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "1" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY, anotherLanguage);
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "1" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY, "40");
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "1" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY, CoreMetrics.COMPLEXITY_KEY);
+
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ assertThat(configurationLoader.getSizeMetricKey(aLanguage)).isEqualTo(CoreMetrics.NCLOC_KEY);
+ assertThat(configurationLoader.getSizeMetricKey(anotherLanguage)).isEqualTo(CoreMetrics.COMPLEXITY_KEY);
+ assertThat(configurationLoader.getDevCost(aLanguage)).isEqualTo(30L);
+ assertThat(configurationLoader.getDevCost(anotherLanguage)).isEqualTo(40L);
+ }
+
+ @Test
+ public void fail_on_invalid_rating_grid_configuration() {
+
+ throwable.expect(IllegalArgumentException.class);
+ settings.setProperty(CoreProperties.RATING_GRID, "a b c");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ configurationLoader.getRatingGrid();
+ }
+
+ @Test
+ public void use_generic_value_when_specific_setting_is_missing() {
+ String aLanguage = "aLanguage";
+
+ settings.setProperty(CoreProperties.SIZE_METRIC, "complexity");
+ settings.setProperty(CoreProperties.DEVELOPMENT_COST, "30");
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS, "0");
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY, aLanguage);
+ settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY, "40");
+
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ assertThat(configurationLoader.getSizeMetricKey(aLanguage)).isEqualTo(CoreMetrics.COMPLEXITY_KEY);
+ assertThat(configurationLoader.getDevCost(aLanguage)).isEqualTo(40L);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/SqaleMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/SqaleMeasuresStepTest.java
new file mode 100644
index 00000000000..1933feb7f12
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/SqaleMeasuresStepTest.java
@@ -0,0 +1,248 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.step;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.DumbComponent;
+import org.sonar.server.computation.component.FileAttributes;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepoEntry;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricImpl;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+import org.sonar.server.computation.sqale.SqaleRatingGrid;
+import org.sonar.server.computation.sqale.SqaleRatingSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
+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.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+import static org.sonar.server.computation.measure.MeasureRepoEntry.toEntries;
+import static org.sonar.server.computation.sqale.SqaleRatingGrid.SqaleRating.A;
+import static org.sonar.server.computation.sqale.SqaleRatingGrid.SqaleRating.C;
+
+public class SqaleMeasuresStepTest {
+
+ private static final String METRIC_KEY_1 = "mKey1";
+ private static final String METRIC_KEY_2 = "mKey2";
+ private static final Metric METRIC_1 = new MetricImpl(1, METRIC_KEY_1, "metric1", Metric.MetricType.FLOAT);
+ private static final Metric METRIC_2 = new MetricImpl(2, METRIC_KEY_2, "metric2", Metric.MetricType.WORK_DUR);
+ private static final String LANGUAGE_KEY_1 = "lKey1";
+ private static final String LANGUAGE_KEY_2 = "lKey2";
+ private static final double[] RATING_GRID = new double[] {34, 50, 362, 900, 36258};
+ private static final long DEV_COST_LANGUAGE_1 = 33;
+ private static final long DEV_COST_LANGUAGE_2 = 42;
+
+ @Rule
+ public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+ @Rule
+ public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(METRIC_1).add(METRIC_2);
+ @Rule
+ public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+ private SqaleRatingSettings sqaleRatingSettings = mock(SqaleRatingSettings.class);
+
+ private SqaleMeasuresStep underTest = new SqaleMeasuresStep(treeRootHolder, metricRepository, measureRepository, sqaleRatingSettings);
+
+ @Before
+ public void setUp() throws Exception {
+ // assumes squale rating configuration is consistent
+ when(sqaleRatingSettings.getRatingGrid()).thenReturn(RATING_GRID);
+ when(sqaleRatingSettings.getSizeMetricKey(LANGUAGE_KEY_1)).thenReturn(METRIC_KEY_1);
+ when(sqaleRatingSettings.getSizeMetricKey(LANGUAGE_KEY_2)).thenReturn(METRIC_KEY_2);
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_KEY_1)).thenReturn(DEV_COST_LANGUAGE_1);
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_KEY_2)).thenReturn(DEV_COST_LANGUAGE_2);
+
+ // this measures are always retrieved by the step
+ metricRepository.add(CoreMetrics.DEVELOPMENT_COST);
+ metricRepository.add(CoreMetrics.TECHNICAL_DEBT);
+ metricRepository.add(CoreMetrics.SQALE_DEBT_RATIO);
+ metricRepository.add(CoreMetrics.SQALE_RATING);
+ }
+
+ @Test
+ public void measures_created_for_project_are_all_zero_when_they_have_no_FILE_child() {
+ DumbComponent root = DumbComponent.builder(PROJECT, 1).build();
+ treeRootHolder.setRoot(root);
+
+ underTest.execute();
+
+ assertThat(toEntries(measureRepository.getRawMeasures(root))).containsOnly(
+ MeasureRepoEntry.entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create("0")),
+ MeasureRepoEntry.entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(0d)),
+ MeasureRepoEntry.entryOf(SQALE_RATING_KEY, createSqaleRatingMeasure(A))
+ );
+ }
+
+ private Measure createSqaleRatingMeasure(SqaleRatingGrid.SqaleRating sqaleRating) {
+ return newMeasureBuilder().create(sqaleRating.getIndex(), sqaleRating.name());
+ }
+
+ @Test
+ public void verify_computation_of_measures_for_file_depending_upon_language_1() {
+ verify_computation_of_measure_for_file(33000l, DEV_COST_LANGUAGE_1, METRIC_KEY_1, LANGUAGE_KEY_1, C);
+ }
+
+ @Test
+ public void verify_computation_of_measures_for_file_depending_upon_language_2() {
+ verify_computation_of_measure_for_file(4200l, DEV_COST_LANGUAGE_2, METRIC_KEY_2, LANGUAGE_KEY_2, A);
+ }
+
+ /**
+ * Verify the computation of measures values depending upon which language is associated to the file by
+ * processing a tree of a single Component of type FILE.
+ */
+ private void verify_computation_of_measure_for_file(long debt, long languageCost, String metricKey, String languageKey,
+ SqaleRatingGrid.SqaleRating expectedRating) {
+ long measureValue = 10;
+
+ DumbComponent fileComponent = createFileComponent(languageKey, 1);
+ treeRootHolder.setRoot(fileComponent);
+ measureRepository.addRawMeasure(fileComponent.getRef(), metricKey, newMeasureBuilder().create(measureValue));
+ measureRepository.addRawMeasure(fileComponent.getRef(), TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt));
+
+ underTest.execute();
+
+ verifyFileMeasures(fileComponent.getRef(), measureValue, debt, languageCost, expectedRating);
+ }
+
+ @Test
+ public void verify_aggregation_of_developmentCost_and_value_of_measures_computed_from_that() {
+ DumbComponent root = DumbComponent.builder(PROJECT, 1)
+ .addChildren(
+ DumbComponent.builder(MODULE, 11)
+ .addChildren(
+ DumbComponent.builder(DIRECTORY, 111)
+ .addChildren(
+ createFileComponent(LANGUAGE_KEY_1, 1111),
+ createFileComponent(LANGUAGE_KEY_2, 1112)
+ ).build(),
+ DumbComponent.builder(DIRECTORY, 112)
+ .addChildren(
+ createFileComponent(LANGUAGE_KEY_2, 1121)
+ ).build()
+ ).build(),
+ DumbComponent.builder(MODULE, 12)
+ .addChildren(
+ DumbComponent.builder(DIRECTORY, 121)
+ .addChildren(
+ createFileComponent(LANGUAGE_KEY_1, 1211)
+ ).build(),
+ DumbComponent.builder(DIRECTORY, 122).build()
+ ).build(),
+ DumbComponent.builder(MODULE, 13).build()
+ ).build();
+
+ treeRootHolder.setRoot(root);
+
+ long measureValue1111 = 10;
+ long debt1111 = 66000l;
+ measureRepository.addRawMeasure(1111, METRIC_KEY_1, newMeasureBuilder().create(measureValue1111));
+ measureRepository.addRawMeasure(1111, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1111));
+
+ long measureValue1112 = 10;
+ long debt1112 = 4200l;
+ measureRepository.addRawMeasure(1112, METRIC_KEY_2, newMeasureBuilder().create(measureValue1112));
+ measureRepository.addRawMeasure(1112, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1112));
+
+ long debt111 = 96325l;
+ measureRepository.addRawMeasure(111, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt111));
+
+ long measureValue1121 = 30;
+ long debt1121 = 25200l;
+ measureRepository.addRawMeasure(1121, METRIC_KEY_2, newMeasureBuilder().create(measureValue1121));
+ measureRepository.addRawMeasure(1121, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1121));
+
+ long debt112 = 99633l;
+ measureRepository.addRawMeasure(112, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt112));
+
+ long measureValue1211 = 20;
+ long debt1211 = 33000l;
+ measureRepository.addRawMeasure(1211, METRIC_KEY_1, newMeasureBuilder().create(measureValue1211));
+ measureRepository.addRawMeasure(1211, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1211));
+
+ long debt121 = 7524l;
+ measureRepository.addRawMeasure(121, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt121));
+
+ long debt1 = 9999l;
+ measureRepository.addRawMeasure(1, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1));
+
+ underTest.execute();
+
+ // verify measures on files
+ verifyFileMeasures(1111, measureValue1111, debt1111, DEV_COST_LANGUAGE_1, C);
+ verifyFileMeasures(1112, measureValue1112, debt1112, DEV_COST_LANGUAGE_2, A);
+ verifyFileMeasures(1121, measureValue1121, debt1121, DEV_COST_LANGUAGE_2, A);
+ verifyFileMeasures(1211, measureValue1211, debt1211, DEV_COST_LANGUAGE_1, C);
+ // directory has no children => no file => 0 everywhere and A rating
+ verifyComponentMeasures(122, 0, 0, A);
+ // directory has children => dev cost is aggregated
+ long devCost111 = measureValue1111 * DEV_COST_LANGUAGE_1 + measureValue1112 * DEV_COST_LANGUAGE_2;
+ verifyComponentMeasures(111, devCost111, debt111 / (double) devCost111, C);
+ long devCost112 = measureValue1121 * DEV_COST_LANGUAGE_2;
+ verifyComponentMeasures(112, devCost112, debt112 / (double) devCost112, C);
+ long devCost121 = measureValue1211 * DEV_COST_LANGUAGE_1;
+ verifyComponentMeasures(121, devCost121, debt121 / (double) devCost121, A);
+ // just for fun, we didn't define any debt on module => they must all have rating A
+ long devCost11 = devCost111 + devCost112;
+ verifyComponentMeasures(11, devCost11, 0, A);
+ long devCost12 = devCost121;
+ verifyComponentMeasures(12, devCost12, 0, A);
+ long devCost13 = 0;
+ verifyComponentMeasures(13, devCost13, 0, A);
+ // project has aggregated dev cost of all files
+ long devCost1 = devCost11 + devCost12 + devCost13;
+ verifyComponentMeasures(1, devCost1, debt1 / (double) devCost1, A);
+ }
+
+ private DumbComponent createFileComponent(String languageKey1, int fileRef) {
+ return DumbComponent.builder(FILE, fileRef).setFileAttributes(new FileAttributes(false, languageKey1)).build();
+ }
+
+ private void verifyNoMeasure(int componentRef) {
+ assertThat(measureRepository.getRawMeasures(componentRef).isEmpty()).isTrue();
+ }
+
+ private void verifyFileMeasures(int componentRef, long measureValue, long debt, long languageCost, SqaleRatingGrid.SqaleRating expectedRating) {
+ long developmentCost = measureValue * languageCost;
+ verifyComponentMeasures(componentRef, developmentCost, debt / developmentCost, expectedRating);
+ }
+
+ private void verifyComponentMeasures(int componentRef, long expectedDevCost, double expectedDebtRatio, SqaleRatingGrid.SqaleRating expectedRating) {
+ assertThat(toEntries(measureRepository.getNewRawMeasures(componentRef))).containsOnly(
+ MeasureRepoEntry.entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create(Long.toString(expectedDevCost))),
+ MeasureRepoEntry.entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(expectedDebtRatio * 100.0)),
+ MeasureRepoEntry.entryOf(SQALE_RATING_KEY, createSqaleRatingMeasure(expectedRating))
+ );
+ }
+
+}