]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9534 compute new_debt measure only from new issues
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 19 Sep 2017 11:27:59 +0000 (13:27 +0200)
committerEric Hartmann <hartmann.eric@gmail.Com>
Mon, 2 Oct 2017 11:03:35 +0000 (13:03 +0200)
and not also from changelog

server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortAggregator.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortAggregatorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculatorTest.java [deleted file]

index 1e9b0f22ddd64e656ccd11ae6e0b85d69018e46b..2538dd69cbe1a0c0a60eccd007b22e09d0db37dc 100644 (file)
@@ -70,7 +70,6 @@ import org.sonar.server.computation.task.projectanalysis.issue.LoadComponentUuid
 import org.sonar.server.computation.task.projectanalysis.issue.MergeBranchTrackerExecution;
 import org.sonar.server.computation.task.projectanalysis.issue.MovedIssueVisitor;
 import org.sonar.server.computation.task.projectanalysis.issue.NewEffortAggregator;
-import org.sonar.server.computation.task.projectanalysis.issue.NewEffortCalculator;
 import org.sonar.server.computation.task.projectanalysis.issue.RemoveProcessedComponentsVisitor;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleRepositoryImpl;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleTagsCopier;
@@ -217,7 +216,6 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
       IssueCreationDateCalculator.class,
       DebtCalculator.class,
       EffortAggregator.class,
-      NewEffortCalculator.class,
       NewEffortAggregator.class,
       IssueAssigner.class,
       IssueCounter.class,
index d0ede5918186cd52c5c730126a013e5924c9f539..7b5522240badaf24f8dc16aff57b567572d8de60 100644 (file)
 package org.sonar.server.computation.task.projectanalysis.issue;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.core.issue.DefaultIssue;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 import org.sonar.server.computation.task.projectanalysis.measure.Measure;
 import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
@@ -41,6 +35,7 @@ import org.sonar.server.computation.task.projectanalysis.period.PeriodHolder;
 import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY;
 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT_KEY;
 import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
+import static org.sonar.api.utils.DateUtils.truncateToSeconds;
 
 /**
  * Compute new effort related measures :
@@ -50,24 +45,18 @@ import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
  */
 public class NewEffortAggregator extends IssueVisitor {
 
-  private final NewEffortCalculator calculator;
   private final PeriodHolder periodHolder;
-  private final DbClient dbClient;
   private final MeasureRepository measureRepository;
 
   private final Metric newMaintainabilityEffortMetric;
   private final Metric newReliabilityEffortMetric;
   private final Metric newSecurityEffortMetric;
 
-  private ListMultimap<String, IssueChangeDto> changesByIssueUuid = ArrayListMultimap.create();
   private Map<Integer, NewEffortCounter> counterByComponentRef = new HashMap<>();
   private NewEffortCounter counter = null;
 
-  public NewEffortAggregator(NewEffortCalculator calculator, PeriodHolder periodHolder, DbClient dbClient,
-    MetricRepository metricRepository, MeasureRepository measureRepository) {
-    this.calculator = calculator;
+  public NewEffortAggregator(PeriodHolder periodHolder, MetricRepository metricRepository, MeasureRepository measureRepository) {
     this.periodHolder = periodHolder;
-    this.dbClient = dbClient;
     this.measureRepository = measureRepository;
 
     this.newMaintainabilityEffortMetric = metricRepository.getByKey(NEW_TECHNICAL_DEBT_KEY);
@@ -77,14 +66,7 @@ public class NewEffortAggregator extends IssueVisitor {
 
   @Override
   public void beforeComponent(Component component) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      List<IssueChangeDto> changes = dbClient.issueChangeDao().selectChangelogOfNonClosedIssuesByComponent(dbSession, component.getUuid());
-      for (IssueChangeDto change : changes) {
-        changesByIssueUuid.put(change.getIssueKey(), change);
-      }
-    }
-
-    counter = new NewEffortCounter(calculator);
+    counter = new NewEffortCounter();
     counterByComponentRef.put(component.getReportAttributes().getRef(), counter);
     for (Component child : component.getChildren()) {
       NewEffortCounter childSum = counterByComponentRef.remove(child.getReportAttributes().getRef());
@@ -97,8 +79,7 @@ public class NewEffortAggregator extends IssueVisitor {
   @Override
   public void onIssue(Component component, DefaultIssue issue) {
     if (issue.resolution() == null && issue.effortInMinutes() != null && periodHolder.hasPeriod()) {
-      List<IssueChangeDto> changelog = changesByIssueUuid.get(issue.key());
-      counter.add(issue, periodHolder.getPeriod(), changelog);
+      counter.add(issue, periodHolder.getPeriod());
     }
   }
 
@@ -107,7 +88,6 @@ public class NewEffortAggregator extends IssueVisitor {
     computeMeasure(component, newMaintainabilityEffortMetric, counter.maintainabilitySum);
     computeMeasure(component, newReliabilityEffortMetric, counter.reliabilitySum);
     computeMeasure(component, newSecurityEffortMetric, counter.securitySum);
-    changesByIssueUuid.clear();
     counter = null;
   }
 
@@ -118,24 +98,18 @@ public class NewEffortAggregator extends IssueVisitor {
   }
 
   private static class NewEffortCounter {
-    private final NewEffortCalculator calculator;
-
     private final EffortSum maintainabilitySum = new EffortSum();
     private final EffortSum reliabilitySum = new EffortSum();
     private final EffortSum securitySum = new EffortSum();
 
-    public NewEffortCounter(NewEffortCalculator calculator) {
-      this.calculator = calculator;
-    }
-
     void add(NewEffortCounter otherCounter) {
       maintainabilitySum.add(otherCounter.maintainabilitySum);
       reliabilitySum.add(otherCounter.reliabilitySum);
       securitySum.add(otherCounter.securitySum);
     }
 
-    void add(DefaultIssue issue, Period period, List<IssueChangeDto> changelog) {
-      long newEffort = calculator.calculate(issue, changelog, period);
+    void add(DefaultIssue issue, Period period) {
+      long newEffort = calculate(issue, period);
       switch (issue.type()) {
         case CODE_SMELL:
           maintainabilitySum.add(newEffort);
@@ -150,6 +124,13 @@ public class NewEffortAggregator extends IssueVisitor {
           throw new IllegalStateException(String.format("Unknown type '%s'", issue.type()));
       }
     }
+
+    long calculate(DefaultIssue issue, Period period) {
+      if (issue.creationDate().getTime() > truncateToSeconds(period.getSnapshotDate())) {
+        return MoreObjects.firstNonNull(issue.effortInMinutes(), 0L);
+      }
+      return 0L;
+    }
   }
 
   private static class EffortSum {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java
deleted file mode 100644 (file)
index 397f3fa..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.task.projectanalysis.issue;
-
-import com.google.common.base.Function;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.db.issue.IssueChangeDto;
-import org.sonar.server.computation.task.projectanalysis.period.Period;
-import org.sonar.server.issue.IssueFieldsSetter;
-
-import static com.google.common.collect.FluentIterable.from;
-import static org.sonar.api.utils.DateUtils.truncateToSeconds;
-
-/**
- * Gets the issue debt that was introduced on a period. The algorithm
- * is based on the issue changelog.
- */
-public class NewEffortCalculator {
-
-  /**
-   * Changelog have to be sorted from newest to oldest.
-   * Null date should be the first as this happen when technical debt has changed since previous analysis.
-   */
-  private static final Comparator<FieldDiffs> CHANGE_ORDERING = Ordering.natural().reverse().nullsFirst().onResultOf((Function<FieldDiffs, Date>) dto -> dto.creationDate());
-
-  public long calculate(DefaultIssue issue, Collection<IssueChangeDto> debtChangelog, Period period) {
-    if (issue.creationDate().getTime() > truncateToSeconds(period.getSnapshotDate())) {
-      return MoreObjects.firstNonNull(issue.effortInMinutes(), 0L);
-    }
-    return calculateFromChangelog(issue, debtChangelog, period.getSnapshotDate());
-  }
-
-  private static long calculateFromChangelog(DefaultIssue issue, Collection<IssueChangeDto> debtChangelog, long periodDate) {
-    List<FieldDiffs> debtDiffs = from(debtChangelog).transform(ToFieldDiffs.INSTANCE).filter(HasDebtChange.INSTANCE).toSortedList(CHANGE_ORDERING);
-    FieldDiffs currentChange = issue.currentChange();
-    if (currentChange != null && HasDebtChange.INSTANCE.apply(currentChange)) {
-      debtDiffs = Lists.newArrayList(debtDiffs);
-      debtDiffs.add(currentChange);
-    }
-    long newDebt = issue.effortInMinutes();
-
-    for (Iterator<FieldDiffs> it = debtDiffs.iterator(); it.hasNext();) {
-      FieldDiffs diffs = it.next();
-      Date date = diffs.creationDate();
-      // TODO use longs
-      if (isBeforeOrEqual(date, new Date(periodDate))) {
-        // return new value from the change that is just before the period date
-        return subtract(newDebt, debtDiff(diffs).newValueLong());
-      }
-      if (!it.hasNext()) {
-        // return old value from the change that is just after the period date when there's no more element in changelog
-        return subtract(newDebt, debtDiff(diffs).oldValueLong());
-      }
-    }
-    // no changelog
-    return 0L;
-  }
-
-  /**
-   * SONAR-5059
-   */
-  @CheckForNull
-  private static long subtract(long newDebt, @Nullable Long with) {
-    if (with != null) {
-      return Math.max(0L, newDebt - with);
-    }
-    return newDebt;
-  }
-
-  private static boolean isBeforeOrEqual(@Nullable Date changeDate, Date periodDate) {
-    return (changeDate != null) && (truncateToSeconds(changeDate.getTime()) <= truncateToSeconds(periodDate.getTime()));
-  }
-
-  private static FieldDiffs.Diff debtDiff(FieldDiffs diffs) {
-    return diffs.diffs().get(IssueFieldsSetter.TECHNICAL_DEBT);
-  }
-
-  private enum ToFieldDiffs implements Function<IssueChangeDto, FieldDiffs> {
-    INSTANCE;
-    @Override
-    public FieldDiffs apply(@Nonnull IssueChangeDto dto) {
-      return dto.toFieldDiffs();
-    }
-  }
-
-  private enum HasDebtChange implements Predicate<FieldDiffs> {
-    INSTANCE;
-    @Override
-    public boolean apply(@Nonnull FieldDiffs diffs) {
-      return diffs.diffs().containsKey(IssueFieldsSetter.TECHNICAL_DEBT);
-    }
-  }
-}
index 5e82b02daac1c530d7d6b74a10db41978f086411..f567083f309aa308cfe509bdc9bfdccab6049839 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.issue;
 
+import java.util.Date;
+import java.util.Random;
 import org.junit.Test;
-import org.mockito.Mockito;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.Duration;
 import org.sonar.core.issue.DefaultIssue;
-import org.sonar.db.DbClient;
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 import org.sonar.server.computation.task.projectanalysis.component.ReportComponent;
 import org.sonar.server.computation.task.projectanalysis.measure.Measure;
@@ -35,12 +35,6 @@ import org.sonar.server.computation.task.projectanalysis.period.PeriodHolderRule
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.guava.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
 import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT;
 import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY;
@@ -56,158 +50,166 @@ import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_PRE
 public class NewEffortAggregatorTest {
 
   private static final Period PERIOD = new Period(LEAK_PERIOD_MODE_PREVIOUS_ANALYSIS, null, 1_500_000_000L, "U1");
+  private static final long[] OLD_ISSUES_DATES = new long[] {
+    PERIOD.getSnapshotDate(),
+    PERIOD.getSnapshotDate() - 1,
+    PERIOD.getSnapshotDate() - 1_200_000L,
+  };
 
   private static final Component FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid("FILE").build();
   private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 2).addChildren(FILE).build();
 
-  private NewEffortCalculator calculator = mock(NewEffortCalculator.class);
-
   @org.junit.Rule
   public PeriodHolderRule periodsHolder = new PeriodHolderRule();
-
   @org.junit.Rule
   public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
     .add(NEW_TECHNICAL_DEBT)
     .add(NEW_RELIABILITY_REMEDIATION_EFFORT)
     .add(NEW_SECURITY_REMEDIATION_EFFORT);
-
   @org.junit.Rule
   public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create();
 
-  private DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
-
-  private NewEffortAggregator underTest = new NewEffortAggregator(calculator, periodsHolder, dbClient, metricRepository, measureRepository);
+  private final Random random = new Random();
+  private NewEffortAggregator underTest = new NewEffortAggregator(periodsHolder, metricRepository, measureRepository);
 
   @Test
   public void sum_new_maintainability_effort_of_issues() {
     periodsHolder.setPeriod(PERIOD);
     DefaultIssue unresolved1 = newCodeSmellIssue(10L);
+    DefaultIssue old1 = oldCodeSmellIssue(100L);
     DefaultIssue unresolved2 = newCodeSmellIssue(30L);
+    DefaultIssue old2 = oldCodeSmellIssue(300L);
     DefaultIssue unresolvedWithoutDebt = newCodeSmellIssueWithoutEffort();
     DefaultIssue resolved = newCodeSmellIssue(50L).setResolution(RESOLUTION_FIXED);
 
-    when(calculator.calculate(same(unresolved1), anyList(), same(PERIOD))).thenReturn(4L);
-    when(calculator.calculate(same(unresolved2), anyList(), same(PERIOD))).thenReturn(3L);
-    verifyNoMoreInteractions(calculator);
-
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, unresolved1);
+    underTest.onIssue(FILE, old1);
     underTest.onIssue(FILE, unresolved2);
+    underTest.onIssue(FILE, old2);
     underTest.onIssue(FILE, unresolvedWithoutDebt);
     underTest.onIssue(FILE, resolved);
     underTest.afterComponent(FILE);
 
-    assertVariation(FILE, NEW_TECHNICAL_DEBT_KEY, 3 + 4);
+    assertVariation(FILE, NEW_TECHNICAL_DEBT_KEY, 10 + 30);
   }
 
   @Test
   public void new_maintainability_effort_is_only_computed_using_code_smell_issues() {
     periodsHolder.setPeriod(PERIOD);
     DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
+    DefaultIssue oldSmellIssue = oldCodeSmellIssue(100);
     // Issues of type BUG and VULNERABILITY should be ignored
     DefaultIssue bugIssue = newBugIssue(15);
+    DefaultIssue oldBugIssue = oldBugIssue(150);
     DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
-
-    when(calculator.calculate(same(codeSmellIssue), anyList(), same(PERIOD))).thenReturn(4L);
-    when(calculator.calculate(same(bugIssue), anyList(), same(PERIOD))).thenReturn(3L);
-    when(calculator.calculate(same(vulnerabilityIssue), anyList(), same(PERIOD))).thenReturn(5L);
+    DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
 
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, codeSmellIssue);
+    underTest.onIssue(FILE, oldSmellIssue);
     underTest.onIssue(FILE, bugIssue);
+    underTest.onIssue(FILE, oldBugIssue);
     underTest.onIssue(FILE, vulnerabilityIssue);
+    underTest.onIssue(FILE, oldVulnerabilityIssue);
     underTest.afterComponent(FILE);
 
     // Only effort of CODE SMELL issue is used
-    assertVariation(FILE, NEW_TECHNICAL_DEBT_KEY, 4);
+    assertVariation(FILE, NEW_TECHNICAL_DEBT_KEY, 10);
   }
 
   @Test
   public void sum_new_reliability_effort_of_issues() {
     periodsHolder.setPeriod(PERIOD);
     DefaultIssue unresolved1 = newBugIssue(10L);
+    DefaultIssue old1 = oldBugIssue(100L);
     DefaultIssue unresolved2 = newBugIssue(30L);
+    DefaultIssue old2 = oldBugIssue(300L);
     DefaultIssue unresolvedWithoutDebt = newBugIssueWithoutEffort();
     DefaultIssue resolved = newBugIssue(50L).setResolution(RESOLUTION_FIXED);
 
-    when(calculator.calculate(same(unresolved1), anyList(), same(PERIOD))).thenReturn(4L);
-    when(calculator.calculate(same(unresolved2), anyList(), same(PERIOD))).thenReturn(3L);
-    verifyNoMoreInteractions(calculator);
-
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, unresolved1);
+    underTest.onIssue(FILE, old1);
     underTest.onIssue(FILE, unresolved2);
+    underTest.onIssue(FILE, old2);
     underTest.onIssue(FILE, unresolvedWithoutDebt);
     underTest.onIssue(FILE, resolved);
     underTest.afterComponent(FILE);
 
-    assertVariation(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 3 + 4);
+    assertVariation(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 10 + 30);
   }
 
   @Test
   public void new_reliability_effort_is_only_computed_using_bug_issues() {
     periodsHolder.setPeriod(PERIOD);
     DefaultIssue bugIssue = newBugIssue(15);
+    DefaultIssue oldBugIssue = oldBugIssue(150);
     // Issues of type CODE SMELL and VULNERABILITY should be ignored
     DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
+    DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100);
     DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
-
-    when(calculator.calculate(same(bugIssue), anyList(), same(PERIOD))).thenReturn(3L);
-    when(calculator.calculate(same(codeSmellIssue), anyList(), same(PERIOD))).thenReturn(4L);
-    when(calculator.calculate(same(vulnerabilityIssue), anyList(), same(PERIOD))).thenReturn(5L);
+    DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
 
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, bugIssue);
+    underTest.onIssue(FILE, oldBugIssue);
     underTest.onIssue(FILE, codeSmellIssue);
+    underTest.onIssue(FILE, oldCodeSmellIssue);
     underTest.onIssue(FILE, vulnerabilityIssue);
+    underTest.onIssue(FILE, oldVulnerabilityIssue);
     underTest.afterComponent(FILE);
 
     // Only effort of BUG issue is used
-    assertVariation(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 3);
+    assertVariation(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 15);
   }
 
   @Test
-  public void sum_new_securtiy_effort_of_issues() {
+  public void sum_new_vulnerability_effort_of_issues() {
     periodsHolder.setPeriod(PERIOD);
     DefaultIssue unresolved1 = newVulnerabilityIssue(10L);
+    DefaultIssue old1 = oldVulnerabilityIssue(100L);
     DefaultIssue unresolved2 = newVulnerabilityIssue(30L);
+    DefaultIssue old2 = oldVulnerabilityIssue(300L);
     DefaultIssue unresolvedWithoutDebt = newVulnerabilityIssueWithoutEffort();
     DefaultIssue resolved = newVulnerabilityIssue(50L).setResolution(RESOLUTION_FIXED);
-
-    when(calculator.calculate(same(unresolved1), anyList(), same(PERIOD))).thenReturn(4L);
-    when(calculator.calculate(same(unresolved2), anyList(), same(PERIOD))).thenReturn(3L);
-    verifyNoMoreInteractions(calculator);
+    DefaultIssue oldResolved = oldVulnerabilityIssue(500L).setResolution(RESOLUTION_FIXED);
 
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, unresolved1);
+    underTest.onIssue(FILE, old1);
     underTest.onIssue(FILE, unresolved2);
+    underTest.onIssue(FILE, old2);
     underTest.onIssue(FILE, unresolvedWithoutDebt);
     underTest.onIssue(FILE, resolved);
+    underTest.onIssue(FILE, oldResolved);
     underTest.afterComponent(FILE);
 
-    assertVariation(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 3 + 4);
+    assertVariation(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 10 + 30);
   }
 
   @Test
   public void new_security_effort_is_only_computed_using_vulnerability_issues() {
     periodsHolder.setPeriod(PERIOD);
     DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
+    DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
     // Issues of type CODE SMELL and BUG should be ignored
     DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
+    DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100);
     DefaultIssue bugIssue = newBugIssue(15);
-
-    when(calculator.calculate(same(vulnerabilityIssue), anyList(), same(PERIOD))).thenReturn(5L);
-    when(calculator.calculate(same(codeSmellIssue), anyList(), same(PERIOD))).thenReturn(4L);
-    when(calculator.calculate(same(bugIssue), anyList(), same(PERIOD))).thenReturn(3L);
+    DefaultIssue oldBugIssue = oldBugIssue(150);
 
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, codeSmellIssue);
+    underTest.onIssue(FILE, oldCodeSmellIssue);
     underTest.onIssue(FILE, bugIssue);
+    underTest.onIssue(FILE, oldBugIssue);
     underTest.onIssue(FILE, vulnerabilityIssue);
+    underTest.onIssue(FILE, oldVulnerabilityIssue);
     underTest.afterComponent(FILE);
 
     // Only effort of VULNERABILITY issue is used
-    assertVariation(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 5);
+    assertVariation(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 12);
   }
 
   @Test
@@ -215,40 +217,45 @@ public class NewEffortAggregatorTest {
     periodsHolder.setPeriod(PERIOD);
 
     DefaultIssue codeSmellIssue = newCodeSmellIssue(10);
-    when(calculator.calculate(same(codeSmellIssue), anyList(), same(PERIOD))).thenReturn(4L);
+    DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100);
     DefaultIssue bugIssue = newBugIssue(8);
-    when(calculator.calculate(same(bugIssue), anyList(), same(PERIOD))).thenReturn(3L);
+    DefaultIssue oldBugIssue = oldBugIssue(80);
     DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12);
-    when(calculator.calculate(same(vulnerabilityIssue), anyList(), same(PERIOD))).thenReturn(6L);
+    DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120);
 
     DefaultIssue codeSmellProjectIssue = newCodeSmellIssue(30);
-    when(calculator.calculate(same(codeSmellProjectIssue), anyList(), same(PERIOD))).thenReturn(1L);
+    DefaultIssue oldCodeSmellProjectIssue = oldCodeSmellIssue(300);
     DefaultIssue bugProjectIssue = newBugIssue(28);
-    when(calculator.calculate(same(bugProjectIssue), anyList(), same(PERIOD))).thenReturn(2L);
+    DefaultIssue oldBugProjectIssue = oldBugIssue(280);
     DefaultIssue vulnerabilityProjectIssue = newVulnerabilityIssue(32);
-    when(calculator.calculate(same(vulnerabilityProjectIssue), anyList(), same(PERIOD))).thenReturn(4L);
+    DefaultIssue oldVulnerabilityProjectIssue = oldVulnerabilityIssue(320);
 
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, codeSmellIssue);
+    underTest.onIssue(FILE, oldCodeSmellIssue);
     underTest.onIssue(FILE, bugIssue);
+    underTest.onIssue(FILE, oldBugIssue);
     underTest.onIssue(FILE, vulnerabilityIssue);
+    underTest.onIssue(FILE, oldVulnerabilityIssue);
     underTest.afterComponent(FILE);
     underTest.beforeComponent(PROJECT);
     underTest.onIssue(PROJECT, codeSmellProjectIssue);
+    underTest.onIssue(PROJECT, oldCodeSmellProjectIssue);
     underTest.onIssue(PROJECT, bugProjectIssue);
+    underTest.onIssue(PROJECT, oldBugProjectIssue);
     underTest.onIssue(PROJECT, vulnerabilityProjectIssue);
+    underTest.onIssue(PROJECT, oldVulnerabilityProjectIssue);
     underTest.afterComponent(PROJECT);
 
-    assertVariation(PROJECT, NEW_TECHNICAL_DEBT_KEY, 4 + 1);
-    assertVariation(PROJECT, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 3 + 2);
-    assertVariation(PROJECT, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 6 + 4);
+    assertVariation(PROJECT, NEW_TECHNICAL_DEBT_KEY, 10 + 30);
+    assertVariation(PROJECT, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 8 + 28);
+    assertVariation(PROJECT, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 12 + 32);
   }
 
   @Test
   public void no_measures_if_no_periods() {
     periodsHolder.setPeriod(null);
-    DefaultIssue unresolved = new DefaultIssue().setEffort(Duration.create(10));
-    verifyZeroInteractions(calculator);
+    DefaultIssue unresolved = newCodeSmellIssue(10);
 
     underTest.beforeComponent(FILE);
     underTest.onIssue(FILE, unresolved);
@@ -263,19 +270,51 @@ public class NewEffortAggregatorTest {
   }
 
   private static DefaultIssue newCodeSmellIssue(long effort) {
-    return newCodeSmellIssueWithoutEffort().setEffort(Duration.create(effort)).setType(RuleType.CODE_SMELL);
+    return newCodeSmellIssueWithoutEffort()
+      .setEffort(Duration.create(effort))
+      .setType(RuleType.CODE_SMELL)
+      .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L));
+  }
+
+  private DefaultIssue oldCodeSmellIssue(long effort) {
+    return newCodeSmellIssueWithoutEffort()
+      .setEffort(Duration.create(effort))
+      .setType(RuleType.CODE_SMELL)
+      .setCreationDate(new Date(OLD_ISSUES_DATES[random.nextInt(OLD_ISSUES_DATES.length)]));
   }
 
   private static DefaultIssue newBugIssue(long effort) {
-    return newCodeSmellIssueWithoutEffort().setEffort(Duration.create(effort)).setType(RuleType.BUG);
+    return newCodeSmellIssueWithoutEffort()
+      .setEffort(Duration.create(effort))
+      .setType(RuleType.BUG)
+      .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L));
+  }
+
+  private DefaultIssue oldBugIssue(long effort) {
+    return newCodeSmellIssueWithoutEffort()
+      .setEffort(Duration.create(effort))
+      .setType(RuleType.BUG)
+      .setCreationDate(new Date(OLD_ISSUES_DATES[random.nextInt(OLD_ISSUES_DATES.length)]));
   }
 
   private static DefaultIssue newVulnerabilityIssue(long effort) {
-    return newCodeSmellIssueWithoutEffort().setEffort(Duration.create(effort)).setType(RuleType.VULNERABILITY);
+    return newCodeSmellIssueWithoutEffort()
+      .setEffort(Duration.create(effort))
+      .setType(RuleType.VULNERABILITY)
+      .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L));
+  }
+
+  private DefaultIssue oldVulnerabilityIssue(long effort) {
+    return newCodeSmellIssueWithoutEffort()
+      .setEffort(Duration.create(effort))
+      .setType(RuleType.VULNERABILITY)
+      .setCreationDate(new Date(OLD_ISSUES_DATES[random.nextInt(OLD_ISSUES_DATES.length)]));
   }
 
   private static DefaultIssue newCodeSmellIssueWithoutEffort() {
-    return new DefaultIssue().setType(CODE_SMELL);
+    return new DefaultIssue()
+      .setType(CODE_SMELL)
+      .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L));
   }
 
   private static DefaultIssue newBugIssueWithoutEffort() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculatorTest.java
deleted file mode 100644 (file)
index 650f2cd..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.task.projectanalysis.issue;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.junit.Test;
-import org.sonar.api.utils.Duration;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.db.issue.IssueChangeDto;
-import org.sonar.server.computation.task.projectanalysis.period.Period;
-
-import static java.util.Collections.emptyList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_PREVIOUS_VERSION;
-
-public class NewEffortCalculatorTest {
-
-  private static final int HOURS_IN_DAY = 8;
-  private static final Duration ONE_DAY = Duration.create(HOURS_IN_DAY * 60 * 60L);
-  private static final Duration TWO_DAYS = Duration.create(2 * HOURS_IN_DAY * 60 * 60L);
-  private static final Duration FOUR_DAYS = Duration.create(4 * HOURS_IN_DAY * 60 * 60L);
-  private static final Duration FIVE_DAYS = Duration.create(5 * HOURS_IN_DAY * 60 * 60L);
-  private static final Duration TEN_DAYS = Duration.create(10 * HOURS_IN_DAY * 60 * 60L);
-  private static final long PERIOD_DATE = 150000000L;
-  private static final String ANALYSIS_UUID = "u1";
-  private static final Period PERIOD = new Period(LEAK_PERIOD_MODE_PREVIOUS_VERSION, null, PERIOD_DATE, ANALYSIS_UUID);
-
-  DefaultIssue issue = new DefaultIssue();
-  NewEffortCalculator underTest = new NewEffortCalculator();
-
-  /**
-   * New debt is the value of the debt when issue is created during the period
-   */
-  @Test
-  public void total_debt_if_issue_created_during_period() {
-    issue.setEffort(TWO_DAYS).setCreationDate(new Date(PERIOD_DATE + 10000));
-
-    long newDebt = underTest.calculate(issue, emptyList(), PERIOD);
-
-    assertThat(newDebt).isEqualTo(TWO_DAYS.toMinutes());
-  }
-
-  @Test
-  public void new_debt_if_issue_created_before_period() {
-    // creation: 1d
-    // before period: increased to 2d
-    // after period: increased to 5d, decreased to 4d then increased to 10d
-    // -> new debt is 10d - 2d = 8d
-    issue.setEffort(TEN_DAYS).setCreationDate(new Date(PERIOD_DATE - 10000));
-    List<IssueChangeDto> changelog = Arrays.asList(
-      newDebtChangelog(ONE_DAY.toMinutes(), TWO_DAYS.toMinutes(), PERIOD_DATE - 9000),
-      newDebtChangelog(TWO_DAYS.toMinutes(), FIVE_DAYS.toMinutes(), PERIOD_DATE + 10000),
-      newDebtChangelog(FIVE_DAYS.toMinutes(), FOUR_DAYS.toMinutes(), PERIOD_DATE + 20000),
-      newDebtChangelog(FOUR_DAYS.toMinutes(), TEN_DAYS.toMinutes(), PERIOD_DATE + 30000)
-    );
-
-    long newDebt = underTest.calculate(issue, changelog, PERIOD);
-
-    assertThat(newDebt).isEqualTo(TEN_DAYS.toMinutes() - TWO_DAYS.toMinutes());
-  }
-
-  @Test
-  public void new_debt_is_positive() {
-    // creation: 1d
-    // before period: increased to 10d
-    // after period: decreased to 2d
-    // -> new debt is 2d - 10d = -8d -> 0d
-    issue.setEffort(TWO_DAYS).setCreationDate(new Date(PERIOD_DATE - 10000));
-    List<IssueChangeDto> changelog = Arrays.asList(
-      newDebtChangelog(ONE_DAY.toMinutes(), TEN_DAYS.toMinutes(), PERIOD_DATE - 9000),
-      newDebtChangelog(TEN_DAYS.toMinutes(), TWO_DAYS.toMinutes(), PERIOD_DATE + 30000)
-    );
-
-    long newDebt = underTest.calculate(issue, changelog, PERIOD);
-
-    assertThat(newDebt).isEqualTo(0L);
-  }
-
-  @Test
-  public void guess_initial_debt_when_first_change_is_after_period() {
-    // creation: 1d
-    // after period: increased to 2d, then to 5d
-    // -> new debt is 5d - 1d = 4d
-    issue.setEffort(FIVE_DAYS).setCreationDate(new Date(PERIOD_DATE - 10000));
-    List<IssueChangeDto> changelog = Arrays.asList(
-      newDebtChangelog(ONE_DAY.toMinutes(), TWO_DAYS.toMinutes(), PERIOD_DATE + 20000),
-      newDebtChangelog(TWO_DAYS.toMinutes(), FIVE_DAYS.toMinutes(), PERIOD_DATE + 30000)
-    );
-
-    long newDebt = underTest.calculate(issue, changelog, PERIOD);
-
-    assertThat(newDebt).isEqualTo(FIVE_DAYS.toMinutes() - ONE_DAY.toMinutes());
-  }
-
-
-  private static IssueChangeDto newDebtChangelog(long previousValue, long value, @Nullable Long date) {
-    FieldDiffs diffs = new FieldDiffs().setDiff("technicalDebt", previousValue, value);
-    if (date != null) {
-      diffs.setCreationDate(new Date(date));
-    }
-    return new IssueChangeDto().setIssueChangeCreationDate(date).setChangeData(diffs.toString());
-  }
-
-}