diff options
29 files changed, 597 insertions, 1122 deletions
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 index e269bbbae36..fa5b1495000 100644 --- 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 @@ -17,29 +17,39 @@ * 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.ImmutableList; +import com.google.common.collect.ListMultimap; +import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.PropertyType; import org.sonar.api.batch.*; +import org.sonar.api.component.ResourcePerspectives; 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.issue.Issuable; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.measures.*; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; -import org.sonar.core.technicaldebt.TechnicalDebtCalculator; import org.sonar.core.technicaldebt.TechnicalDebtCharacteristic; import org.sonar.core.technicaldebt.TechnicalDebtConverter; +import org.sonar.core.technicaldebt.TechnicalDebtModel; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; + /** * Decorator that computes the technical debt metric */ @@ -47,10 +57,15 @@ import java.util.Map; public final class TechnicalDebtDecorator implements Decorator { private static final int DECIMALS_PRECISION = 5; - private TechnicalDebtCalculator costCalculator; - public TechnicalDebtDecorator(TechnicalDebtCalculator costCalculator) { - this.costCalculator = costCalculator; + private final ResourcePerspectives perspectives; + private final TechnicalDebtModel technicalDebtModel; + private final TechnicalDebtConverter converter; + + public TechnicalDebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel technicalDebtModel, TechnicalDebtConverter converter) { + this.perspectives = perspectives; + this.technicalDebtModel = technicalDebtModel; + this.converter = converter; } public boolean shouldExecuteOnProject(Project project) { @@ -63,31 +78,49 @@ public final class TechnicalDebtDecorator implements Decorator { } public void decorate(Resource resource, DecoratorContext context) { - if (ResourceUtils.isPersistable(resource) && !ResourceUtils.isUnitTestClass(resource)) { - costCalculator.compute(context); - saveCostMeasures(context); + Issuable issuable = perspectives.as(Issuable.class, resource); + if (issuable != null && shouldSaveMeasure(context)) { + List<Issue> issues = newArrayList(issuable.issues()); + saveMeasures(context, issues); } } - protected void saveCostMeasures(DecoratorContext context) { - context.saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT, costCalculator.getTotal(), DECIMALS_PRECISION)); - saveCharacteristicCosts(context); - saveRequirementCosts(context); + private void saveMeasures(DecoratorContext context, List<Issue> issues) { + // group issues by requirement + ListMultimap<TechnicalDebtRequirement, Issue> issuesByRequirement = issuesByRequirement(issues); + + double total = 0.0; + Map<TechnicalDebtCharacteristic, Double> characteristicCosts = newHashMap(); + Map<TechnicalDebtRequirement, Double> requirementCosts = newHashMap(); + + for (TechnicalDebtRequirement requirement : technicalDebtModel.getAllRequirements()) { + List<Issue> requirementIssues = issuesByRequirement.get(requirement); + double value = computeTechnicalDebt(CoreMetrics.TECHNICAL_DEBT, context, requirement, requirementIssues); + + requirementCosts.put(requirement, value); + total += value; + propagateTechnicalDebtInParents(requirement.getParent(), value, characteristicCosts); + } + + context.saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT, total, DECIMALS_PRECISION)); + saveOnCharacteristic(context, characteristicCosts); + saveOnRequirement(context, requirementCosts); } - private void saveCharacteristicCosts(DecoratorContext context) { - for (Map.Entry<TechnicalDebtCharacteristic, Double> entry : costCalculator.getCharacteristicCosts().entrySet()) { + private void saveOnCharacteristic(DecoratorContext context, Map<TechnicalDebtCharacteristic, Double> characteristicCosts) { + for (Map.Entry<TechnicalDebtCharacteristic, Double> entry : characteristicCosts.entrySet()) { saveCost(context, entry.getKey().toCharacteristic(), entry.getValue(), false); } } - private void saveRequirementCosts(DecoratorContext context) { - for (Map.Entry<TechnicalDebtRequirement, Double> entry : costCalculator.getRequirementCosts().entrySet()) { + private void saveOnRequirement(DecoratorContext context, Map<TechnicalDebtRequirement, Double> requirementCosts) { + for (Map.Entry<TechnicalDebtRequirement, Double> entry : requirementCosts.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) { + @VisibleForTesting + 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)) { @@ -101,6 +134,54 @@ public final class TechnicalDebtDecorator implements Decorator { } } + @VisibleForTesting + ListMultimap<TechnicalDebtRequirement, Issue> issuesByRequirement(List<Issue> issues) { + ListMultimap<TechnicalDebtRequirement, Issue> issuesByRequirement = ArrayListMultimap.create(); + for (Issue issue : issues) { + String repositoryKey = issue.ruleKey().repository(); + String key = issue.ruleKey().rule(); + TechnicalDebtRequirement requirement = technicalDebtModel.getRequirementByRule(repositoryKey, key); + if (requirement == null) { + LoggerFactory.getLogger(getClass()).debug("No technical debt requirement for: " + repositoryKey + "/" + key); + } else { + issuesByRequirement.put(requirement, issue); + } + } + return issuesByRequirement; + } + + private double computeTechnicalDebt(Metric metric, DecoratorContext context, TechnicalDebtRequirement requirement, Collection<Issue> issues) { + double value = 0.0; + if (issues != null) { + for (Issue issue : issues){ + value += converter.toDays(((DefaultIssue) issue).technicalDebt()); + } + } + + for (Measure measure : context.getChildrenMeasures(MeasuresFilters.characteristic(metric, requirement.toCharacteristic()))) { + if (measure.getCharacteristic() != null && measure.getCharacteristic().equals(requirement.toCharacteristic()) && measure.getValue() != null) { + value += measure.getValue(); + } + } + return value; + } + + private void propagateTechnicalDebtInParents(TechnicalDebtCharacteristic characteristic, double value, Map<TechnicalDebtCharacteristic, Double> characteristicCosts) { + if (characteristic != null) { + Double parentCost = characteristicCosts.get(characteristic); + if (parentCost == null) { + characteristicCosts.put(characteristic, value); + } else { + characteristicCosts.put(characteristic, value + parentCost); + } + propagateTechnicalDebtInParents(characteristic.getParent(), value, characteristicCosts); + } + } + + private boolean shouldSaveMeasure(DecoratorContext context) { + return context.getMeasure(CoreMetrics.TECHNICAL_DEBT) == null; + } + public static List<PropertyDefinition> definitions() { return ImmutableList.of( PropertyDefinition.builder(TechnicalDebtConverter.PROPERTY_HOURS_IN_DAY) 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 index 4ee7fac1346..6293d120229 100644 --- 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 @@ -17,80 +17,226 @@ * 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 org.apache.commons.lang.ObjectUtils; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.component.ResourcePerspectives; +import org.sonar.api.issue.Issuable; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.issue.internal.WorkDayDuration; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasuresFilter; +import org.sonar.api.measures.Metric; 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.resources.Resource; +import org.sonar.api.rule.RuleKey; import org.sonar.api.test.IsMeasure; -import org.sonar.core.technicaldebt.TechnicalDebtCalculator; +import org.sonar.core.technicaldebt.TechnicalDebtCharacteristic; +import org.sonar.core.technicaldebt.TechnicalDebtConverter; +import org.sonar.core.technicaldebt.TechnicalDebtModel; +import org.sonar.core.technicaldebt.TechnicalDebtRequirement; + +import java.util.List; +import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.*; +@RunWith(MockitoJUnitRunner.class) public class TechnicalDebtDecoratorTest { + @Mock + DecoratorContext context; + + @Mock + Resource resource; + + @Mock + TechnicalDebtConverter converter; + + @Mock + TechnicalDebtModel technicalDebtModel; + + @Mock + Issuable issuable; + + TechnicalDebtDecorator decorator; + + @Before + public void before() throws Exception { + ResourcePerspectives perspectives = mock(ResourcePerspectives.class); + when(perspectives.as(Issuable.class, resource)).thenReturn(issuable); + + decorator = new TechnicalDebtDecorator(perspectives, technicalDebtModel, converter); + } + @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); + public void not_save_if_measure_already_computed() { + when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure()); - TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalculator); decorator.decorate(resource, context); - verify(costCalculator, times(1)).compute(context); + verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.TECHNICAL_DEBT))); } @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); + public void group_issues_by_requirement() throws Exception { + TechnicalDebtRequirement requirement1 = mock(TechnicalDebtRequirement.class); + TechnicalDebtRequirement requirement2 = mock(TechnicalDebtRequirement.class); + + Issue issue1 = createIssue("rule1", "repo1"); + Issue issue2 = createIssue("rule1", "repo1"); + Issue issue3 = createIssue("rule2", "repo2"); + Issue issue4 = createIssue("unmatchable", "repo2"); + + List<Issue> issues = newArrayList(issue1, issue2, issue3, issue4); + + when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(requirement1); + when(technicalDebtModel.getRequirementByRule("repo2", "rule2")).thenReturn(requirement2); + + ListMultimap<TechnicalDebtRequirement, Issue> result = decorator.issuesByRequirement(issues); + + assertThat(result.keySet().size()).isEqualTo(2); + assertThat(result.get(requirement1)).containsExactly(issue1, issue2); + assertThat(result.get(requirement2)).containsExactly(issue3); + } + + @Test + public void add_technical_debt_from_one_issue_and_no_parent() throws Exception { + WorkDayDuration technicalDebt = mock(WorkDayDuration.class); + when(converter.toDays(technicalDebt)).thenReturn(1.0); + + Issue issue = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + Characteristic requirement = Characteristic.create(); + TechnicalDebtRequirement technicalDebtRequirement = mock(TechnicalDebtRequirement.class); + when(technicalDebtRequirement.toCharacteristic()).thenReturn(requirement); + when(technicalDebtRequirement.getParent()).thenReturn(null); + + when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(technicalDebtRequirement); + when(technicalDebtModel.getAllRequirements()).thenReturn(newArrayList(technicalDebtRequirement)); - TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalculator); decorator.decorate(resource, context); - verify(costCalculator, never()).compute(context); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, null, 1.0))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement, 1.0))); } @Test - public void save_cost_measures() { - TechnicalDebtCalculator costCalulator = mock(TechnicalDebtCalculator.class); - when(costCalulator.getTotal()).thenReturn(60.0); + public void add_technical_debt_from_one_issue_and_propagate_to_parents() throws Exception { + WorkDayDuration technicalDebt = mock(WorkDayDuration.class); + when(converter.toDays(technicalDebt)).thenReturn(1.0); - TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalulator); - DecoratorContext context = mock(DecoratorContext.class); + Issue issue = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + Characteristic requirement = Characteristic.createByName("requirement"); + Characteristic characteristic = Characteristic.createByName("characteristic"); + Characteristic parentCharacteristic = Characteristic.createByName("parentCharacteristic"); + + TechnicalDebtCharacteristic parentTechDebtCharacteristic = new TechnicalDebtCharacteristic(parentCharacteristic); + TechnicalDebtCharacteristic techDebtCharacteristic = new TechnicalDebtCharacteristic(characteristic, parentTechDebtCharacteristic); + TechnicalDebtRequirement technicalDebtRequirement = mock(TechnicalDebtRequirement.class); + when(technicalDebtRequirement.toCharacteristic()).thenReturn(requirement); + when(technicalDebtRequirement.getParent()).thenReturn(techDebtCharacteristic); + + when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(technicalDebtRequirement); + when(technicalDebtModel.getAllRequirements()).thenReturn(newArrayList(technicalDebtRequirement)); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, null, 1.0))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, parentCharacteristic, 1.0))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, characteristic, 1.0))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement, 1.0))); + } + + @Test + public void add_technical_debt_from_issues() throws Exception { + WorkDayDuration technicalDebt1 = mock(WorkDayDuration.class); + when(converter.toDays(technicalDebt1)).thenReturn(1.0); + + WorkDayDuration technicalDebt2 = mock(WorkDayDuration.class); + when(converter.toDays(technicalDebt2)).thenReturn(2.0); + + Issue issue1 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt1); + Issue issue2 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt1); + Issue issue3 = createIssue("rule2", "repo2").setTechnicalDebt(technicalDebt2); + Issue issue4 = createIssue("rule2", "repo2").setTechnicalDebt(technicalDebt2); + when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4)); + + Characteristic requirement1 = Characteristic.createByName("requirement1"); + Characteristic requirement2 = Characteristic.createByName("requirement2"); - decorator.saveCostMeasures(context); + TechnicalDebtRequirement technicalDebtRequirement1 = mock(TechnicalDebtRequirement.class); + when(technicalDebtRequirement1.toCharacteristic()).thenReturn(requirement1); + TechnicalDebtRequirement technicalDebtRequirement2 = mock(TechnicalDebtRequirement.class); + when(technicalDebtRequirement2.toCharacteristic()).thenReturn(requirement2); - verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.TECHNICAL_DEBT, 60.0))); + + when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(technicalDebtRequirement1); + when(technicalDebtModel.getRequirementByRule("repo2", "rule2")).thenReturn(technicalDebtRequirement2); + when(technicalDebtModel.getAllRequirements()).thenReturn(newArrayList(technicalDebtRequirement1, technicalDebtRequirement2)); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, null, 6.0))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement1, 2.0))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement2, 4.0))); } @Test - public void always_save_cost_for_positive_values() throws Exception { - TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null); + public void add_technical_debt_from_children_measures() throws Exception { + WorkDayDuration technicalDebt1 = mock(WorkDayDuration.class); + when(converter.toDays(technicalDebt1)).thenReturn(1.0); + + Issue issue1 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt1); + Issue issue2 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt1); + when(issuable.issues()).thenReturn(newArrayList(issue1, issue2)); + + Characteristic requirement = Characteristic.createByName("requirement1"); + TechnicalDebtRequirement technicalDebtRequirement = mock(TechnicalDebtRequirement.class); + when(technicalDebtRequirement.toCharacteristic()).thenReturn(requirement); + + when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(technicalDebtRequirement); + when(technicalDebtModel.getAllRequirements()).thenReturn(newArrayList(technicalDebtRequirement)); + Measure measure = new Measure().setCharacteristic(requirement).setValue(5.0); + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(newArrayList(measure)); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, null, 7.0))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement, 7.0))); + } + + @Test + public void always_save_technical_debt_for_positive_values() throws Exception { // for a project DecoratorContext context = mock(DecoratorContext.class); when(context.getResource()).thenReturn(new Project("foo")); @@ -105,14 +251,12 @@ public class TechnicalDebtDecoratorTest { } @Test - public void always_save_cost_for_project_if_top_characteristic() throws Exception { + public void always_save_technical_debt_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)); } @@ -121,33 +265,54 @@ public class TechnicalDebtDecoratorTest { * SQALE-147 */ @Test - public void never_save_cost_for_project_if_not_top_characteristic() throws Exception { + public void never_save_technical_debt_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 { + public void not_save_technical_debt_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_definitions() { - assertThat(TechnicalDebtDecorator.definitions()).hasSize(1); + assertThat(decorator.definitions()).hasSize(1); + } + + private DefaultIssue createIssue(String ruleKey, String repositoryKey) { + return new DefaultIssue().setRuleKey(RuleKey.of(repositoryKey, ruleKey)); } + class IsCharacteristicMeasure extends ArgumentMatcher<Measure> { + Metric metric = null; + Characteristic characteristic = null; + Double value = null; + + public IsCharacteristicMeasure(Metric metric, Characteristic characteristic, Double value) { + this.metric = metric; + this.characteristic = characteristic; + this.value = value; + } + + public boolean matches(Object o) { + if (!(o instanceof Measure)) { + return false; + } + Measure m = (Measure) o; + return ObjectUtils.equals(metric, m.getMetric()) && + ObjectUtils.equals(characteristic, m.getCharacteristic()) && + ObjectUtils.equals(value, m.getValue()); + } + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index c936b06376b..d346a147104 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -53,7 +53,6 @@ import org.sonar.core.issue.workflow.IssueWorkflow; import org.sonar.core.notification.DefaultNotificationManager; import org.sonar.core.technicaldebt.TechnicalDebtConverter; import org.sonar.core.technicaldebt.TechnicalDebtModel; -import org.sonar.core.technicaldebt.functions.*; import org.sonar.core.test.TestPlanBuilder; import org.sonar.core.test.TestPlanPerspectiveLoader; import org.sonar.core.test.TestableBuilder; @@ -150,11 +149,6 @@ public class ProjectScanContainer extends ComponentContainer { // technical debt TechnicalDebtModel.class, TechnicalDebtConverter.class, - ConstantFunction.class, - LinearFunction.class, - LinearWithOffsetFunction.class, - LinearWithThresholdFunction.class, - Functions.class, // Differential periods PeriodsDefinition.class, diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtCalculator.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtCalculator.java index fabda2a3b89..d6a6cdc6c93 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtCalculator.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtCalculator.java @@ -19,132 +19,41 @@ */ package org.sonar.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 com.google.common.base.Objects; import org.sonar.api.BatchExtension; -import org.sonar.api.batch.DecoratorContext; import org.sonar.api.issue.Issue; import org.sonar.api.issue.internal.WorkDayDuration; -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.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<TechnicalDebtCharacteristic, Double> characteristicCosts = Maps.newHashMap(); - private Map<TechnicalDebtRequirement, Double> requirementCosts = Maps.newHashMap(); - - private Functions functions; private final TechnicalDebtConverter converter; private TechnicalDebtModel technicalDebtModel; - public TechnicalDebtCalculator(TechnicalDebtModel technicalDebtModel, Functions functions, TechnicalDebtConverter converter) { + public TechnicalDebtCalculator(TechnicalDebtModel technicalDebtModel, TechnicalDebtConverter converter) { this.technicalDebtModel = technicalDebtModel; - this.functions = functions; this.converter = converter; } public WorkDayDuration calculTechnicalDebt(Issue issue) { TechnicalDebtRequirement requirement = technicalDebtModel.getRequirementByRule(issue.ruleKey().repository(), issue.ruleKey().rule()); if (requirement != null) { - return converter.fromMinutes(functions.costInMinutes(requirement, issue)); + return converter.fromMinutes(calculTechnicalDebt(requirement, issue)); } return null; } - public void compute(DecoratorContext context) { - reset(); - - // group violations by requirement - ListMultimap<TechnicalDebtRequirement, Violation> violationsByRequirement = groupViolations(context); - - // the total cost is: cost(violations) - for (TechnicalDebtRequirement requirement : technicalDebtModel.getAllRequirements()) { - List<Violation> violations = violationsByRequirement.get(requirement); - double allViolationsCost = computeTechnicalDebt(CoreMetrics.TECHNICAL_DEBT, context, requirement, violations); - updateRequirementCosts(requirement, allViolationsCost); - } - } - - public double getTotal() { - return total; - } - - public Map<TechnicalDebtCharacteristic, Double> getCharacteristicCosts() { - return characteristicCosts; - } - - public Map<TechnicalDebtRequirement, Double> getRequirementCosts() { - return requirementCosts; - } - - @VisibleForTesting - protected ListMultimap<TechnicalDebtRequirement, Violation> groupViolations(DecoratorContext context) { - ListMultimap<TechnicalDebtRequirement, Violation> violationsByRequirement = ArrayListMultimap.create(); - for (Violation violation : context.getViolations()) { - String repositoryKey = violation.getRule().getRepositoryKey(); - String key = violation.getRule().getKey(); - TechnicalDebtRequirement 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(TechnicalDebtRequirement requirement, double cost) { - requirementCosts.put(requirement, cost); - total += cost; - propagateCostInParents(requirement.getParent(), cost); - } - - private double computeTechnicalDebt(Metric metric, DecoratorContext context, TechnicalDebtRequirement requirement, Collection<Violation> violations) { - double cost = 0.0; - if (violations != null) { - cost = functions.costInHours(requirement, violations); - } + private long calculTechnicalDebt(TechnicalDebtRequirement requirement, Issue issue) { + long effortToFix = Objects.firstNonNull(issue.effortToFix(), 1l).longValue(); - 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; - } + WorkUnit factorUnit = requirement.getRemediationFactor(); + long factor = factorUnit != null ? converter.toMinutes(factorUnit) : 0l; - private void reset() { - total = 0.0; - characteristicCosts.clear(); - requirementCosts.clear(); - } + WorkUnit offsetUnit = requirement.getOffset(); + long offset = offsetUnit != null ? converter.toMinutes(offsetUnit) : 0l; - private void propagateCostInParents(TechnicalDebtCharacteristic characteristic, double cost) { - if (characteristic != null) { - Double parentCost = characteristicCosts.get(characteristic); - if (parentCost == null) { - characteristicCosts.put(characteristic, cost); - } else { - characteristicCosts.put(characteristic, cost + parentCost); - } - propagateCostInParents(characteristic.getParent(), cost); - } + return effortToFix * factor + offset; } - } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtConverter.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtConverter.java index 962483a6887..c2b204a0a2b 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtConverter.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtConverter.java @@ -35,21 +35,6 @@ public class TechnicalDebtConverter implements BatchComponent, ServerComponent { this.hoursInDay = settings.getInt(PROPERTY_HOURS_IN_DAY); } - public double toDays(WorkUnit factor) { - if (StringUtils.equals(WorkUnit.DAYS, factor.getUnit())) { - return factor.getValue(); - - } else if (StringUtils.equals(WorkUnit.HOURS, factor.getUnit())) { - return factor.getValue() / hoursInDay; - - } else if (StringUtils.equals(WorkUnit.MINUTES, factor.getUnit())) { - return factor.getValue() / (hoursInDay * 60.0); - - } else { - throw new IllegalArgumentException("Unknown remediation factor unit: " + factor.getUnit()); - } - } - public long toMinutes(WorkUnit factor) { if (StringUtils.equals(WorkUnit.DAYS, factor.getUnit())) { return Double.valueOf(factor.getValue() * hoursInDay * 60d).longValue(); diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java index f597f4bace3..df07400c8d6 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java @@ -21,9 +21,8 @@ package org.sonar.core.technicaldebt; import org.sonar.api.qualitymodel.Characteristic; import org.sonar.api.rules.Rule; -import org.sonar.core.technicaldebt.functions.LinearFunction; -import org.sonar.core.technicaldebt.functions.LinearWithOffsetFunction; -import org.sonar.core.technicaldebt.functions.LinearWithThresholdFunction; + +import javax.annotation.CheckForNull; public class TechnicalDebtRequirement { @@ -31,6 +30,10 @@ public class TechnicalDebtRequirement { public static final String PROPERTY_REMEDIATION_FACTOR = "remediationFactor"; public static final String PROPERTY_OFFSET = "offset"; + public static final String FUNCTION_LINEAR = "linear"; + public static final String FUNCTION_LINEAR_WITH_OFFSET = "linear_offset"; + public static final String FUNCTION_CONSTANT_PER_ISSUE = "constant_issue"; + private Rule rule; private TechnicalDebtCharacteristic parent; private org.sonar.api.qualitymodel.Characteristic characteristic; @@ -49,16 +52,17 @@ public class TechnicalDebtRequirement { } private void initFunction() { - function = characteristic.getPropertyTextValue(PROPERTY_REMEDIATION_FUNCTION, LinearFunction.FUNCTION_LINEAR); + function = characteristic.getPropertyTextValue(PROPERTY_REMEDIATION_FUNCTION, FUNCTION_LINEAR); } private void initFactor() { - factor = WorkUnit.create(characteristic.getPropertyValue(PROPERTY_REMEDIATION_FACTOR, null), - characteristic.getPropertyTextValue(PROPERTY_REMEDIATION_FACTOR, null)); + if (FUNCTION_LINEAR.equals(function) || FUNCTION_LINEAR_WITH_OFFSET.equals(function)) { + 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)) { + if (FUNCTION_LINEAR_WITH_OFFSET.equals(function) || FUNCTION_CONSTANT_PER_ISSUE.equals(function)) { offset = WorkUnit.create(characteristic.getPropertyValue(PROPERTY_OFFSET, null), characteristic.getPropertyTextValue(PROPERTY_OFFSET, null)); } @@ -76,10 +80,12 @@ public class TechnicalDebtRequirement { return function; } + @CheckForNull public WorkUnit getRemediationFactor() { return factor; } + @CheckForNull public WorkUnit getOffset() { return offset; } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java index 44ea20fbcd7..860956d5f32 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.ServerExtension; import org.sonar.api.qualitymodel.Characteristic; +import org.sonar.api.qualitymodel.CharacteristicProperty; import org.sonar.api.qualitymodel.Model; import org.sonar.api.rules.Rule; import org.sonar.api.utils.ValidationMessages; @@ -124,8 +125,11 @@ public class TechnicalDebtXMLImporter implements ServerExtension { fillRule(characteristic, ruleRepositoryKey, ruleKey, messages, technicalDebtRuleCache); if (StringUtils.isNotBlank(characteristic.getKey()) || characteristic.getRule() != null) { - addCharacteristicToModel(model, characteristic, children); - return characteristic; + Characteristic convertedCharacteristic = processDeprecatedFunctionsOnRequirement(characteristic, messages); + if (convertedCharacteristic != null) { + addCharacteristicToModel(model, characteristic, children); + return characteristic; + } } return null; } @@ -152,6 +156,27 @@ public class TechnicalDebtXMLImporter implements ServerExtension { } } + private Characteristic processDeprecatedFunctionsOnRequirement(Characteristic characteristic, ValidationMessages messages) { + CharacteristicProperty function = characteristic.getProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION); + if (function != null) { + if ("linear_threshold".equals(function.getTextValue())) { + function.setTextValue(TechnicalDebtRequirement.FUNCTION_LINEAR); + CharacteristicProperty offset = characteristic.getProperty(TechnicalDebtRequirement.PROPERTY_OFFSET); + offset.setValue(0d); + + String message = String.format("Linear with threshold function is no more used, the function of the requirement '%s' is replaced by linear.", characteristic.getRule()); + LOG.warn(message); + messages.addWarningText(message); + } else if ("constant_resource".equals(function.getTextValue())) { + String message = String.format("Constant / file function is no more used, requirement '%s' is ignore.", characteristic.getRule()); + LOG.warn(message); + messages.addWarningText(message); + return null; + } + } + return characteristic; + } + private void processProperty(Characteristic characteristic, SMInputCursor cursor, ValidationMessages messages) throws XMLStreamException { SMInputCursor c = cursor.childElementCursor(); String key = null; diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/AbstractFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/AbstractFunction.java deleted file mode 100644 index 86146993b1b..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/AbstractFunction.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import org.sonar.api.issue.Issue; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; - -import java.util.Collection; - -public abstract class AbstractFunction implements Function { - - private TechnicalDebtConverter converter; - - public AbstractFunction(TechnicalDebtConverter converter) { - this.converter = converter; - } - - protected TechnicalDebtConverter getConverter() { - return converter; - } - - public abstract String getKey(); - - public abstract double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations); - - public abstract long costInMinutes(TechnicalDebtRequirement requirement, Issue issue); - - protected long factorInMinutes(TechnicalDebtRequirement requirement) { - return getConverter().toMinutes(requirement.getRemediationFactor()); - } - -} diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/ConstantFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/ConstantFunction.java deleted file mode 100644 index 7f92e8604b7..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/ConstantFunction.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import org.sonar.api.issue.Issue; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; - -import java.util.Collection; - -public final class ConstantFunction extends AbstractFunction { - - public static final String FUNCTION_CONSTANT_RESOURCE = "constant_resource"; - - public ConstantFunction(TechnicalDebtConverter converter) { - super(converter); - } - - public String getKey() { - return FUNCTION_CONSTANT_RESOURCE; - } - - public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { - double cost = 0.0; - if (!violations.isEmpty()) { - cost = getConverter().toDays(requirement.getRemediationFactor()); - } - return cost; - } - - public long costInMinutes(TechnicalDebtRequirement requirement, Issue issue) { - return factorInMinutes(requirement); - } - -} diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Function.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Function.java deleted file mode 100644 index 65a37918499..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Function.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import org.sonar.api.BatchComponent; -import org.sonar.api.issue.Issue; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; - -import java.util.Collection; - -public interface Function extends BatchComponent { - - String getKey(); - - double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations); - - long costInMinutes(TechnicalDebtRequirement requirement, Issue issue); - -} diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Functions.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Functions.java deleted file mode 100644 index da43d06a508..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Functions.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import com.google.common.collect.Maps; -import org.sonar.api.BatchComponent; -import org.sonar.api.issue.Issue; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; - -import java.util.Collection; -import java.util.Map; - -public class Functions implements BatchComponent { - - 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(TechnicalDebtRequirement requirement) { - return getFunction(requirement.getRemediationFunction()); - } - - public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { - return getFunction(requirement).costInHours(requirement, violations); - } - - public long costInMinutes(TechnicalDebtRequirement requirement, Issue issue) { - return getFunction(requirement).costInMinutes(requirement, issue); - } - -} diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearFunction.java deleted file mode 100644 index d351372576b..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearFunction.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import org.sonar.api.issue.Issue; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; - -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(TechnicalDebtConverter converter) { - super(converter); - } - - public String getKey() { - return FUNCTION_LINEAR; - } - - public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { - double points = 0.0; - for (Violation violation : violations) { - Double effortToFix = violation.getCost(); - points += effortToFix != null ? effortToFix : DEFAULT_VIOLATION_COST; - } - return points * getConverter().toDays(requirement.getRemediationFactor()); - } - - public long costInMinutes(TechnicalDebtRequirement requirement, Issue issue) { - Double effortToFix = issue.effortToFix(); - double points = effortToFix != null ? effortToFix : DEFAULT_VIOLATION_COST; - return Double.valueOf(points * factorInMinutes(requirement)).longValue(); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunction.java deleted file mode 100644 index 890dc779ec3..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunction.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; - -import java.util.Collection; - -public final class LinearWithOffsetFunction extends LinearFunction { - - public static final String FUNCTION_LINEAR_WITH_OFFSET = "linear_offset"; - - public LinearWithOffsetFunction(TechnicalDebtConverter converter) { - super(converter); - } - - public String getKey() { - return FUNCTION_LINEAR_WITH_OFFSET; - } - - public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { - if (violations.isEmpty()) { - return 0.0; - } - double minimunCost = getConverter().toDays(requirement.getOffset()); - return minimunCost + super.costInHours(requirement, violations); - } - -} diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunction.java deleted file mode 100644 index f8fc198556e..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunction.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; - -import java.util.Collection; - -public final class LinearWithThresholdFunction extends LinearFunction { - - public static final String FUNCTION_LINEAR_WITH_THRESHOLD = "linear_threshold"; - - public LinearWithThresholdFunction(TechnicalDebtConverter converter) { - super(converter); - } - - public String getKey() { - return FUNCTION_LINEAR_WITH_THRESHOLD; - } - - public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { - if (violations.isEmpty()) { - return 0.0; - } - double thresholdCost = getConverter().toDays(requirement.getOffset()); - double violationsCost = super.costInHours(requirement, violations); - return violationsCost > thresholdCost ? violationsCost : thresholdCost; - } - -} diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/package-info.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/package-info.java deleted file mode 100644 index dfc07e1508a..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.core.technicaldebt.functions; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtCalculatorTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtCalculatorTest.java index ef3289b938f..2da03d4f46a 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtCalculatorTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtCalculatorTest.java @@ -19,192 +19,104 @@ */ package org.sonar.core.technicaldebt; -import com.google.common.collect.ListMultimap; -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.issue.Issue; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.MeasuresFilter; -import org.sonar.api.qualitymodel.Characteristic; import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.functions.Functions; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +@RunWith(MockitoJUnitRunner.class) 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); - TechnicalDebtConverter converter = mock(TechnicalDebtConverter.class); - remediationCostCalculator = new TechnicalDebtCalculator(technicalDebtModel, functions, converter); - } - - @Test - public void group_violations_by_requirement() throws Exception { - - TechnicalDebtRequirement requirement1 = mock(TechnicalDebtRequirement.class); - TechnicalDebtRequirement requirement2 = mock(TechnicalDebtRequirement.class); + @Mock + TechnicalDebtModel technicalDebtModel; - Violation violation1 = buildViolation("rule1", "repo1", NOW); - Violation violation2 = buildViolation("rule1", "repo1", NOW); - Violation violation3 = buildViolation("rule2", "repo2", NOW); - Violation violation4 = buildViolation("unmatchable", "repo2", NOW); + @Mock + TechnicalDebtConverter converter; - List<Violation> violations = newArrayList(violation1, violation2, violation3, violation4); + WorkUnit tenMinutes = WorkUnit.create(10d, WorkUnit.MINUTES); + WorkUnit fiveMinutes = WorkUnit.create(5d, WorkUnit.MINUTES); - when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(requirement1); - when(technicalDebtModel.getRequirementByRule("repo2", "rule2")).thenReturn(requirement2); + TechnicalDebtCalculator remediationCostCalculator; - DecoratorContext context = mock(DecoratorContext.class); - when(context.getViolations()).thenReturn(violations); - - ListMultimap<TechnicalDebtRequirement, Violation> groupedViolations = remediationCostCalculator.groupViolations(context); + @Before + public void before() { + when(converter.toMinutes(tenMinutes)).thenReturn(10l); + when(converter.toMinutes(fiveMinutes)).thenReturn(5l); - assertThat(groupedViolations.keySet().size()).isEqualTo(2); - assertThat(groupedViolations.get(requirement1)).containsExactly(violation1, violation2); - assertThat(groupedViolations.get(requirement2)).containsExactly(violation3); + remediationCostCalculator = new TechnicalDebtCalculator(technicalDebtModel, converter); } @Test - public void add_cost_with_no_parent() throws Exception { - - double requirementCost = 1.0; + public void calcul_technical_debt() throws Exception { + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")); TechnicalDebtRequirement requirement = mock(TechnicalDebtRequirement.class); - when(requirement.getParent()).thenReturn(null); + Mockito.when(requirement.getRemediationFactor()).thenReturn(tenMinutes); + Mockito.when(requirement.getOffset()).thenReturn(fiveMinutes); + when(technicalDebtModel.getRequirementByRule("squid", "AvoidCycle")).thenReturn(requirement); - remediationCostCalculator.updateRequirementCosts(requirement, requirementCost); + remediationCostCalculator.calculTechnicalDebt(issue); - assertThat(remediationCostCalculator.getRequirementCosts().get(requirement)).isEqualTo(requirementCost); - assertThat(remediationCostCalculator.getTotal()).isEqualTo(requirementCost); + verify(converter).fromMinutes(10l + 5l); } @Test - public void add_cost_and_propagate_to_parents() throws Exception { - - double requirementCost = 1.0; - - TechnicalDebtCharacteristic parentCharacteristic = new TechnicalDebtCharacteristic(Characteristic.create()); - - TechnicalDebtCharacteristic characteristic = new TechnicalDebtCharacteristic(Characteristic.create(), parentCharacteristic); + public void calcul_technical_debt_with_effort_to_fix() throws Exception { + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")).setEffortToFix(2d); TechnicalDebtRequirement requirement = mock(TechnicalDebtRequirement.class); - when(requirement.getParent()).thenReturn(characteristic); + Mockito.when(requirement.getRemediationFactor()).thenReturn(tenMinutes); + Mockito.when(requirement.getOffset()).thenReturn(fiveMinutes); + when(technicalDebtModel.getRequirementByRule("squid", "AvoidCycle")).thenReturn(requirement); - remediationCostCalculator.updateRequirementCosts(requirement, requirementCost); + remediationCostCalculator.calculTechnicalDebt(issue); - assertThat(remediationCostCalculator.getRequirementCosts().get(requirement)).isEqualTo(requirementCost); - assertThat(remediationCostCalculator.getCharacteristicCosts().get(characteristic)).isEqualTo(requirementCost); - assertThat(remediationCostCalculator.getCharacteristicCosts().get(parentCharacteristic)).isEqualTo(requirementCost); + verify(converter).fromMinutes(10l * 2 + 5l); } @Test - public void compute_totals_costs() throws Exception { - - TechnicalDebtRequirement requirement1 = mock(TechnicalDebtRequirement.class); - TechnicalDebtRequirement requirement2 = mock(TechnicalDebtRequirement.class); + public void calcul_technical_debt_with_no_offset() throws Exception { + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")).setEffortToFix(2d); - 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 = newArrayList(violation1, violation2, violation3, violation4); - - when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(requirement1); - when(technicalDebtModel.getRequirementByRule("repo2", "rule2")).thenReturn(requirement2); - when(technicalDebtModel.getAllRequirements()).thenReturn(newArrayList(requirement1, requirement2)); - - when(functions.costInHours(any(TechnicalDebtRequirement.class), any(Collection.class))).thenReturn(1.0); - - DecoratorContext context = mock(DecoratorContext.class); - when(context.getViolations()).thenReturn(violations); - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.EMPTY_LIST); + TechnicalDebtRequirement requirement = mock(TechnicalDebtRequirement.class); + Mockito.when(requirement.getRemediationFactor()).thenReturn(tenMinutes); + Mockito.when(requirement.getOffset()).thenReturn(null); + when(technicalDebtModel.getRequirementByRule("squid", "AvoidCycle")).thenReturn(requirement); - remediationCostCalculator.compute(context); + remediationCostCalculator.calculTechnicalDebt(issue); - assertThat(remediationCostCalculator.getTotal()).isEqualTo(2.0); - assertThat(remediationCostCalculator.getRequirementCosts().get(requirement1)).isEqualTo(1.0); - assertThat(remediationCostCalculator.getRequirementCosts().get(requirement2)).isEqualTo(1.0); + verify(converter).fromMinutes(10l * 2 + 0l); } @Test - public void compute_totals_costs_from_children() throws Exception { - TechnicalDebtCharacteristic parentCharacteristic = new TechnicalDebtCharacteristic(Characteristic.create()); - TechnicalDebtRequirement requirement1 = new TechnicalDebtRequirement(Characteristic.create(), parentCharacteristic);; - - 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 = newArrayList(violation1, violation2, violation3, violation4); - - when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(requirement1); - when(technicalDebtModel.getAllRequirements()).thenReturn(newArrayList(requirement1)); + public void calcul_technical_debt_with_no_factor() throws Exception { + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")).setEffortToFix(2d); - when(functions.costInHours(any(TechnicalDebtRequirement.class), any(Collection.class))).thenReturn(1.0); - - DecoratorContext context = mock(DecoratorContext.class); - when(context.getViolations()).thenReturn(violations); - - Measure measure = new Measure().setCharacteristic(requirement1.toCharacteristic()).setValue(5.0); - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(newArrayList(measure)); - - remediationCostCalculator.compute(context); - - assertThat(remediationCostCalculator.getTotal()).isEqualTo(6.0); - assertThat(remediationCostCalculator.getRequirementCosts().get(requirement1)).isEqualTo(6.0); - } - - @Test - public void technical_debt_from_one_issue() throws Exception { - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")); TechnicalDebtRequirement requirement = mock(TechnicalDebtRequirement.class); + Mockito.when(requirement.getRemediationFactor()).thenReturn(null); + Mockito.when(requirement.getOffset()).thenReturn(fiveMinutes); when(technicalDebtModel.getRequirementByRule("squid", "AvoidCycle")).thenReturn(requirement); - when(functions.costInMinutes(eq(requirement), eq(issue))).thenReturn(10L); remediationCostCalculator.calculTechnicalDebt(issue); - verify(functions).costInMinutes(eq(requirement), eq(issue)); + + verify(converter).fromMinutes(0l * 2 + 5l); } @Test - public void no_technical_debt_from_one_issue_if_requirement_not_found() throws Exception { + public void no_technical_debt_if_requirement_not_found() throws Exception { DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")); when(technicalDebtModel.getRequirementByRule("squid", "AvoidCycle")).thenReturn(null); assertThat(remediationCostCalculator.calculTechnicalDebt(issue)).isNull(); - verify(functions, never()).costInMinutes(any(TechnicalDebtRequirement.class), any(Issue.class)); + verify(converter, never()).fromMinutes(anyLong()); } - 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/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtConverterTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtConverterTest.java index d72511e1a29..a2c04fa960c 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtConverterTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtConverterTest.java @@ -40,13 +40,6 @@ public class TechnicalDebtConverterTest { } @Test - public void convert_work_unit_to_days() { - assertThat(converter.toDays(WorkUnit.create(6.0, WorkUnit.DAYS))).isEqualTo(6.0); - assertThat(converter.toDays(WorkUnit.create(6.0, WorkUnit.HOURS))).isEqualTo(6.0 / 12.0); - assertThat(converter.toDays(WorkUnit.create(60.0, WorkUnit.MINUTES))).isEqualTo(1.0 / 12.0); - } - - @Test public void concert_work_unit_to_minutes() { assertThat(converter.toMinutes(WorkUnit.create(2.0, WorkUnit.DAYS))).isEqualTo(2 * 12 * 60L); assertThat(converter.toMinutes(WorkUnit.create(6.0, WorkUnit.HOURS))).isEqualTo(6 * 60L); diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRequirementTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRequirementTest.java index 1cf6f740e25..f56e86072f8 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRequirementTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRequirementTest.java @@ -21,95 +21,90 @@ package org.sonar.core.technicaldebt; import org.junit.Test; import org.sonar.api.qualitymodel.Characteristic; -import org.sonar.core.technicaldebt.functions.ConstantFunction; -import org.sonar.core.technicaldebt.functions.LinearFunction; -import org.sonar.core.technicaldebt.functions.LinearWithOffsetFunction; -import org.sonar.core.technicaldebt.functions.LinearWithThresholdFunction; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; +import static org.fest.assertions.Assertions.assertThat; public class TechnicalDebtRequirementTest { @Test - public void defaultFactor() { + public void default_factor() { Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(persistedRequirement, null); - assertThat(requirement.getRemediationFactor().getValue(), is(WorkUnit.DEFAULT_VALUE)); - assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DEFAULT_UNIT)); + assertThat(requirement.getRemediationFactor().getValue()).isEqualTo(WorkUnit.DEFAULT_VALUE); + assertThat(requirement.getRemediationFactor().getUnit()).isEqualTo(WorkUnit.DEFAULT_UNIT); } @Test - public void testOverriddenFactor() { + public void overridde_factor() { Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FACTOR, 3.14); + TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(persistedRequirement, null); - assertThat(requirement.getRemediationFactor().getValue(), is(3.14)); - assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DAYS)); + assertThat(requirement.getRemediationFactor().getValue()).isEqualTo(3.14); + assertThat(requirement.getRemediationFactor().getUnit()).isEqualTo(WorkUnit.DAYS); } @Test - public void defaultFunctionIsLinear() { + public void default_function_is_linear() { Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); + TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(persistedRequirement, null); - assertThat(requirement.getRemediationFunction(), is(LinearFunction.FUNCTION_LINEAR)); - assertThat(requirement.getOffset(), is(nullValue())); + assertThat(requirement.getRemediationFunction()).isEqualTo(TechnicalDebtRequirement.FUNCTION_LINEAR); + assertThat(requirement.getOffset()).isNull(); } @Test - public void testOverriddenFunction() { + public void linear() { Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); - persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, ConstantFunction.FUNCTION_CONSTANT_RESOURCE); + persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, TechnicalDebtRequirement.FUNCTION_LINEAR); + persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FACTOR, 3.14); + TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(persistedRequirement, null); - assertThat(requirement.getRemediationFunction(), is(ConstantFunction.FUNCTION_CONSTANT_RESOURCE)); + assertThat(requirement.getRemediationFunction()).isEqualTo(TechnicalDebtRequirement.FUNCTION_LINEAR); + assertThat(requirement.getRemediationFactor().getValue()).isEqualTo(3.14); + assertThat(requirement.getRemediationFactor().getUnit()).isEqualTo(WorkUnit.DAYS); + assertThat(requirement.getOffset()).isNull(); } @Test - public void testDefaultLinearWithOffset() { + public void default_linear_with_offset() { Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); - persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET); + persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, TechnicalDebtRequirement.FUNCTION_LINEAR_WITH_OFFSET); + TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(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)); + assertThat(requirement.getRemediationFunction()).isEqualTo(TechnicalDebtRequirement.FUNCTION_LINEAR_WITH_OFFSET); + assertThat(requirement.getRemediationFactor().getValue()).isEqualTo(WorkUnit.DEFAULT_VALUE); + assertThat(requirement.getRemediationFactor().getUnit()).isEqualTo(WorkUnit.DEFAULT_UNIT); + assertThat(requirement.getOffset().getValue()).isEqualTo(WorkUnit.DEFAULT_VALUE); + assertThat(requirement.getOffset().getUnit()).isEqualTo(WorkUnit.DEFAULT_UNIT); } @Test - public void testCustomizedLinearWithOffset() { + public void linear_with_offset() { Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); - persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET); + persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, TechnicalDebtRequirement.FUNCTION_LINEAR_WITH_OFFSET); persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_OFFSET, 5.0); persistedRequirement.addProperty(persistedRequirement.getProperty(TechnicalDebtRequirement.PROPERTY_OFFSET).setTextValue("h")); - TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(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(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD); TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(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)); + assertThat(requirement.getRemediationFunction()).isEqualTo(TechnicalDebtRequirement.FUNCTION_LINEAR_WITH_OFFSET); + assertThat(requirement.getOffset().getValue()).isEqualTo(5.0); + assertThat(requirement.getOffset().getUnit()).isEqualTo(WorkUnit.HOURS); } @Test - public void testCustomizedLinearWithThreshold() { + public void constant_per_issue() { Characteristic persistedRequirement = Characteristic.createByName("Efficiency"); - persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD); + persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_REMEDIATION_FUNCTION, TechnicalDebtRequirement.FUNCTION_CONSTANT_PER_ISSUE); persistedRequirement.setProperty(TechnicalDebtRequirement.PROPERTY_OFFSET, 5.0); persistedRequirement.addProperty(persistedRequirement.getProperty(TechnicalDebtRequirement.PROPERTY_OFFSET).setTextValue("h")); + TechnicalDebtRequirement requirement = new TechnicalDebtRequirement(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)); + assertThat(requirement.getRemediationFunction()).isEqualTo(TechnicalDebtRequirement.FUNCTION_CONSTANT_PER_ISSUE); + assertThat(requirement.getRemediationFactor()).isNull(); + assertThat(requirement.getOffset().getValue()).isEqualTo(5.0); + assertThat(requirement.getOffset().getUnit()).isEqualTo(WorkUnit.HOURS); } + } diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java index cac08a416cb..6e9c94e246a 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java @@ -22,7 +22,6 @@ package org.sonar.core.technicaldebt; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.io.Resources; -import org.fest.assertions.Assertions; import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; @@ -35,13 +34,15 @@ import org.sonar.api.utils.ValidationMessages; import java.io.IOException; +import static org.fest.assertions.Assertions.assertThat; + public class TechnicalDebtXMLImporterTest { @Test - public void shouldImportXML() { + public void import_xml_with_linear_function() { TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache(); - String xml = getFileContent("shouldImportXML.xml"); + String xml = getFileContent("shouldImportXML_with_linear.xml"); ValidationMessages messages = ValidationMessages.create(); Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache); @@ -50,6 +51,48 @@ public class TechnicalDebtXMLImporterTest { } @Test + public void import_xml_with_linear_with_offset() { + TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache(); + + String xml = getFileContent("shouldImportXML_with_linear_with_offset.xml"); + + ValidationMessages messages = ValidationMessages.create(); + Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache); + + checkXmlCorrectlyImported(sqale, 1.0, messages); + } + + @Test + public void convert_deprecated_linear_with_threshold_function_by_linear_function() { + TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache(); + + String xml = getFileContent("shouldImportXML_with_deprecated_linear_with_threshold.xml"); + + ValidationMessages messages = ValidationMessages.create(); + Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache); + + checkXmlCorrectlyImported(sqale, 0.0, messages); + assertThat(messages.getWarnings()).hasSize(1); + } + + @Test + public void ignore_deprecated_constant_per_file_function() { + TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache(); + + String xml = getFileContent("shouldImportXML_with_deprecated_constant_per_file.xml"); + + ValidationMessages messages = ValidationMessages.create(); + Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache); + + assertThat(messages.getWarnings()).hasSize(1); + + // characteristics + assertThat(sqale.getRootCharacteristics()).hasSize(1); + Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY"); + assertThat(efficiency.getChildren()).isEmpty(); + } + + @Test public void shouldBadlyFormattedImportXML() { TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache(); String xml = getFileContent("shouldImportXML_badly-formatted.xml"); @@ -68,13 +111,13 @@ public class TechnicalDebtXMLImporterTest { Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache); - Assertions.assertThat(messages.getWarnings()).hasSize(1); + assertThat(messages.getWarnings()).hasSize(1); // characteristics - Assertions.assertThat(sqale.getRootCharacteristics()).hasSize(1); + assertThat(sqale.getRootCharacteristics()).hasSize(1); Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY"); - Assertions.assertThat(efficiency.getChildren()).isEmpty(); - Assertions.assertThat(messages.getWarnings().get(0)).contains("findbugs"); + assertThat(efficiency.getChildren()).isEmpty(); + assertThat(messages.getWarnings().get(0)).contains("findbugs"); } @Test @@ -87,8 +130,8 @@ public class TechnicalDebtXMLImporterTest { new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache); - Assertions.assertThat(messages.getErrors()).hasSize(1); - Assertions.assertThat(messages.getErrors().get(0)).isEqualTo("Cannot import value 'abc' for field factor - Expected a numeric value instead"); + assertThat(messages.getErrors()).hasSize(1); + assertThat(messages.getErrors().get(0)).isEqualTo("Cannot import value 'abc' for field factor - Expected a numeric value instead"); } private TechnicalDebtRuleCache mockRuleCache() { @@ -98,23 +141,28 @@ public class TechnicalDebtXMLImporterTest { } private void checkXmlCorrectlyImported(Model sqale, ValidationMessages messages) { + checkXmlCorrectlyImported(sqale, null, messages); + } + + private void checkXmlCorrectlyImported(Model sqale, Double offset, ValidationMessages messages) { - Assertions.assertThat(messages.getErrors()).isEmpty(); - Assertions.assertThat(sqale.getName()).isEqualTo(TechnicalDebtModel.MODEL_NAME); + assertThat(messages.getErrors()).isEmpty(); + assertThat(sqale.getName()).isEqualTo(TechnicalDebtModel.MODEL_NAME); // characteristics - Assertions.assertThat(sqale.getRootCharacteristics()).hasSize(2); - Assertions.assertThat(sqale.getCharacteristicByKey("USABILITY").getDescription()).isEqualTo("Estimate usability"); + assertThat(sqale.getRootCharacteristics()).hasSize(2); + assertThat(sqale.getCharacteristicByKey("USABILITY").getDescription()).isEqualTo("Estimate usability"); Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY"); - Assertions.assertThat(efficiency.getName()).isEqualTo("Efficiency"); + assertThat(efficiency.getName()).isEqualTo("Efficiency"); // sub-characteristics - Assertions.assertThat(efficiency.getChildren()).hasSize(1); + assertThat(efficiency.getChildren()).hasSize(1); Characteristic requirement = efficiency.getChildren().get(0); - Assertions.assertThat(requirement.getRule().getRepositoryKey()).isEqualTo("checkstyle"); - Assertions.assertThat(requirement.getRule().getKey()).isEqualTo("Regexp"); - Assertions.assertThat(requirement.getPropertyTextValue("function", null)).isEqualTo("linear"); - Assertions.assertThat(requirement.getPropertyValue("factor", null)).isEqualTo(3.2); + assertThat(requirement.getRule().getRepositoryKey()).isEqualTo("checkstyle"); + assertThat(requirement.getRule().getKey()).isEqualTo("Regexp"); + assertThat(requirement.getPropertyTextValue("remediationFunction", null)).isEqualTo("linear"); + assertThat(requirement.getPropertyValue("remediationFactor", null)).isEqualTo(3.2); + assertThat(requirement.getPropertyValue("offset", null)).isEqualTo(offset); } private String getFileContent(String file) { diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/ConstantFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/ConstantFunctionTest.java deleted file mode 100644 index 1a377123164..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/ConstantFunctionTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import com.google.common.collect.Lists; -import org.hamcrest.core.Is; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.sonar.api.config.Settings; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; -import org.sonar.core.technicaldebt.WorkUnit; - -import java.util.Collection; -import java.util.Collections; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.when; - -public class ConstantFunctionTest { - - private TechnicalDebtRequirement requirement; - private Function function; - - @Before - public void before() { - function = new ConstantFunction(new TechnicalDebtConverter(new Settings())); - requirement = Mockito.mock(TechnicalDebtRequirement.class); - Mockito.when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14)); - } - - @Test - public void zero_if_no_violations() { - Assert.assertThat(function.costInHours(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); - } - - @Test - public void count_as_if_single_violation() { - Collection<Violation> violations = Lists.newArrayList(); - - Rule rule = Rule.create("checkstyle", "foo", "Foo"); - violations.add(new Violation(rule)); - Assert.assertThat(function.costInHours(requirement, violations), Is.is(3.14)); - - violations.add(new Violation(rule)); - Assert.assertThat(function.costInHours(requirement, violations), Is.is(3.14)); - } - - @Test - public void cost_in_minutes() { - when(requirement.getRemediationFactor()).thenReturn(WorkUnit.create(10d, WorkUnit.MINUTES)); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE"); - assertThat(function.costInMinutes(requirement, issue)).isEqualTo(10L); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/FunctionsTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/FunctionsTest.java deleted file mode 100644 index e829e8e15e8..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/FunctionsTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import org.junit.Test; - -import static org.fest.assertions.Assertions.assertThat; - -public class FunctionsTest { - - @Test - public void register_functions() { - 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/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearFunctionTest.java deleted file mode 100644 index 1b2193298ac..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearFunctionTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import com.google.common.collect.Lists; -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.config.Settings; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; -import org.sonar.core.technicaldebt.WorkUnit; - -import java.util.Collection; -import java.util.Collections; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class LinearFunctionTest { - - private TechnicalDebtRequirement requirement; - private Function function; - - @Before - public void before() { - Settings settings = new Settings(); - settings.setProperty(TechnicalDebtConverter.PROPERTY_HOURS_IN_DAY, 8); - function = new LinearFunction(new TechnicalDebtConverter(settings)); - - requirement = mock(TechnicalDebtRequirement.class); - when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14)); - } - - @Test - public void zero_if_no_violations() { - assertThat(function.costInHours(requirement, Collections.<Violation>emptyList())).isEqualTo(0.0); - } - - @Test - public void count_every_violation() { - Collection<Violation> violations = Lists.newArrayList(); - - Rule rule = Rule.create("checkstyle", "foo", "Foo"); - violations.add(new Violation(rule)); - assertThat(function.costInHours(requirement, violations)).isEqualTo(3.14); - - violations.add(new Violation(rule)); - assertThat(function.costInHours(requirement, violations)).isEqualTo(3.14 * 2); - } - - @Test - public void use_points_when_available() { - 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.costInHours(requirement, violations)).isEqualTo(3.14 * (20.5 + 3.8 + 1)); - } - - @Test - public void cost_in_minutes() { - when(requirement.getRemediationFactor()).thenReturn(WorkUnit.create(10d, WorkUnit.MINUTES)); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setEffortToFix(2.0); - assertThat(function.costInMinutes(requirement, issue)).isEqualTo(20L); - } - - @Test - public void cost_in_minutes_use_default_cost_when_no_effort_to_fix_on_issue() { - when(requirement.getRemediationFactor()).thenReturn(WorkUnit.create(10d, WorkUnit.MINUTES)); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setEffortToFix(null); - assertThat(function.costInMinutes(requirement, issue)).isEqualTo(10L); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java deleted file mode 100644 index 1a0b6c03521..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import com.google.common.collect.Lists; -import org.hamcrest.core.Is; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.sonar.api.config.Settings; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; -import org.sonar.core.technicaldebt.WorkUnit; - -import java.util.Collection; -import java.util.Collections; - -public class LinearWithOffsetFunctionTest { - - private TechnicalDebtRequirement requirement; - private Function function = new LinearWithOffsetFunction(new TechnicalDebtConverter(new Settings())); - - @Before - public void before() { - requirement = Mockito.mock(TechnicalDebtRequirement.class); - Mockito.when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14)); - Mockito.when(requirement.getOffset()).thenReturn(WorkUnit.createInDays(2.12)); - } - - @Test - public void zeroIfNoViolations() { - Assert.assertThat(function.costInHours(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); - } - - @Test - public void countEveryViolation() { - Collection<Violation> violations = Lists.newArrayList(); - - Rule rule = Rule.create("checkstyle", "foo", "Foo"); - violations.add(new Violation(rule)); - Assert.assertThat(function.costInHours(requirement, violations), Is.is(2.12 + 3.14)); - - violations.add(new Violation(rule)); - Assert.assertThat(function.costInHours(requirement, violations), Is.is(2.12 + 3.14 * 2)); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java deleted file mode 100644 index 86c71479c05..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.core.technicaldebt.functions; - -import com.google.common.collect.Lists; -import org.hamcrest.core.Is; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.sonar.api.config.Settings; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtConverter; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; -import org.sonar.core.technicaldebt.WorkUnit; - -import java.util.Collection; -import java.util.Collections; - -public class LinearWithThresholdFunctionTest { - - private TechnicalDebtRequirement requirement; - private Function function = new LinearWithThresholdFunction(new TechnicalDebtConverter(new Settings())); - - @Before - public void before() { - requirement = Mockito.mock(TechnicalDebtRequirement.class); - Mockito.when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(2.0)); - Mockito.when(requirement.getOffset()).thenReturn(WorkUnit.createInDays(5.0)); - } - - @Test - public void zeroIfNoViolations() { - Assert.assertThat(function.costInHours(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); - } - - @Test - public void countEveryViolationAndCheckThreshold() { - Collection<Violation> violations = Lists.newArrayList(); - - Rule rule = Rule.create("checkstyle", "foo", "Foo"); - violations.add(new Violation(rule)); - Assert.assertThat(function.costInHours(requirement, violations), Is.is(5.0)); - - violations.add(new Violation(rule)); - Assert.assertThat(function.costInHours(requirement, violations), Is.is(5.0)); - - violations.add(new Violation(rule)); - Assert.assertThat(function.costInHours(requirement, violations), Is.is(6.0)); - } -} diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_badly-formatted.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_badly-formatted.xml index 373959f1a72..e55fa2d003b 100644 --- a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_badly-formatted.xml +++ b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_badly-formatted.xml @@ -19,13 +19,13 @@ <rule-key>Regexp </rule-key> <prop> - <key>factor + <key>remediationFactor </key> <val>3.2 </val> </prop> <prop> - <key>function + <key>remediationFunction </key> <txt>linear </txt> @@ -33,4 +33,4 @@ </chc> </chc> -</sqale>
\ No newline at end of file +</sqale> diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_deprecated_constant_per_file.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_deprecated_constant_per_file.xml new file mode 100644 index 00000000000..2e13e2bd2a4 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_deprecated_constant_per_file.xml @@ -0,0 +1,20 @@ +<sqale> + <chc> + <key>EFFICIENCY</key> + <name>Efficiency</name> + <!-- Should be ignored --> + <chc> + <rule-repo>checkstyle</rule-repo> + <rule-key>Regexp</rule-key> + <prop> + <key>remediationFactor</key> + <val>3.2</val> + </prop> + <prop> + <key>remediationFunction</key> + <txt>constant_resource</txt> + </prop> + </chc> + </chc> + +</sqale> diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_deprecated_linear_with_threshold.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_deprecated_linear_with_threshold.xml new file mode 100644 index 00000000000..f46aea6e872 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_deprecated_linear_with_threshold.xml @@ -0,0 +1,32 @@ +<sqale> + <chc> + <key>USABILITY</key> + <name>Usability</name> + <desc>Estimate usability</desc> + </chc> + <chc> + <key>EFFICIENCY</key> + <name>Efficiency</name> + <chc> + <rule-repo>checkstyle</rule-repo> + <rule-key>Regexp</rule-key> + <prop> + <key>remediationFunction</key> + <!-- Should be replaced by linear --> + <txt>linear_threshold</txt> + </prop> + <prop> + <key>remediationFactor</key> + <val>3.2</val> + <txt>h</txt> + </prop> + <!-- Should be ignored --> + <prop> + <key>offset</key> + <val>1.0</val> + <txt>h</txt> + </prop> + </chc> + </chc> + +</sqale> diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_linear.xml index e4051894cb0..480df8f2d94 100644 --- a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML.xml +++ b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_linear.xml @@ -12,14 +12,14 @@ <rule-repo>checkstyle</rule-repo> <rule-key>Regexp</rule-key> <prop> - <key>factor</key> + <key>remediationFactor</key> <val>3.2</val> </prop> <prop> - <key>function</key> + <key>remediationFunction</key> <txt>linear</txt> </prop> </chc> </chc> -</sqale>
\ No newline at end of file +</sqale> diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_linear_with_offset.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_linear_with_offset.xml new file mode 100644 index 00000000000..768b43b8242 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_with_linear_with_offset.xml @@ -0,0 +1,30 @@ +<sqale> + <chc> + <key>USABILITY</key> + <name>Usability</name> + <desc>Estimate usability</desc> + </chc> + <chc> + <key>EFFICIENCY</key> + <name>Efficiency</name> + + <chc> + <rule-repo>checkstyle</rule-repo> + <rule-key>Regexp</rule-key> + <prop> + <key>remediationFactor</key> + <val>3.2</val> + </prop> + <prop> + <key>remediationFunction</key> + <txt>linear</txt> + </prop> + <prop> + <key>offset</key> + <val>1.0</val> + <txt>h</txt> + </prop> + </chc> + </chc> + +</sqale> |