@@ -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(); |
@@ -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; | |||
} | |||
} |
@@ -17,20 +17,12 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.technicaldebt; | |||
package org.sonar.plugins.core.technicaldebt; | |||
public final class XMLConstants { | |||
import org.sonar.api.qualitymodel.Characteristic; | |||
private XMLConstants(){ | |||
// Utility class | |||
} | |||
public interface Characteristicable { | |||
public static final String CHARACTERISTIC = "chc"; | |||
public static final String CHARACTERISTIC_KEY = "key"; | |||
public static final String CHARACTERISTIC_NAME = "name"; | |||
public static final String CHARACTERISTIC_DESCRIPTION = "desc"; | |||
public static final String PROPERTY = "prop"; | |||
public static final String PROPERTY_KEY = "key"; | |||
public static final String PROPERTY_VALUE = "val"; | |||
public static final String PROPERTY_TEXT_VALUE = "txt"; | |||
Characteristic toCharacteristic(); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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"; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 |
@@ -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)); | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
@@ -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 */); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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))); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -121,6 +121,12 @@ public interface CoreProperties { | |||
*/ | |||
String CATEGORY_LICENSES = "licenses"; | |||
/** | |||
* @since 4.0 | |||
*/ | |||
String CATEGORY_TECHNICAL_DEBT = "technicalDebt"; | |||
/* Global settings */ | |||
String SONAR_HOME = "SONAR_HOME"; | |||
String PROJECT_BRANCH_PROPERTY = "sonar.branch"; |
@@ -55,6 +55,11 @@ public final class CoreMetrics { | |||
public static String DOMAIN_DUPLICATION = "Duplication"; | |||
public static String DOMAIN_DESIGN = "Design"; | |||
/** | |||
* @since 4.0 | |||
*/ | |||
public static String DOMAIN_TECHNICAL_DEBT = "Technical Debt"; | |||
public static final String LINES_KEY = "lines"; | |||
public static final Metric LINES = new Metric.Builder(LINES_KEY, "Lines", Metric.ValueType.INT) | |||
.setDescription("Lines") | |||
@@ -2009,6 +2014,30 @@ public final class CoreMetrics { | |||
.setHidden(true) | |||
.create(); | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// | |||
// TECHNICAL DEBT | |||
// | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
/** | |||
* @since 4.0 | |||
*/ | |||
public static final String TECHNICAL_DEBT_KEY = "technical_debt"; | |||
/** | |||
* @since 4.0 | |||
*/ | |||
public static final Metric TECHNICAL_DEBT = new Metric.Builder(TECHNICAL_DEBT_KEY, "Technical Debt", Metric.ValueType.FLOAT) | |||
.setDomain(DOMAIN_TECHNICAL_DEBT) | |||
.setDirection(Metric.DIRECTION_WORST) | |||
.setOptimizedBestValue(true) | |||
.setBestValue(0.0) | |||
.setQualitative(true) | |||
.create(); | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// | |||
// FILE DATA |
@@ -31,7 +31,7 @@ public class CoreMetricsTest { | |||
@Test | |||
public void shouldReadMetricsFromClassReflection() { | |||
List<Metric> metrics = CoreMetrics.getMetrics(); | |||
assertThat(metrics).hasSize(148); | |||
assertThat(metrics).hasSize(149); | |||
assertThat(metrics).contains(CoreMetrics.NCLOC, CoreMetrics.DIRECTORIES); | |||
} | |||
} |
@@ -46,22 +46,31 @@ public class XMLImporter implements ServerExtension { | |||
private static final Logger LOG = LoggerFactory.getLogger(XMLImporter.class); | |||
private static final String CHARACTERISTIC = "chc"; | |||
private static final String CHARACTERISTIC_KEY = "key"; | |||
private static final String CHARACTERISTIC_NAME = "name"; | |||
private static final String CHARACTERISTIC_DESCRIPTION = "desc"; | |||
private static final String PROPERTY = "prop"; | |||
private static final String PROPERTY_KEY = "key"; | |||
private static final String PROPERTY_VALUE = "val"; | |||
private static final String PROPERTY_TEXT_VALUE = "txt"; | |||
public Model importXML(String xml, ValidationMessages messages, RuleCache ruleCache) { | |||
return importXML(new StringReader(xml), messages, ruleCache); | |||
} | |||
public Model importXML(Reader xml, ValidationMessages messages, RuleCache repositoryCache) { | |||
Model sqale = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL); | |||
Model model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL); | |||
try { | |||
SMInputFactory inputFactory = initStax(); | |||
SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml); | |||
// advance to <sqale> | |||
cursor.advance(); | |||
SMInputCursor chcCursor = cursor.childElementCursor(XMLConstants.CHARACTERISTIC); | |||
SMInputCursor chcCursor = cursor.childElementCursor(CHARACTERISTIC); | |||
while (chcCursor.getNext() != null) { | |||
processCharacteristic(sqale, chcCursor, messages, repositoryCache); | |||
processCharacteristic(model, chcCursor, messages, repositoryCache); | |||
} | |||
cursor.getStreamReader().closeCompletely(); | |||
@@ -70,7 +79,7 @@ public class XMLImporter implements ServerExtension { | |||
LOG.error("XML is not valid", e); | |||
messages.addErrorText("XML is not valid: " + e.getMessage()); | |||
} | |||
return sqale; | |||
return model; | |||
} | |||
private SMInputFactory initStax() { | |||
@@ -82,7 +91,7 @@ public class XMLImporter implements ServerExtension { | |||
return new SMInputFactory(xmlFactory); | |||
} | |||
private Characteristic processCharacteristic(Model sqale, SMInputCursor chcCursor, ValidationMessages messages, RuleCache ruleCache) throws XMLStreamException { | |||
private Characteristic processCharacteristic(Model model, SMInputCursor chcCursor, ValidationMessages messages, RuleCache ruleCache) throws XMLStreamException { | |||
Characteristic characteristic = Characteristic.create(); | |||
SMInputCursor cursor = chcCursor.childElementCursor(); | |||
@@ -90,20 +99,20 @@ public class XMLImporter implements ServerExtension { | |||
List<Characteristic> children = Lists.newArrayList(); | |||
while (cursor.getNext() != null) { | |||
String node = cursor.getLocalName(); | |||
if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC_KEY)) { | |||
if (StringUtils.equals(node, CHARACTERISTIC_KEY)) { | |||
characteristic.setKey(cursor.collectDescendantText().trim()); | |||
} else if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC_NAME)) { | |||
} else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) { | |||
characteristic.setName(cursor.collectDescendantText().trim(), false); | |||
} else if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC_DESCRIPTION)) { | |||
} else if (StringUtils.equals(node, CHARACTERISTIC_DESCRIPTION)) { | |||
characteristic.setDescription(cursor.collectDescendantText().trim()); | |||
} else if (StringUtils.equals(node, XMLConstants.PROPERTY)) { | |||
} else if (StringUtils.equals(node, PROPERTY)) { | |||
processProperty(characteristic, cursor, messages); | |||
} else if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC)) { | |||
children.add(processCharacteristic(sqale, cursor, messages, ruleCache)); | |||
} else if (StringUtils.equals(node, CHARACTERISTIC)) { | |||
children.add(processCharacteristic(model, cursor, messages, ruleCache)); | |||
} else if (StringUtils.equals(node, "rule-repo")) { | |||
ruleRepositoryKey = cursor.collectDescendantText().trim(); | |||
@@ -115,7 +124,7 @@ public class XMLImporter implements ServerExtension { | |||
fillRule(characteristic, ruleRepositoryKey, ruleKey, messages, ruleCache); | |||
if (StringUtils.isNotBlank(characteristic.getKey()) || characteristic.getRule() != null) { | |||
addCharacteristicToModel(sqale, characteristic, children); | |||
addCharacteristicToModel(model, characteristic, children); | |||
return characteristic; | |||
} | |||
return null; | |||
@@ -133,11 +142,11 @@ public class XMLImporter implements ServerExtension { | |||
} | |||
} | |||
private void addCharacteristicToModel(Model sqale, Characteristic characteristic, List<Characteristic> children) { | |||
sqale.addCharacteristic(characteristic); | |||
private void addCharacteristicToModel(Model model, Characteristic characteristic, List<Characteristic> children) { | |||
model.addCharacteristic(characteristic); | |||
for (Characteristic child : children) { | |||
if (child != null) { | |||
sqale.addCharacteristic(child); | |||
model.addCharacteristic(child); | |||
characteristic.addChild(child); | |||
} | |||
} | |||
@@ -150,10 +159,10 @@ public class XMLImporter implements ServerExtension { | |||
String textValue = null; | |||
while (c.getNext() != null) { | |||
String node = c.getLocalName(); | |||
if (StringUtils.equals(node, XMLConstants.PROPERTY_KEY)) { | |||
if (StringUtils.equals(node, PROPERTY_KEY)) { | |||
key = c.collectDescendantText().trim(); | |||
} else if (StringUtils.equals(node, XMLConstants.PROPERTY_VALUE)) { | |||
} else if (StringUtils.equals(node, PROPERTY_VALUE)) { | |||
String s = c.collectDescendantText().trim(); | |||
try { | |||
value = NumberUtils.createDouble(s); | |||
@@ -162,7 +171,7 @@ public class XMLImporter implements ServerExtension { | |||
LOG.error(message, ex); | |||
messages.addErrorText(message); | |||
} | |||
} else if (StringUtils.equals(node, XMLConstants.PROPERTY_TEXT_VALUE)) { | |||
} else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) { | |||
textValue = c.collectDescendantText().trim(); | |||
} | |||
} |