aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-batch
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2014-07-09 18:36:52 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2014-07-09 18:36:52 +0200
commit4d5d1f5be3d1ace0199d39a086165e1086600a26 (patch)
treef44cfe310ddf3cd277fce68fbec36e90fdb255c8 /sonar-batch
parent6a7de91d1fb7b3197da780079607a2489064d67b (diff)
downloadsonarqube-4d5d1f5be3d1ace0199d39a086165e1086600a26.tar.gz
sonarqube-4d5d1f5be3d1ace0199d39a086165e1086600a26.zip
SONAR-5388 Introduce the SQALE Rating metric in SonarQube Core
Diffstat (limited to 'sonar-batch')
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java145
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingGrid.java80
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java127
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java35
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingDecoratorTest.java189
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingGridTest.java72
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingSettingsTest.java143
7 files changed, 762 insertions, 29 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java
new file mode 100644
index 00000000000..dc973dd109c
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java
@@ -0,0 +1,145 @@
+/*
+ * 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.batch.debt;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+import javax.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Decorator that computes Sqale Rating metric
+ */
+public final class SqaleRatingDecorator implements Decorator {
+
+ private final SqaleRatingSettings sqaleRatingSettings;
+ private final Metric[] metrics;
+ private final FileSystem fs;
+
+ public SqaleRatingDecorator(SqaleRatingSettings sqaleRatingSettings, Metric[] metrics, FileSystem fs) {
+ this.sqaleRatingSettings = sqaleRatingSettings;
+ this.metrics = Arrays.copyOf(metrics, metrics.length);
+ this.fs = fs;
+ }
+
+ @VisibleForTesting
+ SqaleRatingDecorator() {
+ this.sqaleRatingSettings = null;
+ this.metrics = null;
+ this.fs = null;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependsUpon
+ public List<Metric> dependsOnMetrics() {
+ return Lists.<Metric>newArrayList(CoreMetrics.TECHNICAL_DEBT,
+ // ncloc and complexity are the two possible metrics to be used to calculate the development cost
+ CoreMetrics.NCLOC, CoreMetrics.COMPLEXITY);
+ }
+
+ @DependedUpon
+ public List<Metric> generatesMetrics() {
+ return Lists.<Metric>newArrayList(CoreMetrics.RATING, CoreMetrics.DEVELOPMENT_COST);
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (ResourceUtils.isPersistable(resource) && !ResourceUtils.isUnitTestClass(resource)) {
+ Long developmentCost = getDevelopmentCost(context);
+ context.saveMeasure(new Measure(CoreMetrics.DEVELOPMENT_COST, Long.toString(developmentCost)));
+
+ long debt = getMeasureValue(context, CoreMetrics.TECHNICAL_DEBT);
+ double density = computeDensity(debt, developmentCost);
+ SqaleRatingGrid ratingGrid = new SqaleRatingGrid(sqaleRatingSettings.getRatingGrid());
+ context.saveMeasure(createRatingMeasure(ratingGrid.getRatingForDensity(density)));
+ }
+ }
+
+ private Measure createRatingMeasure(int rating) {
+ return new Measure(CoreMetrics.RATING).setIntValue(rating).setData(toRatingLetter(rating));
+ }
+
+ static String toRatingLetter(@Nullable Integer rating) {
+ if (rating != null) {
+ return SqaleRatingGrid.SqaleRating.buildFromIndex(rating).name();
+ }
+ return null;
+ }
+
+ private long getDevelopmentCost(DecoratorContext context) {
+ InputFile file = fs.inputFile(fs.predicates().hasRelativePath(context.getResource().getKey()));
+ if (file != null) {
+ String language = file.language();
+ return getMeasureValue(context, sqaleRatingSettings.getSizeMetric(language, metrics)) * sqaleRatingSettings.getWorkUnitsBySizePoint(language);
+ } else {
+ Collection<Measure> childrenMeasures = context.getChildrenMeasures(CoreMetrics.DEVELOPMENT_COST);
+ Double sum = sum(childrenMeasures);
+ return sum.longValue();
+ }
+ }
+
+ private static Double sum(@Nullable Collection<Measure> measures) {
+ if (measures == null) {
+ return 0d;
+ }
+ double sum = 0d;
+ for (Measure measure : measures) {
+ if (measure.getData() != null) {
+ sum += Double.parseDouble(measure.getData());
+ }
+ }
+ return sum;
+ }
+
+ private long getMeasureValue(DecoratorContext context, Metric metric) {
+ Measure measure = context.getMeasure(metric);
+ if (measure != null) {
+ return measure.getValue().longValue();
+ }
+ return 0;
+ }
+
+ protected double computeDensity(double debt, double developmentCost) {
+ if (developmentCost != 0) {
+ return debt / developmentCost;
+ }
+ return 0;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingGrid.java b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingGrid.java
new file mode 100644
index 00000000000..5db6ab6e2ab
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingGrid.java
@@ -0,0 +1,80 @@
+/*
+ * 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.batch.debt;
+
+import org.sonar.api.utils.MessageException;
+
+import java.util.Arrays;
+
+class SqaleRatingGrid {
+
+ private final double[] gridValues;
+
+ public SqaleRatingGrid(double[] gridValues) {
+ this.gridValues = Arrays.copyOf(gridValues, gridValues.length);
+ }
+
+ public double getGradeLowerBound(SqaleRating rating) {
+ if (rating.getIndex() > 1) {
+ return gridValues[rating.getIndex() - 2];
+ }
+ return 0;
+ }
+
+ 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);
+ }
+
+ enum SqaleRating {
+
+ E(5),
+ D(4),
+ C(3),
+ B(2),
+ A(1);
+
+ private final int index;
+
+ private SqaleRating(int index) {
+ this.index = index;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public static SqaleRating buildFromIndex(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/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java
new file mode 100644
index 00000000000..acba0266e26
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java
@@ -0,0 +1,127 @@
+/*
+ * 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.batch.debt;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.Metric;
+
+public class SqaleRatingSettings implements BatchComponent {
+
+ private final Settings settings;
+
+ public SqaleRatingSettings(Settings settings) {
+ this.settings = settings;
+ }
+
+ public double[] getRatingGrid() {
+ try {
+ String[] ratingGrades = settings.getStringArray(CoreProperties.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 '"
+ + CoreProperties.RATING_GRID_DEF_VALUES + "' and got '"
+ + settings.getString(CoreProperties.RATING_GRID) + "'", e);
+ }
+ }
+
+ public long getWorkUnitsBySizePoint(String languageKey) {
+ try {
+ LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+ if (languageSpecificConfig != null && languageSpecificConfig.getManDays() != null) {
+ return Long.parseLong(languageSpecificConfig.getManDays());
+ }
+ return Long.parseLong(settings.getString(CoreProperties.MAN_DAYS_BY_SIZE_POINT));
+ } catch (Exception e) {
+ throw new IllegalArgumentException("The value of the SQALE property '" + CoreProperties.MAN_DAYS_BY_SIZE_POINT
+ + "' is incorrect. Expected long but got '" + settings.getString(CoreProperties.MAN_DAYS_BY_SIZE_POINT) + "'", e);
+ }
+ }
+
+ public Metric getSizeMetric(String languageKey, Metric[] metrics) {
+ LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+ if (languageSpecificConfig != null && languageSpecificConfig.getMetric() != null) {
+ return getMetricForKey(languageSpecificConfig.getMetric(), metrics);
+ }
+ return getMetricForKey(settings.getString(CoreProperties.SIZE_METRIC), metrics);
+ }
+
+ private Metric getMetricForKey(String sizeMetricKey, Metric[] metrics) {
+ for (Metric metric : metrics) {
+ if (metric.getKey().equals(sizeMetricKey)) {
+ return metric;
+ }
+ }
+ throw new IllegalArgumentException("The metric key used to define the SQALE size metric is unknown : '" + sizeMetricKey + "'");
+ }
+
+ private LanguageSpecificConfiguration getSpecificParametersForLanguage(String languageKey) {
+ String[] languageConfigIndexes = settings.getStringArray(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS);
+ for (String languageConfigIndex : languageConfigIndexes) {
+ String languagePropertyKey = CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + languageConfigIndex + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY;
+ if (languageKey.equals(settings.getString(languagePropertyKey))) {
+ return LanguageSpecificConfiguration.create(settings, languageConfigIndex);
+ }
+ }
+ return null;
+ }
+
+ private static class LanguageSpecificConfiguration {
+
+ private final String language;
+ private final String manDays;
+ private final String metric;
+
+ private LanguageSpecificConfiguration(String language, String manDays, String metric) {
+ this.language = language;
+ this.manDays = manDays;
+ this.metric = metric;
+ }
+
+ static LanguageSpecificConfiguration create(Settings settings, String configurationId) {
+
+ String configurationPrefix = CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + configurationId + ".";
+
+ String language = settings.getString(configurationPrefix + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY);
+ String manDays = settings.getString(configurationPrefix + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY);
+ String metric = settings.getString(configurationPrefix + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY);
+
+ return new LanguageSpecificConfiguration(language, manDays, metric);
+ }
+
+ String getLanguage() {
+ return language;
+ }
+
+ String getManDays() {
+ return manDays;
+ }
+
+ String getMetric() {
+ return metric;
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
index 0935b576062..455567e5e68 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
@@ -28,20 +28,13 @@ import org.sonar.api.batch.rule.CheckFactory;
import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.resources.Project;
import org.sonar.api.scan.filesystem.FileExclusions;
-import org.sonar.batch.DefaultProjectClasspath;
-import org.sonar.batch.DefaultSensorContext;
-import org.sonar.batch.DefaultTimeMachine;
-import org.sonar.batch.ProjectTree;
-import org.sonar.batch.ResourceFilters;
-import org.sonar.batch.ViolationFilters;
+import org.sonar.batch.*;
import org.sonar.batch.bootstrap.BatchExtensionDictionnary;
import org.sonar.batch.bootstrap.ExtensionInstaller;
import org.sonar.batch.bootstrap.ExtensionMatcher;
import org.sonar.batch.bootstrap.ExtensionUtils;
import org.sonar.batch.components.TimeMachineConfiguration;
-import org.sonar.batch.debt.DebtDecorator;
-import org.sonar.batch.debt.IssueChangelogDebtCalculator;
-import org.sonar.batch.debt.NewDebtDecorator;
+import org.sonar.batch.debt.*;
import org.sonar.batch.events.EventBus;
import org.sonar.batch.index.DefaultIndex;
import org.sonar.batch.index.ResourcePersister;
@@ -60,26 +53,8 @@ import org.sonar.batch.phases.PhasesTimeProfiler;
import org.sonar.batch.qualitygate.GenerateQualityGateEvents;
import org.sonar.batch.qualitygate.QualityGateProvider;
import org.sonar.batch.qualitygate.QualityGateVerifier;
-import org.sonar.batch.rule.ActiveRulesProvider;
-import org.sonar.batch.rule.ModuleQProfiles;
-import org.sonar.batch.rule.QProfileDecorator;
-import org.sonar.batch.rule.QProfileEventsDecorator;
-import org.sonar.batch.rule.QProfileSensor;
-import org.sonar.batch.rule.QProfileVerifier;
-import org.sonar.batch.rule.RulesProfileProvider;
-import org.sonar.batch.scan.filesystem.ComponentIndexer;
-import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
-import org.sonar.batch.scan.filesystem.DeprecatedFileFilters;
-import org.sonar.batch.scan.filesystem.ExclusionFilters;
-import org.sonar.batch.scan.filesystem.FileIndexer;
-import org.sonar.batch.scan.filesystem.FileSystemLogger;
-import org.sonar.batch.scan.filesystem.InputFileBuilderFactory;
-import org.sonar.batch.scan.filesystem.LanguageDetectionFactory;
-import org.sonar.batch.scan.filesystem.ModuleFileSystemInitializer;
-import org.sonar.batch.scan.filesystem.ModuleInputFileCache;
-import org.sonar.batch.scan.filesystem.PreviousFileHashLoader;
-import org.sonar.batch.scan.filesystem.ProjectFileSystemAdapter;
-import org.sonar.batch.scan.filesystem.StatusDetectionFactory;
+import org.sonar.batch.rule.*;
+import org.sonar.batch.scan.filesystem.*;
import org.sonar.batch.scan.report.JsonReport;
import org.sonar.batch.scan2.AnalyzerOptimizer;
import org.sonar.core.component.ScanPerspectives;
@@ -189,6 +164,8 @@ public class ModuleScanContainer extends ComponentContainer {
IssueChangelogDebtCalculator.class,
DebtDecorator.class,
NewDebtDecorator.class,
+ SqaleRatingDecorator.class,
+ SqaleRatingSettings.class,
ScanPerspectives.class);
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingDecoratorTest.java
new file mode 100644
index 00000000000..1fe1574bffe
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingDecoratorTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.batch.debt;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.test.IsMeasure;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SqaleRatingDecoratorTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ static final Long ONE_DAY_IN_MINUTES = 8L * 60;
+
+ Settings settings;
+ Metric[] metrics = {CoreMetrics.NCLOC, CoreMetrics.ABSTRACTNESS, CoreMetrics.COMPLEXITY};
+
+ @Mock
+ DecoratorContext context;
+
+ DefaultFileSystem fs;
+
+ File file = File.create("src/main/java/Foo.java");
+
+ SqaleRatingDecorator decorator;
+
+ @Before
+ public void setUp() throws Exception {
+ settings = new Settings();
+
+ fs = new DefaultFileSystem();
+ fs.add(new DefaultInputFile(file.getPath())
+ .setLanguage("java")
+ .setFile(temp.newFile("Foo.java")));
+
+ decorator = new SqaleRatingDecorator(new SqaleRatingSettings(settings), metrics, fs);
+ }
+
+ @Test
+ public void generates_metrics() throws Exception {
+ SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+ assertThat(decorator.generatesMetrics()).hasSize(2);
+ }
+
+ @Test
+ public void depends_on_metrics() {
+ SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+ assertThat(decorator.dependsOnMetrics()).containsOnly(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.NCLOC, CoreMetrics.COMPLEXITY);
+ }
+
+ @Test
+ public void execute_on_project() throws Exception {
+ SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+ assertThat(decorator.shouldExecuteOnProject(null)).isTrue();
+ }
+
+ @Test
+ public void not_execute_on_unit_test() throws Exception {
+ File resource = mock(File.class);
+ when(resource.getQualifier()).thenReturn(Qualifiers.UNIT_TEST_FILE);
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(any(Measure.class));
+ }
+
+ @Test
+ public void save_total_rating_c() {
+ settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, 2 * ONE_DAY_IN_MINUTES);
+ settings.setProperty(CoreProperties.SIZE_METRIC, "ncloc");
+ settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+ when(context.getResource()).thenReturn(file);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 10.0));
+ when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 300.0 * ONE_DAY_IN_MINUTES));
+
+ decorator.decorate(file, context);
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 3.0)));
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+ verify(context).getMeasure(CoreMetrics.NCLOC);
+ }
+
+ @Test
+ public void save_total_rating_a() {
+ settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, 2 * ONE_DAY_IN_MINUTES);
+ settings.setProperty(CoreProperties.SIZE_METRIC, "ncloc");
+ settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+ when(context.getResource()).thenReturn(file);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 10.0));
+ when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 0.0));
+
+ decorator.decorate(file, context);
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 1.0)));
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+ verify(context).getMeasure(CoreMetrics.NCLOC);
+ }
+
+ @Test
+ public void save_total_rating_e() {
+ settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, 2 * ONE_DAY_IN_MINUTES);
+ settings.setProperty(CoreProperties.SIZE_METRIC, "ncloc");
+ settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+ when(context.getResource()).thenReturn(file);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 10.0));
+ when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 100000000000.0));
+
+ decorator.decorate(file, context);
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 5.0)));
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+ verify(context).getMeasure(CoreMetrics.NCLOC);
+ }
+
+ @Test
+ public void save_total_rating_on_project() {
+ settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+ when(context.getResource()).thenReturn(new Project("Sample"));
+ when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 300.0 * ONE_DAY_IN_MINUTES));
+ when(context.getChildrenMeasures(CoreMetrics.DEVELOPMENT_COST)).thenReturn(newArrayList(new Measure(CoreMetrics.DEVELOPMENT_COST, Double.toString(20.0 * ONE_DAY_IN_MINUTES))));
+
+ decorator.decorate(mock(File.class), context);
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 3.0)));
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+ verify(context, never()).getMeasure(CoreMetrics.NCLOC);
+ }
+
+ @Test
+ public void translate_rating_to_letter() {
+ assertThat(SqaleRatingDecorator.toRatingLetter(null)).isNull();
+ assertThat(SqaleRatingDecorator.toRatingLetter(1)).isEqualTo("A");
+ assertThat(SqaleRatingDecorator.toRatingLetter(4)).isEqualTo("D");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void test_rating_out_of_range() {
+ SqaleRatingDecorator.toRatingLetter(89);
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingGridTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingGridTest.java
new file mode 100644
index 00000000000..cabf747826a
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingGridTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.batch.debt;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.fest.assertions.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_grade_lower_bound() throws Exception {
+ assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.A)).isEqualTo(0);
+ assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.B)).isEqualTo(0.1);
+ assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.C)).isEqualTo(0.2);
+ assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.D)).isEqualTo(0.5);
+ assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.E)).isEqualTo(1);
+ }
+
+ @Test
+ public void return_rating_matching_density() throws Exception {
+ assertThat(ratingGrid.getRatingForDensity(0)).isEqualTo(1);
+ assertThat(ratingGrid.getRatingForDensity(0.05)).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() throws Exception {
+ throwable.expect(RuntimeException.class);
+
+ ratingGrid.getRatingForDensity(-1);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingSettingsTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingSettingsTest.java
new file mode 100644
index 00000000000..cd784f135fc
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingSettingsTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.batch.debt;
+
+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 org.sonar.api.measures.Metric;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+
+public class SqaleRatingSettingsTest {
+
+ private static final Metric[] metrics = {CoreMetrics.NCLOC, CoreMetrics.ABSTRACTNESS, CoreMetrics.COMPLEXITY};
+
+ private Settings settings;
+
+ @Rule
+ public ExpectedException throwable = ExpectedException.none();
+
+ @Before
+ public void setUp() {
+ settings = new Settings();
+ }
+
+ @Test
+ public void load_rating_grid() throws Exception {
+ 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() throws Exception {
+ settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, "50");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ assertThat(configurationLoader.getWorkUnitsBySizePoint("defaultLanguage")).isEqualTo(50L);
+ }
+
+ @Test
+ public void load_size_metric_for_language() throws Exception {
+ settings.setProperty(CoreProperties.SIZE_METRIC, "complexity");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ assertThat(configurationLoader.getSizeMetric("defaultLanguage", metrics)).isEqualTo(CoreMetrics.COMPLEXITY);
+ }
+
+ @Test
+ public void load_overridden_values_for_language() throws Exception {
+
+ 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.getSizeMetric(aLanguage, metrics)).isEqualTo(CoreMetrics.NCLOC);
+ assertThat(configurationLoader.getSizeMetric(anotherLanguage, metrics)).isEqualTo(CoreMetrics.COMPLEXITY);
+ assertThat(configurationLoader.getWorkUnitsBySizePoint(aLanguage)).isEqualTo(30L);
+ assertThat(configurationLoader.getWorkUnitsBySizePoint(anotherLanguage)).isEqualTo(40L);
+ }
+
+ @Test
+ public void fail_on_invalid_rating_grid_configuration() throws Exception {
+
+ throwable.expect(IllegalArgumentException.class);
+ settings.setProperty(CoreProperties.RATING_GRID, "a b c");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ configurationLoader.getRatingGrid();
+ }
+
+ @Test
+ public void fail_on_invalid_work_unit_value() throws Exception {
+ throwable.expect(IllegalArgumentException.class);
+ settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, "a");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ configurationLoader.getSizeMetric("aLanguage", metrics);
+ }
+
+ @Test
+ public void fail_on_unknown_metric_key() throws Exception {
+ throwable.expect(IllegalArgumentException.class);
+ settings.setProperty(CoreProperties.SIZE_METRIC, "unknown");
+ SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+ configurationLoader.getSizeMetric("aLanguage", metrics);
+ }
+
+ @Test
+ public void use_generic_value_when_specific_setting_is_missing() throws Exception {
+ String aLanguage = "aLanguage";
+
+ settings.setProperty(CoreProperties.SIZE_METRIC, "complexity");
+ settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, "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.getSizeMetric(aLanguage, metrics)).isEqualTo(CoreMetrics.COMPLEXITY);
+ assertThat(configurationLoader.getWorkUnitsBySizePoint(aLanguage)).isEqualTo(40L);
+ }
+}