diff options
author | Julien Lancelot <julien.lancelot@gmail.com> | 2013-09-27 14:06:47 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@gmail.com> | 2013-09-27 14:06:58 +0200 |
commit | 42cab973f76e48d80b2d5fb621aaa983ad940254 (patch) | |
tree | f5ecab7303736d8a28adaa8ad1e61fc6eda81c6f /plugins | |
parent | 7bf08fcfdac86a4ada7ad739be733f437d0b95fb (diff) | |
download | sonarqube-42cab973f76e48d80b2d5fb621aaa983ad940254.tar.gz sonarqube-42cab973f76e48d80b2d5fb621aaa983ad940254.zip |
SONAR-4715 Compute the new "Technical Debt" metric
Diffstat (limited to 'plugins')
27 files changed, 1886 insertions, 0 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index e672a9d846c..d0315062434 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -41,6 +41,7 @@ import org.sonar.plugins.core.measurefilters.ProjectFilter; import org.sonar.plugins.core.notifications.alerts.NewAlerts; import org.sonar.plugins.core.security.ApplyProjectRolesDecorator; import org.sonar.plugins.core.sensors.*; +import org.sonar.plugins.core.technicaldebt.TechnicalDebtDecorator; import org.sonar.plugins.core.timemachine.*; import org.sonar.plugins.core.web.Lcom4Viewer; import org.sonar.plugins.core.web.TestsViewer; @@ -326,6 +327,7 @@ public final class CorePlugin extends SonarPlugin { extensions.addAll(IgnoreIssuesPlugin.getExtensions()); extensions.addAll(CoverageMeasurementFilter.getPropertyDefinitions()); extensions.addAll(PastSnapshotFinder.getPropertyDefinitions()); + extensions.addAll(TechnicalDebtDecorator.extensions()); extensions.addAll(propertyDefinitions()); return extensions.build(); diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java new file mode 100644 index 00000000000..a3b7cbfe3a4 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java @@ -0,0 +1,74 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import com.google.common.collect.Lists; + +import javax.annotation.Nullable; + +import java.util.List; + +public final class Characteristic implements Characteristicable { + + private String key; + private org.sonar.api.qualitymodel.Characteristic characteristic; + private Characteristic parent = null; + private List<Characteristic> subCharacteristics = Lists.newArrayList(); + private List<Requirement> requirements = Lists.newArrayList(); + + public Characteristic(org.sonar.api.qualitymodel.Characteristic c) { + this(c, null); + } + + public Characteristic(org.sonar.api.qualitymodel.Characteristic c, @Nullable Characteristic parent) { + this.characteristic = c; + this.key = c.getKey(); + this.parent = parent; + for (org.sonar.api.qualitymodel.Characteristic subc : c.getChildren()) { + if (subc.getEnabled()) { + if (subc.getRule() != null) { + requirements.add(new Requirement(subc, this)); + } else { + subCharacteristics.add(new Characteristic(subc, this)); + } + } + } + } + + public String getKey() { + return key; + } + + public List<Characteristic> getSubCharacteristics() { + return subCharacteristics; + } + + public Characteristic getParent() { + return parent; + } + + public List<Requirement> getRequirements() { + return requirements; + } + + public org.sonar.api.qualitymodel.Characteristic toCharacteristic() { + return characteristic; + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java new file mode 100644 index 00000000000..5beeb7263f1 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java @@ -0,0 +1,28 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.sonar.api.qualitymodel.Characteristic; + +public interface Characteristicable { + + Characteristic toCharacteristic(); + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java new file mode 100644 index 00000000000..c10dc52f2a0 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java @@ -0,0 +1,89 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.sonar.api.rules.Rule; +import org.sonar.plugins.core.technicaldebt.functions.LinearFunction; +import org.sonar.plugins.core.technicaldebt.functions.LinearWithOffsetFunction; +import org.sonar.plugins.core.technicaldebt.functions.LinearWithThresholdFunction; + +public class Requirement implements Characteristicable { + + public static final String PROPERTY_REMEDIATION_FUNCTION = "remediationFunction"; + public static final String PROPERTY_REMEDIATION_FACTOR = "remediationFactor"; + public static final String PROPERTY_OFFSET = "offset"; + + private Rule rule; + private Characteristic parent; + private org.sonar.api.qualitymodel.Characteristic characteristic; + private String function; + private WorkUnit factor; + private WorkUnit offset; + + public Requirement(org.sonar.api.qualitymodel.Characteristic requirement, Characteristic parent) { + this.characteristic = requirement; + this.rule = requirement.getRule(); + this.parent = parent; + + initFunction(); + initFactor(); + initOffset(); + } + + private void initFunction() { + function = characteristic.getPropertyTextValue(PROPERTY_REMEDIATION_FUNCTION, LinearFunction.FUNCTION_LINEAR); + } + + private void initFactor() { + factor = WorkUnit.create(characteristic.getPropertyValue(PROPERTY_REMEDIATION_FACTOR, null), + characteristic.getPropertyTextValue(PROPERTY_REMEDIATION_FACTOR, null)); + } + + private void initOffset() { + if (LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET.equals(function) || LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD.equals(function)) { + offset = WorkUnit.create(characteristic.getPropertyValue(PROPERTY_OFFSET, null), + characteristic.getPropertyTextValue(PROPERTY_OFFSET, null)); + } + } + + public Rule getRule() { + return rule; + } + + public Characteristic getParent() { + return parent; + } + + public String getRemediationFunction() { + return function; + } + + public WorkUnit getRemediationFactor() { + return factor; + } + + public WorkUnit getOffset() { + return offset; + } + + public org.sonar.api.qualitymodel.Characteristic toCharacteristic() { + return characteristic; + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java new file mode 100644 index 00000000000..0c4e737ec96 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java @@ -0,0 +1,138 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Maps; +import org.slf4j.LoggerFactory; +import org.sonar.api.BatchExtension; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasuresFilters; +import org.sonar.api.measures.Metric; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.functions.Functions; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Computes the remediation cost based on the quality and analysis models. + */ +public class TechnicalDebtCalculator implements BatchExtension { + + private double total = 0.0; + private Map<Characteristic, Double> characteristicCosts = Maps.newHashMap(); + private Map<Requirement, Double> requirementCosts = Maps.newHashMap(); + + private Functions functions; + private TechnicalDebtModel technicalDebtModel; + + public TechnicalDebtCalculator(TechnicalDebtModel technicalDebtModel, Functions functions) { + this.technicalDebtModel = technicalDebtModel; + this.functions = functions; + } + + public void compute(DecoratorContext context) { + reset(); + + // group violations by requirement + ListMultimap<Requirement, Violation> violationsByRequirement = groupViolations(context); + + // the total cost is: cost(violations) + for (Requirement requirement : technicalDebtModel.getAllRequirements()) { + List<Violation> violations = violationsByRequirement.get(requirement); + double allViolationsCost = computeRemediationCost(CoreMetrics.TECHNICAL_DEBT, context, requirement, violations); + updateRequirementCosts(requirement, allViolationsCost); + } + } + + public double getTotal() { + return total; + } + + public Map<Characteristic, Double> getCharacteristicCosts() { + return characteristicCosts; + } + + public Map<Requirement, Double> getRequirementCosts() { + return requirementCosts; + } + + @VisibleForTesting + protected ListMultimap<Requirement, Violation> groupViolations(DecoratorContext context) { + ListMultimap<Requirement, Violation> violationsByRequirement = ArrayListMultimap.create(); + for (Violation violation : context.getViolations()) { + String repositoryKey = violation.getRule().getRepositoryKey(); + String key = violation.getRule().getKey(); + Requirement requirement = technicalDebtModel.getRequirementByRule(repositoryKey, key); + if (requirement == null) { + LoggerFactory.getLogger(getClass()).debug("No technical debt requirement for: " + repositoryKey + "/" + key); + } else { + violationsByRequirement.put(requirement, violation); + } + } + return violationsByRequirement; + } + + @VisibleForTesting + protected void updateRequirementCosts(Requirement requirement, double cost) { + requirementCosts.put(requirement, cost); + total += cost; + propagateCostInParents(characteristicCosts, requirement.getParent(), cost); + } + + private double computeRemediationCost(Metric metric, DecoratorContext context, Requirement requirement, Collection<Violation> violations) { + double cost = 0.0; + if (violations != null) { + cost = functions.calculateCost(requirement, violations); + } + + for (Measure measure : context.getChildrenMeasures(MeasuresFilters.characteristic(metric, requirement.toCharacteristic()))) { + if (measure.getCharacteristic() != null && measure.getCharacteristic().equals(requirement.toCharacteristic()) && measure.getValue() != null) { + cost += measure.getValue(); + } + } + return cost; + } + + private void reset() { + total = 0.0; + characteristicCosts.clear(); + requirementCosts.clear(); + } + + private void propagateCostInParents(Map<Characteristic, Double> hierarchyMap, Characteristic characteristic, double cost) { + if (characteristic != null) { + Double parentCost = hierarchyMap.get(characteristic); + if (parentCost == null) { + hierarchyMap.put(characteristic, cost); + } else { + hierarchyMap.put(characteristic, cost + parentCost); + } + propagateCostInParents(hierarchyMap, characteristic.getParent(), cost); + } + } + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java new file mode 100644 index 00000000000..00d11f46343 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java @@ -0,0 +1,132 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.*; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.PersistenceMode; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.plugins.core.technicaldebt.functions.*; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * Decorator that computes the technical debt metric + */ +@DependsUpon(DecoratorBarriers.ISSUES_TRACKED) +public final class TechnicalDebtDecorator implements Decorator { + + public static final int DECIMALS_PRECISION = 5; + private TechnicalDebtCalculator costCalculator; + + public TechnicalDebtDecorator(TechnicalDebtCalculator costCalculator) { + this.costCalculator = costCalculator; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + @DependedUpon + public List<Metric> generatesMetrics() { + return Arrays.asList(CoreMetrics.TECHNICAL_DEBT); + } + + @SuppressWarnings("rawtypes") + public void decorate(Resource resource, DecoratorContext context) { + if (!ResourceUtils.isUnitTestClass(resource)) { + costCalculator.compute(context); + saveCostMeasures(context); + } + } + + protected void saveCostMeasures(DecoratorContext context) { + context.saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT, costCalculator.getTotal(), DECIMALS_PRECISION)); + saveCharacteristicCosts(context); + saveRequirementCosts(context); + } + + private void saveCharacteristicCosts(DecoratorContext context) { + for (Map.Entry<Characteristic, Double> entry : costCalculator.getCharacteristicCosts().entrySet()) { + saveCost(context, entry.getKey().toCharacteristic(), entry.getValue(), false); + } + } + + private void saveRequirementCosts(DecoratorContext context) { + for (Map.Entry<Requirement, Double> entry : costCalculator.getRequirementCosts().entrySet()) { + saveCost(context, entry.getKey().toCharacteristic(), entry.getValue(), ResourceUtils.isEntity(context.getResource())); + } + } + + protected void saveCost(DecoratorContext context, org.sonar.api.qualitymodel.Characteristic characteristic, Double value, boolean inMemory) { + // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122) + // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147) + if (value > 0.0 || (ResourceUtils.isProject(context.getResource()) && characteristic.getDepth() == org.sonar.api.qualitymodel.Characteristic.ROOT_DEPTH)) { + Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT); + measure.setCharacteristic(characteristic); + measure.setValue(value, DECIMALS_PRECISION); + if (inMemory) { + measure.setPersistenceMode(PersistenceMode.MEMORY); + } + context.saveMeasure(measure); + } + } + + public static List<?> extensions() { + ImmutableList.Builder<Object> extensions = ImmutableList.builder(); + extensions.addAll(definitions()); + extensions.add( + // base components + TechnicalDebtModel.class, WorkUnitConverter.class, TechnicalDebtCalculator.class, + + // functions + ConstantFunction.class, LinearFunction.class, LinearWithOffsetFunction.class, LinearWithThresholdFunction.class, Functions.class, + + // decorator + TechnicalDebtDecorator.class + ); + + return extensions.build(); + } + + private static List<PropertyDefinition> definitions() { + return ImmutableList.of( + PropertyDefinition.builder(WorkUnitConverter.PROPERTY_HOURS_IN_DAY) + .name("Number of working hours in a day") + .defaultValue("" + WorkUnitConverter.DEFAULT_HOURS_IN_DAY) + .category(CoreProperties.CATEGORY_TECHNICAL_DEBT) + .build() + ); + } + + @Override + public String toString() { + return "Technical Debt decorator"; + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java new file mode 100644 index 00000000000..b5a969d363b --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java @@ -0,0 +1,97 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.sonar.api.BatchExtension; +import org.sonar.api.qualitymodel.Model; +import org.sonar.api.qualitymodel.ModelFinder; +import org.sonar.api.rules.Rule; +import org.sonar.api.utils.SonarException; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class TechnicalDebtModel implements BatchExtension { + + // FIXME Use the same as in RegisterTechnicalDebtModel + public static final String MODEL_NAME = "TECHNICAL_DEBT"; + + private List<Characteristic> characteristics = Lists.newArrayList(); + private Map<Rule, Requirement> requirementsByRule = Maps.newHashMap(); + + public TechnicalDebtModel(ModelFinder modelFinder) { + Model model = modelFinder.findByName(MODEL_NAME); + if (model == null) { + throw new SonarException("Can not find the model in database: " + MODEL_NAME); + } + init(model); + } + + /** + * For unit tests + */ + private TechnicalDebtModel(Model model) { + init(model); + } + + /** + * For unit tests + */ + public static TechnicalDebtModel create(Model model) { + return new TechnicalDebtModel(model); + } + + private void init(Model model) { + for (org.sonar.api.qualitymodel.Characteristic characteristic : model.getRootCharacteristics()) { + if (characteristic.getEnabled()) { + Characteristic sc = new Characteristic(characteristic); + characteristics.add(sc); + registerRequirements(sc); + } + } + } + + private void registerRequirements(Characteristic c) { + for (Requirement requirement : c.getRequirements()) { + + if (requirement.getRule() != null) { + requirementsByRule.put(requirement.getRule(), requirement); + } + } + for (Characteristic subCharacteristic : c.getSubCharacteristics()) { + registerRequirements(subCharacteristic); + } + } + + public List<Characteristic> getCharacteristics() { + return characteristics; + } + + public Collection<Requirement> getAllRequirements() { + return requirementsByRule.values(); + } + + public Requirement getRequirementByRule(String repositoryKey, String key) { + return requirementsByRule.get(Rule.create().setUniqueKey(repositoryKey, key)); + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java new file mode 100644 index 00000000000..f5d20dbb3e9 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java @@ -0,0 +1,68 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; + +import javax.annotation.Nullable; + +public final class WorkUnit { + + public static final String DAYS = "d"; + public static final String MINUTES = "mn"; + public static final String HOURS = "h"; + public static final String DEFAULT_UNIT = DAYS; + private static final String[] UNITS = {DAYS, MINUTES, HOURS}; + + public static final double DEFAULT_VALUE = 1.0; + + private double value; + private String unit; + + WorkUnit(double value, String unit) { + this.value = value; + this.unit = unit; + } + + public double getValue() { + return value; + } + + public String getUnit() { + return unit; + } + + public static WorkUnit create(@Nullable Double value, @Nullable String unit) { + unit = StringUtils.defaultIfEmpty(unit, DEFAULT_UNIT); + if (!ArrayUtils.contains(UNITS, unit)) { + throw new IllegalArgumentException("Remediation factor unit can not be: " + unit + ". Possible values are " + ArrayUtils.toString(UNITS)); + } + double d = value != null ? value : DEFAULT_VALUE; + if (d < 0.0) { + throw new IllegalArgumentException("Remediation factor can not be negative: " + d); + } + return new WorkUnit(d, unit); + } + + public static WorkUnit createInDays(Double value) { + return create(value, DAYS); + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java new file mode 100644 index 00000000000..0255f160e71 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.BatchExtension; + +public final class WorkUnitConverter implements BatchExtension { + + public static final int DEFAULT_HOURS_IN_DAY = 8; + + public static final String PROPERTY_HOURS_IN_DAY = "sonar.technicalDebt.hoursInDay"; + + private int hoursInDay = DEFAULT_HOURS_IN_DAY; + + public WorkUnitConverter(Configuration configuration) { + this.hoursInDay = configuration.getInt(PROPERTY_HOURS_IN_DAY, DEFAULT_HOURS_IN_DAY); + } + + public int getHoursInDay() { + return hoursInDay; + } + + public double toDays(WorkUnit factor) { + double result; + if (StringUtils.equals(WorkUnit.DAYS, factor.getUnit())) { + result = factor.getValue(); + + } else if (StringUtils.equals(WorkUnit.HOURS, factor.getUnit())) { + result = factor.getValue() / hoursInDay; + + } else if (StringUtils.equals(WorkUnit.MINUTES, factor.getUnit())) { + result = factor.getValue() / (hoursInDay * 60.0); + + } else { + throw new IllegalArgumentException("Unknown remediation factor unit: " + factor.getUnit()); + } + return result; + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java new file mode 100644 index 00000000000..d04d3f0dd99 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java @@ -0,0 +1,44 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; + +public abstract class AbstractFunction implements Function { + + private WorkUnitConverter converter; + + public AbstractFunction(WorkUnitConverter converter) { + this.converter = converter; + } + + protected WorkUnitConverter getConverter() { + return converter; + } + + public abstract String getKey(); + + public abstract double calculateCost(Requirement requirement, Collection<Violation> violations); + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java new file mode 100644 index 00000000000..91d57632acd --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java @@ -0,0 +1,48 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; + +public final class ConstantFunction extends AbstractFunction { + + public static final String FUNCTION_CONSTANT_RESOURCE = "constant_resource"; + + public ConstantFunction(WorkUnitConverter converter) { + super(converter); + } + + public String getKey() { + return FUNCTION_CONSTANT_RESOURCE; + } + + public double calculateCost(Requirement requirement, Collection<Violation> violations) { + double cost = 0.0; + if (!violations.isEmpty()) { + cost = getConverter().toDays(requirement.getRemediationFactor()); + } + return cost; + } + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java new file mode 100644 index 00000000000..8d6d6ba564d --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java @@ -0,0 +1,34 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import org.sonar.api.BatchExtension; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; + +import java.util.Collection; + +public interface Function extends BatchExtension { + + String getKey(); + + double calculateCost(Requirement requirement, Collection<Violation> violations); + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java new file mode 100644 index 00000000000..7d149a5ae53 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java @@ -0,0 +1,52 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import com.google.common.collect.Maps; +import org.sonar.api.BatchExtension; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; + +import java.util.Collection; +import java.util.Map; + +public class Functions implements BatchExtension { + + private final Map<String, Function> functionsByKey = Maps.newHashMap(); + + public Functions(final Function[] functions) { + for (Function function : functions) { + functionsByKey.put(function.getKey(), function); + } + } + + Function getFunction(String key) { + return functionsByKey.get(key); + } + + public Function getFunction(Requirement requirement) { + return getFunction(requirement.getRemediationFunction()); + } + + public double calculateCost(Requirement requirement, Collection<Violation> violations) { + return getFunction(requirement).calculateCost(requirement, violations); + } + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java new file mode 100644 index 00000000000..3718e2a6967 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java @@ -0,0 +1,49 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; + +public class LinearFunction extends AbstractFunction { + + public static final String FUNCTION_LINEAR = "linear"; + + public static final double DEFAULT_VIOLATION_COST = 1.0; + + public LinearFunction(WorkUnitConverter converter) { + super(converter); + } + + public String getKey() { + return FUNCTION_LINEAR; + } + + public double calculateCost(Requirement requirement, Collection<Violation> violations) { + double points = 0.0; + for (Violation violation : violations) { + points += (violation.getCost() != null ? violation.getCost() : DEFAULT_VIOLATION_COST); + } + return points * getConverter().toDays(requirement.getRemediationFactor()); + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java new file mode 100644 index 00000000000..5c402408db5 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java @@ -0,0 +1,48 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; + +public final class LinearWithOffsetFunction extends LinearFunction { + + public static final String FUNCTION_LINEAR_WITH_OFFSET = "linear_offset"; + + public LinearWithOffsetFunction(WorkUnitConverter converter) { + super(converter); + } + + public String getKey() { + return FUNCTION_LINEAR_WITH_OFFSET; + } + + public double calculateCost(Requirement requirement, Collection<Violation> violations) { + if (violations.isEmpty()) { + return 0.0; + } + double minimunCost = getConverter().toDays(requirement.getOffset()); + return minimunCost + super.calculateCost(requirement, violations); + } + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java new file mode 100644 index 00000000000..502aa33202e --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java @@ -0,0 +1,49 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; + +public final class LinearWithThresholdFunction extends LinearFunction { + + public static final String FUNCTION_LINEAR_WITH_THRESHOLD = "linear_threshold"; + + public LinearWithThresholdFunction(WorkUnitConverter converter) { + super(converter); + } + + public String getKey() { + return FUNCTION_LINEAR_WITH_THRESHOLD; + } + + public double calculateCost(Requirement requirement, Collection<Violation> violations) { + if (violations.isEmpty()) { + return 0.0; + } + double thresholdCost = getConverter().toDays(requirement.getOffset()); + double violationsCost = super.calculateCost(requirement, violations); + return violationsCost > thresholdCost ? violationsCost : thresholdCost; + } + +} diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties index 57dceb2706e..3f4bd1b2a96 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -841,6 +841,7 @@ property.category.exclusions.coverage=Code Coverage property.category.exclusions.coverage.description=Configure files which should be considered for code coverage. property.sonar.coverage.exclusions.name=Coverage Exclusions property.sonar.coverage.exclusions.description=Patterns used to exclude some files from coverage report. +property.category.technicalDebt=Technical Debt property.error.notBoolean=Valid options are "true" and "false" property.error.notInteger=Only digits are allowed property.error.notFloat=Not a floating point number @@ -2252,6 +2253,18 @@ metric.reopened_issues.description=Reopened issues metric.confirmed_issues.name=Confirmed issues metric.confirmed_issues.description=Confirmed issues + +#-------------------------------------------------------------------------------------------------------------------- +# +# TECHNICAL DEBT METRICS +# +#-------------------------------------------------------------------------------------------------------------------- + +metric.technical_debt.name=Technical Debt +metric.technical_debt.description=The technical debt is the total effort required to fully adhere all quality requirements defined by a user. + + + #------------------------------------------------------------------------------ # # GLOBAL PERMISSIONS diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java new file mode 100644 index 00000000000..97fb8ce4026 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.junit.Test; +import org.sonar.api.qualitymodel.Characteristic; +import org.sonar.plugins.core.technicaldebt.functions.ConstantFunction; +import org.sonar.plugins.core.technicaldebt.functions.LinearFunction; +import org.sonar.plugins.core.technicaldebt.functions.LinearWithOffsetFunction; +import org.sonar.plugins.core.technicaldebt.functions.LinearWithThresholdFunction; + +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class RequirementTest { + + @Test + public void defaultFactor() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFactor().getValue(), is(WorkUnit.DEFAULT_VALUE)); + assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DEFAULT_UNIT)); + } + + @Test + public void testOverriddenFactor() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FACTOR, 3.14); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFactor().getValue(), is(3.14)); + assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DAYS)); + } + + @Test + public void defaultFunctionIsLinear() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFunction(), is(LinearFunction.FUNCTION_LINEAR)); + assertThat(requirement.getOffset(), is(nullValue())); + } + + @Test + public void testOverriddenFunction() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, ConstantFunction.FUNCTION_CONSTANT_RESOURCE); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFunction(), is(ConstantFunction.FUNCTION_CONSTANT_RESOURCE)); + } + + @Test + public void testDefaultLinearWithOffset() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFunction(), is(LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET)); + assertThat(requirement.getRemediationFactor().getValue(), is(WorkUnit.DEFAULT_VALUE)); + assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DEFAULT_UNIT)); + assertThat(requirement.getOffset().getValue(), is(WorkUnit.DEFAULT_VALUE)); + assertThat(requirement.getOffset().getUnit(), is(WorkUnit.DEFAULT_UNIT)); + } + + @Test + public void testCustomizedLinearWithOffset() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET); + persistedRequirement.setProperty(Requirement.PROPERTY_OFFSET, 5.0); + persistedRequirement.addProperty(persistedRequirement.getProperty(Requirement.PROPERTY_OFFSET).setTextValue("h")); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFunction(), is(LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET)); + assertThat(requirement.getOffset().getValue(), is(5.0)); + assertThat(requirement.getOffset().getUnit(), is(WorkUnit.HOURS)); + } + + @Test + public void testDefaultLinearWithThreshold() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFunction(), is(LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD)); + assertThat(requirement.getRemediationFactor().getValue(), is(WorkUnit.DEFAULT_VALUE)); + assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DEFAULT_UNIT)); + assertThat(requirement.getOffset().getValue(), is(WorkUnit.DEFAULT_VALUE)); + assertThat(requirement.getOffset().getUnit(), is(WorkUnit.DEFAULT_UNIT)); + } + + @Test + public void testCustomizedLinearWithThreshold() { + Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD); + persistedRequirement.setProperty(Requirement.PROPERTY_OFFSET, 5.0); + persistedRequirement.addProperty(persistedRequirement.getProperty(Requirement.PROPERTY_OFFSET).setTextValue("h")); + Requirement requirement = new Requirement(persistedRequirement, null); + assertThat(requirement.getRemediationFunction(), is(LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD)); + assertThat(requirement.getOffset().getValue(), is(5.0)); + assertThat(requirement.getOffset().getUnit(), is(WorkUnit.HOURS)); + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java new file mode 100644 index 00000000000..95205e2084f --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java @@ -0,0 +1,153 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import org.apache.commons.lang.time.DateUtils; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.MeasuresFilter; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.functions.Functions; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +public class TechnicalDebtCalculatorTest { + + private static final Date NOW = new Date(System.currentTimeMillis()); + private static final Date YESTERDAY = DateUtils.addDays(NOW, -1); + private static final Date LAST_MONTH = DateUtils.addMonths(NOW, -1); + + private TechnicalDebtModel technicalDebtModel; + private Functions functions; + private TechnicalDebtCalculator remediationCostCalculator; + + @Before + public void initMocks() { + technicalDebtModel = mock(TechnicalDebtModel.class); + functions = mock(Functions.class); + remediationCostCalculator = new TechnicalDebtCalculator(technicalDebtModel, functions); + } + + @Test + public void group_violations_by_requirement() throws Exception { + + Requirement requirement1 = mock(Requirement.class); + Requirement requirement2 = mock(Requirement.class); + + Violation violation1 = buildViolation("rule1", "repo1", NOW); + Violation violation2 = buildViolation("rule1", "repo1", NOW); + Violation violation3 = buildViolation("rule2", "repo2", NOW); + Violation violation4 = buildViolation("unmatchable", "repo2", NOW); + + List<Violation> violations = Lists.newArrayList(violation1, violation2, violation3, violation4); + + stub(technicalDebtModel.getRequirementByRule("repo1", "rule1")).toReturn(requirement1); + stub(technicalDebtModel.getRequirementByRule("repo2", "rule2")).toReturn(requirement2); + + DecoratorContext context = mock(DecoratorContext.class); + when(context.getViolations()).thenReturn(violations); + + ListMultimap<Requirement, Violation> groupedViolations = remediationCostCalculator.groupViolations(context); + + assertThat(groupedViolations.keySet().size()).isEqualTo(2); + assertThat(groupedViolations.get(requirement1)).containsExactly(violation1, violation2); + assertThat(groupedViolations.get(requirement2)).containsExactly(violation3); + } + + @Test + public void add_cost_with_no_parent() throws Exception { + + double requirementCost = 1.0; + + Requirement requirement = mock(Requirement.class); + when(requirement.getParent()).thenReturn(null); + + remediationCostCalculator.updateRequirementCosts(requirement, requirementCost); + + assertThat(remediationCostCalculator.getRequirementCosts().get(requirement)).isEqualTo(requirementCost); + assertThat(remediationCostCalculator.getTotal()).isEqualTo(requirementCost); + } + + @Test + public void add_cost_and_propagate_to_parents() throws Exception { + + double requirementCost = 1.0; + + Characteristic parentCharacteristic = new Characteristic(org.sonar.api.qualitymodel.Characteristic.create()); + + Characteristic characteristic = new Characteristic(org.sonar.api.qualitymodel.Characteristic.create(), parentCharacteristic); + + Requirement requirement = mock(Requirement.class); + when(requirement.getParent()).thenReturn(characteristic); + + remediationCostCalculator.updateRequirementCosts(requirement, requirementCost); + + assertThat(remediationCostCalculator.getRequirementCosts().get(requirement)).isEqualTo(requirementCost); + assertThat(remediationCostCalculator.getCharacteristicCosts().get(characteristic)).isEqualTo(requirementCost); + assertThat(remediationCostCalculator.getCharacteristicCosts().get(parentCharacteristic)).isEqualTo(requirementCost); + } + + @Test + public void compute_totals_costs() throws Exception { + + Requirement requirement1 = mock(Requirement.class); + Requirement requirement2 = mock(Requirement.class); + + Violation violation1 = buildViolation("rule1", "repo1", NOW); + Violation violation2 = buildViolation("rule1", "repo1", NOW); + Violation violation3 = buildViolation("rule2", "repo2", YESTERDAY); + Violation violation4 = buildViolation("rule2", "repo2", LAST_MONTH); + + List<Violation> violations = Lists.newArrayList(violation1, violation2, violation3, violation4); + + stub(technicalDebtModel.getRequirementByRule("repo1", "rule1")).toReturn(requirement1); + stub(technicalDebtModel.getRequirementByRule("repo2", "rule2")).toReturn(requirement2); + stub(technicalDebtModel.getAllRequirements()).toReturn(Lists.newArrayList(requirement1, requirement2)); + + stub(functions.calculateCost(any(Requirement.class), any(Collection.class))).toReturn(1.0); + + DecoratorContext context = mock(DecoratorContext.class); + stub(context.getViolations()).toReturn(violations); + stub(context.getChildrenMeasures(any(MeasuresFilter.class))).toReturn(Collections.EMPTY_LIST); + + remediationCostCalculator.compute(context); + + assertThat(remediationCostCalculator.getTotal()).isEqualTo(2.0); + } + + private Violation buildViolation(String ruleKey, String repositoryKey, Date creationDate) { + Violation violation = mock(Violation.class); + stub(violation.getRule()).toReturn(Rule.create(repositoryKey, ruleKey)); + stub(violation.getCreatedAt()).toReturn(creationDate); + return violation; + } +} + diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java new file mode 100644 index 00000000000..14cb4d56353 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java @@ -0,0 +1,152 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.qualitymodel.Characteristic; +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 org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.*; + +public class TechnicalDebtDecoratorTest { + + @Test + public void generates_metrics() throws Exception { + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null); + assertThat(decorator.generatesMetrics()).hasSize(1); + } + + @Test + public void execute_on_project() throws Exception { + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null); + assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); + } + + @Test + public void execute_on_source_file() throws Exception { + TechnicalDebtCalculator costCalculator = mock(TechnicalDebtCalculator.class); + File resource = mock(File.class); + DecoratorContext context = mock(DecoratorContext.class); + + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalculator); + decorator.decorate(resource, context); + + verify(costCalculator, times(1)).compute(context); + } + + @Test + public void not_execute_on_unit_test() throws Exception { + TechnicalDebtCalculator costCalculator = mock(TechnicalDebtCalculator.class); + File resource = mock(File.class); + when(resource.getQualifier()).thenReturn(Qualifiers.UNIT_TEST_FILE); + DecoratorContext context = mock(DecoratorContext.class); + + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalculator); + decorator.decorate(resource, context); + + verify(costCalculator, never()).compute(context); + } + + @Test + public void save_cost_measures() { + TechnicalDebtCalculator costCalulator = mock(TechnicalDebtCalculator.class); + when(costCalulator.getTotal()).thenReturn(60.0); + + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalulator); + DecoratorContext context = mock(DecoratorContext.class); + + decorator.saveCostMeasures(context); + + verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.TECHNICAL_DEBT, 60.0))); + } + + @Test + public void always_save_cost_for_positive_values() throws Exception { + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null); + + // for a project + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new Project("foo")); + decorator.saveCost(context, null, 12.0, false); + verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); + + // or for a file + context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new File("foo")); + decorator.saveCost(context, null, 12.0, false); + verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); + } + + @Test + public void always_save_cost_for_project_if_top_characteristic() throws Exception { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new Project("foo")); + // this is a top characteristic + Characteristic topCharacteristic = Characteristic.create(); + + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null); + + decorator.saveCost(context, topCharacteristic, 0.0, true); + verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT).setCharacteristic(topCharacteristic)); + } + + /** + * SQALE-147 + */ + @Test + public void never_save_cost_for_project_if_not_top_characteristic() throws Exception { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new Project("foo")); + Characteristic topCharacteristic = Characteristic.create(); + Characteristic childCharacteristic = Characteristic.create(); + topCharacteristic.addChild(childCharacteristic); + + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null); + + decorator.saveCost(context, childCharacteristic, 0.0, true); + verify(context, never()).saveMeasure(any(Measure.class)); + } + + @Test + public void not_save_cost_for_file_if_zero() throws Exception { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new File("foo")); + + TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null); + + decorator.saveCost(context, null, 0.0, true); + verify(context, never()).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); + } + + @Test + public void check_extensions() { + assertThat(TechnicalDebtDecorator.extensions()).hasSize(1 /* properties */ + 9 /* extensions */); + } + +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java new file mode 100644 index 00000000000..c6222bbdd6d --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java @@ -0,0 +1,74 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.junit.Test; +import org.sonar.api.qualitymodel.Characteristic; +import org.sonar.api.qualitymodel.Model; +import org.sonar.api.qualitymodel.ModelFinder; +import org.sonar.api.rules.Rule; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TechnicalDebtModelTest { + + private Characteristic disabledCharacteristic = Characteristic.createByKey("DISABLED", "Disabled").setEnabled(false); + + @Test + public void load_model() { + ModelFinder modelFinder = mock(ModelFinder.class); + Model persistedModel = createModel(); + when(modelFinder.findByName(TechnicalDebtModel.MODEL_NAME)).thenReturn(persistedModel); + + TechnicalDebtModel technicalDebtModel = new TechnicalDebtModel(modelFinder); + + assertThat(technicalDebtModel.getCharacteristics()).hasSize(2); + assertThat(technicalDebtModel.getAllRequirements()).hasSize(1); + assertThat(technicalDebtModel.getRequirementByRule("repo", "check1").getRule().getKey()).isEqualTo("check1"); + assertThat(technicalDebtModel.getRequirementByRule("repo", "check1").getParent().getKey()).isEqualTo("CPU_EFFICIENCY"); + + // ignore disabled characteristics/requirements + assertThat(technicalDebtModel.getCharacteristics()).excludes(disabledCharacteristic); + assertThat(technicalDebtModel.getRequirementByRule("repo", "check2")).isNull(); + } + + private Model createModel() { + Model model = Model.createByName(TechnicalDebtModel.MODEL_NAME); + Characteristic efficiency = model.createCharacteristicByKey("EFFICIENCY", "Efficiency"); + model.createCharacteristicByKey("TESTABILITY", "Testability"); + + Characteristic cpuEfficiency = model.createCharacteristicByKey("CPU_EFFICIENCY", "CPU Efficiency"); + efficiency.addChild(cpuEfficiency); + + Characteristic requirement1 = model.createCharacteristicByRule(Rule.create("repo", "check1", "Check One")); + cpuEfficiency.addChild(requirement1); + + // disabled requirement + Characteristic requirement2 = model.createCharacteristicByRule(Rule.create("repo", "check2", "Check Two")); + requirement2.setEnabled(false); + cpuEfficiency.addChild(requirement2); + + // disabled characteristic + model.addCharacteristic(disabledCharacteristic); + return model; + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java new file mode 100644 index 00000000000..39cd912a36a --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class WorkUnitConverterTest { + + @Test + public void shouldUseDefaultConfiguration() { + WorkUnitConverter converter = new WorkUnitConverter(new PropertiesConfiguration()); + assertThat(converter.getHoursInDay(), is(WorkUnitConverter.DEFAULT_HOURS_IN_DAY)); + } + + @Test + public void shouldConvertValueToDays() { + PropertiesConfiguration configuration = new PropertiesConfiguration(); + configuration.setProperty(WorkUnitConverter.PROPERTY_HOURS_IN_DAY, "12"); + WorkUnitConverter converter = new WorkUnitConverter(configuration); + + assertThat(converter.toDays(WorkUnit.create(6.0, WorkUnit.DAYS)), is(6.0)); + assertThat(converter.toDays(WorkUnit.create(6.0, WorkUnit.HOURS)), is(6.0 / 12.0)); + assertThat(converter.toDays(WorkUnit.create(60.0 , WorkUnit.MINUTES)), is(1.0/12.0)); + } + +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java new file mode 100644 index 00000000000..06dca29af8c --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java @@ -0,0 +1,68 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import com.google.common.collect.Lists; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnit; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; +import java.util.Collections; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ConstantFunctionTest { + + private Requirement requirement; + private Function function; + + @Before + public void before() { + function = new ConstantFunction(new WorkUnitConverter(new PropertiesConfiguration())); + requirement = mock(Requirement.class); + when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14)); + } + + @Test + public void zeroIfNoViolations() { + assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0)); + } + + @Test + public void countAsIfSingleViolation() { + Collection<Violation> violations = Lists.newArrayList(); + + Rule rule = Rule.create("checkstyle", "foo", "Foo"); + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(3.14)); + + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(3.14)); + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java new file mode 100644 index 00000000000..b3f33466bdd --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java @@ -0,0 +1,37 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import org.junit.Test; + +import static org.fest.assertions.Assertions.assertThat; + +public class FunctionsTest { + + @Test + public void registerFunctions() { + Functions functions = new Functions(new Function[]{new LinearFunction(null), new LinearWithOffsetFunction(null), + new ConstantFunction(null)}); + assertThat(functions.getFunction(LinearFunction.FUNCTION_LINEAR)).isInstanceOf(LinearFunction.class); + assertThat(functions.getFunction(LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET)).isInstanceOf(LinearWithOffsetFunction.class); + assertThat(functions.getFunction(ConstantFunction.FUNCTION_CONSTANT_RESOURCE)).isInstanceOf(ConstantFunction.class); + assertThat(functions.getFunction("foo")).isNull(); + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java new file mode 100644 index 00000000000..40955e95089 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import com.google.common.collect.Lists; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnit; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; +import java.util.Collections; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class LinearFunctionTest { + + private Requirement requirement; + private Function function = new LinearFunction(new WorkUnitConverter(new PropertiesConfiguration())); + + @Before + public void before() { + requirement = mock(Requirement.class); + when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14)); + } + + @Test + public void zeroIfNoViolations() { + assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0)); + } + + @Test + public void countEveryViolation() { + Collection<Violation> violations = Lists.newArrayList(); + + Rule rule = Rule.create("checkstyle", "foo", "Foo"); + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(3.14)); + + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(3.14 * 2)); + } + + @Test + public void usePointsWhenAvailable() { + Collection<Violation> violations = Lists.newArrayList(); + + Rule rule = Rule.create("checkstyle", "foo", "Foo"); + violations.add(new Violation(rule).setCost(20.5)); + violations.add(new Violation(rule).setCost(3.8)); + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(3.14 * (20.5 + 3.8 + 1))); + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java new file mode 100644 index 00000000000..46fa3d92b2c --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java @@ -0,0 +1,68 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import com.google.common.collect.Lists; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnit; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; +import java.util.Collections; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class LinearWithOffsetFunctionTest { + + private Requirement requirement; + private Function function = new LinearWithOffsetFunction(new WorkUnitConverter(new PropertiesConfiguration())); + + @Before + public void before() { + requirement = mock(Requirement.class); + when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14)); + when(requirement.getOffset()).thenReturn(WorkUnit.createInDays(2.12)); + } + + @Test + public void zeroIfNoViolations() { + assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0)); + } + + @Test + public void countEveryViolation() { + Collection<Violation> violations = Lists.newArrayList(); + + Rule rule = Rule.create("checkstyle", "foo", "Foo"); + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(2.12 + 3.14)); + + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(2.12 + 3.14 * 2)); + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java new file mode 100644 index 00000000000..51e17f25966 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java @@ -0,0 +1,71 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions; + +import com.google.common.collect.Lists; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.Violation; +import org.sonar.plugins.core.technicaldebt.Requirement; +import org.sonar.plugins.core.technicaldebt.WorkUnit; +import org.sonar.plugins.core.technicaldebt.WorkUnitConverter; + +import java.util.Collection; +import java.util.Collections; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class LinearWithThresholdFunctionTest { + + private Requirement requirement; + private Function function = new LinearWithThresholdFunction(new WorkUnitConverter(new PropertiesConfiguration())); + + @Before + public void before() { + requirement = mock(Requirement.class); + when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(2.0)); + when(requirement.getOffset()).thenReturn(WorkUnit.createInDays(5.0)); + } + + @Test + public void zeroIfNoViolations() { + assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0)); + } + + @Test + public void countEveryViolationAndCheckThreshold() { + Collection<Violation> violations = Lists.newArrayList(); + + Rule rule = Rule.create("checkstyle", "foo", "Foo"); + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(5.0)); + + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(5.0)); + + violations.add(new Violation(rule)); + assertThat(function.calculateCost(requirement, violations), is(6.0)); + } +} |