]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6588 move computation of debt to Compute Engine 404/head
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 30 Jun 2015 07:02:14 +0000 (09:02 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 2 Jul 2015 17:44:28 +0000 (19:44 +0200)
101 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolder.java
server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolderImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/debt/MutableDebtModelHolder.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtAggregator.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCounter.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitor.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtAggregator.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/Rule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepositoryImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUserLoader.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java
server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolderImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java
server/sonar-server/src/test/java/org/sonar/server/computation/debt/DebtModelHolderImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/issue/CountIssuesListenerTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtAggregatorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtCalculatorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DefaultAssigneeTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DumbRule.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueAssignerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtAggregatorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtCalculatorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryRule.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleTagsCopierTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceAuthorsHolderTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedDebtModelStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistMeasuresStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml
server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/insert_new_issue.xml
sonar-batch-protocol/src/main/gen-java/org/sonar/batch/protocol/output/BatchReport.java
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java
sonar-batch-protocol/src/main/protobuf/batch_report.proto
sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java
sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java
sonar-batch/src/main/java/org/sonar/batch/report/IssuesPublisher.java
sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java
sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java
sonar-batch/src/test/java/org/sonar/batch/report/IssuesPublisherTest.java
sonar-core/src/main/java/org/sonar/core/computation/package-info.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java
sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/BlockHashSequence.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/BlockRecognizer.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java
sonar-core/src/main/java/org/sonar/core/measure/db/MeasureDto.java
sonar-core/src/main/java/org/sonar/core/rule/RuleKeyFunctions.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java
sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java
sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java
sonar-core/src/test/java/org/sonar/core/rule/RuleKeyFunctionsTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfUnresolvedIssuesByComponent.xml [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/condition/HasIssuePropertyCondition.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/MeasuresFilters.java
sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtRemediationFunction.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/rule/internal/RulesBuilderTest.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/action/ActionTest.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasIssuePropertyConditionTest.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasResolutionTest.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasStatusTest.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/IsUnResolvedTest.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/NotConditionTest.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java [deleted file]
sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/FieldDiffsTest.java [deleted file]

index 7bbd85c8748c8d75e60951deceb06fc6a50e8cea..210d9d9842e870ed35439a7c397429268b0b2944 100644 (file)
@@ -45,6 +45,7 @@ import org.sonar.server.computation.debt.DebtModelHolderImpl;
 import org.sonar.server.computation.event.EventRepositoryImpl;
 import org.sonar.server.computation.formula.CoreFormulaRepositoryImpl;
 import org.sonar.server.computation.issue.BaseIssuesLoader;
+import org.sonar.server.computation.issue.DebtCalculator;
 import org.sonar.server.computation.issue.IssueCounter;
 import org.sonar.server.computation.issue.DebtAggregator;
 import org.sonar.server.computation.issue.DefaultAssignee;
@@ -54,8 +55,8 @@ import org.sonar.server.computation.issue.IssueLifecycle;
 import org.sonar.server.computation.issue.IssueVisitors;
 import org.sonar.server.computation.issue.NewDebtAggregator;
 import org.sonar.server.computation.issue.NewDebtCalculator;
-import org.sonar.server.computation.issue.RuleCache;
 import org.sonar.server.computation.issue.RuleCacheLoader;
+import org.sonar.server.computation.issue.RuleRepositoryImpl;
 import org.sonar.server.computation.issue.RuleTagsCopier;
 import org.sonar.server.computation.issue.ScmAccountToUser;
 import org.sonar.server.computation.issue.ScmAccountToUserLoader;
@@ -168,16 +169,17 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
       NewCoverageMetricKeysModule.class,
 
       // issues
+      RuleCacheLoader.class,
+      RuleRepositoryImpl.class,
       ScmAccountToUserLoader.class,
       ScmAccountToUser.class,
-      RuleCache.class,
-      RuleCacheLoader.class,
       IssueCache.class,
       DefaultAssignee.class,
       IssueVisitors.class,
       IssueLifecycle.class,
 
-      // order is important, NewDebtAggregator is based on DebtAggregator (new debt requires debt)
+      // order is important: DebtAggregator then NewDebtAggregator (new debt requires debt)
+      DebtCalculator.class,
       DebtAggregator.class,
       NewDebtCalculator.class,
       NewDebtAggregator.class,
index f10dbfe5af92789386968fd7ccc175db4a48f187..0290164b18b8f79cf161a70eb6ff6f3595937d3d 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.sonar.server.computation.debt;
 
+import java.util.List;
+
 public interface DebtModelHolder {
 
   /**
@@ -30,4 +32,5 @@ public interface DebtModelHolder {
    */
   Characteristic getCharacteristicById(int id);
 
+  List<Characteristic> getRootCharacteristics();
 }
index 206ed5f3a8b2791f8f3a95a8d6a06bd52b041f47..a66b83542716b27bb17dcd5e2030746dc861cf72 100644 (file)
@@ -20,7 +20,9 @@
 
 package org.sonar.server.computation.debt;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -29,23 +31,26 @@ import static java.util.Objects.requireNonNull;
 
 public class DebtModelHolderImpl implements MutableDebtModelHolder {
 
+  private final List<Characteristic> rootCharacteristics = new ArrayList<>();
   private final Map<Integer, Characteristic> characteristicById = new HashMap<>();
 
   @Override
-  public void addCharacteristics(Characteristic rootCharacteristic, Iterable<? extends Characteristic> subCharacteristics) {
+  public DebtModelHolderImpl addCharacteristics(Characteristic rootCharacteristic, Iterable<? extends Characteristic> subCharacteristics) {
     requireNonNull(rootCharacteristic, "rootCharacteristic cannot be null");
     requireNonNull(subCharacteristics, "subCharacteristics cannot be null");
     checkArgument(subCharacteristics.iterator().hasNext(), "subCharacteristics cannot be empty");
 
+    rootCharacteristics.add(rootCharacteristic);
     characteristicById.put(rootCharacteristic.getId(), rootCharacteristic);
     for (Characteristic characteristic : subCharacteristics) {
       characteristicById.put(characteristic.getId(), characteristic);
     }
+    return this;
   }
 
   @Override
   public Characteristic getCharacteristicById(int id) {
-    checkCharacteristicsAreInitialized();
+    checkInitialized();
     Characteristic characteristic = characteristicById.get(id);
     if (characteristic == null) {
       throw new IllegalStateException("Debt characteristic with id [" + id + "] does not exist");
@@ -53,7 +58,13 @@ public class DebtModelHolderImpl implements MutableDebtModelHolder {
     return characteristic;
   }
 
-  private void checkCharacteristicsAreInitialized() {
+  @Override
+  public List<Characteristic> getRootCharacteristics() {
+    checkInitialized();
+    return rootCharacteristics;
+  }
+
+  private void checkInitialized() {
     checkState(!characteristicById.isEmpty(), "Characteristics have not been initialized yet");
   }
 }
index 665986425bf2d240fff96e6ddbb374f75594e49c..45ab2d84fa21c8cc941f3e9a5002776acc07cb86 100644 (file)
@@ -31,5 +31,5 @@ public interface MutableDebtModelHolder extends DebtModelHolder {
    * @throws NullPointerException if {@code rootCharacteristic} is {@code null}
    * @throws NullPointerException if {@code subCharacteristics} is {@code null}
    */
-  void addCharacteristics(Characteristic rootCharacteristic, Iterable<? extends Characteristic> subCharacteristics);
+  MutableDebtModelHolder addCharacteristics(Characteristic rootCharacteristic, Iterable<? extends Characteristic> subCharacteristics);
 }
index 216fe6db9b95cc575b1b00a8ea7a2c64caf1ab15..8ebfb64479e26f31e3decae3974e2214cc1fe8e0 100644 (file)
  */
 package org.sonar.server.computation.issue;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import javax.annotation.Nonnull;
 import org.apache.ibatis.session.ResultContext;
 import org.apache.ibatis.session.ResultHandler;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.issue.db.IssueMapper;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.rule.RuleDto;
 import org.sonar.server.computation.batch.BatchReportReader;
 import org.sonar.server.computation.component.TreeRootHolder;
 import org.sonar.server.db.DbClient;
 
 import static com.google.common.collect.FluentIterable.from;
+import static org.sonar.core.rule.RuleKeyFunctions.stringToRuleKey;
 
 /**
  * Loads all the project open issues from database, including manual issues.
@@ -52,14 +49,14 @@ public class BaseIssuesLoader {
   private final Set<RuleKey> activeRuleKeys;
   private final TreeRootHolder treeRootHolder;
   private final DbClient dbClient;
-  private final RuleCache ruleCache;
+  private final RuleRepository ruleRepository;
 
   public BaseIssuesLoader(BatchReportReader reportReader, TreeRootHolder treeRootHolder,
-    DbClient dbClient, RuleCache ruleCache) {
-    this.activeRuleKeys = from(reportReader.readMetadata().getActiveRuleKeyList()).transform(ToRuleKey.INSTANCE).toSet();
+    DbClient dbClient, RuleRepository ruleRepository) {
+    this.activeRuleKeys = from(reportReader.readMetadata().getActiveRuleKeyList()).transform(stringToRuleKey()).toSet();
     this.treeRootHolder = treeRootHolder;
     this.dbClient = dbClient;
-    this.ruleCache = ruleCache;
+    this.ruleRepository = ruleRepository;
   }
 
   public List<DefaultIssue> loadForComponentUuid(String componentUuid) {
@@ -73,15 +70,13 @@ public class BaseIssuesLoader {
           DefaultIssue issue = ((IssueDto) resultContext.getResultObject()).toDefaultIssue();
 
           // TODO this field should be set outside this class
-          RuleDto rule = ruleCache.getNullable(issue.ruleKey());
-          if (rule == null || rule.getStatus() == RuleStatus.REMOVED || !isActive(issue.ruleKey())) {
+          if (!isActive(issue.ruleKey()) || ruleRepository.getByKey(issue.ruleKey()).getStatus() == RuleStatus.REMOVED) {
             issue.setOnDisabledRule(true);
             // TODO to be improved, why setOnDisabledRule(true) is not enough ?
             issue.setBeingClosed(true);
           }
           // FIXME
           issue.setSelectedAt(System.currentTimeMillis());
-          Loggers.get(getClass()).info("Loaded from db: " + issue);
           result.add(issue);
         }
       });
@@ -98,7 +93,7 @@ public class BaseIssuesLoader {
   /**
    * Uuids of all the components that have open issues on this project.
    */
-  public Set<String> loadComponentUuids() {
+  public Set<String> loadUuidsOfComponentsWithOpenIssues() {
     DbSession session = dbClient.openSession(false);
     try {
       return dbClient.issueDao().selectComponentUuidsOfOpenIssuesForProjectUuid(session, treeRootHolder.getRoot().getUuid());
@@ -106,13 +101,4 @@ public class BaseIssuesLoader {
       MyBatis.closeQuietly(session);
     }
   }
-
-  private enum ToRuleKey implements Function<String, RuleKey> {
-    INSTANCE;
-    @Nonnull
-    @Override
-    public RuleKey apply(@Nonnull String input) {
-      return RuleKey.parse(input);
-    }
-  }
 }
index 8bce13283e3a239996f8a2ad677b626aff885620..c8c410a65035d35cc179880edaa85ceb525e08bf 100644 (file)
@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.tracking.Tracking;
-import org.sonar.core.rule.RuleDto;
 import org.sonar.server.computation.component.Component;
 import org.sonar.server.computation.debt.Characteristic;
 import org.sonar.server.computation.debt.DebtModelHolder;
@@ -40,7 +39,7 @@ import static com.google.common.collect.Maps.newHashMap;
 
 public class DebtAggregator extends IssueVisitor {
 
-  private final RuleCache ruleCache;
+  private final RuleRepository ruleRepository;
   private final DebtModelHolder debtModelHolder;
   private final MetricRepository metricRepository;
   private final MeasureRepository measureRepository;
@@ -48,9 +47,9 @@ public class DebtAggregator extends IssueVisitor {
   private final Map<Integer, Debt> debtsByComponentRef = new HashMap<>();
   private Debt currentDebt;
 
-  public DebtAggregator(RuleCache ruleCache, DebtModelHolder debtModelHolder,
+  public DebtAggregator(RuleRepository ruleRepository, DebtModelHolder debtModelHolder,
     MetricRepository metricRepository, MeasureRepository measureRepository) {
-    this.ruleCache = ruleCache;
+    this.ruleRepository = ruleRepository;
     this.debtModelHolder = debtModelHolder;
     this.metricRepository = metricRepository;
     this.measureRepository = measureRepository;
@@ -58,7 +57,8 @@ public class DebtAggregator extends IssueVisitor {
 
   @Override
   public void beforeComponent(Component component, Tracking tracking) {
-    this.currentDebt = new Debt();
+    currentDebt = new Debt();
+    debtsByComponentRef.put(component.getRef(), currentDebt);
 
     // aggregate children counters
     for (Component child : component.getChildren()) {
@@ -79,26 +79,35 @@ public class DebtAggregator extends IssueVisitor {
 
   @Override
   public void afterComponent(Component component) {
-    if (this.currentDebt.minutes > 0L) {
-      Metric metric = metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY);
+    Metric metric = metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY);
 
-      // total value
-      measureRepository.add(component, metric, Measure.newMeasureBuilder().create(this.currentDebt.minutes));
+    // total value
+    measureRepository.add(component, metric, Measure.newMeasureBuilder().create(this.currentDebt.minutes));
 
-      // distribution by rule
-      for (Map.Entry<Integer, Long> entry : currentDebt.minutesByRuleId.entrySet()) {
-        int ruleId = entry.getKey();
-        long ruleDebt = entry.getValue();
-        measureRepository.add(component, metric, Measure.newMeasureBuilder().forRule(ruleId).create(ruleDebt));
-      }
+    // distribution by rule
+    for (Map.Entry<Integer, Long> entry : currentDebt.minutesByRuleId.entrySet()) {
+      int ruleId = entry.getKey();
+      long ruleDebt = entry.getValue();
+      // debt can't be zero.
+      measureRepository.add(component, metric, Measure.newMeasureBuilder().forRule(ruleId).create(ruleDebt));
+    }
+
+    // distribution by characteristic/sub-characteristic
+    for (Map.Entry<Integer, Long> entry : currentDebt.minutesByCharacteristicId.entrySet()) {
+      int characteristicId = entry.getKey();
+      long characteristicDebt = entry.getValue();
+      // debt can't be zero
+      measureRepository.add(component, metric, Measure.newMeasureBuilder().forCharacteristic(characteristicId).create(characteristicDebt));
+    }
 
-      // distribution by characteristic
-      for (Map.Entry<Integer, Long> entry : currentDebt.minutesByCharacteristicId.entrySet()) {
-        int characteristicId = entry.getKey();
-        long characteristicDebt = entry.getValue();
-        measureRepository.add(component, metric, Measure.newMeasureBuilder().forCharacteristic(characteristicId).create(characteristicDebt));
+    if (!component.getType().isDeeperThan(Component.Type.MODULE)) {
+      for (Characteristic rootCharacteristic : debtModelHolder.getRootCharacteristics()) {
+        if (currentDebt.minutesByCharacteristicId.get(rootCharacteristic.getId()) == null) {
+          measureRepository.add(component, metric, Measure.newMeasureBuilder().forCharacteristic(rootCharacteristic.getId()).create(0L));
+        }
       }
     }
+
     this.currentDebt = null;
   }
 
@@ -112,7 +121,7 @@ public class DebtAggregator extends IssueVisitor {
       if (issueMinutes != null && issueMinutes != 0L) {
         this.minutes += issueMinutes;
 
-        RuleDto rule = ruleCache.get(issue.ruleKey());
+        Rule rule = ruleRepository.getByKey(issue.ruleKey());
         this.minutesByRuleId.add(rule.getId(), issueMinutes);
 
         Integer subCharacteristicId = rule.getSubCharacteristicId();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java
new file mode 100644 (file)
index 0000000..0a7e527
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import javax.annotation.CheckForNull;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.debt.DebtRemediationFunction.Type;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
+import org.sonar.core.issue.DefaultIssue;
+
+public class DebtCalculator {
+
+  private final RuleRepository ruleRepository;
+  private final Durations durations;
+
+  public DebtCalculator(RuleRepository ruleRepository, Durations durations) {
+    this.ruleRepository = ruleRepository;
+    this.durations = durations;
+  }
+
+  @CheckForNull
+  public Duration calculate(DefaultIssue issue) {
+    Rule rule = ruleRepository.getByKey(issue.ruleKey());
+    DebtRemediationFunction fn = rule.getRemediationFunction();
+    if (fn != null) {
+      verifyEffortToFix(issue, fn);
+
+      Duration debt = Duration.create(0);
+      if (fn.type().usesCoefficient() && !Strings.isNullOrEmpty(fn.coefficient())) {
+        int effortToFixValue = Objects.firstNonNull(issue.effortToFix(), 1).intValue();
+        // TODO convert to Duration directly in Rule#remediationFunction -> better performance + error handling
+        debt = durations.decode(fn.coefficient()).multiply(effortToFixValue);
+      }
+      if (fn.type().usesOffset() && !Strings.isNullOrEmpty(fn.offset())) {
+        // TODO convert to Duration directly in Rule#remediationFunction -> better performance + error handling
+        debt = debt.add(durations.decode(fn.offset()));
+      }
+      return debt;
+    }
+    return null;
+  }
+
+  private static void verifyEffortToFix(DefaultIssue issue, DebtRemediationFunction fn) {
+    if (Type.CONSTANT_ISSUE.equals(fn.type()) && issue.effortToFix() != null) {
+      throw new IllegalArgumentException("Rule '" + issue.getRuleKey() + "' can not use 'Constant/issue' remediation function " +
+        "because this rule does not have a fixed remediation cost.");
+    }
+  }
+}
index 466575e7b2a34f3a470ead4bcfea2070a8cca138..7cdd0a5da73fbd1ec0869a8c2722e26d74c230a3 100644 (file)
@@ -65,7 +65,7 @@ public class DefaultAssignee {
   private boolean isValidLogin(String s) {
     UserDoc user = userIndex.getNullableByLogin(s);
     if (user == null) {
-      LOG.info("the {} property was set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, s);
+      LOG.info("Property {} is set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, s);
       return false;
     }
     return true;
index af1dc395b594468d6aeb01f9a5e96dee98ee20d0..916a68d411802ce0434abc890701db1c81c96bb9 100644 (file)
@@ -29,7 +29,6 @@ import org.sonar.server.util.cache.DiskCache;
 /**
  * Cache of all the issues involved in the analysis. Their state is as it will be
  * persisted in database (after issue tracking, auto-assignment, ...)
- *
  */
 public class IssueCache extends DiskCache<DefaultIssue> {
 
index 2e49500d8d20db88262d0af140c752fe64acff16..8c27aefac3589336aad86e957592ca6e92907f82 100644 (file)
@@ -75,7 +75,7 @@ import static org.sonar.api.rule.Severity.MINOR;
  */
 public class IssueCounter extends IssueVisitor {
 
-  private final static Map<String, String> SEVERITY_TO_METRIC_KEY = ImmutableMap.of(
+  private static final Map<String, String> SEVERITY_TO_METRIC_KEY = ImmutableMap.of(
     BLOCKER, BLOCKER_VIOLATIONS_KEY,
     CRITICAL, CRITICAL_VIOLATIONS_KEY,
     MAJOR, MAJOR_VIOLATIONS_KEY,
@@ -83,7 +83,7 @@ public class IssueCounter extends IssueVisitor {
     INFO, INFO_VIOLATIONS_KEY
     );
 
-  private final static Map<String, String> SEVERITY_TO_NEW_METRIC_KEY = ImmutableMap.of(
+  private static final Map<String, String> SEVERITY_TO_NEW_METRIC_KEY = ImmutableMap.of(
     BLOCKER, NEW_BLOCKER_VIOLATIONS_KEY,
     CRITICAL, NEW_CRITICAL_VIOLATIONS_KEY,
     MAJOR, NEW_MAJOR_VIOLATIONS_KEY,
@@ -99,7 +99,7 @@ public class IssueCounter extends IssueVisitor {
   private Counters currentCounters;
 
   public IssueCounter(PeriodsHolder periodsHolder,
-                      MetricRepository metricRepository, MeasureRepository measureRepository) {
+    MetricRepository metricRepository, MeasureRepository measureRepository) {
     this.periodsHolder = periodsHolder;
     this.metricRepository = metricRepository;
     this.measureRepository = measureRepository;
@@ -173,20 +173,14 @@ public class IssueCounter extends IssueVisitor {
         String severity = entry.getKey();
         String metricKey = entry.getValue();
         Double[] variations = new Double[PeriodsHolder.MAX_NUMBER_OF_PERIODS];
-        boolean set = false;
         for (Period period : periodsHolder.getPeriods()) {
           Multiset<String> bag = currentCounters.counterForPeriod(period.getIndex()).severityBag;
-          if (bag.contains(severity)) {
-            variations[period.getIndex() - 1] = new Double(bag.count(severity));
-            set = true;
-          }
-        }
-        if (set) {
-          Metric metric = metricRepository.getByKey(metricKey);
-          measureRepository.add(component, metric, Measure.newMeasureBuilder()
-            .setVariations(new MeasureVariations(variations))
-            .createNoValue());
+          variations[period.getIndex() - 1] = new Double(bag.count(severity));
         }
+        Metric metric = metricRepository.getByKey(metricKey);
+        measureRepository.add(component, metric, Measure.newMeasureBuilder()
+          .setVariations(new MeasureVariations(variations))
+          .createNoValue());
       }
     }
   }
index 10bd8af2edfc00981fd5b38843f7c812d9491aca..31856ec5b6f2f8c67186095567d5762268f4e7a1 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.computation.issue;
 
 import java.util.Date;
+import javax.annotation.Nullable;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.utils.internal.Uuids;
 import org.sonar.core.issue.DefaultIssue;
@@ -28,15 +29,25 @@ import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.workflow.IssueWorkflow;
 import org.sonar.server.computation.batch.BatchReportReader;
 
+/**
+ * Sets the appropriate fields when an issue is :
+ * <ul>
+ *   <li>newly created</li>
+ *   <li>merged the related base issue</li>
+ *   <li>relocated (only manual issues)</li>
+ * </ul>
+ */
 public class IssueLifecycle {
 
   private final IssueWorkflow workflow;
   private final IssueChangeContext changeContext;
   private final IssueUpdater updater;
+  private final DebtCalculator debtCalculator;
 
-  public IssueLifecycle(BatchReportReader reportReader, IssueWorkflow workflow, IssueUpdater updater) {
+  public IssueLifecycle(BatchReportReader reportReader, IssueWorkflow workflow, IssueUpdater updater, DebtCalculator debtCalculator) {
     this.workflow = workflow;
     this.updater = updater;
+    this.debtCalculator = debtCalculator;
     this.changeContext = IssueChangeContext.createScan(new Date(reportReader.readMetadata().getAnalysisDate()));
   }
 
@@ -45,6 +56,7 @@ public class IssueLifecycle {
     issue.setCreationDate(changeContext.date());
     issue.setUpdateDate(changeContext.date());
     issue.setStatus(Issue.STATUS_OPEN);
+    issue.setDebt(debtCalculator.calculate(issue));
   }
 
   public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
@@ -59,6 +71,8 @@ public class IssueLifecycle {
     raw.setAssignee(base.assignee());
     raw.setAuthorLogin(base.authorLogin());
     raw.setTags(base.tags());
+    raw.setDebt(debtCalculator.calculate(raw));
+    raw.setOnDisabledRule(base.isOnDisabledRule());
     if (base.manualSeverity()) {
       raw.setManualSeverity(true);
       raw.setSeverity(base.severity());
@@ -66,7 +80,7 @@ public class IssueLifecycle {
       updater.setPastSeverity(raw, base.severity(), changeContext);
     }
 
-    // TODO attributes + changelog
+    // TODO attributes
 
     // fields coming from raw
     updater.setPastLine(raw, base.getLine());
@@ -76,6 +90,10 @@ public class IssueLifecycle {
     raw.setSelectedAt(base.selectedAt());
   }
 
+  public void moveOpenManualIssue(DefaultIssue manualIssue, @Nullable Integer toLine) {
+    updater.setLine(manualIssue, toLine);
+  }
+
   public void doAutomaticTransition(DefaultIssue issue) {
     workflow.doAutomaticTransition(issue, changeContext);
   }
index 7385ff05326043b3c4cd8499247fd40fbf9f1027..6b3768dc26bc9a8e1686e14ce1fbd96d50027d94 100644 (file)
@@ -34,8 +34,9 @@ public abstract class IssueVisitor {
   }
 
   /**
-   * This method is called when tracking is done and issue is initialized. That means that the following fields
-   * are set: resolution, status, line, creation date, uuid and all the fields merged from base issues.
+   * This method is called for each issue of a component when tracking is done and issue is initialized.
+   * That means that the following fields are set: resolution, status, line, creation date, uuid
+   * and all the fields merged from base issues.
    */
   public void onIssue(Component component, DefaultIssue issue) {
 
index dd212257ab98f72c9028a33babf3720df7802f9f..d26b324ff14d1a816830159e3897c1e7aab6168b 100644 (file)
@@ -64,27 +64,27 @@ public class NewDebtAggregator extends IssueVisitor {
   public void beforeComponent(Component component, Tracking tracking) {
     currentSum = new DebtSum();
     sumsByComponentRef.put(component.getRef(), currentSum);
-    List<IssueChangeDto> changes = dbClient.issueChangeDao().selectChangelogOfUnresolvedIssuesByComponent(component.getUuid());
+    List<IssueChangeDto> changes = dbClient.issueChangeDao().selectChangelogOfNonClosedIssuesByComponent(component.getUuid());
     for (IssueChangeDto change : changes) {
       changesByIssueUuid.put(change.getIssueKey(), change);
     }
+    for (Component child : component.getChildren()) {
+      DebtSum childSum = sumsByComponentRef.remove(child.getRef());
+      if (childSum != null) {
+        currentSum.add(childSum);
+      }
+    }
   }
 
   @Override
   public void onIssue(Component component, DefaultIssue issue) {
-    if (issue.debtInMinutes() != null && !periodsHolder.getPeriods().isEmpty()) {
+    if (issue.resolution() == null && issue.debtInMinutes() != null && !periodsHolder.getPeriods().isEmpty()) {
       List<IssueChangeDto> changelog = changesByIssueUuid.get(issue.key());
       for (Period period : periodsHolder.getPeriods()) {
         long newDebt = calculator.calculate(issue, changelog, period);
         currentSum.add(period.getIndex(), newDebt);
       }
     }
-    for (Component child : component.getChildren()) {
-      DebtSum childSum = sumsByComponentRef.remove(child.getRef());
-      if (childSum != null) {
-        currentSum.add(childSum);
-      }
-    }
   }
 
   @Override
@@ -99,7 +99,7 @@ public class NewDebtAggregator extends IssueVisitor {
   }
 
   private static class DebtSum {
-    private Double[] sums = new Double[PeriodsHolder.MAX_NUMBER_OF_PERIODS];
+    private final Double[] sums = new Double[PeriodsHolder.MAX_NUMBER_OF_PERIODS];
     private boolean isEmpty = true;
 
     void add(int periodIndex, long newDebt) {
index 6d7c85941c3f198e7b6fe13430967d27207701fc..ed7502e7b22413580610515a8d9c89f7ff6cdd7e 100644 (file)
@@ -22,8 +22,10 @@ package org.sonar.server.computation.issue;
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.Iterator;
@@ -46,16 +48,22 @@ import static com.google.common.collect.FluentIterable.from;
  */
 public class NewDebtCalculator {
 
-  public long calculate(DefaultIssue issue, List<IssueChangeDto> debtChangelog, Period period) {
+  public long calculate(DefaultIssue issue, Collection<IssueChangeDto> debtChangelog, Period period) {
+
     if (issue.creationDate().getTime() > period.getSnapshotDate() + 1000L) {
       return Objects.firstNonNull(issue.debtInMinutes(), 0L);
     }
     return calculateFromChangelog(issue, debtChangelog, period.getSnapshotDate());
   }
 
-  private long calculateFromChangelog(DefaultIssue issue, List<IssueChangeDto> debtChangelog, long periodDate) {
+  private long calculateFromChangelog(DefaultIssue issue, Collection<IssueChangeDto> debtChangelog, long periodDate) {
     List<FieldDiffs> debtDiffs = from(debtChangelog).transform(ToFieldDiffs.INSTANCE).filter(HasDebtChange.INSTANCE).toSortedList(CHANGE_ORDERING);
-    long newDebt = issue.debtInMinutes().longValue();
+    FieldDiffs currentChange = issue.currentChange();
+    if (currentChange != null && HasDebtChange.INSTANCE.apply(currentChange)) {
+      debtDiffs = Lists.newArrayList(debtDiffs);
+      debtDiffs.add(currentChange);
+    }
+    long newDebt = issue.debtInMinutes();
 
     for (Iterator<FieldDiffs> it = debtDiffs.iterator(); it.hasNext();) {
       FieldDiffs diffs = it.next();
@@ -80,7 +88,7 @@ public class NewDebtCalculator {
   @CheckForNull
   private static long subtract(long newDebt, @Nullable Long with) {
     if (with != null) {
-      return Math.min(0L, newDebt - with);
+      return Math.max(0L, newDebt - with);
     }
     return newDebt;
   }
@@ -108,13 +116,7 @@ public class NewDebtCalculator {
     INSTANCE;
     @Override
     public FieldDiffs apply(@Nonnull IssueChangeDto dto) {
-      FieldDiffs diffs = FieldDiffs.parse(dto.getChangeData());
-      diffs.setIssueKey(dto.getIssueKey());
-      Long date = dto.getIssueChangeCreationDate();
-      if (date != null) {
-        diffs.setCreationDate(new Date(date));
-      }
-      return diffs;
+      return dto.toFieldDiffs();
     }
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/Rule.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/Rule.java
new file mode 100644 (file)
index 0000000..acf8bd3
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+
+public interface Rule {
+
+  int getId();
+
+  RuleKey getKey();
+
+  String getName();
+
+  RuleStatus getStatus();
+
+  /**
+   * Is activated in a Quality Profile associated to the project
+   */
+  boolean isActivated();
+
+  /**
+   * Get all tags, whatever system or user tags.
+   */
+  Set<String> getTags();
+
+  @CheckForNull
+  Integer getSubCharacteristicId();
+
+  @CheckForNull
+  DebtRemediationFunction getRemediationFunction();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java
deleted file mode 100644 (file)
index 6b6a07a..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.issue;
-
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.rule.RuleDto;
-import org.sonar.server.util.cache.MemoryCache;
-
-import javax.annotation.CheckForNull;
-
-/**
- * Cache of the rules involved during the current analysis
- */
-public class RuleCache extends MemoryCache<RuleKey, RuleDto> {
-
-  public RuleCache(RuleCacheLoader loader) {
-    super(loader);
-  }
-
-  @CheckForNull
-  public String ruleName(RuleKey key) {
-    RuleDto rule = getNullable(key);
-    return rule != null ? rule.getName() : null;
-  }
-}
index e6439fd5baa83896bf83cc555437af2db5ce484e..f655769e55d82411de2145601797030d43a36b89 100644 (file)
  */
 package org.sonar.server.computation.issue;
 
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.rule.RuleDto;
+import org.sonar.server.computation.batch.BatchReportReader;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.util.cache.CacheLoader;
 
-import java.util.Collection;
-import java.util.Map;
+import static com.google.common.collect.FluentIterable.from;
+import static org.sonar.core.rule.RuleKeyFunctions.stringToRuleKey;
 
-public class RuleCacheLoader implements CacheLoader<RuleKey, RuleDto> {
+public class RuleCacheLoader implements CacheLoader<RuleKey, Rule> {
 
   private final DbClient dbClient;
+  private final Set<RuleKey> activatedKeys;
 
-  public RuleCacheLoader(DbClient dbClient) {
+  public RuleCacheLoader(DbClient dbClient, BatchReportReader batchReportReader) {
     this.dbClient = dbClient;
+    this.activatedKeys = from(batchReportReader.readMetadata().getActiveRuleKeyList()).transform(stringToRuleKey()).toSet();
   }
 
   @Override
-  public RuleDto load(RuleKey key) {
+  public Rule load(RuleKey key) {
     DbSession session = dbClient.openSession(false);
     try {
-      return dbClient.ruleDao().getNullableByKey(session, key);
+      RuleDto dto = dbClient.ruleDao().getNullableByKey(session, key);
+      if (dto != null) {
+        return new RuleImpl(dto, activatedKeys.contains(key));
+      }
+      return null;
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
   @Override
-  public Map<RuleKey, RuleDto> loadAll(Collection<? extends RuleKey> keys) {
-    throw new UnsupportedOperationException("See RuleDao");
+  public Map<RuleKey, Rule> loadAll(Collection<? extends RuleKey> keys) {
+    throw new UnsupportedOperationException("Not implemented yet");
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleImpl.java
new file mode 100644 (file)
index 0000000..07548d5
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import com.google.common.base.Objects;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.core.rule.RuleDto;
+
+import static com.google.common.collect.Sets.union;
+
+@Immutable
+public class RuleImpl implements Rule {
+
+  private final int id;
+  private final RuleKey key;
+  private final String name;
+  private final RuleStatus status;
+  private final boolean isActivated;
+  private final Integer subCharacteristicId;
+  private final Set<String> tags;
+  private final DebtRemediationFunction remediationFunction;
+
+  public RuleImpl(RuleDto dto, boolean isActivated) {
+    this.id = dto.getId();
+    this.key = dto.getKey();
+    this.name = dto.getName();
+    this.status = dto.getStatus();
+    this.subCharacteristicId = effectiveCharacteristicId(dto);
+    this.tags = union(dto.getSystemTags(), dto.getTags());
+    this.remediationFunction = effectiveRemediationFunction(dto);
+    this.isActivated = isActivated;
+  }
+
+  @Override
+  public int getId() {
+    return id;
+  }
+
+  @Override
+  public RuleKey getKey() {
+    return key;
+  }
+
+  @Override
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public RuleStatus getStatus() {
+    return status;
+  }
+
+  @Override
+  public boolean isActivated() {
+    return isActivated;
+  }
+
+  @Override
+  public Set<String> getTags() {
+    return tags;
+  }
+
+  @Override
+  public Integer getSubCharacteristicId() {
+    return subCharacteristicId;
+  }
+
+  @Override
+  public DebtRemediationFunction getRemediationFunction() {
+    return remediationFunction;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    RuleImpl rule = (RuleImpl) o;
+    return key.equals(rule.key);
+  }
+
+  @Override
+  public int hashCode() {
+    return key.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this)
+      .add("id", id)
+      .add("key", key)
+      .add("name", name)
+      .add("status", status)
+      .add("isActivated", isActivated)
+      .add("subCharacteristicId", subCharacteristicId)
+      .add("tags", tags)
+      .toString();
+  }
+
+  @CheckForNull
+  private static Integer effectiveCharacteristicId(RuleDto dto) {
+    if (isEnabledCharacteristicId(dto.getSubCharacteristicId())) {
+      return dto.getSubCharacteristicId();
+    }
+    if (isEnabledCharacteristicId(dto.getDefaultSubCharacteristicId())) {
+      return dto.getDefaultSubCharacteristicId();
+    }
+    return null;
+  }
+
+  private static boolean isEnabledCharacteristicId(@Nullable Integer id) {
+    return (id != null) && (id.intValue() != RuleDto.DISABLED_CHARACTERISTIC_ID);
+  }
+
+  @CheckForNull
+  private static DebtRemediationFunction effectiveRemediationFunction(RuleDto dto) {
+    String fn = dto.getRemediationFunction();
+    if (fn != null) {
+      return new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.valueOf(fn), dto.getRemediationCoefficient(), dto.getRemediationOffset());
+    }
+    String defaultFn = dto.getDefaultRemediationFunction();
+    if (defaultFn != null) {
+      return new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.valueOf(defaultFn), dto.getDefaultRemediationCoefficient(), dto.getDefaultRemediationOffset());
+    }
+    return null;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java
new file mode 100644 (file)
index 0000000..3b86d2c
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import org.sonar.api.rule.RuleKey;
+
+public interface RuleRepository {
+
+  Rule getByKey(RuleKey key);
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepositoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepositoryImpl.java
new file mode 100644 (file)
index 0000000..ee3314c
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import org.sonar.api.rule.RuleKey;
+import org.sonar.server.util.cache.MemoryCache;
+
+public class RuleRepositoryImpl implements RuleRepository {
+
+  private final MemoryCache<RuleKey, Rule> cache;
+
+  public RuleRepositoryImpl(RuleCacheLoader cacheLoader) {
+    this.cache = new MemoryCache<>(cacheLoader);
+  }
+
+  @Override
+  public Rule getByKey(RuleKey key) {
+    return cache.get(key);
+  }
+}
index 75530ff601d51f4189cb7d4b77467ec18a753663..c44f4710b9c91188fb23ff336b79c06912cd76d5 100644 (file)
  */
 package org.sonar.server.computation.issue;
 
-import java.util.Set;
 import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.rule.RuleDto;
 import org.sonar.server.computation.component.Component;
 
 import static com.google.common.collect.Sets.union;
 
 public class RuleTagsCopier extends IssueVisitor {
 
-  private final RuleCache ruleCache;
+  private final RuleRepository ruleRepository;
 
-  public RuleTagsCopier(RuleCache ruleCache) {
-    this.ruleCache = ruleCache;
+  public RuleTagsCopier(RuleRepository ruleRepository) {
+    this.ruleRepository = ruleRepository;
   }
 
   @Override
   public void onIssue(Component component, DefaultIssue issue) {
     if (issue.isNew()) {
       // analyzer can provide some tags. They must be merged with rule tags
-      RuleDto rule = ruleCache.get(issue.ruleKey());
-      Set<String> ruleTags = union(rule.getTags(), rule.getSystemTags());
-      issue.setTags(union(issue.tags(), ruleTags));
+      Rule rule = ruleRepository.getByKey(issue.ruleKey());
+      issue.setTags(union(issue.tags(), rule.getTags()));
     }
   }
 }
index c1035975b09849096def19817e2013138c750428..6eb42c29ba8005ff39a9e543518d3e58614856b6 100644 (file)
  */
 package org.sonar.server.computation.issue;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.collect.Collections2;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.server.user.index.UserDoc;
 import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.util.cache.CacheLoader;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
 /**
  * Loads the association between a SCM account and a SQ user
  */
 public class ScmAccountToUserLoader implements CacheLoader<String, String> {
 
-  private final Logger log;
+  private static final Logger log = Loggers.get(ScmAccountToUserLoader.class);
   private final UserIndex index;
 
   public ScmAccountToUserLoader(UserIndex index) {
-    this(index, Loggers.get(ScmAccountToUserLoader.class));
-  }
-
-  @VisibleForTesting
-  ScmAccountToUserLoader(UserIndex index, Logger log) {
-    this.log = log;
     this.index = index;
   }
 
@@ -60,12 +54,7 @@ public class ScmAccountToUserLoader implements CacheLoader<String, String> {
     if (!users.isEmpty()) {
       // multiple users are associated to the same SCM account, for example
       // the same email
-      Collection<String> logins = Collections2.transform(users, new Function<UserDoc, String>() {
-        @Override
-        public String apply(UserDoc input) {
-          return input.login();
-        }
-      });
+      Collection<String> logins = Collections2.transform(users, UserDocToLogin.INSTANCE);
       log.warn(String.format("Multiple users share the SCM account '%s': %s", scmAccount, Joiner.on(", ").join(logins)));
     }
     return null;
@@ -75,4 +64,13 @@ public class ScmAccountToUserLoader implements CacheLoader<String, String> {
   public Map<String, String> loadAll(Collection<? extends String> scmAccounts) {
     throw new UnsupportedOperationException("Loading by multiple scm accounts is not supported yet");
   }
+
+  private enum UserDocToLogin implements Function<UserDoc, String> {
+    INSTANCE;
+    @Nullable
+    @Override
+    public String apply(@Nonnull UserDoc user) {
+      return user.login();
+    }
+  }
 }
index e61e9a578bae38f728593169d8a3826151823896..6f7cc4640cfcce70beceb443333e9ff54d8a8de3 100644 (file)
@@ -26,8 +26,7 @@ import java.util.Collections;
 import java.util.List;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.Duration;
-import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.KeyValueFormat;
 import org.sonar.batch.protocol.output.BatchReport;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.tracking.Input;
@@ -41,12 +40,10 @@ public class TrackerRawInputFactory {
 
   private final TreeRootHolder treeRootHolder;
   private final BatchReportReader reportReader;
-  private final RuleCache ruleCache;
 
-  public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, RuleCache ruleCache) {
+  public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader) {
     this.treeRootHolder = treeRootHolder;
     this.reportReader = reportReader;
-    this.ruleCache = ruleCache;
   }
 
   public Input<DefaultIssue> create(Component component) {
@@ -80,10 +77,7 @@ public class TrackerRawInputFactory {
         LineHashSequence lineHashSeq = getLineHashSequence();
         for (BatchReport.Issue reportIssue : reportIssues) {
           DefaultIssue issue = toIssue(lineHashSeq, reportIssue);
-          if (isValid(issue, lineHashSeq)) {
-            Loggers.get(getClass()).info("Loaded from report: " + issue);
-            issues.add(issue);
-          }
+          issues.add(issue);
         }
       }
       return issues;
@@ -114,25 +108,11 @@ public class TrackerRawInputFactory {
       if (reportIssue.hasEffortToFix()) {
         issue.setEffortToFix(reportIssue.getEffortToFix());
       }
-      if (reportIssue.hasDebtInMinutes()) {
-        issue.setDebt(Duration.create(reportIssue.getDebtInMinutes()));
-      }
       issue.setTags(Sets.newHashSet(reportIssue.getTagList()));
-      // TODO issue attributes
+      if (reportIssue.hasAttributes()) {
+        issue.setAttributes(KeyValueFormat.parse(reportIssue.getAttributes()));
+      }
       return issue;
     }
   }
-
-  private boolean isValid(DefaultIssue issue, LineHashSequence lineHashSeq) {
-    // TODO log debug when invalid ? Or throw exception ?
-
-    if (ruleCache.getNullable(issue.ruleKey()) == null) {
-      return false;
-    }
-    if (issue.getLine() != null && !lineHashSeq.hasLine(issue.getLine())) {
-      // FIXME
-      //return false;
-    }
-    return true;
-  }
 }
index f671559674f8a5b0718b62f3e85011047ad33a47..8c4cb7c8f8f974833243779983baa302273c73b9 100644 (file)
@@ -51,7 +51,7 @@ public class PeriodsHolderImpl implements PeriodsHolder {
   public void setPeriods(Iterable<Period> periods) {
     requireNonNull(periods, "Periods cannot be null");
     checkArgument(Iterables.size(periods) <= MAX_NUMBER_OF_PERIODS, String.format("There can not be more than %d periods", MAX_NUMBER_OF_PERIODS));
-      checkState(this.periods == null, "Periods have already been initialized");
+    checkState(this.periods == null, "Periods have already been initialized");
 
     Period[] newPeriods = new Period[MAX_NUMBER_OF_PERIODS];
     for (Period period : from(periods).filter(CheckNotNull.INSTANCE)) {
index bc5d9633880851c4fc5fb215aea0f64edf1f38ff..1d7f0e161e53dc70975a7e6a30c43e99c02daeb8 100644 (file)
@@ -52,10 +52,10 @@ public class ComputationSteps {
 
       // data computation
       IntegrateIssuesStep.class,
-      ComputeIssueMeasuresStep.class,
       CustomMeasuresCopyStep.class,
       ComputeFormulaMeasuresStep.class,      
-      CustomMeasuresCopyStep.class,
+
+      // SQALE measures depend on issues
       SqaleMeasuresStep.class,
       NewCoverageMeasuresStep.class,
       NewCoverageAggregationStep.class,
index f9c08506ed514fe842d22726b6e1227e73b99fd8..c1c234d32e2fdba1c22871e79ffcc526759531a0 100644 (file)
@@ -61,7 +61,7 @@ public class IntegrateIssuesStep implements ComputationStep {
   @Override
   public void execute() {
     // all the components that had issues before this analysis
-    final Set<String> unprocessedComponentUuids = Sets.newHashSet(baseIssuesLoader.loadComponentUuids());
+    final Set<String> unprocessedComponentUuids = Sets.newHashSet(baseIssuesLoader.loadUuidsOfComponentsWithOpenIssues());
 
     new DepthTraversalTypeAwareVisitor(Component.Type.FILE, POST_ORDER) {
       @Override
@@ -76,8 +76,6 @@ public class IntegrateIssuesStep implements ComputationStep {
 
   private void processIssues(Component component) {
     Tracking<DefaultIssue, DefaultIssue> tracking = tracker.track(component);
-    Loggers.get(getClass()).info("----- tracking ------");
-    Loggers.get(getClass()).info("" + tracking);
     DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender();
     try {
       issueVisitors.beforeComponent(component, tracking);
@@ -92,44 +90,35 @@ public class IntegrateIssuesStep implements ComputationStep {
 
   private void fillNewOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
     Set<DefaultIssue> issues = tracking.getUnmatchedRaws();
-    Loggers.get(getClass()).info("----- fillNewOpenIssues on " + component.getKey());
     for (DefaultIssue issue : issues) {
       issueLifecycle.initNewOpenIssue(issue);
-      Loggers.get(getClass()).info("new " + issue);
       process(component, issue, cacheAppender);
     }
-    Loggers.get(getClass()).info("----- /fillNewOpenIssues on " + component.getKey());
   }
 
   private void fillExistingOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
-    Loggers.get(getClass()).info("----- fillExistingOpenIssues on " + component.getKey());
     for (Map.Entry<DefaultIssue, DefaultIssue> entry : tracking.getMatchedRaws().entrySet()) {
       DefaultIssue raw = entry.getKey();
       DefaultIssue base = entry.getValue();
       issueLifecycle.mergeExistingOpenIssue(raw, base);
-      Loggers.get(getClass()).info("merged " + raw);
       process(component, raw, cacheAppender);
     }
     for (Map.Entry<Integer, DefaultIssue> entry : tracking.getOpenManualIssuesByLine().entries()) {
-      int line = entry.getKey();
+      Integer line = entry.getKey();
       DefaultIssue manualIssue = entry.getValue();
-      manualIssue.setLine(line == 0 ? null : line);
-      Loggers.get(getClass()).info("kept manual " + manualIssue);
+      issueLifecycle.moveOpenManualIssue(manualIssue, line);
       process(component, manualIssue, cacheAppender);
     }
-    Loggers.get(getClass()).info("----- /fillExistingOpenIssues on " + component.getKey());
   }
 
   private void closeUnmatchedBaseIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
-    Loggers.get(getClass()).info("----- closeUnmatchedBaseIssues on " + component.getKey());
     for (DefaultIssue issue : tracking.getUnmatchedBases()) {
+      Loggers.get(getClass()).info("--- close base " + issue);
       // TODO should replace flag "beingClosed" by express call to transition "automaticClose"
       issue.setBeingClosed(true);
-      Loggers.get(getClass()).info("closing " + issue);
       // TODO manual issues -> was updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);. Is it a problem ?
       process(component, issue, cacheAppender);
     }
-    Loggers.get(getClass()).info("----- /closeUnmatchedBaseIssues on " + component.getKey());
   }
 
   private void process(Component component, DefaultIssue issue, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
@@ -145,10 +134,9 @@ public class IntegrateIssuesStep implements ComputationStep {
         List<DefaultIssue> issues = baseIssuesLoader.loadForComponentUuid(deletedComponentUuid);
         for (DefaultIssue issue : issues) {
           issue.setBeingClosed(true);
-          // FIXME should be renamed "setToRemovedStatus"
-          issue.setOnDisabledRule(true);
+          // TODO should be renamed
+          issue.setOnDisabledRule(false);
           issueLifecycle.doAutomaticTransition(issue);
-          // TODO execute visitors ? Component is currently missing.
           cacheAppender.append(issue);
         }
       }
index a5ac16a7477a41ac6d5a9608562b1c5d0ac40b80..6ffda565d47fa3232e04dbec9669d074ec558cca 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.computation.step;
 
 import org.sonar.api.issue.IssueComment;
 import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.DefaultIssueComment;
 import org.sonar.core.issue.FieldDiffs;
@@ -33,7 +32,7 @@ import org.sonar.core.issue.db.UpdateConflictResolver;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.server.computation.issue.IssueCache;
-import org.sonar.server.computation.issue.RuleCache;
+import org.sonar.server.computation.issue.RuleRepository;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.util.CloseableIterator;
 
@@ -42,15 +41,15 @@ public class PersistIssuesStep implements ComputationStep {
   private final DbClient dbClient;
   private final System2 system2;
   private final UpdateConflictResolver conflictResolver;
-  private final RuleCache ruleCache;
+  private final RuleRepository ruleRepository;
   private final IssueCache issueCache;
 
   public PersistIssuesStep(DbClient dbClient, System2 system2, UpdateConflictResolver conflictResolver,
-    RuleCache ruleCache, IssueCache issueCache) {
+    RuleRepository ruleRepository, IssueCache issueCache) {
     this.dbClient = dbClient;
     this.system2 = system2;
     this.conflictResolver = conflictResolver;
-    this.ruleCache = ruleCache;
+    this.ruleRepository = ruleRepository;
     this.issueCache = issueCache;
   }
 
@@ -66,25 +65,19 @@ public class PersistIssuesStep implements ComputationStep {
         DefaultIssue issue = issues.next();
         boolean saved = false;
         if (issue.isNew()) {
-          Integer ruleId = ruleCache.get(issue.ruleKey()).getId();
+          Integer ruleId = ruleRepository.getByKey(issue.ruleKey()).getId();
           IssueDto dto = IssueDto.toDtoForComputationInsert(issue, ruleId, system2.now());
-          Loggers.get(getClass()).info("---- INSERT " + dto);
           mapper.insert(dto);
           saved = true;
         } else if (issue.isChanged()) {
           IssueDto dto = IssueDto.toDtoForUpdate(issue, system2.now());
-          Loggers.get(getClass()).info("---- UPDATE " + dto);
           int updateCount = mapper.updateIfBeforeSelectedDate(dto);
           if (updateCount == 0) {
-            Loggers.get(getClass()).info("---- CONFLICT: " + updateCount);
             // End-user and scan changed the issue at the same time.
             // See https://jira.sonarsource.com/browse/SONAR-4309
             conflictResolver.resolve(issue, mapper);
           }
-
           saved = true;
-        } else {
-          Loggers.get(getClass()).info("---- UNCHANGED "  + issue);
         }
         if (saved) {
           insertChanges(changeMapper, issue);
index 2c8aa110430b3cd44ccab76191573d4c12120c1f..f55a33ac0ad30ef7fd77b0370d66fcb48740576b 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.server.computation.batch.BatchReportReader;
 import org.sonar.server.computation.component.Component;
 import org.sonar.server.computation.component.TreeRootHolder;
 import org.sonar.server.computation.issue.IssueCache;
-import org.sonar.server.computation.issue.RuleCache;
+import org.sonar.server.computation.issue.RuleRepository;
 import org.sonar.server.issue.notification.IssueChangeNotification;
 import org.sonar.server.issue.notification.MyNewIssuesNotification;
 import org.sonar.server.issue.notification.NewIssuesNotification;
@@ -49,14 +49,15 @@ public class SendIssueNotificationsStep implements ComputationStep {
   static final Set<String> NOTIF_TYPES = ImmutableSet.of(IssueChangeNotification.TYPE, NewIssuesNotification.TYPE, MyNewIssuesNotification.MY_NEW_ISSUES_NOTIF_TYPE);
 
   private final IssueCache issueCache;
-  private final RuleCache rules;
+  private final RuleRepository rules;
   private final TreeRootHolder treeRootHolder;
   private final NotificationService service;
   private final BatchReportReader reportReader;
   private NewIssuesNotificationFactory newIssuesNotificationFactory;
 
-  public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules, TreeRootHolder treeRootHolder, NotificationService service,
-    BatchReportReader reportReader, NewIssuesNotificationFactory newIssuesNotificationFactory) {
+  public SendIssueNotificationsStep(IssueCache issueCache, RuleRepository rules, TreeRootHolder treeRootHolder,
+    NotificationService service, BatchReportReader reportReader,
+    NewIssuesNotificationFactory newIssuesNotificationFactory) {
     this.issueCache = issueCache;
     this.rules = rules;
     this.treeRootHolder = treeRootHolder;
@@ -84,7 +85,7 @@ public class SendIssueNotificationsStep implements ComputationStep {
           newIssuesStats.add(issue);
         } else if (issue.isChanged() && issue.mustSendNotifications()) {
           IssueChangeNotification changeNotification = new IssueChangeNotification();
-          changeNotification.setRuleName(rules.ruleName(issue.ruleKey()));
+          changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName());
           changeNotification.setIssue(issue);
           changeNotification.setProject(project.getKey(), projectName);
           service.deliver(changeNotification);
index 134a9414a4bf40b3bd5d13041dfddf689d99fc05..15c65e354cdd85504ac08c7167f51eff147875b1 100644 (file)
@@ -50,6 +50,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.persistence.DbSession;
+import org.sonar.core.rule.RuleKeyFunctions;
 import org.sonar.server.component.ComponentService;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.issue.filter.IssueFilterParameters;
@@ -419,12 +420,7 @@ public class IssueQueryService {
   @CheckForNull
   private static Collection<RuleKey> stringsToRules(@Nullable Collection<String> rules) {
     if (rules != null) {
-      return newArrayList(Iterables.transform(rules, new Function<String, RuleKey>() {
-        @Override
-        public RuleKey apply(@Nullable String s) {
-          return s != null ? RuleKey.parse(s) : null;
-        }
-      }));
+      return newArrayList(Iterables.transform(rules, RuleKeyFunctions.stringToRuleKey()));
     }
     return null;
   }
index a9172d9842e9ba457ce1cbb049f16a800d05fd74..09556a133636b50ede87956c47322c08354a94fa 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//
-//package org.sonar.server.computation.debt;
-//
-//import java.util.Arrays;
-//import java.util.Collections;
-//import org.junit.Rule;
-//import org.junit.Test;
-//import org.junit.rules.ExpectedException;
-//
-//import static java.util.Collections.singletonList;
-//
-//public class DebtModelHolderImplTest {
-//
-//  @Rule
-//  public ExpectedException thrown = ExpectedException.none();
-//
-//  private static final Characteristic PORTABILITY = new Characteristic(1, "PORTABILITY", null);
-//  private static final Characteristic COMPILER_RELATED_PORTABILITY = new Characteristic(2, "COMPILER_RELATED_PORTABILITY", 1);
-//  private static final Characteristic HARDWARE_RELATED_PORTABILITY = new Characteristic(3, "HARDWARE_RELATED_PORTABILITY", 1);
-//
-//  private static final Characteristic MAINTAINABILITY = new Characteristic(4, "MAINTAINABILITY", null);
-//  private static final Characteristic READABILITY = new Characteristic(5, "READABILITY", null);
-//
-//  DebtModelHolderImpl sut = new DebtModelHolderImpl();
-//
-//  @Test
-//  public void add_characteristics() throws Exception {
-//    sut.addCharacteristics(PORTABILITY, Arrays.asList(COMPILER_RELATED_PORTABILITY, HARDWARE_RELATED_PORTABILITY));
-//    sut.addCharacteristics(MAINTAINABILITY, singletonList(READABILITY));
-//
-//    assertThat(sut.findRootCharacteristics()).hasSize(2);
-//    assertThat(sut.findSubCharacteristicsByRootKey("PORTABILITY")).hasSize(2);
-//  }
-//
-//  @Test
-//  public void add_characteristics_fail_with_NPE_if_root_characteristic_is_null() throws Exception {
-//    thrown.expect(NullPointerException.class);
-//    thrown.expectMessage("rootCharacteristic cannot be null");
-//
-//    sut.addCharacteristics(null, singletonList(COMPILER_RELATED_PORTABILITY));
-//  }
-//
-//  @Test
-//  public void add_characteristics_fail_with_NPE_if_sub_characteristics_are_null() throws Exception {
-//    thrown.expect(NullPointerException.class);
-//    thrown.expectMessage("subCharacteristics cannot be null");
-//
-//    sut.addCharacteristics(PORTABILITY, null);
-//  }
-//
-//  @Test
-//  public void add_characteristics_fail_with_IAE_if_sub_characteristics_are_empty() throws Exception {
-//    thrown.expect(IllegalArgumentException.class);
-//    thrown.expectMessage("subCharacteristics cannot be empty");
-//
-//    sut.addCharacteristics(PORTABILITY, Collections.<Characteristic>emptyList());
-//  }
-//
-//  @Test
-//  public void get_characteristic_by_key() throws Exception {
-//    sut.addCharacteristics(PORTABILITY, singletonList(COMPILER_RELATED_PORTABILITY));
-//
-//    assertThat(sut.getCharacteristicByKey("PORTABILITY").get()).isEqualTo(PORTABILITY);
-//    assertThat(sut.getCharacteristicByKey("COMPILER_RELATED_PORTABILITY").get()).isEqualTo(COMPILER_RELATED_PORTABILITY);
-//    assertThat(sut.getCharacteristicByKey("UNKNOWN").isPresent()).isFalse();
-//  }
-//
-//  @Test
-//  public void get_characteristic_by_key_throws_ISE_when_not_initialized() throws Exception {
-//    thrown.expect(IllegalStateException.class);
-//    thrown.expectMessage("Characteristics have not been initialized yet");
-//
-//    sut.getCharacteristicByKey("PORTABILITY");
-//  }
-//
-//  @Test
-//  public void get_root_characteristics() throws Exception {
-//    sut.addCharacteristics(PORTABILITY, Arrays.asList(COMPILER_RELATED_PORTABILITY, READABILITY));
-//    sut.addCharacteristics(MAINTAINABILITY, singletonList(READABILITY));
-//
-//    assertThat(sut.findRootCharacteristics()).hasSize(2);
-//  }
-//
-//  @Test
-//  public void get_root_characteristics_throws_ISE_when_not_initialized() throws Exception {
-//    thrown.expect(IllegalStateException.class);
-//    thrown.expectMessage("Characteristics have not been initialized yet");
-//
-//    sut.findRootCharacteristics();
-//  }
-//
-//  @Test
-//  public void get_sub_characteristics_by_root_key() throws Exception {
-//    sut.addCharacteristics(PORTABILITY, Arrays.asList(COMPILER_RELATED_PORTABILITY, READABILITY));
-//
-//    assertThat(sut.findSubCharacteristicsByRootKey("PORTABILITY")).hasSize(2);
-//    assertThat(sut.findSubCharacteristicsByRootKey("UNKNOWN")).isEmpty();
-//  }
-//
-//  @Test
-//  public void get_sub_characteristics_by_root_key_throws_a_ISE_when_not_initialized() throws Exception {
-//    thrown.expect(IllegalStateException.class);
-//    thrown.expectMessage("Characteristics have not been initialized yet");
-//
-//    sut.findSubCharacteristicsByRootKey("PORTABILITY");
-//  }
-//}
+package org.sonar.server.computation.debt;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DebtModelHolderImplTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private static final Characteristic PORTABILITY = new Characteristic(1, "PORTABILITY", null);
+  private static final Characteristic COMPILER_RELATED_PORTABILITY = new Characteristic(2, "COMPILER_RELATED_PORTABILITY", 1);
+  private static final Characteristic HARDWARE_RELATED_PORTABILITY = new Characteristic(3, "HARDWARE_RELATED_PORTABILITY", 1);
+
+  private static final Characteristic MAINTAINABILITY = new Characteristic(4, "MAINTAINABILITY", null);
+  private static final Characteristic READABILITY = new Characteristic(5, "READABILITY", null);
+
+  DebtModelHolderImpl sut = new DebtModelHolderImpl();
+
+  @Test
+  public void add_and_get_characteristics() throws Exception {
+    sut.addCharacteristics(PORTABILITY, Arrays.asList(COMPILER_RELATED_PORTABILITY, HARDWARE_RELATED_PORTABILITY));
+    sut.addCharacteristics(MAINTAINABILITY, singletonList(READABILITY));
+
+    assertThat(sut.getRootCharacteristics()).hasSize(2);
+    assertThat(sut.getCharacteristicById(PORTABILITY.getId()).getKey()).isEqualTo("PORTABILITY");
+    assertThat(sut.getCharacteristicById(COMPILER_RELATED_PORTABILITY.getId()).getKey()).isEqualTo("COMPILER_RELATED_PORTABILITY");
+  }
+
+  @Test
+  public void add_characteristics_fail_with_NPE_if_root_characteristic_is_null() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("rootCharacteristic cannot be null");
+
+    sut.addCharacteristics(null, singletonList(COMPILER_RELATED_PORTABILITY));
+  }
+
+  @Test
+  public void add_characteristics_fail_with_NPE_if_sub_characteristics_are_null() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("subCharacteristics cannot be null");
+
+    sut.addCharacteristics(PORTABILITY, null);
+  }
+
+  @Test
+  public void add_characteristics_fail_with_IAE_if_sub_characteristics_are_empty() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("subCharacteristics cannot be empty");
+
+    sut.addCharacteristics(PORTABILITY, Collections.<Characteristic>emptyList());
+  }
+
+  @Test
+  public void get_root_characteristics() throws Exception {
+    sut.addCharacteristics(PORTABILITY, Arrays.asList(COMPILER_RELATED_PORTABILITY, READABILITY));
+    sut.addCharacteristics(MAINTAINABILITY, singletonList(READABILITY));
+
+    assertThat(sut.getRootCharacteristics()).hasSize(2);
+  }
+
+  @Test
+  public void getCharacteristicById_throws_ISE_when_not_initialized() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Characteristics have not been initialized yet");
+
+    sut.getCharacteristicById(1);
+  }
+
+  @Test
+  public void getRootCharacteristics_throws_ISE_when_not_initialized() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Characteristics have not been initialized yet");
+
+    sut.getRootCharacteristics();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CountIssuesListenerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CountIssuesListenerTest.java
deleted file mode 100644 (file)
index 5a7fa45..0000000
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.issue;
-
-import java.util.Date;
-import javax.annotation.Nullable;
-import org.assertj.core.data.Offset;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Tracking;
-import org.sonar.server.computation.batch.BatchReportReaderRule;
-import org.sonar.server.computation.batch.TreeRootHolderRule;
-import org.sonar.server.computation.component.Component;
-import org.sonar.server.computation.measure.MeasureRepository;
-import org.sonar.server.computation.measure.MeasureRepositoryImpl;
-import org.sonar.server.computation.measure.MeasureVariations;
-import org.sonar.server.computation.metric.Metric;
-import org.sonar.server.computation.metric.MetricImpl;
-import org.sonar.server.computation.metric.MetricRepository;
-import org.sonar.server.computation.period.Period;
-import org.sonar.server.computation.period.PeriodsHolderRule;
-import org.sonar.server.rule.RuleTesting;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
-import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
-import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
-import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY;
-import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY;
-import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY;
-import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY;
-import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY;
-import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
-import static org.sonar.api.rule.Severity.BLOCKER;
-import static org.sonar.api.rule.Severity.CRITICAL;
-import static org.sonar.api.rule.Severity.MAJOR;
-import static org.sonar.server.computation.component.DumbComponent.builder;
-import static org.sonar.server.computation.metric.Metric.MetricType.INT;
-
-public class CountIssuesListenerTest {
-
-  static final Component FILE1 = builder(Component.Type.FILE, 1).build();
-  static final Component FILE2 = builder(Component.Type.FILE, 2).build();
-  static final Component FILE3 = builder(Component.Type.FILE, 3).build();
-  static final Component PROJECT = builder(Component.Type.PROJECT, 4).addChildren(FILE1, FILE2, FILE3).build();
-
-  static final Metric ISSUES_METRIC = new MetricImpl(1, VIOLATIONS_KEY, VIOLATIONS_KEY, INT);
-  static final Metric OPEN_ISSUES_METRIC = new MetricImpl(2, OPEN_ISSUES_KEY, OPEN_ISSUES_KEY, INT);
-  static final Metric REOPENED_ISSUES_METRIC = new MetricImpl(3, REOPENED_ISSUES_KEY, REOPENED_ISSUES_KEY, INT);
-  static final Metric CONFIRMED_ISSUES_METRIC = new MetricImpl(4, CONFIRMED_ISSUES_KEY, CONFIRMED_ISSUES_KEY, INT);
-  static final Metric BLOCKER_ISSUES_METRIC = new MetricImpl(5, BLOCKER_VIOLATIONS_KEY, BLOCKER_VIOLATIONS_KEY, INT);
-  static final Metric CRITICAL_ISSUES_METRIC = new MetricImpl(6, CRITICAL_VIOLATIONS_KEY, CRITICAL_VIOLATIONS_KEY, INT);
-  static final Metric MAJOR_ISSUES_METRIC = new MetricImpl(7, MAJOR_VIOLATIONS_KEY, MAJOR_VIOLATIONS_KEY, INT);
-  static final Metric MINOR_ISSUES_METRIC = new MetricImpl(8, MINOR_VIOLATIONS_KEY, MINOR_VIOLATIONS_KEY, INT);
-  static final Metric INFO_ISSUES_METRIC = new MetricImpl(9, INFO_VIOLATIONS_KEY, INFO_VIOLATIONS_KEY, INT);
-  static final Metric NEW_ISSUES_METRIC = new MetricImpl(10, NEW_VIOLATIONS_KEY, NEW_VIOLATIONS_KEY, INT);
-  static final Metric NEW_BLOCKER_ISSUES_METRIC = new MetricImpl(11, NEW_BLOCKER_VIOLATIONS_KEY, NEW_BLOCKER_VIOLATIONS_KEY, INT);
-  static final Metric NEW_CRITICAL_ISSUES_METRIC = new MetricImpl(12, NEW_CRITICAL_VIOLATIONS_KEY, NEW_CRITICAL_VIOLATIONS_KEY, INT);
-  static final Metric NEW_MAJOR_ISSUES_METRIC = new MetricImpl(13, NEW_MAJOR_VIOLATIONS_KEY, NEW_MAJOR_VIOLATIONS_KEY, INT);
-  static final Metric NEW_MINOR_ISSUES_METRIC = new MetricImpl(14, NEW_MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY, INT);
-  static final Metric NEW_INFO_ISSUES_METRIC = new MetricImpl(15, NEW_INFO_VIOLATIONS_KEY, NEW_INFO_VIOLATIONS_KEY, INT);
-  static final Metric FALSE_POSITIVE_ISSUES_METRIC = new MetricImpl(16, FALSE_POSITIVE_ISSUES_KEY, FALSE_POSITIVE_ISSUES_KEY, INT);
-
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
-  @Rule
-  public PeriodsHolderRule periodsHolder = new PeriodsHolderRule();
-
-  Tracking tracking = mock(Tracking.class);
-  MetricRepository metricRepository = mock(MetricRepository.class);
-  MeasureRepository measureRepository;
-  IssueCounter sut;
-
-  @Before
-  public void setUp() throws Exception {
-    initMetrics();
-    measureRepository = new MeasureRepositoryImpl(null, reportReader, metricRepository);
-
-    sut = new IssueCounter(periodsHolder, metricRepository, measureRepository);
-  }
-
-  @Test
-  public void count_issues_by_status() throws Exception {
-    periodsHolder.setPeriods();
-
-    // bottom-up traversal -> from files to project
-    sut.beforeComponent(FILE1, tracking);
-    sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
-    sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
-    sut.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR));
-    sut.afterComponent(FILE1);
-
-    sut.beforeComponent(FILE2, tracking);
-    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
-    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
-    sut.afterComponent(FILE2);
-
-    sut.beforeComponent(FILE3, tracking);
-    sut.afterComponent(FILE3);
-
-    sut.beforeComponent(PROJECT, tracking);
-    sut.afterComponent(PROJECT);
-
-    // count by status
-    assertThat(measureRepository.getRawMeasure(FILE1, ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-    assertThat(measureRepository.getRawMeasure(FILE1, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-    assertThat(measureRepository.getRawMeasure(FILE1, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-    assertThat(measureRepository.getRawMeasure(FILE1, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-
-    assertThat(measureRepository.getRawMeasure(FILE2, ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
-    assertThat(measureRepository.getRawMeasure(FILE2, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-    assertThat(measureRepository.getRawMeasure(FILE2, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-    assertThat(measureRepository.getRawMeasure(FILE2, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
-
-    assertThat(measureRepository.getRawMeasure(FILE3, ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-
-    assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(3);
-    assertThat(measureRepository.getRawMeasure(PROJECT, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-    assertThat(measureRepository.getRawMeasure(PROJECT, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-    assertThat(measureRepository.getRawMeasure(PROJECT, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
-  }
-
-  @Test
-  public void count_unresolved_issues_by_severity() throws Exception {
-    periodsHolder.setPeriods();
-
-    // bottom-up traversal -> from files to project
-    sut.beforeComponent(FILE1, tracking);
-    sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
-    // this resolved issue is ignored
-    sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
-    sut.afterComponent(FILE1);
-
-    sut.beforeComponent(FILE2, tracking);
-    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
-    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
-    sut.afterComponent(FILE2);
-
-    sut.beforeComponent(PROJECT, tracking);
-    sut.afterComponent(PROJECT);
-
-    assertThat(measureRepository.getRawMeasure(FILE1, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-    assertThat(measureRepository.getRawMeasure(FILE1, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-    assertThat(measureRepository.getRawMeasure(FILE1, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-
-    assertThat(measureRepository.getRawMeasure(FILE2, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-    assertThat(measureRepository.getRawMeasure(FILE2, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-    assertThat(measureRepository.getRawMeasure(FILE2, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-
-    assertThat(measureRepository.getRawMeasure(PROJECT, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
-    assertThat(measureRepository.getRawMeasure(PROJECT, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
-    assertThat(measureRepository.getRawMeasure(PROJECT, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
-  }
-
-  @Test
-  public void count_new_issues() throws Exception {
-    Period period = newPeriod(3, 1500000000000L);
-    periodsHolder.setPeriods(period);
-
-    sut.beforeComponent(FILE1, tracking);
-    // created before -> existing issues
-    sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate() - 1000000L));
-    // created during the first analysis starting the period -> existing issues
-    sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate()));
-    // created after -> new issues
-    sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L));
-    sut.onIssue(FILE1, createIssueAt(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getSnapshotDate() + 200000L));
-    sut.afterComponent(FILE1);
-
-    sut.beforeComponent(FILE2, tracking);
-    sut.afterComponent(FILE2);
-
-    sut.beforeComponent(PROJECT, tracking);
-    sut.afterComponent(PROJECT);
-
-    assertVariation(FILE1, NEW_ISSUES_METRIC, period.getIndex(), 1);
-    assertVariation(FILE1, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1);
-    assertThat(measureRepository.getRawMeasure(FILE1, NEW_BLOCKER_ISSUES_METRIC).isPresent()).isFalse();
-    assertThat(measureRepository.getRawMeasure(FILE1, NEW_MAJOR_ISSUES_METRIC).isPresent()).isFalse();
-
-    assertVariation(PROJECT, NEW_ISSUES_METRIC, period.getIndex(), 1);
-    assertVariation(PROJECT, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1);
-    assertThat(measureRepository.getRawMeasure(PROJECT, NEW_BLOCKER_ISSUES_METRIC).isPresent()).isFalse();
-    assertThat(measureRepository.getRawMeasure(PROJECT, NEW_MAJOR_ISSUES_METRIC).isPresent()).isFalse();
-  }
-
-  private void assertVariation(Component component, Metric metric, int periodIndex, int expectedVariation) {
-    MeasureVariations variations = measureRepository.getRawMeasure(component, metric).get().getVariations();
-    assertThat(variations.getVariation(periodIndex)).isEqualTo((double) expectedVariation, Offset.offset(0.01));
-  }
-
-  private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) {
-    return new DefaultIssue()
-      .setResolution(resolution).setStatus(status)
-      .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
-      .setCreationDate(new Date());
-  }
-
-  private static DefaultIssue createIssueAt(@Nullable String resolution, String status, String severity, long creationDate) {
-    return new DefaultIssue()
-      .setResolution(resolution).setStatus(status)
-      .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
-      .setCreationDate(new Date(creationDate));
-  }
-
-  private static Period newPeriod(int index, long date) {
-    return new Period(index, "mode", null, date, 42l);
-  }
-
-  private void initMetrics() {
-    when(metricRepository.getByKey(ISSUES_METRIC.getKey())).thenReturn(ISSUES_METRIC);
-    when(metricRepository.getByKey(OPEN_ISSUES_METRIC.getKey())).thenReturn(OPEN_ISSUES_METRIC);
-    when(metricRepository.getByKey(REOPENED_ISSUES_METRIC.getKey())).thenReturn(REOPENED_ISSUES_METRIC);
-    when(metricRepository.getByKey(CONFIRMED_ISSUES_METRIC.getKey())).thenReturn(CONFIRMED_ISSUES_METRIC);
-    when(metricRepository.getByKey(BLOCKER_ISSUES_METRIC.getKey())).thenReturn(BLOCKER_ISSUES_METRIC);
-    when(metricRepository.getByKey(CRITICAL_ISSUES_METRIC.getKey())).thenReturn(CRITICAL_ISSUES_METRIC);
-    when(metricRepository.getByKey(MAJOR_ISSUES_METRIC.getKey())).thenReturn(MAJOR_ISSUES_METRIC);
-    when(metricRepository.getByKey(MINOR_ISSUES_METRIC.getKey())).thenReturn(MINOR_ISSUES_METRIC);
-    when(metricRepository.getByKey(INFO_ISSUES_METRIC.getKey())).thenReturn(INFO_ISSUES_METRIC);
-    when(metricRepository.getByKey(NEW_ISSUES_METRIC.getKey())).thenReturn(NEW_ISSUES_METRIC);
-    when(metricRepository.getByKey(NEW_BLOCKER_ISSUES_METRIC.getKey())).thenReturn(NEW_BLOCKER_ISSUES_METRIC);
-    when(metricRepository.getByKey(NEW_CRITICAL_ISSUES_METRIC.getKey())).thenReturn(NEW_CRITICAL_ISSUES_METRIC);
-    when(metricRepository.getByKey(NEW_MAJOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MAJOR_ISSUES_METRIC);
-    when(metricRepository.getByKey(NEW_MINOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MINOR_ISSUES_METRIC);
-    when(metricRepository.getByKey(NEW_INFO_ISSUES_METRIC.getKey())).thenReturn(NEW_INFO_ISSUES_METRIC);
-    when(metricRepository.getByKey(FALSE_POSITIVE_ISSUES_METRIC.getKey())).thenReturn(FALSE_POSITIVE_ISSUES_METRIC);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtAggregatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtAggregatorTest.java
new file mode 100644 (file)
index 0000000..2542ac0
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import com.google.common.base.Optional;
+import javax.annotation.CheckForNull;
+import org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.Duration;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.DumbComponent;
+import org.sonar.server.computation.debt.Characteristic;
+import org.sonar.server.computation.debt.DebtModelHolderImpl;
+import org.sonar.server.computation.debt.MutableDebtModelHolder;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+import org.sonar.server.rule.RuleTesting;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+
+public class DebtAggregatorTest {
+
+  /**
+   * Root characteristic
+   */
+  public static final int PORTABILITY_ID = 1000;
+
+  /**
+   * Sub-characteristic of {@link #PORTABILITY_ID}
+   */
+  public static final int PORTABILITY_SOFT_ID = 1001;
+
+  /**
+   * Sub-characteristic of {@link #PORTABILITY_ID}
+   */
+  public static final int PORTABILITY_HARD_ID = 1002;
+
+  /**
+   * Root characteristic
+   */
+  public static final int RELIABILITY_ID = 1003;
+
+  Component file = DumbComponent.builder(Component.Type.FILE, 1).build();
+  Component project = DumbComponent.builder(Component.Type.PROJECT, 2).addChildren(file).build();
+
+  DumbRule rule = new DumbRule(RuleTesting.XOO_X1).setId(100).setSubCharacteristicId(PORTABILITY_SOFT_ID);
+
+  @org.junit.Rule
+  public RuleRepositoryRule ruleRepository = new RuleRepositoryRule().add(rule);
+
+  MutableDebtModelHolder debtModelHolder = new DebtModelHolderImpl()
+    .addCharacteristics(new Characteristic(PORTABILITY_ID, "PORTABILITY", null),
+      asList(new Characteristic(PORTABILITY_SOFT_ID, "PORTABILITY_HARDWARE", PORTABILITY_ID), new Characteristic(PORTABILITY_HARD_ID, "PORTABILITY_SOFTWARE", PORTABILITY_ID)))
+    .addCharacteristics(new Characteristic(RELIABILITY_ID, "RELIABILITY", null),
+      asList(new Characteristic(1004, "DATA_RELIABILITY", RELIABILITY_ID))
+    );
+
+  @org.junit.Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(200, CoreMetrics.TECHNICAL_DEBT);
+
+  @org.junit.Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create();
+
+  DebtAggregator underTest = new DebtAggregator(ruleRepository, debtModelHolder, metricRepository, measureRepository);
+
+  @Test
+  public void sum_debt_of_unresolved_issues() throws Exception {
+    DefaultIssue unresolved1 = new DefaultIssue().setDebt(Duration.create(10)).setRuleKey(rule.getKey());
+    DefaultIssue unresolved2 = new DefaultIssue().setDebt(Duration.create(30)).setRuleKey(rule.getKey());
+    DefaultIssue unresolvedWithoutDebt = new DefaultIssue().setRuleKey(rule.getKey());
+    DefaultIssue resolved = new DefaultIssue().setDebt(Duration.create(50)).setResolution(RESOLUTION_FIXED).setRuleKey(rule.getKey());
+
+    underTest.beforeComponent(file, mock(Tracking.class));
+    underTest.onIssue(file, unresolved1);
+    underTest.onIssue(file, unresolved2);
+    underTest.onIssue(file, unresolvedWithoutDebt);
+    underTest.onIssue(file, resolved);
+    underTest.afterComponent(file);
+
+    // total debt
+    assertThat(debtMeasure(file).get().getLongValue()).isEqualTo(10 + 30);
+
+    // debt by rule
+    assertThat(debtRuleMeasure(file, rule.getId()).get().getLongValue()).isEqualTo(10 + 30);
+
+    // debt by characteristic. Root characteristics with zero values are not saved for files.
+    assertThat(debtCharacteristicMeasure(file, PORTABILITY_ID).get().getLongValue()).isEqualTo(10 + 30);
+    assertThat(debtCharacteristicMeasure(file, PORTABILITY_SOFT_ID).get().getLongValue()).isEqualTo(10 + 30);
+    assertThat(debtCharacteristicMeasure(file, PORTABILITY_HARD_ID).isPresent()).isFalse();
+    assertThat(debtCharacteristicMeasure(file, RELIABILITY_ID).isPresent()).isFalse();
+  }
+
+  @Test
+  public void aggregate_debt_of_children() throws Exception {
+    DefaultIssue fileIssue = new DefaultIssue().setDebt(Duration.create(10)).setRuleKey(rule.getKey());
+    DefaultIssue projectIssue = new DefaultIssue().setDebt(Duration.create(30)).setRuleKey(rule.getKey());
+
+    underTest.beforeComponent(file, mock(Tracking.class));
+    underTest.onIssue(file, fileIssue);
+    underTest.afterComponent(file);
+    underTest.beforeComponent(project, mock(Tracking.class));
+    underTest.onIssue(project, projectIssue);
+    underTest.afterComponent(project);
+
+    // total debt of project
+    assertThat(debtMeasure(project).get().getLongValue()).isEqualTo(10 + 30);
+
+    // debt by rule
+    assertThat(debtRuleMeasure(project, rule.getId()).get().getLongValue()).isEqualTo(10 + 30);
+
+    // debt by characteristic. Root characteristics with zero values are stored for modules and projects.
+    assertThat(debtCharacteristicMeasure(project, PORTABILITY_ID).get().getLongValue()).isEqualTo(10 + 30);
+    assertThat(debtCharacteristicMeasure(project, PORTABILITY_SOFT_ID).get().getLongValue()).isEqualTo(10 + 30);
+    assertThat(debtCharacteristicMeasure(project, PORTABILITY_HARD_ID).isPresent()).isFalse();
+    assertThat(debtCharacteristicMeasure(project, RELIABILITY_ID).get().getLongValue()).isZero();
+  }
+
+  @CheckForNull
+  private Optional<Measure> debtMeasure(Component component) {
+    return measureRepository.getRawMeasure(component, metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY));
+  }
+
+  @CheckForNull
+  private Optional<Measure> debtRuleMeasure(Component component, int ruleId) {
+    return measureRepository.getRawRuleMeasure(component, metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY), ruleId);
+  }
+
+  @CheckForNull
+  private Optional<Measure> debtCharacteristicMeasure(Component component, int characteristicId) {
+    return measureRepository.getRawCharacteristicMeasure(component, metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY), characteristicId);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtCalculatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtCalculatorTest.java
new file mode 100644 (file)
index 0000000..cf57d9a
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.api.utils.Durations;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.rule.RuleTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class DebtCalculatorTest {
+
+  DumbRule rule = new DumbRule(RuleTesting.XOO_X1);
+  DefaultIssue issue = new DefaultIssue().setRuleKey(rule.getKey());
+
+  @org.junit.Rule
+  public RuleRepositoryRule ruleRepository = new RuleRepositoryRule().add(rule);
+
+  DebtCalculator underTest = new DebtCalculator(ruleRepository, new Durations(new Settings(), mock(I18n.class)));
+
+  @Test
+  public void no_debt_if_function_is_not_defined() throws Exception {
+    DefaultIssue issue = new DefaultIssue().setRuleKey(rule.getKey());
+
+    assertThat(underTest.calculate(issue)).isNull();
+  }
+
+  @Test
+  public void default_effort_to_fix_is_one_for_linear_function() throws Exception {
+    int coefficient = 2;
+    rule.setFunction(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, coefficient + "min", null));
+
+    assertThat(underTest.calculate(issue).toMinutes()).isEqualTo(coefficient * 1);
+  }
+
+  @Test
+  public void linear_function() throws Exception {
+    double effortToFix = 3.0;
+    int coefficient = 2;
+    issue.setEffortToFix(effortToFix);
+    rule.setFunction(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, coefficient + "min", null));
+
+    assertThat(underTest.calculate(issue).toMinutes()).isEqualTo((int) (coefficient * effortToFix));
+  }
+
+  @Test
+  public void constant_function() throws Exception {
+    int constant = 2;
+    issue.setEffortToFix(null);
+    rule.setFunction(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, constant + "min"));
+
+    assertThat(underTest.calculate(issue).toMinutes()).isEqualTo(2);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void effort_to_fix_must_not_be_set_with_constant_function() throws Exception {
+    int constant = 2;
+    issue.setEffortToFix(3.0);
+    rule.setFunction(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, constant + "min"));
+
+    underTest.calculate(issue);
+  }
+
+  @Test
+  public void linear_with_offset_function() throws Exception {
+    double effortToFix = 3.0;
+    int coefficient = 2;
+    int offset = 5;
+    issue.setEffortToFix(effortToFix);
+    rule.setFunction(new DefaultDebtRemediationFunction(
+      DebtRemediationFunction.Type.LINEAR_OFFSET, coefficient + "min", offset + "min"));
+
+    assertThat(underTest.calculate(issue).toMinutes()).isEqualTo((int) ((coefficient * effortToFix) + offset));
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DefaultAssigneeTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DefaultAssigneeTest.java
new file mode 100644 (file)
index 0000000..e1e846b
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.ProjectSettingsRepository;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DefaultAssigneeTest {
+
+  public static final String PROJECT_KEY = "PROJECT_KEY";
+
+  TreeRootHolderRule rootHolder = mock(TreeRootHolderRule.class, Mockito.RETURNS_DEEP_STUBS);
+  UserIndex userIndex = mock(UserIndex.class);
+  Settings settings = new Settings();
+  ProjectSettingsRepository settingsRepository = mock(ProjectSettingsRepository.class);
+
+  DefaultAssignee underTest = new DefaultAssignee(rootHolder, userIndex, settingsRepository);
+
+  @Before
+  public void before() {
+    when(rootHolder.getRoot().getKey()).thenReturn(PROJECT_KEY);
+    when(settingsRepository.getProjectSettings(PROJECT_KEY)).thenReturn(settings);
+  }
+
+  @Test
+  public void no_default_assignee() throws Exception {
+    assertThat(underTest.getLogin()).isNull();
+  }
+
+  @Test
+  public void default_assignee() throws Exception {
+    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
+    when(userIndex.getNullableByLogin("erik")).thenReturn(new UserDoc().setLogin("erik"));
+
+    assertThat(underTest.getLogin()).isEqualTo("erik");
+  }
+
+  @Test
+  public void configured_login_does_not_exist() throws Exception {
+    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
+    when(userIndex.getNullableByLogin("erik")).thenReturn(null);
+
+    assertThat(underTest.getLogin()).isNull();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DumbRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DumbRule.java
new file mode 100644 (file)
index 0000000..79be4f1
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+
+import static java.util.Objects.requireNonNull;
+
+public class DumbRule implements Rule {
+  private Integer id;
+  private RuleKey key;
+  private String name;
+  private RuleStatus status = RuleStatus.READY;
+  private boolean isActivated = false;
+  private Set<String> tags = new HashSet<>();
+  private Integer subCharacteristicId;
+  private DebtRemediationFunction function;
+
+  public DumbRule(RuleKey key) {
+    this.key = key;
+  }
+
+  @Override
+  public int getId() {
+    return requireNonNull(id);
+  }
+
+  @Override
+  public RuleKey getKey() {
+    return requireNonNull(key);
+  }
+
+  @Override
+  public String getName() {
+    return requireNonNull(name);
+  }
+
+  @Override
+  public RuleStatus getStatus() {
+    return requireNonNull(status);
+  }
+
+  @Override
+  public boolean isActivated() {
+    return isActivated;
+  }
+
+  @Override
+  public Set<String> getTags() {
+    return requireNonNull(tags);
+  }
+
+  @Override
+  public Integer getSubCharacteristicId() {
+    return subCharacteristicId;
+  }
+
+  @Override
+  public DebtRemediationFunction getRemediationFunction() {
+    return function;
+  }
+
+  public DumbRule setId(Integer id) {
+    this.id = id;
+    return this;
+  }
+
+  public DumbRule setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public DumbRule setStatus(RuleStatus status) {
+    this.status = status;
+    return this;
+  }
+
+  public DumbRule setIsActivated(boolean isActivated) {
+    this.isActivated = isActivated;
+    return this;
+  }
+
+  public DumbRule setSubCharacteristicId(@Nullable Integer subCharacteristicId) {
+    this.subCharacteristicId = subCharacteristicId;
+    return this;
+  }
+
+  public DumbRule setFunction(@Nullable DebtRemediationFunction function) {
+    this.function = function;
+    return this;
+  }
+
+  public void setTags(Set<String> tags) {
+    this.tags = tags;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueAssignerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueAssignerTest.java
new file mode 100644 (file)
index 0000000..d5f8ab5
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import java.util.Date;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.batch.BatchReportReaderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.DumbComponent;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.source.index.SourceLineDoc;
+import org.sonar.server.source.index.SourceLineIndex;
+import org.sonar.server.source.index.SourceLineIndexDefinition;
+
+import static org.mockito.Mockito.mock;
+
+public class IssueAssignerTest {
+
+  @ClassRule
+  public static EsTester esTester = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings()));
+
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+  ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class);
+  DefaultAssignee defaultAssignee = mock(DefaultAssignee.class);
+  Component file = DumbComponent.builder(Component.Type.FILE, 1).build();
+
+  IssueAssigner underTest;
+
+  @Before
+  public void setUp() throws Exception {
+    esTester.truncateIndices();
+    underTest = new IssueAssigner(new SourceLineIndex(esTester.client()), reportReader, scmAccountToUser, defaultAssignee);
+  }
+
+  @Test
+  public void line_author_from_report() {
+    reportReader.putChangesets(BatchReport.Changesets.newBuilder()
+      .setComponentRef(123_456_789)
+      .addChangeset(newChangeset("charb", "123-456-789", 123_456_789L))
+      .addChangeset(newChangeset("wolinski", "987-654-321", 987_654_321L))
+      .addChangesetIndexByLine(0)
+      .addChangesetIndexByLine(0)
+      .addChangesetIndexByLine(1)
+      .build());
+
+    underTest.beforeComponent(file, mock(Tracking.class));
+//    underTest.onIssue(file, issue);
+//    sut.init("ANY_UUID", 123_456_789, reportReader);
+//
+//    assertThat(sut.lineAuthor(1)).isEqualTo("charb");
+//    assertThat(sut.lineAuthor(2)).isEqualTo("charb");
+//    assertThat(sut.lineAuthor(3)).isEqualTo("wolinski");
+//    // compute last author
+//    assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
+//    assertThat(sut.lineAuthor(null)).isEqualTo("wolinski");
+  }
+
+//  @Test
+//  public void line_author_from_index() throws Exception {
+//    esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE,
+//      newSourceLine("cabu", "123-456-789", 123_456_789, 1),
+//      newSourceLine("cabu", "123-456-789", 123_456_789, 2),
+//      newSourceLine("cabu", "123-123-789", 123_456_789, 3),
+//      newSourceLine("wolinski", "987-654-321", 987_654_321, 4),
+//      newSourceLine("cabu", "123-456-789", 123_456_789, 5)
+//      );
+//
+//    sut.init("DEFAULT_UUID", 123, reportReader);
+//
+//    assertThat(sut.lineAuthor(1)).isEqualTo("cabu");
+//    assertThat(sut.lineAuthor(2)).isEqualTo("cabu");
+//    assertThat(sut.lineAuthor(3)).isEqualTo("cabu");
+//    assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
+//    assertThat(sut.lineAuthor(5)).isEqualTo("cabu");
+//    assertThat(sut.lineAuthor(6)).isEqualTo("wolinski");
+//  }
+//
+//  @Test(expected = IllegalStateException.class)
+//  public void fail_when_component_ref_is_not_filled() {
+//    sut.init("ANY_UUID", null, reportReader);
+//    sut.lineAuthor(0);
+//  }
+
+  private BatchReport.Changesets.Changeset.Builder newChangeset(String author, String revision, long date) {
+    return BatchReport.Changesets.Changeset.newBuilder()
+      .setAuthor(author)
+      .setRevision(revision)
+      .setDate(date);
+  }
+
+  private SourceLineDoc newSourceLine(String author, String revision, long date, int lineNumber) {
+    return new SourceLineDoc()
+      .setScmAuthor(author)
+      .setScmRevision(revision)
+      .setScmDate(new Date(date))
+      .setLine(lineNumber)
+      .setProjectUuid("PROJECT_UUID")
+      .setFileUuid("DEFAULT_UUID");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java
new file mode 100644 (file)
index 0000000..67988ec
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import java.util.Date;
+import javax.annotation.Nullable;
+import org.assertj.core.data.Offset;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.batch.BatchReportReaderRule;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.measure.MeasureRepository;
+import org.sonar.server.computation.measure.MeasureRepositoryImpl;
+import org.sonar.server.computation.measure.MeasureVariations;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricImpl;
+import org.sonar.server.computation.metric.MetricRepository;
+import org.sonar.server.computation.period.Period;
+import org.sonar.server.computation.period.PeriodsHolderRule;
+import org.sonar.server.rule.RuleTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY;
+import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY;
+import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY;
+import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY;
+import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
+import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.api.rule.Severity.CRITICAL;
+import static org.sonar.api.rule.Severity.MAJOR;
+import static org.sonar.server.computation.component.DumbComponent.builder;
+import static org.sonar.server.computation.metric.Metric.MetricType.INT;
+
+public class IssueCounterTest {
+
+  static final Component FILE1 = builder(Component.Type.FILE, 1).build();
+  static final Component FILE2 = builder(Component.Type.FILE, 2).build();
+  static final Component FILE3 = builder(Component.Type.FILE, 3).build();
+  static final Component PROJECT = builder(Component.Type.PROJECT, 4).addChildren(FILE1, FILE2, FILE3).build();
+
+  static final Metric ISSUES_METRIC = new MetricImpl(1, VIOLATIONS_KEY, VIOLATIONS_KEY, INT);
+  static final Metric OPEN_ISSUES_METRIC = new MetricImpl(2, OPEN_ISSUES_KEY, OPEN_ISSUES_KEY, INT);
+  static final Metric REOPENED_ISSUES_METRIC = new MetricImpl(3, REOPENED_ISSUES_KEY, REOPENED_ISSUES_KEY, INT);
+  static final Metric CONFIRMED_ISSUES_METRIC = new MetricImpl(4, CONFIRMED_ISSUES_KEY, CONFIRMED_ISSUES_KEY, INT);
+  static final Metric BLOCKER_ISSUES_METRIC = new MetricImpl(5, BLOCKER_VIOLATIONS_KEY, BLOCKER_VIOLATIONS_KEY, INT);
+  static final Metric CRITICAL_ISSUES_METRIC = new MetricImpl(6, CRITICAL_VIOLATIONS_KEY, CRITICAL_VIOLATIONS_KEY, INT);
+  static final Metric MAJOR_ISSUES_METRIC = new MetricImpl(7, MAJOR_VIOLATIONS_KEY, MAJOR_VIOLATIONS_KEY, INT);
+  static final Metric MINOR_ISSUES_METRIC = new MetricImpl(8, MINOR_VIOLATIONS_KEY, MINOR_VIOLATIONS_KEY, INT);
+  static final Metric INFO_ISSUES_METRIC = new MetricImpl(9, INFO_VIOLATIONS_KEY, INFO_VIOLATIONS_KEY, INT);
+  static final Metric NEW_ISSUES_METRIC = new MetricImpl(10, NEW_VIOLATIONS_KEY, NEW_VIOLATIONS_KEY, INT);
+  static final Metric NEW_BLOCKER_ISSUES_METRIC = new MetricImpl(11, NEW_BLOCKER_VIOLATIONS_KEY, NEW_BLOCKER_VIOLATIONS_KEY, INT);
+  static final Metric NEW_CRITICAL_ISSUES_METRIC = new MetricImpl(12, NEW_CRITICAL_VIOLATIONS_KEY, NEW_CRITICAL_VIOLATIONS_KEY, INT);
+  static final Metric NEW_MAJOR_ISSUES_METRIC = new MetricImpl(13, NEW_MAJOR_VIOLATIONS_KEY, NEW_MAJOR_VIOLATIONS_KEY, INT);
+  static final Metric NEW_MINOR_ISSUES_METRIC = new MetricImpl(14, NEW_MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY, INT);
+  static final Metric NEW_INFO_ISSUES_METRIC = new MetricImpl(15, NEW_INFO_VIOLATIONS_KEY, NEW_INFO_VIOLATIONS_KEY, INT);
+  static final Metric FALSE_POSITIVE_ISSUES_METRIC = new MetricImpl(16, FALSE_POSITIVE_ISSUES_KEY, FALSE_POSITIVE_ISSUES_KEY, INT);
+
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  @Rule
+  public PeriodsHolderRule periodsHolder = new PeriodsHolderRule();
+
+  Tracking tracking = mock(Tracking.class);
+  MetricRepository metricRepository = mock(MetricRepository.class);
+  MeasureRepository measureRepository;
+  IssueCounter sut;
+
+  @Before
+  public void setUp() throws Exception {
+    initMetrics();
+    measureRepository = new MeasureRepositoryImpl(null, reportReader, metricRepository);
+
+    sut = new IssueCounter(periodsHolder, metricRepository, measureRepository);
+  }
+
+  @Test
+  public void count_issues_by_status() throws Exception {
+    periodsHolder.setPeriods();
+
+    // bottom-up traversal -> from files to project
+    sut.beforeComponent(FILE1, tracking);
+    sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
+    sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
+    sut.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR));
+    sut.afterComponent(FILE1);
+
+    sut.beforeComponent(FILE2, tracking);
+    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
+    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
+    sut.afterComponent(FILE2);
+
+    sut.beforeComponent(FILE3, tracking);
+    sut.afterComponent(FILE3);
+
+    sut.beforeComponent(PROJECT, tracking);
+    sut.afterComponent(PROJECT);
+
+    // count by status
+    assertThat(measureRepository.getRawMeasure(FILE1, ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE1, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE1, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE1, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+
+    assertThat(measureRepository.getRawMeasure(FILE2, ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getRawMeasure(FILE2, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+    assertThat(measureRepository.getRawMeasure(FILE2, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+    assertThat(measureRepository.getRawMeasure(FILE2, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
+
+    assertThat(measureRepository.getRawMeasure(FILE3, ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+
+    assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(3);
+    assertThat(measureRepository.getRawMeasure(PROJECT, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(PROJECT, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(PROJECT, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
+  }
+
+  @Test
+  public void count_unresolved_issues_by_severity() throws Exception {
+    periodsHolder.setPeriods();
+
+    // bottom-up traversal -> from files to project
+    sut.beforeComponent(FILE1, tracking);
+    sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
+    // this resolved issue is ignored
+    sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
+    sut.afterComponent(FILE1);
+
+    sut.beforeComponent(FILE2, tracking);
+    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
+    sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
+    sut.afterComponent(FILE2);
+
+    sut.beforeComponent(PROJECT, tracking);
+    sut.afterComponent(PROJECT);
+
+    assertThat(measureRepository.getRawMeasure(FILE1, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE1, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+    assertThat(measureRepository.getRawMeasure(FILE1, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+
+    assertThat(measureRepository.getRawMeasure(FILE2, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE2, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+    assertThat(measureRepository.getRawMeasure(FILE2, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+
+    assertThat(measureRepository.getRawMeasure(PROJECT, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getRawMeasure(PROJECT, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+    assertThat(measureRepository.getRawMeasure(PROJECT, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+  }
+
+  @Test
+  public void count_new_issues() throws Exception {
+    Period period = newPeriod(3, 1500000000000L);
+    periodsHolder.setPeriods(period);
+
+    sut.beforeComponent(FILE1, tracking);
+    // created before -> existing issues
+    sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate() - 1000000L));
+    // created during the first analysis starting the period -> existing issues
+    sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate()));
+    // created after -> new issues
+    sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L));
+    sut.onIssue(FILE1, createIssueAt(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getSnapshotDate() + 200000L));
+    sut.afterComponent(FILE1);
+
+    sut.beforeComponent(FILE2, tracking);
+    sut.afterComponent(FILE2);
+
+    sut.beforeComponent(PROJECT, tracking);
+    sut.afterComponent(PROJECT);
+
+    assertVariation(FILE1, NEW_ISSUES_METRIC, period.getIndex(), 1);
+    assertVariation(FILE1, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1);
+    assertVariation(FILE1, NEW_BLOCKER_ISSUES_METRIC, period.getIndex(), 0);
+    assertVariation(FILE1, NEW_MAJOR_ISSUES_METRIC, period.getIndex(), 0);
+
+    assertVariation(PROJECT, NEW_ISSUES_METRIC, period.getIndex(), 1);
+    assertVariation(PROJECT, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1);
+    assertVariation(PROJECT, NEW_BLOCKER_ISSUES_METRIC, period.getIndex(), 0);
+    assertVariation(PROJECT, NEW_MAJOR_ISSUES_METRIC, period.getIndex(), 0);
+  }
+
+  private void assertVariation(Component component, Metric metric, int periodIndex, int expectedVariation) {
+    MeasureVariations variations = measureRepository.getRawMeasure(component, metric).get().getVariations();
+    assertThat(variations.getVariation(periodIndex)).isEqualTo((double) expectedVariation, Offset.offset(0.01));
+  }
+
+  private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) {
+    return new DefaultIssue()
+      .setResolution(resolution).setStatus(status)
+      .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
+      .setCreationDate(new Date());
+  }
+
+  private static DefaultIssue createIssueAt(@Nullable String resolution, String status, String severity, long creationDate) {
+    return new DefaultIssue()
+      .setResolution(resolution).setStatus(status)
+      .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
+      .setCreationDate(new Date(creationDate));
+  }
+
+  private static Period newPeriod(int index, long date) {
+    return new Period(index, "mode", null, date, 42l);
+  }
+
+  private void initMetrics() {
+    when(metricRepository.getByKey(ISSUES_METRIC.getKey())).thenReturn(ISSUES_METRIC);
+    when(metricRepository.getByKey(OPEN_ISSUES_METRIC.getKey())).thenReturn(OPEN_ISSUES_METRIC);
+    when(metricRepository.getByKey(REOPENED_ISSUES_METRIC.getKey())).thenReturn(REOPENED_ISSUES_METRIC);
+    when(metricRepository.getByKey(CONFIRMED_ISSUES_METRIC.getKey())).thenReturn(CONFIRMED_ISSUES_METRIC);
+    when(metricRepository.getByKey(BLOCKER_ISSUES_METRIC.getKey())).thenReturn(BLOCKER_ISSUES_METRIC);
+    when(metricRepository.getByKey(CRITICAL_ISSUES_METRIC.getKey())).thenReturn(CRITICAL_ISSUES_METRIC);
+    when(metricRepository.getByKey(MAJOR_ISSUES_METRIC.getKey())).thenReturn(MAJOR_ISSUES_METRIC);
+    when(metricRepository.getByKey(MINOR_ISSUES_METRIC.getKey())).thenReturn(MINOR_ISSUES_METRIC);
+    when(metricRepository.getByKey(INFO_ISSUES_METRIC.getKey())).thenReturn(INFO_ISSUES_METRIC);
+    when(metricRepository.getByKey(NEW_ISSUES_METRIC.getKey())).thenReturn(NEW_ISSUES_METRIC);
+    when(metricRepository.getByKey(NEW_BLOCKER_ISSUES_METRIC.getKey())).thenReturn(NEW_BLOCKER_ISSUES_METRIC);
+    when(metricRepository.getByKey(NEW_CRITICAL_ISSUES_METRIC.getKey())).thenReturn(NEW_CRITICAL_ISSUES_METRIC);
+    when(metricRepository.getByKey(NEW_MAJOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MAJOR_ISSUES_METRIC);
+    when(metricRepository.getByKey(NEW_MINOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MINOR_ISSUES_METRIC);
+    when(metricRepository.getByKey(NEW_INFO_ISSUES_METRIC.getKey())).thenReturn(NEW_INFO_ISSUES_METRIC);
+    when(metricRepository.getByKey(FALSE_POSITIVE_ISSUES_METRIC.getKey())).thenReturn(FALSE_POSITIVE_ISSUES_METRIC);
+  }
+}
index 17c4dc053d31ef9a1fe067e2b413761a8e18bc12..cf864b8a8e05891eaf5e9686c3cbbe68eee5ec06 100644 (file)
  * 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.issue;
-//
-//import java.util.Date;
-//import org.apache.commons.lang.ObjectUtils;
-//import org.apache.commons.lang.time.DateUtils;
-//import org.junit.Before;
-//import org.junit.Test;
-//import org.mockito.ArgumentMatcher;
-//import org.mockito.Mock;
-//import org.sonar.api.CoreProperties;
-//import org.sonar.api.batch.DecoratorContext;
-//import org.sonar.api.component.ResourcePerspectives;
-//import org.sonar.api.config.Settings;
-//import org.sonar.api.issue.Issuable;
-//import org.sonar.api.issue.Issue;
-//import org.sonar.api.measures.CoreMetrics;
-//import org.sonar.api.measures.Measure;
-//import org.sonar.api.measures.Metric;
-//import org.sonar.api.resources.Resource;
-//import org.sonar.api.utils.Duration;
-//import org.sonar.core.issue.DefaultIssue;
-//import org.sonar.core.issue.FieldDiffs;
-//
-//import static com.google.common.collect.Lists.newArrayList;
-//import static org.assertj.core.api.Assertions.assertThat;
-//import static org.mockito.Matchers.argThat;
-//import static org.mockito.Mockito.mock;
-//import static org.mockito.Mockito.never;
-//import static org.mockito.Mockito.verify;
-//import static org.mockito.Mockito.when;
-//
-//public class NewDebtAggregatorTest {
-//  NewDebtDecorator decorator;
-//
-//  @Mock
-//  TimeMachineConfiguration timeMachineConfiguration;
-//
-//  @Mock
-//  Resource resource;
-//
-//  @Mock
-//  Issuable issuable;
-//
-//  @Mock
-//  DecoratorContext context;
-//
-//  Date rightNow;
-//  Date elevenDaysAgo;
-//  Date tenDaysAgo;
-//  Date nineDaysAgo;
-//  Date fiveDaysAgo;
-//  Date fourDaysAgo;
-//
-//  static final int HOURS_IN_DAY = 8;
-//
-//  static final Long ONE_DAY_IN_MINUTES = 1L * HOURS_IN_DAY * 60;
-//  static final Long TWO_DAYS_IN_MINUTES = 2L * HOURS_IN_DAY * 60;
-//  static final Long FIVE_DAYS_IN_MINUTES = 5L * HOURS_IN_DAY * 60;
-//
-//  @Before
-//  public void setup() {
-//    Settings settings = new Settings();
-//    settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY);
-//
-//    ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
-//    when(perspectives.as(Issuable.class, resource)).thenReturn(issuable);
-//
-//    rightNow = new Date();
-//    elevenDaysAgo = DateUtils.addDays(rightNow, -11);
-//    tenDaysAgo = DateUtils.addDays(rightNow, -10);
-//    nineDaysAgo = DateUtils.addDays(rightNow, -9);
-//    fiveDaysAgo = DateUtils.addDays(rightNow, -5);
-//    fourDaysAgo = DateUtils.addDays(rightNow, -4);
-//
-//    when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, fiveDaysAgo), new Period(2, tenDaysAgo)));
-//
-//    decorator = new NewDebtDecorator(perspectives, timeMachineConfiguration, new IssueChangelogDebtCalculator());
-//  }
-//
-//  @Test
-//  public void generates_metrics() {
-//    assertThat(decorator.generatesMetrics()).hasSize(1);
-//  }
-//
-//  @Test
-//  public void execute_on_project() {
-//    assertThat(decorator.shouldExecuteOnProject(null)).isTrue();
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_with_one_new_changelog() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        // changelog created at is null because it has just been created on the current analysis
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(null)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 1.0 * ONE_DAY_IN_MINUTES, 1.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_with_changelog() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(null),
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_with_changelog_only_in_the_past() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        // Change before all periods
-//        new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(elevenDaysAgo)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_with_changelog_having_null_value() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null),
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo),
-//        new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_with_changelog_and_periods_have_no_dates() {
-//    when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null)));
-//
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null),
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo),
-//        new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_with_changelog_having_not_only_technical_debt_changes() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs()
-//          .setDiff("actionPlan", "1.0", "1.1").setCreationDate(fourDaysAgo)
-//          .setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_issues_with_changelog() {
-//    Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow),
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo),
-//        new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
-//      )
-//    );
-//    Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow),
-//        new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue1, issue2));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 7.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_without_changelog() {
-//    when(issuable.issues()).thenReturn(newArrayList(
-//        (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)))
-//    );
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 5.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_without_technical_debt_and_without_changelog() {
-//    when(issuable.issues()).thenReturn(newArrayList(
-//        (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(null))
-//    );
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0)));
-//  }
-//
-//  @Test
-//  public void save_on_one_issue_without_changelog_and_periods_have_no_dates() {
-//    when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null)));
-//
-//    when(issuable.issues()).thenReturn(newArrayList(
-//        (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)))
-//    );
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is null, period2 is null
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_issues_without_changelog() {
-//    when(issuable.issues()).thenReturn(newArrayList(
-//      (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)),
-//      new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES))
-//    ));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 7.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void save_on_issues_with_changelog_and_issues_without_changelog() {
-//    // issue1 and issue2 have changelog
-//    Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow),
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo),
-//        new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
-//      )
-//    );
-//    Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow),
-//        new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
-//      )
-//    );
-//
-//    // issue3 and issue4 have no changelog
-//    Issue issue3 = new DefaultIssue().setKey("C").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES));
-//    Issue issue4 = new DefaultIssue().setKey("D").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES));
-//    when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 14.0 * ONE_DAY_IN_MINUTES)));
-//  }
-//
-//  @Test
-//  public void not_save_if_measure_already_computed() {
-//    when(context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT)).thenReturn(new Measure());
-//    when(issuable.issues()).thenReturn(newArrayList(
-//      (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)),
-//      new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES))
-//    ));
-//
-//    decorator.decorate(resource, context);
-//
-//    verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.NEW_TECHNICAL_DEBT)));
-//  }
-//
-//  /**
-//   * SONAR-5059
-//   */
-//  @Test
-//  public void not_return_negative_debt() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges(
-//      newArrayList(
-//        // changelog created at is null because it has just been created on the current analysis
-//        new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, ONE_DAY_IN_MINUTES).setCreationDate(null)
-//      )
-//    );
-//    when(issuable.issues()).thenReturn(newArrayList(issue));
-//
-//    decorator.decorate(resource, context);
-//
-//    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-//    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0)));
-//  }
-//
-//
-//  class IsVariationMeasure extends ArgumentMatcher<Measure> {
-//    Metric metric = null;
-//    Double var1 = null;
-//    Double var2 = null;
-//
-//    public IsVariationMeasure(Metric metric, Double var1, Double var2) {
-//      this.metric = metric;
-//      this.var1 = var1;
-//      this.var2 = var2;
-//    }
-//
-//    public boolean matches(Object o) {
-//      if (!(o instanceof Measure)) {
-//        return false;
-//      }
-//      Measure m = (Measure) o;
-//      return ObjectUtils.equals(metric, m.getMetric()) &&
-//        ObjectUtils.equals(var1, m.getVariation1()) &&
-//        ObjectUtils.equals(var2, m.getVariation2());
-//    }
-//  }
-//
-//}
+package org.sonar.server.computation.issue;
+
+import com.google.common.base.Optional;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.Duration;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.DumbComponent;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+import org.sonar.server.computation.period.Period;
+import org.sonar.server.computation.period.PeriodsHolderRule;
+import org.sonar.server.db.DbClient;
+
+import static org.assertj.core.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.CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+
+public class NewDebtAggregatorTest {
+
+  private static final Period PERIOD = new Period(1, TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, null, 1_500_000_000L, 1000L);
+
+  Component file = DumbComponent.builder(Component.Type.FILE, 1).setUuid("FILE").build();
+  Component project = DumbComponent.builder(Component.Type.PROJECT, 2).setUuid("PROJECT").addChildren(file).build();
+
+  NewDebtCalculator calculator = mock(NewDebtCalculator.class);
+
+  @org.junit.Rule
+  public PeriodsHolderRule periodsHolder = new PeriodsHolderRule();
+
+  DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
+
+  @org.junit.Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(CoreMetrics.NEW_TECHNICAL_DEBT);
+
+  @org.junit.Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create();
+
+  NewDebtAggregator underTest = new NewDebtAggregator(calculator, periodsHolder, dbClient, metricRepository, measureRepository);
+
+  @Test
+  public void sum_new_debt_of_issues() {
+    periodsHolder.setPeriods(PERIOD);
+    DefaultIssue unresolved1 = new DefaultIssue().setDebt(Duration.create(10));
+    DefaultIssue unresolved2 = new DefaultIssue().setDebt(Duration.create(30));
+    DefaultIssue unresolvedWithoutDebt = new DefaultIssue().setDebt(null);
+    DefaultIssue resolved = new DefaultIssue().setDebt(Duration.create(50)).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, mock(Tracking.class));
+    underTest.onIssue(file, unresolved1);
+    underTest.onIssue(file, unresolved2);
+    underTest.onIssue(file, unresolvedWithoutDebt);
+    underTest.onIssue(file, resolved);
+    underTest.afterComponent(file);
+
+    Measure newDebtMeasure = newDebtMeasure(file).get();
+    assertThat(newDebtMeasure.getVariations().getVariation(PERIOD.getIndex())).isEqualTo(3 + 4);
+    assertThat(newDebtMeasure.getVariations().hasVariation(PERIOD.getIndex() + 1)).isFalse();
+  }
+
+  private Optional<Measure> newDebtMeasure(Component component) {
+    return measureRepository.getRawMeasure(component, metricRepository.getByKey(CoreMetrics.NEW_TECHNICAL_DEBT_KEY));
+  }
+
+  @Test
+  public void aggregate_new_debt_of_children() {
+
+  }
+
+  @Test
+  public void no_measures_if_no_periods() throws Exception {
+    periodsHolder.setPeriods();
+    DefaultIssue unresolved = new DefaultIssue().setDebt(Duration.create(10));
+    verifyZeroInteractions(calculator);
+
+    underTest.beforeComponent(file, mock(Tracking.class));
+    underTest.onIssue(file, unresolved);
+    underTest.afterComponent(file);
+
+    assertThat(newDebtMeasure(file).isPresent()).isFalse();
+  }
+}
index bfbcb7a87ce531d3b82bc5e2f5ea1d608f3dfe67..37add915d097a6c35d768687ecda3a5b3831c8d3 100644 (file)
  * 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.issue;
-//
-//import java.util.Date;
-//import org.apache.commons.lang.time.DateUtils;
-//import org.junit.Before;
-//import org.junit.Test;
-//import org.sonar.api.issue.Issue;
-//import org.sonar.api.utils.Duration;
-//import org.sonar.core.issue.DefaultIssue;
-//import org.sonar.core.issue.FieldDiffs;
-//
-//import static com.google.common.collect.Lists.newArrayList;
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//public class NewDebtCalculatorTest {
-//  private static final int HOURS_IN_DAY = 8;
-//
-//  IssueChangelogDebtCalculator issueChangelogDebtCalculator;
-//
-//  Date rightNow = new Date();
-//  Date elevenDaysAgo = DateUtils.addDays(rightNow, -11);
-//  Date tenDaysAgo = DateUtils.addDays(rightNow, -10);
-//  Date nineDaysAgo = DateUtils.addDays(rightNow, -9);
-//  Date fiveDaysAgo = DateUtils.addDays(rightNow, -5);
-//  Date fourDaysAgo = DateUtils.addDays(rightNow, -4);
-//
-//  long oneDay = 1 * HOURS_IN_DAY * 60 * 60L;
-//  long twoDays = 2 * HOURS_IN_DAY * 60 * 60L;
-//  long fiveDays = 5 * HOURS_IN_DAY * 60 * 60L;
-//
-//  Duration oneDayDebt = Duration.create(oneDay);
-//  Duration twoDaysDebt = Duration.create(twoDays);
-//  Duration fiveDaysDebt = Duration.create(fiveDays);
-//
-//  @Before
-//  public void setUp() {
-//    issueChangelogDebtCalculator = new IssueChangelogDebtCalculator();
-//  }
-//
-//  @Test
-//  public void calculate_new_technical_debt_with_one_diff_in_changelog() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(twoDaysDebt).setChanges(
-//      newArrayList(
-//        // changelog created at is null because it has just been created on the current analysis
-//        new FieldDiffs().setDiff("technicalDebt", oneDay, twoDays).setCreationDate(null)
-//      )
-//    );
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, rightNow)).isEqualTo(oneDay);
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, fiveDaysAgo)).isEqualTo(oneDay);
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, elevenDaysAgo)).isEqualTo(twoDays);
-//  }
-//
-//  @Test
-//  public void calculate_new_technical_debt_with_many_diffs_in_changelog() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(fiveDaysDebt).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", twoDays, fiveDays).setCreationDate(null),
-//        new FieldDiffs().setDiff("technicalDebt", oneDay, twoDays).setCreationDate(fourDaysAgo)
-//      )
-//    );
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, rightNow)).isEqualTo(3 * oneDay);
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, fiveDaysAgo)).isEqualTo(4 * oneDay);
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, elevenDaysAgo)).isEqualTo(5 * oneDay);
-//  }
-//
-//  @Test
-//  public void changelog_can_be_in_wrong_order() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(fiveDaysDebt).setChanges(
-//      newArrayList(
-//        // 3rd
-//        new FieldDiffs().setDiff("technicalDebt", null, oneDay).setCreationDate(nineDaysAgo),
-//        // 1st
-//        new FieldDiffs().setDiff("technicalDebt", twoDays, fiveDays).setCreationDate(rightNow),
-//        // 2nd
-//        new FieldDiffs().setDiff("technicalDebt", oneDay, twoDays).setCreationDate(fourDaysAgo)
-//      )
-//    );
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, fiveDaysAgo)).isEqualTo(4 * oneDay);
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, elevenDaysAgo)).isEqualTo(5 * oneDay);
-//  }
-//
-//  @Test
-//  public void calculate_new_technical_debt_with_null_date() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(twoDaysDebt).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", oneDay, twoDays).setCreationDate(null)
-//      )
-//    );
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, null)).isEqualTo(2 * oneDay);
-//  }
-//
-//  @Test
-//  public void calculate_new_technical_debt_when_new_debt_is_null() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(null).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", oneDay, null).setCreationDate(null),
-//        new FieldDiffs().setDiff("technicalDebt", null, oneDay).setCreationDate(nineDaysAgo)
-//      )
-//    );
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, rightNow)).isNull();
-//  }
-//
-//  @Test
-//  public void calculate_new_technical_debt_on_issue_without_technical_debt_and_without_changelog() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo);
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, rightNow)).isNull();
-//  }
-//
-//  @Test
-//  public void not_return_negative_debt() {
-//    Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(oneDayDebt).setChanges(
-//      newArrayList(
-//        new FieldDiffs().setDiff("technicalDebt", twoDays, oneDay).setCreationDate(null)
-//      )
-//    );
-//
-//    assertThat(issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, rightNow)).isNull();
-//  }
-//
-//}
+package org.sonar.server.computation.issue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.utils.Duration;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.server.computation.period.Period;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NewDebtCalculatorTest {
+
+  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 long SNAPSHOT_ID = 1000L;
+  private static final Period PERIOD = new Period(1, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION, null, PERIOD_DATE, SNAPSHOT_ID);
+
+  DefaultIssue issue = new DefaultIssue();
+  NewDebtCalculator underTest = new NewDebtCalculator();
+
+  /**
+   * 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.setDebt(TWO_DAYS).setCreationDate(new Date(PERIOD_DATE + 10000));
+
+    long newDebt = underTest.calculate(issue, Collections.<IssueChangeDto>emptyList(), PERIOD);
+
+    assertThat(newDebt).isEqualTo(TWO_DAYS.toMinutes());
+  }
+
+  @Test
+  public void new_debt_if_issue_created_before_period() throws Exception {
+    // 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.setDebt(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() throws Exception {
+    // creation: 1d
+    // before period: increased to 10d
+    // after period: decreased to 2d
+    // -> new debt is 2d - 10d = -8d -> 0d
+    issue.setDebt(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() throws Exception {
+    // creation: 1d
+    // after period: increased to 2d, then to 5d
+    // -> new debt is 5d - 1d = 4d
+    issue.setDebt(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());
+  }
+
+}
index d65db7e530710f938342228e0a815a2983527101..0457c5dc0f222d8f60dd5c1d418520ee009ecb87 100644 (file)
  */
 package org.sonar.server.computation.issue;
 
+import java.util.Collections;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
+import org.junit.experimental.categories.Category;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.System2;
+import org.sonar.batch.protocol.output.BatchReport;
 import org.sonar.core.persistence.DbTester;
+import org.sonar.server.computation.batch.BatchReportReaderRule;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.rule.db.RuleDao;
+import org.sonar.test.DbTests;
 
-import java.util.Collections;
-
+import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
+@Category(DbTests.class)
 public class RuleCacheLoaderTest {
 
   @ClassRule
   public static DbTester dbTester = new DbTester();
 
+  @org.junit.Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
   @Before
   public void setUp() {
     dbTester.truncateTables();
@@ -46,18 +54,31 @@ public class RuleCacheLoaderTest {
 
   @Test
   public void load_by_key() {
+    BatchReport.Metadata metadata = BatchReport.Metadata.newBuilder()
+      .addAllActiveRuleKey(asList("java:JAV01")).build();
+    reportReader.setMetadata(metadata);
+
     dbTester.prepareDbUnit(getClass(), "shared.xml");
     DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new RuleDao(mock(System2.class)));
-    RuleCacheLoader loader = new RuleCacheLoader(dbClient);
+    RuleCacheLoader loader = new RuleCacheLoader(dbClient, reportReader);
+
+    Rule javaRule = loader.load(RuleKey.of("java", "JAV01"));
+    assertThat(javaRule.getName()).isEqualTo("Java One");
+    assertThat(javaRule.isActivated()).isTrue();
 
-    assertThat(loader.load(RuleKey.of("squid", "R001")).getName()).isEqualTo("Rule One");
-    assertThat(loader.load(RuleKey.of("squid", "MISSING"))).isNull();
+    Rule jsRule = loader.load(RuleKey.of("js", "JS01"));
+    assertThat(jsRule.getName()).isEqualTo("JS One");
+    assertThat(jsRule.isActivated()).isFalse();
+
+    assertThat(loader.load(RuleKey.of("java", "MISSING"))).isNull();
   }
 
   @Test
   public void load_by_keys_is_not_supported() {
+    reportReader.setMetadata(BatchReport.Metadata.newBuilder().build());
+
     DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new RuleDao(mock(System2.class)));
-    RuleCacheLoader loader = new RuleCacheLoader(dbClient);
+    RuleCacheLoader loader = new RuleCacheLoader(dbClient, reportReader);
     try {
       loader.loadAll(Collections.<RuleKey>emptyList());
       fail();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java
deleted file mode 100644 (file)
index 57172b9..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.issue;
-
-import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.rule.RuleDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class RuleCacheTest {
-
-  @Test
-  public void ruleName() {
-    RuleCacheLoader loader = mock(RuleCacheLoader.class);
-    when(loader.load(RuleKey.of("squid", "R002"))).thenReturn(new RuleDto().setName("Rule Two"));
-    RuleCache cache = new RuleCache(loader);
-    assertThat(cache.ruleName(RuleKey.of("squid", "R001"))).isNull();
-    assertThat(cache.ruleName(RuleKey.of("squid", "R002"))).isEqualTo("Rule Two");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryImplTest.java
new file mode 100644 (file)
index 0000000..a7f5c5f
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+import static org.sonar.server.rule.RuleTesting.XOO_X1;
+
+public class RuleRepositoryImplTest {
+
+  RuleCacheLoader cacheLoader = mock(RuleCacheLoader.class);
+  RuleRepositoryImpl underTest = new RuleRepositoryImpl(cacheLoader);
+
+  @Test
+  public void getByKey() throws Exception {
+    when(cacheLoader.load(XOO_X1)).thenReturn(new DumbRule(XOO_X1));
+
+    assertThat(underTest.getByKey(XOO_X1).getKey()).isEqualTo(XOO_X1);
+
+    // second call -> get from cache
+    assertThat(underTest.getByKey(XOO_X1).getKey()).isEqualTo(XOO_X1);
+    verify(cacheLoader, times(1)).load(XOO_X1);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryRule.java
new file mode 100644 (file)
index 0000000..860ab25
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.rules.ExternalResource;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.server.exceptions.NotFoundException;
+
+import static java.util.Objects.requireNonNull;
+
+public class RuleRepositoryRule extends ExternalResource implements RuleRepository {
+
+  private final Map<RuleKey, Rule> rulesByKey = new HashMap<>();
+
+  @Override
+  protected void after() {
+    rulesByKey.clear();
+  }
+
+  @Override
+  public Rule getByKey(RuleKey key) {
+    Rule rule = rulesByKey.get(key);
+    if (rule == null) {
+      throw new NotFoundException();
+    }
+    return rule;
+  }
+
+  public DumbRule add(RuleKey key) {
+    DumbRule rule = new DumbRule(key);
+    rulesByKey.put(key, rule);
+    return rule;
+  }
+
+  public RuleRepositoryRule add(DumbRule rule) {
+    rulesByKey.put(rule.getKey(), rule);
+    return this;
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleTagsCopierTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleTagsCopierTest.java
new file mode 100644 (file)
index 0000000..df0a3d4
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import org.junit.Test;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.component.Component;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.server.rule.RuleTesting.XOO_X1;
+
+public class RuleTagsCopierTest {
+
+  DumbRule rule = new DumbRule(XOO_X1);
+
+  @org.junit.Rule
+  public RuleRepositoryRule ruleRepository = new RuleRepositoryRule().add(rule);
+
+  DefaultIssue issue = new DefaultIssue().setRuleKey(rule.getKey());
+  RuleTagsCopier underTest = new RuleTagsCopier(ruleRepository);
+
+  @Test
+  public void copy_tags_if_new_rule() throws Exception {
+    rule.setTags(Sets.newHashSet("bug", "performance"));
+    issue.setNew(true);
+
+    underTest.onIssue(mock(Component.class), issue);
+
+    assertThat(issue.tags()).containsExactly("bug", "performance");
+  }
+
+  @Test
+  public void do_not_copy_tags_if_existing_rule() throws Exception {
+    rule.setTags(Sets.newHashSet("bug", "performance"));
+    issue.setNew(false).setTags(asList("misra"));
+
+    underTest.onIssue(mock(Component.class), issue);
+
+    assertThat(issue.tags()).containsExactly("misra");
+  }
+
+  @Test
+  public void do_not_copy_tags_if_existing_rule_without_tags() throws Exception {
+    rule.setTags(Sets.newHashSet("bug", "performance"));
+    issue.setNew(false).setTags(Collections.<String>emptyList());
+
+    underTest.onIssue(mock(Component.class), issue);
+
+    assertThat(issue.tags()).isEmpty();
+  }
+}
index a25df3a68affd4e157725c4ad14d8e1c5141b1e6..40c587344454d30edfc5c9b10e48d59bc07497a3 100644 (file)
@@ -24,21 +24,23 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.api.config.Settings;
-import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.user.index.UserIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 
 public class ScmAccountToUserLoaderTest {
 
   @ClassRule
   public static EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
 
+  @ClassRule
+  public static LogTester logTester = new LogTester();
+
   @Before
   public void setUp() {
     esTester.truncateIndices();
@@ -58,11 +60,10 @@ public class ScmAccountToUserLoaderTest {
   public void warn_if_multiple_users_share_same_scm_account() throws Exception {
     esTester.putDocuments("users", "user", getClass(), "charlie.json", "charlie_conflict.json");
     UserIndex index = new UserIndex(esTester.client());
-    Logger log = mock(Logger.class);
-    ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index, log);
+    ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index);
 
     assertThat(loader.load("charlie")).isNull();
-    verify(log).warn("Multiple users share the SCM account 'charlie': charlie, another.charlie");
+    assertThat(logTester.logs(LoggerLevel.WARN)).contains("Multiple users share the SCM account 'charlie': charlie, another.charlie");
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceAuthorsHolderTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceAuthorsHolderTest.java
deleted file mode 100644 (file)
index ccfcddd..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//package org.sonar.server.computation.issue;
-//
-//import java.util.Date;
-//import org.junit.Before;
-//import org.junit.ClassRule;
-//import org.junit.Rule;
-//import org.junit.Test;
-//import org.junit.experimental.categories.Category;
-//import org.sonar.api.config.Settings;
-//import org.sonar.batch.protocol.output.BatchReport;
-//import org.sonar.server.computation.batch.BatchReportReaderRule;
-//import org.sonar.server.es.EsTester;
-//import org.sonar.server.source.index.SourceLineDoc;
-//import org.sonar.server.source.index.SourceLineIndex;
-//import org.sonar.server.source.index.SourceLineIndexDefinition;
-//import org.sonar.test.DbTests;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//@Category(DbTests.class)
-//public class SourceAuthorsHolderTest {
-//
-//  @ClassRule
-//  public static EsTester esTester = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings()));
-//  @Rule
-//  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-//
-//  SourceAuthorsHolder sut;
-//
-//  @Before
-//  public void setUp() throws Exception {
-//    esTester.truncateIndices();
-//    sut = new SourceAuthorsHolder(new SourceLineIndex(esTester.client()), reportReader);
-//  }
-//
-//  @Test
-//  public void line_author_from_report() {
-//    reportReader.putChangesets(BatchReport.Changesets.newBuilder()
-//      .setComponentRef(123_456_789)
-//      .addChangeset(newChangeset("charb", "123-456-789", 123_456_789L))
-//      .addChangeset(newChangeset("wolinski", "987-654-321", 987_654_321L))
-//      .addChangesetIndexByLine(0)
-//      .addChangesetIndexByLine(0)
-//      .addChangesetIndexByLine(1)
-//      .build());
-//
-//    sut.init("ANY_UUID", 123_456_789, reportReader);
-//
-//    assertThat(sut.lineAuthor(1)).isEqualTo("charb");
-//    assertThat(sut.lineAuthor(2)).isEqualTo("charb");
-//    assertThat(sut.lineAuthor(3)).isEqualTo("wolinski");
-//    // compute last author
-//    assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
-//    assertThat(sut.lineAuthor(null)).isEqualTo("wolinski");
-//  }
-//
-//  @Test
-//  public void line_author_from_index() throws Exception {
-//    esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE,
-//      newSourceLine("cabu", "123-456-789", 123_456_789, 1),
-//      newSourceLine("cabu", "123-456-789", 123_456_789, 2),
-//      newSourceLine("cabu", "123-123-789", 123_456_789, 3),
-//      newSourceLine("wolinski", "987-654-321", 987_654_321, 4),
-//      newSourceLine("cabu", "123-456-789", 123_456_789, 5)
-//      );
-//
-//    sut.init("DEFAULT_UUID", 123, reportReader);
-//
-//    assertThat(sut.lineAuthor(1)).isEqualTo("cabu");
-//    assertThat(sut.lineAuthor(2)).isEqualTo("cabu");
-//    assertThat(sut.lineAuthor(3)).isEqualTo("cabu");
-//    assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
-//    assertThat(sut.lineAuthor(5)).isEqualTo("cabu");
-//    assertThat(sut.lineAuthor(6)).isEqualTo("wolinski");
-//  }
-//
-//  @Test(expected = IllegalStateException.class)
-//  public void fail_when_component_ref_is_not_filled() {
-//    sut.init("ANY_UUID", null, reportReader);
-//    sut.lineAuthor(0);
-//  }
-//
-//  private BatchReport.Changesets.Changeset.Builder newChangeset(String author, String revision, long date) {
-//    return BatchReport.Changesets.Changeset.newBuilder()
-//      .setAuthor(author)
-//      .setRevision(revision)
-//      .setDate(date);
-//  }
-//
-//  private SourceLineDoc newSourceLine(String author, String revision, long date, int lineNumber) {
-//    return new SourceLineDoc()
-//      .setScmAuthor(author)
-//      .setScmRevision(revision)
-//      .setScmDate(new Date(date))
-//      .setLine(lineNumber)
-//      .setProjectUuid("PROJECT_UUID")
-//      .setFileUuid("DEFAULT_UUID");
-//  }
-//}
index 381579a9e1e68e4965f67bad31efb3399e5e0475..28e2332d11596894a4678620968aac32457b62a4 100644 (file)
@@ -167,11 +167,19 @@ public class MeasureRepositoryRule extends ExternalResource implements MeasureRe
     return Optional.fromNullable(rawMeasures.get(new InternalKey(component, metric, rule.getId(), null)));
   }
 
+  public Optional<Measure> getRawRuleMeasure(Component component, Metric metric, int ruleId) {
+    return Optional.fromNullable(rawMeasures.get(new InternalKey(component, metric, ruleId, null)));
+  }
+
   @Override
   public Optional<Measure> getRawMeasure(Component component, Metric metric, Characteristic characteristic) {
     return Optional.fromNullable(rawMeasures.get(new InternalKey(component, metric, null, characteristic.getId())));
   }
 
+  public Optional<Measure> getRawCharacteristicMeasure(Component component, Metric metric, int characteristicId) {
+    return Optional.fromNullable(rawMeasures.get(new InternalKey(component, metric, null, characteristicId)));
+  }
+
   @Override
   public SetMultimap<String, Measure> getRawMeasures(Component component) {
     ImmutableSetMultimap.Builder<String, Measure> builder = ImmutableSetMultimap.builder();
index edf10791eb23a6784fd3dfcf5eae2a6467cfaaa4..429b2c3282f6c46980232ed147340069ff10d5d4 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//
-//package org.sonar.server.computation.step;
-//
-//import java.util.Collection;
-//import org.junit.After;
-//import org.junit.Before;
-//import org.junit.ClassRule;
-//import org.junit.Test;
-//import org.junit.experimental.categories.Category;
-//import org.sonar.core.persistence.DbSession;
-//import org.sonar.core.persistence.DbTester;
-//import org.sonar.core.technicaldebt.db.CharacteristicDao;
-//import org.sonar.server.computation.debt.Characteristic;
-//import org.sonar.server.computation.debt.DebtModelHolderImpl;
-//import org.sonar.server.computation.debt.MutableDebtModelHolder;
-//import org.sonar.server.db.DbClient;
-//import org.sonar.test.DbTests;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//@Category(DbTests.class)
-//public class FeedDebtModelStepTest extends BaseStepTest {
-//
-//  @ClassRule
-//  public static final DbTester dbTester = new DbTester();
-//
-//  DbClient dbClient;
-//
-//  DbSession dbSession;
-//
-//  MutableDebtModelHolder debtModelHolder = new DebtModelHolderImpl();
-//
-//  FeedDebtModelStep sut;
-//
-//  @Before
-//  public void setUp() throws Exception {
-//    dbTester.truncateTables();
-//    dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new CharacteristicDao(dbTester.myBatis()));
-//    dbSession = dbClient.openSession(false);
-//
-//    sut = new FeedDebtModelStep(dbClient, debtModelHolder);
-//  }
-//
-//  @After
-//  public void tearDown() throws Exception {
-//    dbSession.close();
-//  }
-//
-//  @Override
-//  protected ComputationStep step() {
-//    return sut;
-//  }
-//
-//  @Test
-//  public void feed_characteristics() throws Exception {
-//    dbTester.prepareDbUnit(getClass(), "shared.xml");
-//
-//    sut.execute();
-//
-//    Collection<Characteristic> rootChars = debtModelHolder.findRootCharacteristics();
-//    assertThat(rootChars).extracting("id").containsOnly(1);
-//    assertThat(rootChars).extracting("key").containsOnly("PORTABILITY");
-//
-//    Collection<Characteristic> subChars = debtModelHolder.findSubCharacteristicsByRootKey("PORTABILITY");
-//    assertThat(subChars).extracting("id").containsOnly(2, 3);
-//    assertThat(subChars).extracting("key").containsOnly("COMPILER_RELATED_PORTABILITY", "HARDWARE_RELATED_PORTABILITY");
-//  }
-//}
+package org.sonar.server.computation.step;
+
+import java.util.Collection;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.server.computation.debt.Characteristic;
+import org.sonar.server.computation.debt.DebtModelHolderImpl;
+import org.sonar.server.computation.debt.MutableDebtModelHolder;
+import org.sonar.server.db.DbClient;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class FeedDebtModelStepTest extends BaseStepTest {
+
+  @ClassRule
+  public static final DbTester dbTester = new DbTester();
+
+  DbClient dbClient;
+
+  DbSession dbSession;
+
+  MutableDebtModelHolder debtModelHolder = new DebtModelHolderImpl();
+
+  FeedDebtModelStep sut;
+
+  @Before
+  public void setUp() throws Exception {
+    dbTester.truncateTables();
+    dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new CharacteristicDao(dbTester.myBatis()));
+    dbSession = dbClient.openSession(false);
+
+    sut = new FeedDebtModelStep(dbClient, debtModelHolder);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    dbSession.close();
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return sut;
+  }
+
+  @Test
+  public void feed_characteristics() throws Exception {
+    dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+    sut.execute();
+
+    Collection<Characteristic> rootChars = debtModelHolder.getRootCharacteristics();
+    assertThat(rootChars).extracting("id").containsOnly(1);
+    assertThat(rootChars).extracting("key").containsOnly("PORTABILITY");
+
+    Characteristic subChar = debtModelHolder.getCharacteristicById(1);
+    assertThat(subChar).isNotNull();
+  }
+}
index ff4b22ba991b4b8078d8fc8428ee2b5bb79b9156..fbb5aae1c1abb63d55a9d961708325bf478e4d70 100644 (file)
@@ -30,15 +30,17 @@ import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.utils.System2;
+import org.sonar.batch.protocol.output.BatchReport;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.DefaultIssueComment;
 import org.sonar.core.issue.FieldDiffs;
 import org.sonar.core.issue.db.UpdateConflictResolver;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.DbTester;
+import org.sonar.server.computation.batch.BatchReportReaderRule;
 import org.sonar.server.computation.issue.IssueCache;
-import org.sonar.server.computation.issue.RuleCache;
 import org.sonar.server.computation.issue.RuleCacheLoader;
+import org.sonar.server.computation.issue.RuleRepositoryImpl;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.issue.db.IssueDao;
 import org.sonar.server.rule.db.RuleDao;
@@ -56,6 +58,9 @@ public class PersistIssuesStepTest extends BaseStepTest {
   @ClassRule
   public static DbTester dbTester = new DbTester();
 
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
   DbSession session;
 
   DbClient dbClient;
@@ -76,11 +81,12 @@ public class PersistIssuesStepTest extends BaseStepTest {
     dbTester.truncateTables();
     session = dbTester.myBatis().openSession(false);
     dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new IssueDao(dbTester.myBatis()), new RuleDao(system2));
-
     issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
     system2 = mock(System2.class);
     when(system2.now()).thenReturn(NOW);
-    step = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleCache(new RuleCacheLoader(dbClient)), issueCache);
+    reportReader.setMetadata(BatchReport.Metadata.getDefaultInstance());
+
+    step = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleRepositoryImpl(new RuleCacheLoader(dbClient, reportReader)), issueCache);
   }
 
   @After
index b99f981492ae96da0fcbf1d3c50bdaefdc6e0296..627545565ae0d2ac636c4871f9fefd01d0ce344a 100644 (file)
@@ -29,42 +29,31 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.sonar.api.measures.Metric;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.Uuids;
 import org.sonar.batch.protocol.Constants.MeasureValueType;
 import org.sonar.batch.protocol.output.BatchReport;
 import org.sonar.core.component.ComponentDto;
+import org.sonar.core.metric.db.MetricDto;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.DbTester;
-import org.sonar.core.rule.RuleDto;
 import org.sonar.server.component.db.ComponentDao;
 import org.sonar.server.computation.batch.BatchReportReaderRule;
 import org.sonar.server.computation.batch.TreeRootHolderRule;
 import org.sonar.server.computation.component.Component;
 import org.sonar.server.computation.component.DbIdsRepository;
 import org.sonar.server.computation.component.DumbComponent;
-import org.sonar.server.computation.issue.RuleCache;
-import org.sonar.server.computation.issue.RuleCacheLoader;
 import org.sonar.server.computation.measure.MeasureRepository;
 import org.sonar.server.computation.measure.MeasureRepositoryImpl;
-import org.sonar.server.computation.metric.MetricRepositoryRule;
+import org.sonar.server.computation.metric.MetricRepositoryImpl;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.measure.persistence.MeasureDao;
 import org.sonar.server.metric.persistence.MetricDao;
-import org.sonar.server.rule.RuleTesting;
 import org.sonar.server.rule.db.RuleDao;
 import org.sonar.test.DbTests;
 
-import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.measures.CoreMetrics.DUPLICATIONS_DATA;
-import static org.sonar.api.measures.CoreMetrics.DUPLICATIONS_DATA_KEY;
-import static org.sonar.api.measures.CoreMetrics.FILE_COMPLEXITY_DISTRIBUTION;
-import static org.sonar.api.measures.CoreMetrics.FILE_COMPLEXITY_DISTRIBUTION_KEY;
-import static org.sonar.api.measures.CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION;
-import static org.sonar.api.measures.CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY;
 
 @Category(DbTests.class)
 public class PersistMeasuresStepTest extends BaseStepTest {
@@ -73,31 +62,23 @@ public class PersistMeasuresStepTest extends BaseStepTest {
   private static final String STRING_METRIC_KEY = "string-metric-key";
   private static final String DOUBLE_METRIC_KEY = "double-metric-key";
   private static final String OPTIMIZED_METRIC_KEY = "optimized-metric-key";
-
-  private static final Metric STRING_METRIC = new Metric.Builder(STRING_METRIC_KEY, "String metric", Metric.ValueType.STRING).create();
-  private static final Metric DOUBLE_METRIC = new Metric.Builder(DOUBLE_METRIC_KEY, "Double metric", Metric.ValueType.FLOAT).create();
-
   private static final RuleKey RULE_KEY = RuleKey.of("repo", "rule-key");
-
   private static final int PROJECT_REF = 1;
   private static final int FILE_REF = 2;
 
   @ClassRule
   public static DbTester dbTester = new DbTester();
-
   @Rule
   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
   @Rule
   public BatchReportReaderRule reportReader = new BatchReportReaderRule();
 
-  @Rule
-  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
-
   DbClient dbClient;
   DbSession session;
   DbIdsRepository dbIdsRepository = new DbIdsRepository();
-  RuleDto rule;
+  MetricDto stringMetric;
+  MetricDto doubleMetric;
+  MetricDto optimizedMetric;
   ComponentDto projectDto;
   ComponentDto fileDto;
 
@@ -110,12 +91,18 @@ public class PersistMeasuresStepTest extends BaseStepTest {
     dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new MeasureDao(), new ComponentDao(), new MetricDao(), new RuleDao(System2.INSTANCE));
     session = dbClient.openSession(false);
 
-    rule = RuleTesting.newDto(RULE_KEY);
-    dbClient.ruleDao().insert(session, rule);
+    stringMetric = new MetricDto().setValueType("STRING").setShortName("String metric").setKey(STRING_METRIC_KEY).setEnabled(true);
+    dbClient.metricDao().insert(session, stringMetric);
+    doubleMetric = new MetricDto().setValueType("FLOAT").setShortName("Double metric").setKey(DOUBLE_METRIC_KEY).setEnabled(true);
+    dbClient.metricDao().insert(session, doubleMetric);
+    optimizedMetric = new MetricDto().setValueType("BOOL").setShortName("Optimized metric").setKey(OPTIMIZED_METRIC_KEY).setEnabled(true).setOptimizedBestValue(true)
+      .setBestValue(1d);
+    dbClient.metricDao().insert(session, optimizedMetric);
     session.commit();
 
-    RuleCache ruleCache = new RuleCache(new RuleCacheLoader(dbClient));
-    MeasureRepository measureRepository = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository, ruleCache);
+    MetricRepositoryImpl metricRepository = new MetricRepositoryImpl(dbClient);
+    metricRepository.start();
+    MeasureRepository measureRepository = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository);
     session.commit();
 
     sut = new PersistMeasuresStep(dbClient, metricRepository, dbIdsRepository, treeRootHolder, measureRepository);
@@ -140,9 +127,6 @@ public class PersistMeasuresStepTest extends BaseStepTest {
 
   @Test
   public void insert_measures_from_report() throws Exception {
-    metricRepository.add(1, STRING_METRIC);
-    metricRepository.add(2, DOUBLE_METRIC);
-
     reportReader.putMeasures(PROJECT_REF, Arrays.asList(
       BatchReport.Measure.newBuilder()
         .setValueType(MeasureValueType.STRING)
@@ -172,14 +156,14 @@ public class PersistMeasuresStepTest extends BaseStepTest {
     sut.execute();
     session.commit();
 
-    assertThat(dbTester.countRowsOfTable("project_measures")).isEqualTo(2);
+    assertThat(dbTester.countRowsOfTable("project_measures")).isEqualTo(FILE_REF);
 
     List<Map<String, Object>> dtos = retrieveDtos();
 
     Map<String, Object> dto = dtos.get(0);
     assertThat(dto.get("snapshotId")).isEqualTo(3L);
     assertThat(dto.get("componentId")).isEqualTo(projectDto.getId());
-    assertThat(dto.get("metricId")).isEqualTo(1L);
+    assertThat(dto.get("metricId")).isEqualTo(stringMetric.getId().longValue());
     assertThat(dto.get("ruleId")).isNull();
     assertThat(dto.get("textValue")).isEqualTo("measure-data");
     assertThat(dto.get("severity")).isNull();
@@ -187,15 +171,9 @@ public class PersistMeasuresStepTest extends BaseStepTest {
     dto = dtos.get(PROJECT_REF);
     assertThat(dto.get("snapshotId")).isEqualTo(4L);
     assertThat(dto.get("componentId")).isEqualTo(fileDto.getId());
-<<<<<<< HEAD
-    assertThat(dto.get("metricId")).isEqualTo(2L);
-    assertThat(dto.get("ruleId")).isEqualTo(rule.getId().longValue());
+    assertThat(dto.get("metricId")).isEqualTo(doubleMetric.getId().longValue());
     assertThat(dto.get("characteristicId")).isNull();
     assertThat(dto.get("value")).isEqualTo(123.1d);
-=======
-    assertThat(dto.get("metricId")).isEqualTo(doubleMetric.getId().longValue());
-    assertThat(dto.get("value")).isEqualTo(123.123d);
->>>>>>> SONAR-6588 integrate issues to Compute Engine
     assertThat(dto.get("severity")).isNull();
   }
 
@@ -207,8 +185,6 @@ public class PersistMeasuresStepTest extends BaseStepTest {
 
   @Test
   public void bestValue_measure_of_bestValueOptimized_metrics_are_not_persisted() {
-    metricRepository.add(1, new Metric.Builder(OPTIMIZED_METRIC_KEY, "Optimized metric", Metric.ValueType.BOOL).setOptimizedBestValue(true).setBestValue(1d).create());
-
     reportReader.putMeasures(FILE_REF, Arrays.asList(
       BatchReport.Measure.newBuilder()
         .setValueType(MeasureValueType.BOOLEAN)
@@ -224,9 +200,6 @@ public class PersistMeasuresStepTest extends BaseStepTest {
 
   @Test
   public void empty_values_are_not_persisted() {
-    metricRepository.add(1, STRING_METRIC);
-    metricRepository.add(2, DOUBLE_METRIC);
-
     reportReader.putMeasures(FILE_REF, Arrays.asList(
       BatchReport.Measure.newBuilder()
         .setValueType(MeasureValueType.STRING)
@@ -244,86 +217,6 @@ public class PersistMeasuresStepTest extends BaseStepTest {
     assertThat(retrieveDtos()).isEmpty();
   }
 
-  @Test(expected = IllegalStateException.class)
-  public void fail_with_ISE_when_trying_to_insert_forbidden_measures() throws Exception {
-    metricRepository.add(1, DUPLICATIONS_DATA);
-
-    reportReader.putMeasures(FILE_REF, Arrays.asList(
-      BatchReport.Measure.newBuilder()
-        .setValueType(MeasureValueType.STRING)
-        .setStringValue("{duplications}")
-        .setMetricKey(DUPLICATIONS_DATA_KEY)
-        .build()));
-
-    sut.execute();
-  }
-
-  @Test
-  public void do_not_insert_file_complexity_distribution_metric_on_files() throws Exception {
-    metricRepository.add(1, FILE_COMPLEXITY_DISTRIBUTION);
-
-    reportReader.putMeasures(PROJECT_REF, Arrays.asList(
-      BatchReport.Measure.newBuilder()
-        .setValueType(MeasureValueType.STRING)
-        .setStringValue("0=1;2=10")
-        .setMetricKey(FILE_COMPLEXITY_DISTRIBUTION_KEY)
-        .build()));
-
-    // Should not be persisted
-    reportReader.putMeasures(FILE_REF, Arrays.asList(
-      BatchReport.Measure.newBuilder()
-        .setValueType(MeasureValueType.STRING)
-        .setStringValue("0=1;2=10")
-        .setMetricKey(FILE_COMPLEXITY_DISTRIBUTION_KEY)
-        .build()));
-
-    sut.execute();
-
-    session.commit();
-
-    assertThat(dbTester.countRowsOfTable("project_measures")).isEqualTo(1);
-
-    List<Map<String, Object>> dtos = retrieveDtos();
-
-    Map<String, Object> dto = dtos.get(0);
-    assertThat(dto.get("snapshotId")).isEqualTo(3L);
-    assertThat(dto.get("componentId")).isEqualTo(projectDto.getId());
-    assertThat(dto.get("textValue")).isEqualTo("0=1;2=10");
-  }
-
-  @Test
-  public void do_not_insert_function_complexity_distribution_metric_on_files() throws Exception {
-    metricRepository.add(1, FUNCTION_COMPLEXITY_DISTRIBUTION);
-
-    reportReader.putMeasures(PROJECT_REF, Arrays.asList(
-      BatchReport.Measure.newBuilder()
-        .setValueType(MeasureValueType.STRING)
-        .setStringValue("0=1;2=10")
-        .setMetricKey(FUNCTION_COMPLEXITY_DISTRIBUTION_KEY)
-        .build()));
-
-    // Should not be persisted
-    reportReader.putMeasures(FILE_REF, Arrays.asList(
-      BatchReport.Measure.newBuilder()
-        .setValueType(MeasureValueType.STRING)
-        .setStringValue("0=1;2=10")
-        .setMetricKey(FUNCTION_COMPLEXITY_DISTRIBUTION_KEY)
-        .build()));
-
-    sut.execute();
-
-    session.commit();
-
-    assertThat(dbTester.countRowsOfTable("project_measures")).isEqualTo(1);
-
-    List<Map<String, Object>> dtos = retrieveDtos();
-
-    Map<String, Object> dto = dtos.get(0);
-    assertThat(dto.get("snapshotId")).isEqualTo(3L);
-    assertThat(dto.get("componentId")).isEqualTo(projectDto.getId());
-    assertThat(dto.get("textValue")).isEqualTo("0=1;2=10");
-  }
-
   private ComponentDto addComponent(String key) {
     ComponentDto componentDto = new ComponentDto().setKey(key).setUuid(Uuids.create());
     dbClient.componentDao().insert(session, componentDto);
index e79b559ed79fa75f6073dad9643ab9d2e1e38d94..b846287b270d44fa7f5d57ca7037d0c963eb971b 100644 (file)
@@ -25,18 +25,18 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.mockito.Mockito;
-import org.sonar.core.issue.DefaultIssue;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.utils.System2;
 import org.sonar.batch.protocol.Constants;
 import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.core.issue.DefaultIssue;
 import org.sonar.server.computation.batch.BatchReportReaderRule;
 import org.sonar.server.computation.batch.TreeRootHolderRule;
 import org.sonar.server.computation.component.Component;
 import org.sonar.server.computation.component.DumbComponent;
 import org.sonar.server.computation.issue.IssueCache;
-import org.sonar.server.computation.issue.RuleCache;
+import org.sonar.server.computation.issue.RuleRepository;
 import org.sonar.server.issue.notification.IssueChangeNotification;
 import org.sonar.server.issue.notification.NewIssuesNotification;
 import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
@@ -71,7 +71,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest {
   public void setUp() throws Exception {
     issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
     NewIssuesNotificationFactory newIssuesNotificationFactory = mock(NewIssuesNotificationFactory.class, Mockito.RETURNS_DEEP_STUBS);
-    sut = new SendIssueNotificationsStep(issueCache, mock(RuleCache.class), treeRootHolder, notifService, reportReader, newIssuesNotificationFactory);
+    sut = new SendIssueNotificationsStep(issueCache, mock(RuleRepository.class), treeRootHolder, notifService, reportReader, newIssuesNotificationFactory);
 
     treeRootHolder.setRoot(DumbComponent.builder(Component.Type.PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build());
 
index 97ee8157a48df3e9c966206a96e89583e05e94ac..075ab07d14f4101fe00ecaa0794196085d88b15d 100644 (file)
@@ -1,11 +1,11 @@
 <dataset>
-  <rules id="1" name="Rule One" plugin_name="squid" plugin_rule_key="R001"
+  <rules id="1" name="JS One" plugin_name="js" plugin_rule_key="JS01"
          plugin_config_key="[null]" description="[null]" priority="4"
          status="READY"
          is_template="[false]" template_id="[null]"
          tags="[null]" system_tags="[null]"/>
 
-  <rules id="2" name="Rule Two" plugin_name="squid" plugin_rule_key="R002"
+  <rules id="2" name="Java One" plugin_name="java" plugin_rule_key="JAV01"
          plugin_config_key="[null]" description="[null]" priority="4"
          status="READY"
          is_template="[false]" template_id="[null]"
index 0d56aa5ec07f42505a9f7030a2ba9eb2317530d0..9f3a7f46ea4f69e83e380286f723655020f7aa1b 100644 (file)
@@ -4,7 +4,7 @@
          plugin_config_key="[null]" priority="0" is_template="[true]" language="xoo"  template_id="[null]"
          note_data="[null]" note_user_login="[null]" note_created_at="[null]" note_updated_at="[null]"
          characteristic_id="100" default_characteristic_id="101"
-         remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET"
+         remediation_function="LINEAR_OFFSET" default_remediation_function="LINEAR_OFFSET"
          remediation_coeff="1h" default_remediation_coeff="5d"
          remediation_offset="5min" default_remediation_offset="10h"
          effort_to_fix_description="[null]" description_format="MARKDOWN"
index 21d7b857ca9b8be245a5f46c3a9085f7568a6291..93549a0163f0e96f1ce2d920c553d5976bc456d4 100644 (file)
@@ -5764,6 +5764,10 @@ public final class BatchReport {
   }
   /**
    * Protobuf type {@code Measures}
+   *
+   * <pre>
+   * TODO to be removed. It prevents streaming 
+   * </pre>
    */
   public static final class Measures extends
       com.google.protobuf.GeneratedMessage implements
@@ -6040,6 +6044,10 @@ public final class BatchReport {
     }
     /**
      * Protobuf type {@code Measures}
+     *
+     * <pre>
+     * TODO to be removed. It prevents streaming 
+     * </pre>
      */
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder<Builder> implements
@@ -6587,23 +6595,6 @@ public final class BatchReport {
      */
     com.google.protobuf.ByteString
         getAttributesBytes();
-
-    /**
-     * <code>optional int64 debt_in_minutes = 9;</code>
-     *
-     * <pre>
-     * TODO should it be moved to compute engine?
-     * </pre>
-     */
-    boolean hasDebtInMinutes();
-    /**
-     * <code>optional int64 debt_in_minutes = 9;</code>
-     *
-     * <pre>
-     * TODO should it be moved to compute engine?
-     * </pre>
-     */
-    long getDebtInMinutes();
   }
   /**
    * Protobuf type {@code Issue}
@@ -6711,11 +6702,6 @@ public final class BatchReport {
               attributes_ = bs;
               break;
             }
-            case 72: {
-              bitField0_ |= 0x00000080;
-              debtInMinutes_ = input.readInt64();
-              break;
-            }
           }
         }
       } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -7001,29 +6987,6 @@ public final class BatchReport {
       }
     }
 
-    public static final int DEBT_IN_MINUTES_FIELD_NUMBER = 9;
-    private long debtInMinutes_;
-    /**
-     * <code>optional int64 debt_in_minutes = 9;</code>
-     *
-     * <pre>
-     * TODO should it be moved to compute engine?
-     * </pre>
-     */
-    public boolean hasDebtInMinutes() {
-      return ((bitField0_ & 0x00000080) == 0x00000080);
-    }
-    /**
-     * <code>optional int64 debt_in_minutes = 9;</code>
-     *
-     * <pre>
-     * TODO should it be moved to compute engine?
-     * </pre>
-     */
-    public long getDebtInMinutes() {
-      return debtInMinutes_;
-    }
-
     private void initFields() {
       ruleRepository_ = "";
       ruleKey_ = "";
@@ -7033,7 +6996,6 @@ public final class BatchReport {
       tag_ = com.google.protobuf.LazyStringArrayList.EMPTY;
       effortToFix_ = 0D;
       attributes_ = "";
-      debtInMinutes_ = 0L;
     }
     private byte memoizedIsInitialized = -1;
     public final boolean isInitialized() {
@@ -7072,9 +7034,6 @@ public final class BatchReport {
       if (((bitField0_ & 0x00000040) == 0x00000040)) {
         output.writeBytes(8, getAttributesBytes());
       }
-      if (((bitField0_ & 0x00000080) == 0x00000080)) {
-        output.writeInt64(9, debtInMinutes_);
-      }
       getUnknownFields().writeTo(output);
     }
 
@@ -7121,10 +7080,6 @@ public final class BatchReport {
         size += com.google.protobuf.CodedOutputStream
           .computeBytesSize(8, getAttributesBytes());
       }
-      if (((bitField0_ & 0x00000080) == 0x00000080)) {
-        size += com.google.protobuf.CodedOutputStream
-          .computeInt64Size(9, debtInMinutes_);
-      }
       size += getUnknownFields().getSerializedSize();
       memoizedSerializedSize = size;
       return size;
@@ -7258,8 +7213,6 @@ public final class BatchReport {
         bitField0_ = (bitField0_ & ~0x00000040);
         attributes_ = "";
         bitField0_ = (bitField0_ & ~0x00000080);
-        debtInMinutes_ = 0L;
-        bitField0_ = (bitField0_ & ~0x00000100);
         return this;
       }
 
@@ -7321,10 +7274,6 @@ public final class BatchReport {
           to_bitField0_ |= 0x00000040;
         }
         result.attributes_ = attributes_;
-        if (((from_bitField0_ & 0x00000100) == 0x00000100)) {
-          to_bitField0_ |= 0x00000080;
-        }
-        result.debtInMinutes_ = debtInMinutes_;
         result.bitField0_ = to_bitField0_;
         onBuilt();
         return result;
@@ -7380,9 +7329,6 @@ public final class BatchReport {
           attributes_ = other.attributes_;
           onChanged();
         }
-        if (other.hasDebtInMinutes()) {
-          setDebtInMinutes(other.getDebtInMinutes());
-        }
         this.mergeUnknownFields(other.getUnknownFields());
         return this;
       }
@@ -7906,54 +7852,6 @@ public final class BatchReport {
         return this;
       }
 
-      private long debtInMinutes_ ;
-      /**
-       * <code>optional int64 debt_in_minutes = 9;</code>
-       *
-       * <pre>
-       * TODO should it be moved to compute engine?
-       * </pre>
-       */
-      public boolean hasDebtInMinutes() {
-        return ((bitField0_ & 0x00000100) == 0x00000100);
-      }
-      /**
-       * <code>optional int64 debt_in_minutes = 9;</code>
-       *
-       * <pre>
-       * TODO should it be moved to compute engine?
-       * </pre>
-       */
-      public long getDebtInMinutes() {
-        return debtInMinutes_;
-      }
-      /**
-       * <code>optional int64 debt_in_minutes = 9;</code>
-       *
-       * <pre>
-       * TODO should it be moved to compute engine?
-       * </pre>
-       */
-      public Builder setDebtInMinutes(long value) {
-        bitField0_ |= 0x00000100;
-        debtInMinutes_ = value;
-        onChanged();
-        return this;
-      }
-      /**
-       * <code>optional int64 debt_in_minutes = 9;</code>
-       *
-       * <pre>
-       * TODO should it be moved to compute engine?
-       * </pre>
-       */
-      public Builder clearDebtInMinutes() {
-        bitField0_ = (bitField0_ & ~0x00000100);
-        debtInMinutes_ = 0L;
-        onChanged();
-        return this;
-      }
-
       // @@protoc_insertion_point(builder_scope:Issue)
     }
 
@@ -8004,6 +7902,10 @@ public final class BatchReport {
   }
   /**
    * Protobuf type {@code Issues}
+   *
+   * <pre>
+   * TODO to be removed. It prevents streaming 
+   * </pre>
    */
   public static final class Issues extends
       com.google.protobuf.GeneratedMessage implements
@@ -8280,6 +8182,10 @@ public final class BatchReport {
     }
     /**
      * Protobuf type {@code Issues}
+     *
+     * <pre>
+     * TODO to be removed. It prevents streaming 
+     * </pre>
      */
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder<Builder> implements
@@ -19463,42 +19369,41 @@ public final class BatchReport {
       "alue_4\030\021 \001(\001\022\031\n\021variation_value_5\030\022 \001(\001\022",
       "\021\n\tperson_id\030\024 \001(\005\"<\n\010Measures\022\025\n\rcompon" +
       "ent_ref\030\001 \001(\005\022\031\n\007measure\030\002 \003(\0132\010.Measure" +
-      "\"\273\001\n\005Issue\022\027\n\017rule_repository\030\001 \001(\t\022\020\n\010r" +
+      "\"\242\001\n\005Issue\022\027\n\017rule_repository\030\001 \001(\t\022\020\n\010r" +
       "ule_key\030\002 \001(\t\022\014\n\004line\030\003 \001(\005\022\013\n\003msg\030\004 \001(\t" +
       "\022\033\n\010severity\030\005 \001(\0162\t.Severity\022\013\n\003tag\030\006 \003" +
       "(\t\022\025\n\reffort_to_fix\030\007 \001(\001\022\022\n\nattributes\030" +
-      "\010 \001(\t\022\027\n\017debt_in_minutes\030\t \001(\003\"6\n\006Issues" +
-      "\022\025\n\rcomponent_ref\030\001 \001(\005\022\025\n\005issue\030\002 \003(\0132\006" +
-      ".Issue\"\254\001\n\nChangesets\022\025\n\rcomponent_ref\030\001" +
-      " \001(\005\022(\n\tchangeset\030\002 \003(\0132\025.Changesets.Cha",
-      "ngeset\022 \n\024changesetIndexByLine\030\003 \003(\005B\002\020\001" +
-      "\032;\n\tChangeset\022\020\n\010revision\030\001 \001(\t\022\016\n\006autho" +
-      "r\030\002 \001(\t\022\014\n\004date\030\003 \001(\003\"R\n\tDuplicate\022\026\n\016ot" +
-      "her_file_ref\030\001 \001(\005\022\025\n\005range\030\002 \001(\0132\006.Rang" +
-      "e\022\026\n\016other_file_key\030\003 \001(\t\"M\n\013Duplication" +
-      "\022\037\n\017origin_position\030\001 \001(\0132\006.Range\022\035\n\tdup" +
-      "licate\030\002 \003(\0132\n.Duplicate\"H\n\014Duplications" +
-      "\022\025\n\rcomponent_ref\030\001 \001(\005\022!\n\013duplication\030\002" +
-      " \003(\0132\014.Duplication\"W\n\005Range\022\022\n\nstart_lin" +
-      "e\030\001 \001(\005\022\020\n\010end_line\030\002 \001(\005\022\024\n\014start_offse",
-      "t\030\003 \001(\005\022\022\n\nend_offset\030\004 \001(\005\"~\n\007Symbols\022\020" +
-      "\n\010file_ref\030\001 \001(\005\022\037\n\006symbol\030\002 \003(\0132\017.Symbo" +
-      "ls.Symbol\032@\n\006Symbol\022\033\n\013declaration\030\001 \001(\013" +
-      "2\006.Range\022\031\n\treference\030\002 \003(\0132\006.Range\"\260\001\n\010" +
-      "Coverage\022\014\n\004line\030\001 \001(\005\022\022\n\nconditions\030\002 \001" +
-      "(\005\022\017\n\007ut_hits\030\003 \001(\010\022\017\n\007it_hits\030\004 \001(\010\022\035\n\025" +
-      "ut_covered_conditions\030\005 \001(\005\022\035\n\025it_covere" +
-      "d_conditions\030\006 \001(\005\022\"\n\032overall_covered_co" +
-      "nditions\030\007 \001(\005\"L\n\022SyntaxHighlighting\022\025\n\005" +
-      "range\030\001 \001(\0132\006.Range\022\037\n\004type\030\002 \001(\0162\021.High",
-      "lightingType\"j\n\004Test\022\014\n\004name\030\001 \001(\t\022\033\n\006st" +
-      "atus\030\002 \001(\0162\013.TestStatus\022\026\n\016duration_in_m" +
-      "s\030\003 \001(\003\022\022\n\nstacktrace\030\004 \001(\t\022\013\n\003msg\030\005 \001(\t" +
-      "\"\221\001\n\016CoverageDetail\022\021\n\ttest_name\030\001 \001(\t\0221" +
-      "\n\014covered_file\030\002 \003(\0132\033.CoverageDetail.Co" +
-      "veredFile\0329\n\013CoveredFile\022\020\n\010file_ref\030\001 \001" +
-      "(\005\022\030\n\014covered_line\030\002 \003(\005B\002\020\001B#\n\037org.sona" +
-      "r.batch.protocol.outputH\001"
+      "\010 \001(\t\"6\n\006Issues\022\025\n\rcomponent_ref\030\001 \001(\005\022\025" +
+      "\n\005issue\030\002 \003(\0132\006.Issue\"\254\001\n\nChangesets\022\025\n\r" +
+      "component_ref\030\001 \001(\005\022(\n\tchangeset\030\002 \003(\0132\025" +
+      ".Changesets.Changeset\022 \n\024changesetIndexB",
+      "yLine\030\003 \003(\005B\002\020\001\032;\n\tChangeset\022\020\n\010revision" +
+      "\030\001 \001(\t\022\016\n\006author\030\002 \001(\t\022\014\n\004date\030\003 \001(\003\"R\n\t" +
+      "Duplicate\022\026\n\016other_file_ref\030\001 \001(\005\022\025\n\005ran" +
+      "ge\030\002 \001(\0132\006.Range\022\026\n\016other_file_key\030\003 \001(\t" +
+      "\"M\n\013Duplication\022\037\n\017origin_position\030\001 \001(\013" +
+      "2\006.Range\022\035\n\tduplicate\030\002 \003(\0132\n.Duplicate\"" +
+      "H\n\014Duplications\022\025\n\rcomponent_ref\030\001 \001(\005\022!" +
+      "\n\013duplication\030\002 \003(\0132\014.Duplication\"W\n\005Ran" +
+      "ge\022\022\n\nstart_line\030\001 \001(\005\022\020\n\010end_line\030\002 \001(\005" +
+      "\022\024\n\014start_offset\030\003 \001(\005\022\022\n\nend_offset\030\004 \001",
+      "(\005\"~\n\007Symbols\022\020\n\010file_ref\030\001 \001(\005\022\037\n\006symbo" +
+      "l\030\002 \003(\0132\017.Symbols.Symbol\032@\n\006Symbol\022\033\n\013de" +
+      "claration\030\001 \001(\0132\006.Range\022\031\n\treference\030\002 \003" +
+      "(\0132\006.Range\"\260\001\n\010Coverage\022\014\n\004line\030\001 \001(\005\022\022\n" +
+      "\nconditions\030\002 \001(\005\022\017\n\007ut_hits\030\003 \001(\010\022\017\n\007it" +
+      "_hits\030\004 \001(\010\022\035\n\025ut_covered_conditions\030\005 \001" +
+      "(\005\022\035\n\025it_covered_conditions\030\006 \001(\005\022\"\n\032ove" +
+      "rall_covered_conditions\030\007 \001(\005\"L\n\022SyntaxH" +
+      "ighlighting\022\025\n\005range\030\001 \001(\0132\006.Range\022\037\n\004ty" +
+      "pe\030\002 \001(\0162\021.HighlightingType\"j\n\004Test\022\014\n\004n",
+      "ame\030\001 \001(\t\022\033\n\006status\030\002 \001(\0162\013.TestStatus\022\026" +
+      "\n\016duration_in_ms\030\003 \001(\003\022\022\n\nstacktrace\030\004 \001" +
+      "(\t\022\013\n\003msg\030\005 \001(\t\"\221\001\n\016CoverageDetail\022\021\n\tte" +
+      "st_name\030\001 \001(\t\0221\n\014covered_file\030\002 \003(\0132\033.Co" +
+      "verageDetail.CoveredFile\0329\n\013CoveredFile\022" +
+      "\020\n\010file_ref\030\001 \001(\005\022\030\n\014covered_line\030\002 \003(\005B" +
+      "\002\020\001B#\n\037org.sonar.batch.protocol.outputH\001"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
         new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
@@ -19548,7 +19453,7 @@ public final class BatchReport {
     internal_static_Issue_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_Issue_descriptor,
-        new java.lang.String[] { "RuleRepository", "RuleKey", "Line", "Msg", "Severity", "Tag", "EffortToFix", "Attributes", "DebtInMinutes", });
+        new java.lang.String[] { "RuleRepository", "RuleKey", "Line", "Msg", "Severity", "Tag", "EffortToFix", "Attributes", });
     internal_static_Issues_descriptor =
       getDescriptor().getMessageTypes().get(6);
     internal_static_Issues_fieldAccessorTable = new
index fe310bafadfde763679ec9c1ef7a71fa23de2290..0c13820f339623b21e8f058fecd7fb8224e342fc 100644 (file)
@@ -24,7 +24,6 @@ import java.util.Collections;
 import java.util.List;
 import javax.annotation.CheckForNull;
 import org.sonar.batch.protocol.ProtobufUtil;
-import org.sonar.batch.protocol.output.BatchReport.Issues;
 
 public class BatchReportReader {
 
index 361c0419fff471a334c8dcf61a76efe8c7236b43..08c5340d3662b90b08858eab60fb61470af83629 100644 (file)
@@ -99,6 +99,7 @@ message Measure {
   optional int32 person_id = 20;
 }
 
+/* TODO to be removed. It prevents streaming */
 message Measures {
   optional int32 component_ref = 1;
   repeated Measure measure = 2;
@@ -113,11 +114,9 @@ message Issue {
   repeated string tag = 6;
   optional double effort_to_fix = 7;
   optional string attributes = 8;
-
-  // TODO should it be moved to compute engine?
-  optional int64 debt_in_minutes = 9;
 }
 
+/* TODO to be removed. It prevents streaming */
 message Issues {
   optional int32 component_ref = 1;
   repeated Issue issue = 2;
index 2ff7190907b2ecccbdb44fa21a26f8b7112efc4d..73c7604860c19f7b66a26c15b5242d818aafb1eb 100644 (file)
  */
 package org.sonar.batch.issue;
 
-import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import javax.annotation.Nullable;
-import org.sonar.api.batch.debt.DebtRemediationFunction;
 import org.sonar.api.batch.rule.ActiveRule;
 import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.batch.rule.Rule;
@@ -31,7 +29,6 @@ import org.sonar.api.batch.rule.internal.DefaultActiveRule;
 import org.sonar.api.resources.Project;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Violation;
-import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.MessageException;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.DefaultIssueBuilder;
@@ -118,31 +115,5 @@ public class ModuleIssues {
     if (issue.severity() == null) {
       issue.setSeverity(activeRule.severity());
     }
-    if (rule != null) {
-      DebtRemediationFunction function = rule.debtRemediationFunction();
-      if (function != null) {
-        issue.setDebt(calculateDebt(function, issue.effortToFix(), rule.key()));
-      }
-    }
-  }
-
-  private Duration calculateDebt(DebtRemediationFunction function, @Nullable Double effortToFix, RuleKey ruleKey) {
-    if (DebtRemediationFunction.Type.CONSTANT_ISSUE.equals(function.type()) && effortToFix != null) {
-      throw new IllegalArgumentException("Rule '" + ruleKey + "' can not use 'Constant/issue' remediation function " +
-        "because this rule does not have a fixed remediation cost.");
-    }
-    Duration result = Duration.create(0);
-    Duration factor = function.coefficient();
-    Duration offset = function.offset();
-
-    if (factor != null) {
-      int effortToFixValue = Objects.firstNonNull(effortToFix, 1).intValue();
-      result = factor.multiply(effortToFixValue);
-    }
-    if (offset != null) {
-      result = result.add(offset);
-    }
-    return result;
   }
-
 }
index 3a616e62465f170a14b2a93bbda9f60db8cf5958..e41acc6760da8a11f99bfe6211cfd6a16b1f91a8 100644 (file)
@@ -108,7 +108,7 @@ public final class PhaseExecutor {
       postJobsExecutor.execute(sensorContext);
     }
     cleanMemory();
-      eventBus.fireEvent(new ProjectAnalysisEvent(module, false));
+    eventBus.fireEvent(new ProjectAnalysisEvent(module, false));
   }
 
   private void publishReportJob() {
index 9c56d2404afe9748d814a2a308f81081f8ea0082..b3e3692d937c4bdfc53d71b303aecfd3a497411d 100644 (file)
@@ -79,11 +79,6 @@ public class IssuesPublisher implements ReportPublisherStep {
     if (effortToFix != null) {
       builder.setEffortToFix(effortToFix);
     }
-    Long debtInMinutes = issue.debtInMinutes();
-    if (debtInMinutes != null) {
-      builder.setDebtInMinutes(debtInMinutes);
-    }
-
     return builder.build();
   }
 
index e0fb8bb54a97eac8299df762593d608f26f61486..b9c8f06511b86bf152a1da30013f72e04c3b28a8 100644 (file)
@@ -27,7 +27,6 @@ import org.sonar.api.batch.measure.MetricFinder;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.PersistenceMode;
-import org.sonar.api.technicaldebt.batch.Requirement;
 
 class MeasureValueCoder implements ValueCoder {
 
index 6f9961c681a14c2bdef56adf2a4823350cee2106..356b9f9e43633ecd297c093c3c7b5b9e1da79f76 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.batch.issue;
 
+import java.util.Calendar;
+import java.util.Date;
 import org.apache.commons.lang.time.DateUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,10 +28,8 @@ import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.batch.debt.DebtRemediationFunction;
 import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
 import org.sonar.api.batch.rule.internal.RulesBuilder;
-import org.sonar.core.issue.DefaultIssue;
 import org.sonar.api.resources.File;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
@@ -37,11 +37,8 @@ import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RulePriority;
 import org.sonar.api.rules.Violation;
-import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.MessageException;
-
-import java.util.Calendar;
-import java.util.Date;
+import org.sonar.core.issue.DefaultIssue;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -249,106 +246,6 @@ public class ModuleIssuesTest {
     verifyZeroInteractions(cache);
   }
 
-  @Test
-  public void set_debt_with_linear_function() {
-    ruleBuilder.add(SQUID_RULE_KEY)
-      .setName(SQUID_RULE_NAME)
-      .setDebtRemediationFunction(DebtRemediationFunction.createLinear(Duration.create(10L)));
-    activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate();
-    initModuleIssues();
-
-    Date analysisDate = new Date();
-    when(project.getAnalysisDate()).thenReturn(analysisDate);
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(SQUID_RULE_KEY)
-      .setSeverity(Severity.CRITICAL)
-      .setEffortToFix(2d);
-
-    when(filters.accept(issue)).thenReturn(true);
-    moduleIssues.initAndAddIssue(issue);
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(cache).put(argument.capture());
-    assertThat(argument.getValue().debt()).isEqualTo(Duration.create(20L));
-  }
-
-  @Test
-  public void set_debt_with_linear_with_offset_function() {
-    ruleBuilder.add(SQUID_RULE_KEY)
-      .setName(SQUID_RULE_NAME)
-      .setDebtRemediationFunction(DebtRemediationFunction.createLinearWithOffset(Duration.create(10L), Duration.create(25L)));
-    activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate();
-    initModuleIssues();
-
-    Date analysisDate = new Date();
-    when(project.getAnalysisDate()).thenReturn(analysisDate);
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(SQUID_RULE_KEY)
-      .setSeverity(Severity.CRITICAL)
-      .setEffortToFix(2d);
-
-    when(filters.accept(issue)).thenReturn(true);
-    moduleIssues.initAndAddIssue(issue);
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(cache).put(argument.capture());
-    assertThat(argument.getValue().debt()).isEqualTo(Duration.create(45L));
-  }
-
-  @Test
-  public void set_debt_with_constant_issue_function() {
-    ruleBuilder.add(SQUID_RULE_KEY)
-      .setName(SQUID_RULE_NAME)
-      .setDebtRemediationFunction(DebtRemediationFunction.createConstantPerIssue(Duration.create(10L)));
-    activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate();
-    initModuleIssues();
-
-    Date analysisDate = new Date();
-    when(project.getAnalysisDate()).thenReturn(analysisDate);
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(SQUID_RULE_KEY)
-      .setSeverity(Severity.CRITICAL)
-      .setEffortToFix(null);
-
-    when(filters.accept(issue)).thenReturn(true);
-    moduleIssues.initAndAddIssue(issue);
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(cache).put(argument.capture());
-    assertThat(argument.getValue().debt()).isEqualTo(Duration.create(10L));
-  }
-
-  @Test
-  public void fail_to_set_debt_with_constant_issue_function_when_effort_to_fix_is_set() {
-    ruleBuilder.add(SQUID_RULE_KEY)
-      .setName(SQUID_RULE_NAME)
-      .setDebtRemediationFunction(DebtRemediationFunction.createConstantPerIssue(Duration.create(25L)));
-    activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate();
-    initModuleIssues();
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(SQUID_RULE_KEY)
-      .setSeverity(Severity.CRITICAL)
-      .setEffortToFix(2d);
-
-    when(filters.accept(issue)).thenReturn(true);
-
-    try {
-      moduleIssues.initAndAddIssue(issue);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class)
-        .hasMessage("Rule 'squid:AvoidCycle' can not use 'Constant/issue' remediation function because this rule does not have a fixed remediation cost.");
-    }
-  }
-
   /**
    * Every rules and active rules has to be added in builders before creating ModuleIssues
    */
index bdcce1bd74f6b542f94182a9e26c4f7e7304198e..a27ce7d24478fe028be1b5065d08a318849c738e 100644 (file)
@@ -31,7 +31,6 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.resources.Project;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.Duration;
 import org.sonar.batch.index.BatchComponentCache;
 import org.sonar.batch.issue.IssueCache;
 import org.sonar.batch.protocol.output.BatchReportReader;
@@ -81,7 +80,6 @@ public class IssuesPublisherTest {
     issue2.setLine(2);
     issue2.setMessage("msg");
     issue2.setEffortToFix(2d);
-    issue2.setDebt(Duration.create(2));
     issue2.setResolution("FIXED");
     issue2.setStatus("RESOLVED");
     issue2.setChecksum("checksum");
diff --git a/sonar-core/src/main/java/org/sonar/core/computation/package-info.java b/sonar-core/src/main/java/org/sonar/core/computation/package-info.java
deleted file mode 100644 (file)
index 218db23..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-@ParametersAreNonnullByDefault
-package org.sonar.core.computation;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index 3e7c21e8d2398408fde53f43b800d25886763e92..23018566dc450efa605139fe25f437b9898804c6 100644 (file)
@@ -65,11 +65,11 @@ public class IssueChangeDao implements DaoComponent {
     }
   }
 
-  public List<IssueChangeDto> selectChangelogOfUnresolvedIssuesByComponent(String componentUuid) {
+  public List<IssueChangeDto> selectChangelogOfNonClosedIssuesByComponent(String componentUuid) {
     DbSession session = mybatis.openSession(false);
     try {
       IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class);
-      return mapper.selectChangelogOfUnresolvedIssuesByComponent(componentUuid, IssueChangeDto.TYPE_FIELD_CHANGE);
+      return mapper.selectChangelogOfNonClosedIssuesByComponent(componentUuid, IssueChangeDto.TYPE_FIELD_CHANGE);
 
     } finally {
       MyBatis.closeQuietly(session);
index 732ddc5c75bf2fa2ca186b4bc0cefd0ef96d4844..61d6b8e01ab70d2d7cf3dc0b7dfc473e6a0769a7 100644 (file)
@@ -41,5 +41,5 @@ public interface IssueChangeMapper {
   List<IssueChangeDto> selectByIssuesAndType(@Param("issueKeys") List<String> issueKeys,
     @Param("changeType") String changeType);
 
-  List<IssueChangeDto> selectChangelogOfUnresolvedIssuesByComponent(@Param("componentUuid") String componentUuid, @Param("changeType") String changeType);
+  List<IssueChangeDto> selectChangelogOfNonClosedIssuesByComponent(@Param("componentUuid") String componentUuid, @Param("changeType") String changeType);
 }
index 7c6b48a160343a60856d6763fbe49fec6207bf33..ccc841391540bf48637c9dcd0bbb3f6388c63d20 100644 (file)
@@ -35,7 +35,7 @@ public class UpdateConflictResolver {
   private static final Logger LOG = Loggers.get(UpdateConflictResolver.class);
 
   public void resolve(DefaultIssue issue, IssueMapper mapper) {
-    LOG.debug("Resolve conflict on issue " + issue.key());
+    LOG.debug("Resolve conflict on issue {}", issue.key());
 
     IssueDto dbIssue = mapper.selectByKey(issue.key());
     if (dbIssue != null) {
index 7a4d38671ca478172bb9f140764a195b7868162e..9a9a179378fcc35251e75666ab54be701c3010f1 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.core.issue.tracking;
 
 import java.util.List;
+import javax.annotation.Nullable;
 
 public class BlockHashSequence {
 
@@ -57,6 +58,10 @@ public class BlockHashSequence {
     return blockHashes[line - 1];
   }
 
+  public boolean hasLine(@Nullable Integer line) {
+    return (line != null) && (line > 0) && (line <= blockHashes.length);
+  }
+
   private static class BlockHashFactory {
     private static final int PRIME_BASE = 31;
 
index d4361819fbb3019f0d9a5acb274fa95de7c77549..55138a349950c9bd69e03dcb5b1bae5cafefced9 100644 (file)
@@ -36,22 +36,22 @@ class BlockRecognizer<RAW extends Trackable, BASE extends Trackable> {
    * Only the issues associated to a line can be matched here.
    */
   void match(Input<RAW> rawInput, Input<BASE> baseInput, Tracking<RAW, BASE> tracking) {
-    BlockHashSequence baseHashSequence = baseInput.getBlockHashSequence();
     BlockHashSequence rawHashSequence = rawInput.getBlockHashSequence();
+    BlockHashSequence baseHashSequence = baseInput.getBlockHashSequence();
 
-    Multimap<Integer, RAW> rawsByLine = groupByLine(tracking.getUnmatchedRaws());
-    Multimap<Integer, BASE> basesByLine = groupByLine(tracking.getUnmatchedBases());
-    Map<Integer, HashOccurrence> map = new HashMap<>();
+    Multimap<Integer, RAW> rawsByLine = groupByLine(tracking.getUnmatchedRaws(), rawHashSequence);
+    Multimap<Integer, BASE> basesByLine = groupByLine(tracking.getUnmatchedBases(), baseHashSequence);
+    Map<Integer, HashOccurrence> occurrencesByHash = new HashMap<>();
 
     for (Integer line : basesByLine.keySet()) {
       int hash = baseHashSequence.getBlockHashForLine(line);
-      HashOccurrence hashOccurrence = map.get(hash);
+      HashOccurrence hashOccurrence = occurrencesByHash.get(hash);
       if (hashOccurrence == null) {
         // first occurrence in base
         hashOccurrence = new HashOccurrence();
         hashOccurrence.baseLine = line;
         hashOccurrence.baseCount = 1;
-        map.put(hash, hashOccurrence);
+        occurrencesByHash.put(hash, hashOccurrence);
       } else {
         hashOccurrence.baseCount++;
       }
@@ -59,14 +59,14 @@ class BlockRecognizer<RAW extends Trackable, BASE extends Trackable> {
 
     for (Integer line : rawsByLine.keySet()) {
       int hash = rawHashSequence.getBlockHashForLine(line);
-      HashOccurrence hashOccurrence = map.get(hash);
+      HashOccurrence hashOccurrence = occurrencesByHash.get(hash);
       if (hashOccurrence != null) {
         hashOccurrence.rawLine = line;
         hashOccurrence.rawCount++;
       }
     }
 
-    for (HashOccurrence hashOccurrence : map.values()) {
+    for (HashOccurrence hashOccurrence : occurrencesByHash.values()) {
       if (hashOccurrence.baseCount == 1 && hashOccurrence.rawCount == 1) {
         // Guaranteed that baseLine has been moved to rawLine, so we can map all issues on baseLine to all issues on rawLine
         map(rawsByLine.get(hashOccurrence.rawLine), basesByLine.get(hashOccurrence.baseLine), tracking);
@@ -131,11 +131,11 @@ class BlockRecognizer<RAW extends Trackable, BASE extends Trackable> {
     }
   }
 
-  private static <T extends Trackable> Multimap<Integer, T> groupByLine(Collection<T> trackables) {
+  private static <T extends Trackable> Multimap<Integer, T> groupByLine(Collection<T> trackables, BlockHashSequence hashSequence) {
     Multimap<Integer, T> result = LinkedHashMultimap.create();
     for (T trackable : trackables) {
       Integer line = trackable.getLine();
-      if (line != null) {
+      if (hashSequence.hasLine(line)) {
         result.put(line, trackable);
       }
     }
index 5da2ca5da04440457379c5ae531bd5db6d4473f0..e7cd196bbca521c2b9c2a7819a6cf31388163651 100644 (file)
@@ -25,11 +25,11 @@ import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Objects;
 import javax.annotation.Nonnull;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.log.Loggers;
 
 import static com.google.common.collect.FluentIterable.from;
 
@@ -38,6 +38,8 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
   public Tracking<RAW, BASE> track(Input<RAW> rawInput, Input<BASE> baseInput) {
     Tracking<RAW, BASE> tracking = new Tracking<>(rawInput, baseInput);
 
+    relocateManualIssues(rawInput, baseInput, tracking);
+
     // 1. match issues with same rule, same line and same line hash, but not necessarily with same message
     match(tracking, LineAndLineHashKeyFactory.INSTANCE);
 
@@ -54,9 +56,6 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
     // See SONAR-2812
     match(tracking, LineHashKeyFactory.INSTANCE);
 
-    // TODO what about issues on line 0 ?
-    relocateManualIssues(rawInput, tracking);
-
     return tracking;
   }
 
@@ -92,23 +91,28 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
     tracking.markRawsAsAssociated(trackedRaws);
   }
 
-  private void relocateManualIssues(Input<RAW> rawInput, Tracking<RAW, BASE> tracking) {
-    Iterable<BASE> manualIssues = from(tracking.getUnmatchedBases()).filter(IsManual.INSTANCE);
+  private void relocateManualIssues(Input<RAW> rawInput, Input<BASE> baseInput, Tracking<RAW, BASE> tracking) {
+    // FIXME copy of Set if required to avoid concurrent modifications (see tracking.associateManualIssueToLine())
+    Iterable<BASE> manualIssues = from(new HashSet<>(tracking.getUnmatchedBases())).filter(IsManual.INSTANCE);
     for (BASE base : manualIssues) {
       if (base.getLine() == null) {
         // no need to relocate. Location is unchanged.
-        tracking.associateManualIssueToLine(base, 0);
+        tracking.associateManualIssueToLine(base, null);
       } else {
-        String lineHash = base.getLineHash();
-        if (!Strings.isNullOrEmpty(lineHash)) {
-          int[] rawLines = rawInput.getLineHashSequence().getLinesForHash(lineHash);
+        String baseHash = base.getLineHash();
+        if (Strings.isNullOrEmpty(baseHash)) {
+          baseHash = baseInput.getLineHashSequence().getHashForLine(base.getLine());
+        }
+        if (!Strings.isNullOrEmpty(baseHash)) {
+          int[] rawLines = rawInput.getLineHashSequence().getLinesForHash(baseHash);
           if (rawLines.length == 1) {
             tracking.associateManualIssueToLine(base, rawLines[0]);
-          } else if (rawLines.length == 0 && base.getLine() <= rawInput.getLineHashSequence().length()) {
+          } else if (rawLines.length == 0 && rawInput.getLineHashSequence().hasLine(base.getLine())) {
             // still valid (???). We didn't manage to correctly detect code move, so the
             // issue is kept at the same location, even if code changes
             tracking.associateManualIssueToLine(base, base.getLine());
           }
+          // TODO if hash found multiple times, , pick the closest line
         }
       }
     }
@@ -147,13 +151,9 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
       }
       LineAndLineHashKey that = (LineAndLineHashKey) o;
       // start with most discriminant field
-      if (!Objects.equals(line, that.line)) {
-        return false;
-      }
-      if (!lineHash.equals(that.lineHash)) {
-        return false;
-      }
-      return ruleKey.equals(that.ruleKey);
+      return Objects.equals(line, that.line)
+        && lineHash.equals(that.lineHash)
+        && ruleKey.equals(that.ruleKey);
     }
 
     @Override
@@ -175,7 +175,8 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
 
   private static class LineHashAndMessageKey implements SearchKey {
     private final RuleKey ruleKey;
-    private final String message, lineHash;
+    private final String message;
+    private final String lineHash;
 
     LineHashAndMessageKey(Trackable trackable) {
       this.ruleKey = trackable.getRuleKey();
@@ -190,13 +191,9 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
       }
       LineHashAndMessageKey that = (LineHashAndMessageKey) o;
       // start with most discriminant field
-      if (!lineHash.equals(that.lineHash)) {
-        return false;
-      }
-      if (!message.equals(that.message)) {
-        return false;
-      }
-      return ruleKey.equals(that.ruleKey);
+      return lineHash.equals(that.lineHash)
+        && message.equals(that.message)
+        && ruleKey.equals(that.ruleKey);
     }
 
     @Override
@@ -234,13 +231,9 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
       }
       LineAndMessageKey that = (LineAndMessageKey) o;
       // start with most discriminant field
-      if (!Objects.equals(line, that.line)) {
-        return false;
-      }
-      if (!message.equals(that.message)) {
-        return false;
-      }
-      return ruleKey.equals(that.ruleKey);
+      return Objects.equals(line, that.line)
+        && message.equals(that.message)
+        && ruleKey.equals(that.ruleKey);
     }
 
     @Override
@@ -276,10 +269,8 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
       }
       LineAndLineHashKey that = (LineAndLineHashKey) o;
       // start with most discriminant field
-      if (!lineHash.equals(that.lineHash)) {
-        return false;
-      }
-      return ruleKey.equals(that.ruleKey);
+      return lineHash.equals(that.lineHash)
+        && ruleKey.equals(that.ruleKey);
     }
 
     @Override
index f2a5306e500677611298a3a68814f7c0aae98c04..579c6f51623cedcad37e912f6039b99e3d22b5ba 100644 (file)
@@ -28,6 +28,7 @@ import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 
 public class Tracking<RAW extends Trackable, BASE extends Trackable> {
 
@@ -112,7 +113,7 @@ public class Tracking<RAW extends Trackable, BASE extends Trackable> {
     return openManualIssues;
   }
 
-  void associateManualIssueToLine(BASE manualIssue, int line) {
+  void associateManualIssueToLine(BASE manualIssue, @Nullable Integer line) {
     openManualIssues.put(line, manualIssue);
     unmatchedBases.remove(manualIssue);
   }
index 5b54986c84fd07b17b486cd7f0453f21e159179f..5d5275f8502674c3c48de1c449235d4a9875b761 100644 (file)
@@ -157,8 +157,8 @@ public class IssueWorkflow implements Startable {
         .build())
       .transition(Transition.builder("automaticclosemanual")
         .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
-        .conditions(new NotCondition(IsBeingClosed.INSTANCE), IsManual.INSTANCE)
-        .functions(new SetCloseDate(true))
+        .conditions(IsManual.INSTANCE)
+        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
         .automatic()
         .build())
 
index 49b874220e7db730d9822c757e9a87e86a846f60..06b71d28ff0af5dc8cec9bb15750b47dcd6d914f 100644 (file)
@@ -28,9 +28,6 @@ public enum SetClosed implements Function {
   @Override
   public void execute(Context context) {
     DefaultIssue issue = (DefaultIssue) context.issue();
-    if (!issue.isBeingClosed()) {
-      throw new IllegalStateException("Issue is still open: " + issue);
-    }
     if (issue.isOnDisabledRule()) {
       context.setResolution(Issue.RESOLUTION_REMOVED);
     } else {
index 04b36bf3ef3e0cd3ab03508faf81cac8aad90eb9..1e288b3cea3d8ac5c6fa166c4c0dfdbfe1216e0a 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.sonar.core.measure.db;
 
+import com.google.common.base.Objects;
 import java.nio.charset.StandardCharsets;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -239,4 +240,29 @@ public class MeasureDto {
     return this;
   }
 
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this)
+      .add("id", id)
+      .add("value", value)
+      .add("textValue", textValue)
+      .add("dataValue", dataValue)
+      .add("variation1", variation1)
+      .add("variation2", variation2)
+      .add("variation3", variation3)
+      .add("variation4", variation4)
+      .add("variation5", variation5)
+      .add("alertStatus", alertStatus)
+      .add("alertText", alertText)
+      .add("description", description)
+      .add("componentId", componentId)
+      .add("snapshotId", snapshotId)
+      .add("metricId", metricId)
+      .add("ruleId", ruleId)
+      .add("characteristicId", characteristicId)
+      .add("personId", personId)
+      .add("metricKey", metricKey)
+      .add("componentKey", componentKey)
+      .toString();
+  }
 }
diff --git a/sonar-core/src/main/java/org/sonar/core/rule/RuleKeyFunctions.java b/sonar-core/src/main/java/org/sonar/core/rule/RuleKeyFunctions.java
new file mode 100644 (file)
index 0000000..9a4f9c0
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.rule;
+
+import com.google.common.base.Function;
+import javax.annotation.Nonnull;
+import org.sonar.api.rule.RuleKey;
+
+public final class RuleKeyFunctions {
+
+  private RuleKeyFunctions() {
+    // only static methods
+  }
+
+  public static Function<String, RuleKey> stringToRuleKey() {
+    return StringToRuleKey.INSTANCE;
+  }
+
+  /**
+   * Transforms a string representation of key to {@link RuleKey}. It
+   * does not accept null string inputs.
+   */
+  private enum StringToRuleKey implements Function<String, RuleKey> {
+    INSTANCE;
+    @Nonnull
+    @Override
+    public RuleKey apply(@Nonnull String input) {
+      return RuleKey.parse(input);
+    }
+  }
+
+}
index e810f1dc33c8d14322c7a640a2f7f5a383c9d641..b4aa445dae3eff653102c1ef00d002159d8675c6 100644 (file)
     order by created_at asc
   </select>
 
-  <select id="selectChangelogOfUnresolvedIssuesByComponent" parameterType="map" resultType="IssueChange">
+  <select id="selectChangelogOfNonClosedIssuesByComponent" parameterType="map" resultType="IssueChange">
     select
     <include refid="issueChangeColumns"/>
     from issue_changes c
     inner join issues i on i.kee = c.issue_key
     where i.component_uuid=#{componentUuid}
     and c.change_type=#{changeType}
-    and i.resolution is null
+    and i.status &lt;&gt; 'CLOSED'
   </select>
 </mapper>
 
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java
new file mode 100644 (file)
index 0000000..4572e8a
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.issue;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.IssueComment;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.Duration;
+
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+public class DefaultIssueTest {
+
+  DefaultIssue issue = new DefaultIssue();
+
+  @Test
+  public void test_setters_and_getters() throws Exception {
+    issue.setKey("ABCD")
+      .setComponentKey("org.sample.Sample")
+      .setProjectKey("Sample")
+      .setRuleKey(RuleKey.of("squid", "S100"))
+      .setLanguage("xoo")
+      .setSeverity("MINOR")
+      .setManualSeverity(true)
+      .setMessage("a message")
+      .setLine(7)
+      .setEffortToFix(1.2d)
+      .setDebt(Duration.create(28800L))
+      .setActionPlanKey("BCDE")
+      .setStatus(Issue.STATUS_CLOSED)
+      .setResolution(Issue.RESOLUTION_FIXED)
+      .setReporter("simon")
+      .setAssignee("julien")
+      .setAuthorLogin("steph")
+      .setChecksum("c7b5db46591806455cf082bb348631e8")
+      .setNew(true)
+      .setBeingClosed(true)
+      .setOnDisabledRule(true)
+      .setChanged(true)
+      .setSendNotifications(true)
+      .setCreationDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"))
+      .setUpdateDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-20"))
+      .setCloseDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-21"))
+      .setSelectedAt(1400000000000L);
+
+    assertThat(issue.key()).isEqualTo("ABCD");
+    assertThat(issue.componentKey()).isEqualTo("org.sample.Sample");
+    assertThat(issue.projectKey()).isEqualTo("Sample");
+    assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("squid", "S100"));
+    assertThat(issue.language()).isEqualTo("xoo");
+    assertThat(issue.severity()).isEqualTo("MINOR");
+    assertThat(issue.manualSeverity()).isTrue();
+    assertThat(issue.message()).isEqualTo("a message");
+    assertThat(issue.line()).isEqualTo(7);
+    assertThat(issue.effortToFix()).isEqualTo(1.2d);
+    assertThat(issue.debt()).isEqualTo(Duration.create(28800L));
+    assertThat(issue.actionPlanKey()).isEqualTo("BCDE");
+    assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
+    assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
+    assertThat(issue.reporter()).isEqualTo("simon");
+    assertThat(issue.assignee()).isEqualTo("julien");
+    assertThat(issue.authorLogin()).isEqualTo("steph");
+    assertThat(issue.checksum()).isEqualTo("c7b5db46591806455cf082bb348631e8");
+    assertThat(issue.isNew()).isTrue();
+    assertThat(issue.isBeingClosed()).isTrue();
+    assertThat(issue.isOnDisabledRule()).isTrue();
+    assertThat(issue.isChanged()).isTrue();
+    assertThat(issue.mustSendNotifications()).isTrue();
+    assertThat(issue.creationDate()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"));
+    assertThat(issue.updateDate()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-20"));
+    assertThat(issue.closeDate()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-21"));
+    assertThat(issue.selectedAt()).isEqualTo(1400000000000L);
+  }
+
+  @Test
+  public void set_empty_dates() {
+    issue
+      .setCreationDate(null)
+      .setUpdateDate(null)
+      .setCloseDate(null)
+      .setSelectedAt(null);
+
+    assertThat(issue.creationDate()).isNull();
+    assertThat(issue.updateDate()).isNull();
+    assertThat(issue.closeDate()).isNull();
+    assertThat(issue.selectedAt()).isNull();
+  }
+
+  @Test
+  public void test_attributes() throws Exception {
+    assertThat(issue.attribute("foo")).isNull();
+    issue.setAttribute("foo", "bar");
+    assertThat(issue.attribute("foo")).isEqualTo("bar");
+    issue.setAttribute("foo", "newbar");
+    assertThat(issue.attribute("foo")).isEqualTo("newbar");
+    issue.setAttribute("foo", null);
+    assertThat(issue.attribute("foo")).isNull();
+  }
+
+  @Test
+  public void setAttributes_should_not_clear_existing_values() {
+    issue.setAttributes(ImmutableMap.of("1", "one"));
+    assertThat(issue.attribute("1")).isEqualTo("one");
+
+    issue.setAttributes(ImmutableMap.of("2", "two"));
+    assertThat(issue.attributes()).containsOnly(entry("1", "one"), entry("2", "two"));
+
+    issue.setAttributes(null);
+    assertThat(issue.attributes()).containsOnly(entry("1", "one"), entry("2", "two"));
+  }
+
+  @Test
+  public void fail_on_empty_status() {
+    try {
+      issue.setStatus("");
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("Status must be set");
+    }
+  }
+
+  @Test
+  public void fail_on_bad_severity() {
+    try {
+      issue.setSeverity("FOO");
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("Not a valid severity: FOO");
+    }
+  }
+
+  @Test
+  public void message_should_be_abbreviated_if_too_long() {
+    issue.setMessage(StringUtils.repeat("a", 5000));
+    assertThat(issue.message()).hasSize(4000);
+  }
+
+  @Test
+  public void message_should_be_trimmed() {
+    issue.setMessage("    foo     ");
+    assertThat(issue.message()).isEqualTo("foo");
+  }
+
+  @Test
+  public void message_could_be_null() {
+    issue.setMessage(null);
+    assertThat(issue.message()).isNull();
+  }
+
+  @Test
+  public void test_nullable_fields() throws Exception {
+    issue.setEffortToFix(null).setSeverity(null).setLine(null);
+    assertThat(issue.effortToFix()).isNull();
+    assertThat(issue.severity()).isNull();
+    assertThat(issue.line()).isNull();
+  }
+
+  @Test
+  public void test_equals_and_hashCode() throws Exception {
+    DefaultIssue a1 = new DefaultIssue().setKey("AAA");
+    DefaultIssue a2 = new DefaultIssue().setKey("AAA");
+    DefaultIssue b = new DefaultIssue().setKey("BBB");
+    assertThat(a1).isEqualTo(a1);
+    assertThat(a1).isEqualTo(a2);
+    assertThat(a1).isNotEqualTo(b);
+    assertThat(a1.hashCode()).isEqualTo(a1.hashCode());
+  }
+
+  @Test
+  public void comments_should_not_be_modifiable() {
+    DefaultIssue issue = new DefaultIssue().setKey("AAA");
+
+    List<IssueComment> comments = issue.comments();
+    assertThat(comments).isEmpty();
+
+    try {
+      comments.add(new DefaultIssueComment());
+      fail();
+    } catch (UnsupportedOperationException e) {
+      // ok
+    } catch (Exception e) {
+      fail("Unexpected exception: " + e);
+    }
+  }
+
+  @Test
+  public void all_changes_contain_current_change() {
+    IssueChangeContext issueChangeContext = mock(IssueChangeContext.class);
+    DefaultIssue issue = new DefaultIssue().setKey("AAA").setFieldChange(issueChangeContext, "actionPlan", "1.0", "1.1");
+
+    assertThat(issue.changes()).hasSize(1);
+  }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java b/sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java
new file mode 100644 (file)
index 0000000..beb207f
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.issue;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class FieldDiffsTest {
+
+  FieldDiffs diffs = new FieldDiffs();
+
+  @Test
+  public void diffs_should_be_empty_by_default() {
+    assertThat(diffs.diffs()).isEmpty();
+  }
+
+  @Test
+  public void test_diff() throws Exception {
+    diffs.setDiff("severity", "BLOCKER", "INFO");
+    diffs.setDiff("resolution", "OPEN", "FIXED");
+
+    assertThat(diffs.diffs()).hasSize(2);
+
+    FieldDiffs.Diff diff = diffs.diffs().get("severity");
+    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
+    assertThat(diff.newValue()).isEqualTo("INFO");
+
+    diff = diffs.diffs().get("resolution");
+    assertThat(diff.oldValue()).isEqualTo("OPEN");
+    assertThat(diff.newValue()).isEqualTo("FIXED");
+  }
+
+  @Test
+  public void diff_with_long_values() {
+    diffs.setDiff("technicalDebt", 50l, "100");
+
+    FieldDiffs.Diff diff = diffs.diffs().get("technicalDebt");
+    assertThat(diff.oldValueLong()).isEqualTo(50l);
+    assertThat(diff.newValueLong()).isEqualTo(100l);
+  }
+
+  @Test
+  public void diff_with_empty_long_values() {
+    diffs.setDiff("technicalDebt", null, "");
+
+    FieldDiffs.Diff diff = diffs.diffs().get("technicalDebt");
+    assertThat(diff.oldValueLong()).isNull();
+    assertThat(diff.newValueLong()).isNull();
+  }
+
+  @Test
+  public void test_diff_by_key() throws Exception {
+    diffs.setDiff("severity", "BLOCKER", "INFO");
+    diffs.setDiff("resolution", "OPEN", "FIXED");
+
+    assertThat(diffs.diffs()).hasSize(2);
+
+    FieldDiffs.Diff diff = diffs.diffs().get("severity");
+    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
+    assertThat(diff.newValue()).isEqualTo("INFO");
+
+    diff = diffs.diffs().get("resolution");
+    assertThat(diff.oldValue()).isEqualTo("OPEN");
+    assertThat(diff.newValue()).isEqualTo("FIXED");
+  }
+
+  @Test
+  public void should_keep_old_value() {
+    diffs.setDiff("severity", "BLOCKER", "INFO");
+    diffs.setDiff("severity", null, "MAJOR");
+    FieldDiffs.Diff diff = diffs.diffs().get("severity");
+    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
+    assertThat(diff.newValue()).isEqualTo("MAJOR");
+  }
+
+  @Test
+  public void test_toString() throws Exception {
+    diffs.setDiff("severity", "BLOCKER", "INFO");
+    diffs.setDiff("resolution", "OPEN", "FIXED");
+
+    assertThat(diffs.toString()).isEqualTo("severity=BLOCKER|INFO,resolution=OPEN|FIXED");
+  }
+
+  @Test
+  public void test_toString_with_null_values() throws Exception {
+    diffs.setDiff("severity", null, "INFO");
+    diffs.setDiff("assignee", "user1", null);
+
+    assertThat(diffs.toString()).isEqualTo("severity=INFO,assignee=");
+  }
+
+  @Test
+  public void test_parse() throws Exception {
+    diffs = FieldDiffs.parse("severity=BLOCKER|INFO,resolution=OPEN|FIXED");
+    assertThat(diffs.diffs()).hasSize(2);
+
+    FieldDiffs.Diff diff = diffs.diffs().get("severity");
+    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
+    assertThat(diff.newValue()).isEqualTo("INFO");
+
+    diff = diffs.diffs().get("resolution");
+    assertThat(diff.oldValue()).isEqualTo("OPEN");
+    assertThat(diff.newValue()).isEqualTo("FIXED");
+  }
+
+  @Test
+  public void test_parse_empty_values() throws Exception {
+    diffs = FieldDiffs.parse("severity=INFO,resolution=");
+    assertThat(diffs.diffs()).hasSize(2);
+
+    FieldDiffs.Diff diff = diffs.diffs().get("severity");
+    assertThat(diff.oldValue()).isEqualTo("");
+    assertThat(diff.newValue()).isEqualTo("INFO");
+
+    diff = diffs.diffs().get("resolution");
+    assertThat(diff.oldValue()).isEqualTo("");
+    assertThat(diff.newValue()).isEqualTo("");
+  }
+
+  @Test
+  public void test_parse_null() throws Exception {
+    diffs = FieldDiffs.parse(null);
+    assertThat(diffs.diffs()).isEmpty();
+  }
+
+  @Test
+  public void test_parse_empty() throws Exception {
+    diffs = FieldDiffs.parse("");
+    assertThat(diffs.diffs()).isEmpty();
+  }
+}
index 2d379cfee6cb6f5d42263bdd5ca9e6afa57b5358..91530c8898301cd0f1cf52d496f73a50ed259eda 100644 (file)
@@ -113,11 +113,11 @@ public class IssueChangeDaoTest extends AbstractDaoTestCase {
   }
 
   @Test
-  public void selectChangelogOfUnresolvedIssuesByComponent() {
-    setupData("selectChangelogOfUnresolvedIssuesByComponent");
+  public void selectChangelogOfNonClosedIssuesByComponent() {
+    setupData("selectChangelogOfNonClosedIssuesByComponent");
 
-    List<IssueChangeDto> dtos = dao.selectChangelogOfUnresolvedIssuesByComponent("FILE_1");
-    assertThat(dtos).extracting("id").containsExactly(100L);
+    List<IssueChangeDto> dtos = dao.selectChangelogOfNonClosedIssuesByComponent("FILE_1");
+    assertThat(dtos).extracting("id").containsExactly(100L, 103L);
   }
 
   @Test
index a9fe647095e205692d4bfa6debd55fa9630b2bb8..79a77fbd9e26908482ad7f8a69d3128527870071 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.core.issue.tracking;
 
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -39,6 +41,7 @@ public class TrackerTest {
   public static final RuleKey RULE_UNUSED_LOCAL_VARIABLE = RuleKey.of("java", "UnusedLocalVariable");
   public static final RuleKey RULE_UNUSED_PRIVATE_METHOD = RuleKey.of("java", "UnusedPrivateMethod");
   public static final RuleKey RULE_NOT_DESIGNED_FOR_EXTENSION = RuleKey.of("java", "NotDesignedForExtension");
+  public static final RuleKey RULE_MANUAL = RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "CodeReview");
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
@@ -59,20 +62,6 @@ public class TrackerTest {
     assertThat(tracking.baseFor(raw)).isNull();
   }
 
-  @Test
-  @Ignore
-  public void different_issues_do_not_match() {
-    FakeInput baseInput = new FakeInput("H1");
-    Issue base = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg1");
-
-    FakeInput rawInput = new FakeInput("H2", "H3", "H4", "H5", "H6");
-    Issue raw = rawInput.createIssueOnLine(5, RULE_SYSTEM_PRINT, "msg2");
-
-    Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput);
-    assertThat(tracking.baseFor(raw)).isNull();
-    assertThat(tracking.getUnmatchedBases()).containsOnly(base);
-  }
-
   @Test
   public void line_hash_has_greater_priority_than_line() {
     FakeInput baseInput = new FakeInput("H1", "H2", "H3");
@@ -369,6 +358,65 @@ public class TrackerTest {
     assertThat(tracking.getUnmatchedBases()).containsOnly(base2);
   }
 
+  @Test
+  public void move_manual_issue_to_line_with_same_hash() {
+    FakeInput baseInput = new FakeInput("H1", "H2");
+    Issue issue = baseInput.createIssueOnLine(1, RULE_MANUAL, "message");
+    FakeInput rawInput = new FakeInput("H3", "H4", "H1");
+
+    Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput);
+
+    assertThat(tracking.getUnmatchedBases()).isEmpty();
+    Multimap<Integer, Issue> openManualIssues = tracking.getOpenManualIssuesByLine();
+    assertThat(openManualIssues.keySet()).containsOnly(3);
+    assertThat(Iterables.getOnlyElement(openManualIssues.get(3))).isSameAs(issue);
+  }
+
+  @Test
+  public void do_not_move_manual_issue_if_line_hash_not_found_in_raw() {
+    FakeInput baseInput = new FakeInput("H1", "H2");
+    Issue issue = baseInput.createIssueOnLine(1, RULE_MANUAL, "message");
+    FakeInput rawInput = new FakeInput("H3", "H4", "H5");
+
+    Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput);
+
+    assertThat(tracking.getUnmatchedBases()).isEmpty();
+    Multimap<Integer, Issue> openManualIssues = tracking.getOpenManualIssuesByLine();
+    assertThat(openManualIssues.keySet()).containsOnly(1);
+    assertThat(Iterables.getOnlyElement(openManualIssues.get(1))).isSameAs(issue);
+  }
+
+  @Test
+  public void do_not_match_manual_issue_if_hash_and_line_do_not_exist() {
+    // manual issue is on line 3 (hash H3) but this hash does not exist
+    // anymore nor the line 3.
+    FakeInput baseInput = new FakeInput("H1", "H2", "H3");
+    Issue issue = baseInput.createIssueOnLine(3, RULE_MANUAL, "message");
+    FakeInput rawInput = new FakeInput("H4");
+
+    Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput);
+
+    assertThat(tracking.getUnmatchedBases()).containsOnly(issue);
+    assertThat(tracking.getOpenManualIssuesByLine().isEmpty()).isTrue();
+  }
+
+  @Test
+  @Ignore("not implemented yet")
+  public void move_to_closest_line_if_manual_issue_matches_multiple_hashes() {
+    // manual issue is on line 3 (hash H3) but this hash does not exist
+    // anymore nor the line 3.
+    FakeInput baseInput = new FakeInput("H1", "H2");
+    Issue issue = baseInput.createIssueOnLine(1, RULE_MANUAL, "message");
+    FakeInput rawInput = new FakeInput("H1", "H3", "H1");
+
+    Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput);
+
+    assertThat(tracking.getUnmatchedBases()).isEmpty();
+    Multimap<Integer, Issue> openManualIssues = tracking.getOpenManualIssuesByLine();
+    assertThat(openManualIssues.keySet()).containsOnly(1);
+    assertThat(Iterables.getOnlyElement(openManualIssues.get(1))).isSameAs(issue);
+  }
+
   private static class Issue implements Trackable {
     private final RuleKey ruleKey;
     private final Integer line;
index ad5b9bef6e8c39d559dc487fe1f06146c9ab1d80..5ea870b09fbec8b78de857c7307b31966741a386 100644 (file)
@@ -146,6 +146,7 @@ public class IssueWorkflowTest {
 
     DefaultIssue issue = new DefaultIssue()
       .setKey("ABCDE")
+      .setRuleKey(RuleKey.of("js", "S001"))
       .setResolution(Issue.RESOLUTION_FIXED)
       .setStatus(Issue.STATUS_RESOLVED)
       .setNew(false)
@@ -269,12 +270,10 @@ public class IssueWorkflowTest {
 
   @Test
   public void manual_issues_be_resolved_then_closed() {
-    // Manual issue because of reporter
     DefaultIssue issue = new DefaultIssue()
       .setKey("ABCDE")
       .setStatus(Issue.STATUS_OPEN)
-      .setRuleKey(RuleKey.of("manual", "Performance"))
-      .setReporter("simon");
+      .setRuleKey(RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "Performance"));
 
     workflow.start();
 
index 3418cd9d1d3c2743788d3cdb96e6f6e008b11d0d..f288fcf4cfb57d492897506b879c42f9736ff4c9 100644 (file)
@@ -23,9 +23,10 @@ import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.core.issue.DefaultIssue;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.sonar.core.issue.workflow.SetClosed.INSTANCE;
 
 public class SetClosedTest {
@@ -48,19 +49,6 @@ public class SetClosedTest {
     verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED);
   }
 
-  @Test
-  public void should_fail_if_issue_is_not_resolved() {
-    Issue issue = new DefaultIssue().setBeingClosed(false);
-    when(context.issue()).thenReturn(issue);
-    try {
-      INSTANCE.execute(context);
-      fail();
-    } catch (IllegalStateException e) {
-      assertThat(e.getMessage()).contains("Issue is still open");
-      verify(context, never()).setResolution(anyString());
-    }
-  }
-
   @Test
   public void line_number_must_be_unset() {
     Issue issue = new DefaultIssue().setBeingClosed(true).setLine(10);
diff --git a/sonar-core/src/test/java/org/sonar/core/rule/RuleKeyFunctionsTest.java b/sonar-core/src/test/java/org/sonar/core/rule/RuleKeyFunctionsTest.java
new file mode 100644 (file)
index 0000000..cf430a1
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.rule;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.test.TestUtils;
+
+import static com.google.common.collect.FluentIterable.from;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RuleKeyFunctionsTest {
+
+  @Test
+  public void stringToRuleKey() throws Exception {
+    Collection<String> strings = Arrays.asList("js:S001", "java:S002");
+    List<RuleKey> keys = from(strings).transform(RuleKeyFunctions.stringToRuleKey()).toList();
+
+    assertThat(keys).containsExactly(RuleKey.of("js", "S001"), RuleKey.of("java", "S002"));
+  }
+
+  @Test
+  public void on_static_methods() throws Exception {
+    assertThat(TestUtils.hasOnlyPrivateConstructors(RuleKeyFunctions.class)).isTrue();
+  }
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml
new file mode 100644 (file)
index 0000000..d809d01
--- /dev/null
@@ -0,0 +1,182 @@
+<dataset>
+
+  <!-- Unresolved. To be included -->
+  <issues
+      id="1"
+      kee="UNRESOLVED_ON_FILE_1"
+      component_uuid="FILE_1"
+      project_uuid="PROJECT_1"
+      resolution="[null]"
+      status="OPEN"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      message="[null]"
+      line="200"
+      effort_to_fix="[null]"
+      checksum="[null]"
+      reporter="user"
+      assignee="user"
+      author_login="[null]"
+      issue_attributes="[null]"
+      issue_creation_date="1366063200000"
+      issue_update_date="1366063200000"
+      issue_close_date="1366063200000"
+      created_at="1400000000000"
+      updated_at="[null]"
+      />
+
+  <!-- diff -->
+  <issue_changes
+      id="100"
+      kee="100"
+      issue_key="UNRESOLVED_ON_FILE_1"
+      user_login="arthur"
+      change_type="diff"
+      change_data="severity=MAJOR|BLOCKER"
+      created_at="1410213600000"
+      updated_at="1410213600000"
+      issue_change_creation_date="1410213600000"
+      />
+
+    <!-- comment -->
+  <issue_changes
+      id="102"
+      kee="102"
+      issue_key="UNRESOLVED_ON_FILE_1"
+      user_login="arthur"
+      change_type="comment"
+      change_data="recent comment"
+      created_at="1410213600000"
+      updated_at="1410213600000"
+      issue_change_creation_date="[null]"
+      />
+
+  <!-- Resolved but not closed. To be included -->
+  <issues
+      id="2"
+      kee="RESOLVED_ON_FILE_1"
+      component_uuid="FILE_1"
+      project_uuid="PROJECT_1"
+      resolution="FIXED"
+      status="RESOLVED"
+      rule_id="501"
+      severity="MAJOR"
+      manual_severity="[false]"
+      message="[null]"
+      line="120"
+      effort_to_fix="[null]"
+      checksum="[null]"
+      reporter="[null]"
+      assignee="user"
+      author_login="[null]"
+      issue_attributes="[null]"
+      issue_creation_date="1366063200000"
+      issue_update_date="1366063200000"
+      issue_close_date="1366063200000"
+      created_at="1400000000000"
+      updated_at="[null]"
+      />
+
+  <issue_changes
+      id="103"
+      kee="103"
+      issue_key="RESOLVED_ON_FILE_1"
+      user_login="arthur"
+      change_type="diff"
+      change_data="severity=MAJOR|BLOCKER"
+      created_at="1410213600000"
+      updated_at="1410213600000"
+      issue_change_creation_date="1410213600000"
+      />
+
+  <!-- Closed. To be excluded -->
+  <issues
+    id="3"
+    kee="CLOSED_ON_FILE_1"
+    component_uuid="FILE_1"
+    project_uuid="PROJECT_1"
+    resolution="FIXED"
+    status="CLOSED"
+    rule_id="501"
+    severity="MAJOR"
+    manual_severity="[false]"
+    message="[null]"
+    line="120"
+    effort_to_fix="[null]"
+    checksum="[null]"
+    reporter="[null]"
+    assignee="user"
+    author_login="[null]"
+    issue_attributes="[null]"
+    issue_creation_date="1366063200000"
+    issue_update_date="1366063200000"
+    issue_close_date="1366063200000"
+    created_at="1400000000000"
+    updated_at="[null]"
+    />
+
+  <issue_changes
+    id="104"
+    kee="104"
+    issue_key="CLOSED_ON_FILE_1"
+    user_login="arthur"
+    change_type="diff"
+    change_data="severity=MAJOR|BLOCKER"
+    created_at="1410213600000"
+    updated_at="1410213600000"
+    issue_change_creation_date="1410213600000"
+    />
+
+  <!-- Unresolved on other file -->
+    <issues
+      id="4"
+      kee="UNRESOLVED_ON_FILE_2"
+      component_uuid="FILE_2"
+      project_uuid="PROJECT_1"
+      resolution="[null]"
+      status="OPEN"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      message="[null]"
+      line="200"
+      effort_to_fix="[null]"
+      checksum="[null]"
+      reporter="user"
+      assignee="user"
+      author_login="[null]"
+      issue_attributes="[null]"
+      issue_creation_date="1366063200000"
+      issue_update_date="1366063200000"
+      issue_close_date="1366063200000"
+      created_at="1400000000000"
+      updated_at="[null]"
+      />
+
+    <!-- diff -->
+    <issue_changes
+      id="105"
+      kee="105"
+      issue_key="UNRESOLVED_ON_FILE_2"
+      user_login="arthur"
+      change_type="diff"
+      change_data="severity=MAJOR|BLOCKER"
+      created_at="1410213600000"
+      updated_at="1410213600000"
+      issue_change_creation_date="1410213600000"
+      />
+
+    <!-- comment -->
+    <issue_changes
+      id="106"
+      kee="106"
+      issue_key="UNRESOLVED_ON_FILE_2"
+      user_login="arthur"
+      change_type="comment"
+      change_data="recent comment"
+      created_at="1410213600000"
+      updated_at="1410213600000"
+      issue_change_creation_date="[null]"
+      />
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfUnresolvedIssuesByComponent.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfUnresolvedIssuesByComponent.xml
deleted file mode 100644 (file)
index 966f7c3..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-<dataset>
-
-  <!-- Unresolved -->
-  <issues
-      id="1"
-      kee="UNRESOLVED_ON_FILE_1"
-      component_uuid="FILE_1"
-      project_uuid="PROJECT_1"
-      resolution="[null]"
-      status="OPEN"
-      rule_id="500"
-      severity="BLOCKER"
-      manual_severity="[false]"
-      message="[null]"
-      line="200"
-      effort_to_fix="[null]"
-      checksum="[null]"
-      reporter="user"
-      assignee="user"
-      author_login="[null]"
-      issue_attributes="[null]"
-      issue_creation_date="1366063200000"
-      issue_update_date="1366063200000"
-      issue_close_date="1366063200000"
-      created_at="1400000000000"
-      updated_at="[null]"
-      />
-
-  <!-- diff -->
-  <issue_changes
-      id="100"
-      kee="100"
-      issue_key="UNRESOLVED_ON_FILE_1"
-      user_login="arthur"
-      change_type="diff"
-      change_data="severity=MAJOR|BLOCKER"
-      created_at="1410213600000"
-      updated_at="1410213600000"
-      issue_change_creation_date="1410213600000"
-      />
-
-    <!-- comment -->
-  <issue_changes
-      id="102"
-      kee="102"
-      issue_key="UNRESOLVED_ON_FILE_1"
-      user_login="arthur"
-      change_type="comment"
-      change_data="recent comment"
-      created_at="1410213600000"
-      updated_at="1410213600000"
-      issue_change_creation_date="[null]"
-      />
-
-  <!-- Resolved: to be ignored -->
-  <issues
-      id="2"
-      kee="RESOLVED_ON_FILE_1"
-      component_uuid="FILE_1"
-      project_uuid="PROJECT_1"
-      resolution="FIXED"
-      status="RESOLVED"
-      rule_id="501"
-      severity="MAJOR"
-      manual_severity="[false]"
-      message="[null]"
-      line="120"
-      effort_to_fix="[null]"
-      checksum="[null]"
-      reporter="[null]"
-      assignee="user"
-      author_login="[null]"
-      issue_attributes="[null]"
-      issue_creation_date="1366063200000"
-      issue_update_date="1366063200000"
-      issue_close_date="1366063200000"
-      created_at="1400000000000"
-      updated_at="[null]"
-      />
-
-  <issue_changes
-      id="103"
-      kee="103"
-      issue_key="RESOLVED_ON_FILE_1"
-      user_login="arthur"
-      change_type="diff"
-      change_data="severity=MAJOR|BLOCKER"
-      created_at="1410213600000"
-      updated_at="1410213600000"
-      issue_change_creation_date="1410213600000"
-      />
-
-  <!-- Unresolved on other file -->
-    <issues
-      id="3"
-      kee="UNRESOLVED_ON_FILE_2"
-      component_uuid="FILE_2"
-      project_uuid="PROJECT_1"
-      resolution="[null]"
-      status="OPEN"
-      rule_id="500"
-      severity="BLOCKER"
-      manual_severity="[false]"
-      message="[null]"
-      line="200"
-      effort_to_fix="[null]"
-      checksum="[null]"
-      reporter="user"
-      assignee="user"
-      author_login="[null]"
-      issue_attributes="[null]"
-      issue_creation_date="1366063200000"
-      issue_update_date="1366063200000"
-      issue_close_date="1366063200000"
-      created_at="1400000000000"
-      updated_at="[null]"
-      />
-
-    <!-- diff -->
-    <issue_changes
-      id="104"
-      kee="104"
-      issue_key="UNRESOLVED_ON_FILE_2"
-      user_login="arthur"
-      change_type="diff"
-      change_data="severity=MAJOR|BLOCKER"
-      created_at="1410213600000"
-      updated_at="1410213600000"
-      issue_change_creation_date="1410213600000"
-      />
-
-    <!-- comment -->
-    <issue_changes
-      id="105"
-      kee="105"
-      issue_key="UNRESOLVED_ON_FILE_2"
-      user_login="arthur"
-      change_type="comment"
-      change_data="recent comment"
-      created_at="1410213600000"
-      updated_at="1410213600000"
-      issue_change_creation_date="[null]"
-      />
-</dataset>
index 0866cb8b203ffa2d79dc2376eccb75f5a6381ab8..9d334b6d5c614506807cc8274979ca89f511d9ed 100644 (file)
@@ -50,11 +50,19 @@ public interface Rule {
   RuleStatus status();
 
   /**
-   * Remediation function : can by Linear (with a coefficient), Linear with offset (with a coefficient and an offset) or Constant per issue (with an offset)
-   *
    * @since 4.3
+   * @deprecated since 5.2 as any computation of data are moved to server's Compute Engine. Calling this method throws an exception.
    */
   @CheckForNull
+  @Deprecated
+  String debtSubCharacteristic();
+
+  /**
+   * @since 4.3
+   * @deprecated since 5.2 as any computation of data are moved to server's Compute Engine. Calling this method throws an exception.
+   */
+  @CheckForNull
+  @Deprecated
   DebtRemediationFunction debtRemediationFunction();
 
 }
index 097694e146cf76494cee990c69ef65ff362114a2..cd6a738e3f1bfcfcc53391a15402c075a4f43372 100644 (file)
@@ -42,7 +42,6 @@ public class DefaultRule implements Rule {
   private final String description;
   private final String internalKey;
   private final RuleStatus status;
-  private final DebtRemediationFunction debtRemediationFunction;
   private final Map<String, RuleParam> params;
 
   DefaultRule(NewRule newRule) {
@@ -53,7 +52,6 @@ public class DefaultRule implements Rule {
     this.description = newRule.description;
     this.internalKey = newRule.internalKey;
     this.status = newRule.status;
-    this.debtRemediationFunction = newRule.debtRemediationFunction;
 
     ImmutableMap.Builder<String, RuleParam> builder = ImmutableMap.builder();
     for (NewRuleParam newRuleParam : newRule.params.values()) {
@@ -97,9 +95,14 @@ public class DefaultRule implements Rule {
     return status;
   }
 
+  @Override
+  public String debtSubCharacteristic() {
+    throw new UnsupportedOperationException("Debt characteristic is not available by analyzer since version 5.2 (data computation moved to server)");
+  }
+
   @Override
   public DebtRemediationFunction debtRemediationFunction() {
-    return debtRemediationFunction;
+    throw new UnsupportedOperationException("Debt remediation function is not available by analyzer since version 5.2 (data computation moved to server)");
   }
 
   @Override
index 2e174b2b2ae1d85eb274d089e72bb0c5fae95e63..5d6e585b78f2bc51a896ba71bec5c3014d1d9b59 100644 (file)
  */
 package org.sonar.api.batch.rule.internal;
 
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
 import org.apache.commons.lang.ObjectUtils;
 import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.debt.DebtRemediationFunction;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rule.Severity;
 
-import javax.annotation.Nullable;
-
-import java.util.HashMap;
-import java.util.Map;
-
 public class NewRule {
 
   private static final String DEFAULT_SEVERITY = Severity.defaultSeverity();
@@ -41,7 +38,6 @@ public class NewRule {
   String description;
   String severity = DEFAULT_SEVERITY;
   String internalKey;
-  DebtRemediationFunction debtRemediationFunction;
   RuleStatus status = RuleStatus.defaultStatus();
   Map<String, NewRuleParam> params = new HashMap<>();
 
@@ -79,11 +75,6 @@ public class NewRule {
     return this;
   }
 
-  public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction f) {
-    this.debtRemediationFunction = f;
-    return this;
-  }
-
   public NewRuleParam addParam(String paramKey) {
     if (params.containsKey(paramKey)) {
       throw new IllegalStateException(String.format("Parameter '%s' already exists on rule '%s'", paramKey, key));
index 6f62374263876476fc64664d388a07222b4d6b8d..cde5ae2bc09f9829e7643434aeac7a1a496f14d7 100644 (file)
@@ -43,6 +43,6 @@ public final class HasIssuePropertyCondition implements Condition {
 
   @Override
   public boolean matches(Issue issue) {
-    return !Strings.isNullOrEmpty(issue.attributes().get(propertyKey));
+    return !Strings.isNullOrEmpty(issue.attribute(propertyKey));
   }
 }
index 391cc49eb9965840fdd153f2412cdca3fae1a9d7..b5f423c257e4210fd1ec793a4097abd70c66edde 100644 (file)
 package org.sonar.api.measures;
 
 import com.google.common.annotations.Beta;
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Date;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
 import org.apache.commons.lang.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang.math.NumberUtils;
 import org.sonar.api.technicaldebt.batch.Characteristic;
 import org.sonar.api.technicaldebt.batch.Requirement;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+
 /**
  * A class to handle measures.
  *
@@ -60,6 +62,8 @@ public class Measure<G extends Serializable> implements Serializable {
   protected Double variation4;
   protected Double variation5;
   protected String url;
+  protected Characteristic characteristic;
+  protected Requirement requirement;
   protected Integer personId;
   protected PersistenceMode persistenceMode = PersistenceMode.FULL;
   private boolean fromCore;
@@ -627,13 +631,14 @@ public class Measure<G extends Serializable> implements Serializable {
    */
   @CheckForNull
   public final Characteristic getCharacteristic() {
-    return null;
+    return characteristic;
   }
 
   /**
    * @since 4.1
    */
   public final Measure<G> setCharacteristic(@Nullable Characteristic characteristic) {
+    this.characteristic = characteristic;
     return this;
   }
 
@@ -644,7 +649,7 @@ public class Measure<G extends Serializable> implements Serializable {
   @Deprecated
   @CheckForNull
   public final Requirement getRequirement() {
-    return null;
+    return requirement;
   }
 
   /**
@@ -653,6 +658,7 @@ public class Measure<G extends Serializable> implements Serializable {
    */
   @Deprecated
   public final Measure<G> setRequirement(@Nullable Requirement requirement) {
+    this.requirement = requirement;
     return this;
   }
 
@@ -731,12 +737,16 @@ public class Measure<G extends Serializable> implements Serializable {
     if (metricKey != null ? !metricKey.equals(measure.metricKey) : (measure.metricKey != null)) {
       return false;
     }
+    if (characteristic != null ? !characteristic.equals(measure.characteristic) : (measure.characteristic != null)) {
+      return false;
+    }
     return !(personId != null ? !personId.equals(measure.personId) : (measure.personId != null));
   }
 
   @Override
   public int hashCode() {
     int result = metricKey != null ? metricKey.hashCode() : 0;
+    result = 31 * result + (characteristic != null ? characteristic.hashCode() : 0);
     result = 31 * result + (personId != null ? personId.hashCode() : 0);
     return result;
   }
index e3be32a43a0c9e112c40d38beb7449863ac1941c..f872d322aea4d8b0a09f5655a41527830eebe120 100644 (file)
@@ -205,7 +205,7 @@ public final class MeasuresFilters {
   }
 
   /**
-   * @deprecated since 5.2. Useless by design because of Compute Engine
+   * @deprecated since 5.2. The measures related to rules are computed on server side by Compute Engine.
    */
   @Deprecated
   private abstract static class AbstractRuleMeasureFilter<M> extends MetricFilter<M> {
index 7c23bc4734f904e15b63cb25fee7354027fe31d4..0caa78b2d5e028a6c13a12d1cf112feab3004fa5 100644 (file)
@@ -23,12 +23,14 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 
 import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
 
 /**
  * Key of a rule. Unique among all the rule repositories.
  *
  * @since 3.6
  */
+@Immutable
 public class RuleKey implements Serializable {
 
   public static final String MANUAL_REPOSITORY_KEY = "manual";
index d8f6456e9f2e57314dac254a7dceb9ea08ac859a..73160afcab2abc383908c28706c3270aa6fb2a6b 100644 (file)
@@ -43,7 +43,23 @@ import javax.annotation.CheckForNull;
 public interface DebtRemediationFunction {
 
   enum Type {
-    LINEAR, LINEAR_OFFSET, CONSTANT_ISSUE
+    LINEAR(true, false), LINEAR_OFFSET(true, true), CONSTANT_ISSUE(false, true);
+
+    private final boolean usesCoefficient;
+    private final boolean usesOffset;
+
+    Type(boolean usesCoefficient, boolean usesOffset) {
+      this.usesCoefficient = usesCoefficient;
+      this.usesOffset = usesOffset;
+    }
+
+    public boolean usesCoefficient() {
+      return usesCoefficient;
+    }
+
+    public boolean usesOffset() {
+      return usesOffset;
+    }
   }
 
   Type type();
index 3c5270f699630f2f63a9877b95764bb9f296cd81..cceb006ae11551428070fbf7933dab10eb6549b7 100644 (file)
@@ -51,7 +51,6 @@ public class RulesBuilderTest {
     newSquid1.setInternalKey("foo=bar");
     newSquid1.setSeverity(Severity.CRITICAL);
     newSquid1.setStatus(RuleStatus.BETA);
-    newSquid1.setDebtRemediationFunction(DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR_OFFSET, Duration.create(10), Duration.create(60)));
     newSquid1.addParam("min");
     newSquid1.addParam("max").setDescription("Maximum");
     // most simple rule
@@ -73,9 +72,6 @@ public class RulesBuilderTest {
     assertThat(squid1.internalKey()).isEqualTo("foo=bar");
     assertThat(squid1.status()).isEqualTo(RuleStatus.BETA);
     assertThat(squid1.severity()).isEqualTo(Severity.CRITICAL);
-    assertThat(squid1.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
-    assertThat(squid1.debtRemediationFunction().coefficient()).isEqualTo(Duration.create(10));
-    assertThat(squid1.debtRemediationFunction().offset()).isEqualTo(Duration.create(60));
     assertThat(squid1.params()).hasSize(2);
     assertThat(squid1.param("min").key()).isEqualTo("min");
     assertThat(squid1.param("min").description()).isNull();
@@ -89,7 +85,6 @@ public class RulesBuilderTest {
     assertThat(squid2.internalKey()).isNull();
     assertThat(squid2.status()).isEqualTo(RuleStatus.defaultStatus());
     assertThat(squid2.severity()).isEqualTo(Severity.defaultSeverity());
-    assertThat(squid2.debtRemediationFunction()).isNull();
     assertThat(squid2.params()).isEmpty();
   }
 
index 3f0275401c0fa6e2596223f5efa7c00ee0edeb3a..3d7d40bc6b0f905cda6231a31fca86ff5267d45d 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//
-//package org.sonar.api.issue.action;
-//
-//import org.junit.Rule;
-//import org.junit.Test;
-//import org.junit.rules.ExpectedException;
-//import org.sonar.api.issue.condition.Condition;
-//import org.sonar.api.issue.internal.DefaultIssue;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//import static org.mockito.Mockito.mock;
-//import static org.mockito.Mockito.when;
-//
-//public class ActionTest {
-//  @Rule
-//  public ExpectedException thrown = ExpectedException.none();
-//
-//  Condition condition1 = mock(Condition.class);
-//  Condition condition2 = mock(Condition.class);
-//  Function function1 = mock(Function.class);
-//  Function function2 = mock(Function.class);
-//
-//  @Test
-//  public void test_action() throws Exception {
-//    Action action = new Action("link-to-jira")
-//      .setConditions(condition1, condition2)
-//      .setFunctions(function1, function2);
-//
-//    assertThat(action.key()).isEqualTo("link-to-jira");
-//    assertThat(action.conditions()).containsOnly(condition1, condition2);
-//    assertThat(action.functions()).containsOnly(function1, function2);
-//  }
-//
-//  @Test
-//  public void key_should_be_set() {
-//    thrown.expectMessage("Action key must be set");
-//
-//    new Action("");
-//  }
-//
-//  @Test
-//  public void should_verify_conditions() {
-//    DefaultIssue issue = new DefaultIssue();
-//    Action action = new Action("link-to-jira")
-//      .setConditions(condition1, condition2);
-//
-//    when(condition1.matches(issue)).thenReturn(true);
-//    when(condition2.matches(issue)).thenReturn(false);
-//    assertThat(action.supports(issue)).isFalse();
-//
-//    when(condition1.matches(issue)).thenReturn(true);
-//    when(condition2.matches(issue)).thenReturn(true);
-//    assertThat(action.supports(issue)).isTrue();
-//  }
-//
-//  @Test
-//  public void test_equals_and_hashCode() throws Exception {
-//    Action t1 = new Action("link-to-jira");
-//    Action t2 = new Action("link-to-jira");
-//    Action t3 = new Action("comment");
-//
-//    assertThat(t1).isEqualTo(t1);
-//    assertThat(t1).isEqualTo(t2);
-//    assertThat(t1).isNotEqualTo(t3);
-//
-//    assertThat(t1.hashCode()).isEqualTo(t1.hashCode());
-//  }
-//
-//  @Test
-//  public void test_toString() throws Exception {
-//    Action t1 = new Action("link-to-jira");
-//    assertThat(t1.toString()).isEqualTo("link-to-jira");
-//  }
-//
-//}
+package org.sonar.api.issue.action;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ActionTest {
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  Condition condition1 = mock(Condition.class);
+  Condition condition2 = mock(Condition.class);
+  Function function1 = mock(Function.class);
+  Function function2 = mock(Function.class);
+
+  @Test
+  public void test_action() throws Exception {
+    Action action = new Action("link-to-jira")
+      .setConditions(condition1, condition2)
+      .setFunctions(function1, function2);
+
+    assertThat(action.key()).isEqualTo("link-to-jira");
+    assertThat(action.conditions()).containsOnly(condition1, condition2);
+    assertThat(action.functions()).containsOnly(function1, function2);
+  }
+
+  @Test
+  public void key_should_be_set() {
+    thrown.expectMessage("Action key must be set");
+
+    new Action("");
+  }
+
+  @Test
+  public void should_verify_conditions() {
+    Issue issue = mock(Issue.class);
+    Action action = new Action("link-to-jira")
+      .setConditions(condition1, condition2);
+
+    when(condition1.matches(issue)).thenReturn(true);
+    when(condition2.matches(issue)).thenReturn(false);
+    assertThat(action.supports(issue)).isFalse();
+
+    when(condition1.matches(issue)).thenReturn(true);
+    when(condition2.matches(issue)).thenReturn(true);
+    assertThat(action.supports(issue)).isTrue();
+  }
+
+  @Test
+  public void test_equals_and_hashCode() throws Exception {
+    Action t1 = new Action("link-to-jira");
+    Action t2 = new Action("link-to-jira");
+    Action t3 = new Action("comment");
+
+    assertThat(t1).isEqualTo(t1);
+    assertThat(t1).isEqualTo(t2);
+    assertThat(t1).isNotEqualTo(t3);
+
+    assertThat(t1.hashCode()).isEqualTo(t1.hashCode());
+  }
+
+  @Test
+  public void test_toString() throws Exception {
+    Action t1 = new Action("link-to-jira");
+    assertThat(t1.toString()).isEqualTo("link-to-jira");
+  }
+
+}
index 9f2d40e12cb1e74c6a3e73d99bef61550d05c045..b9bef1f1f0e22a45a2a12f8b8841e2001cdfe655 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//package org.sonar.api.issue.condition;
-//
-//import org.junit.Rule;
-//import org.junit.Test;
-//import org.junit.rules.ExpectedException;
-//import org.sonar.api.issue.internal.DefaultIssue;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//public class HasIssuePropertyConditionTest {
-//
-//  @Rule
-//  public ExpectedException thrown = ExpectedException.none();
-//
-//  DefaultIssue issue = new DefaultIssue();
-//
-//  @Test
-//  public void should_match() {
-//    HasIssuePropertyCondition condition = new HasIssuePropertyCondition("foo");
-//
-//    assertThat(condition.matches(issue)).isFalse();
-//    assertThat(condition.matches(issue.setAttribute("foo", ""))).isFalse();
-//    assertThat(condition.matches(issue.setAttribute("foo", "bar"))).isTrue();
-//  }
-//
-//  @Test
-//  public void should_get_property_key() {
-//    HasIssuePropertyCondition condition = new HasIssuePropertyCondition("foo");
-//    assertThat(condition.getPropertyKey()).isEqualTo("foo");
-//  }
-//
-//  @Test
-//  public void shoul_fail_if_null_property() {
-//    thrown.expect(IllegalArgumentException.class);
-//    new HasIssuePropertyCondition(null);
-//  }
-//
-//  @Test
-//  public void should_fail_if_empty_property() {
-//    thrown.expect(IllegalArgumentException.class);
-//    new HasIssuePropertyCondition("");
-//  }
-//
-//}
+package org.sonar.api.issue.condition;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.issue.Issue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class HasIssuePropertyConditionTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  Issue issue = mock(Issue.class);
+
+  @Test
+  public void should_match() {
+    HasIssuePropertyCondition condition = new HasIssuePropertyCondition("foo");
+
+    assertThat(condition.matches(issue)).isFalse();
+
+    when(issue.attribute("foo")).thenReturn("");
+    assertThat(condition.matches(issue)).isFalse();
+
+    when(issue.attribute("foo")).thenReturn("bar");
+    assertThat(condition.matches(issue)).isTrue();
+  }
+
+  @Test
+  public void should_get_property_key() {
+    HasIssuePropertyCondition condition = new HasIssuePropertyCondition("foo");
+    assertThat(condition.getPropertyKey()).isEqualTo("foo");
+  }
+
+  @Test
+  public void shoul_fail_if_null_property() {
+    thrown.expect(IllegalArgumentException.class);
+    new HasIssuePropertyCondition(null);
+  }
+
+  @Test
+  public void should_fail_if_empty_property() {
+    thrown.expect(IllegalArgumentException.class);
+    new HasIssuePropertyCondition("");
+  }
+
+}
index 0762d06330b4ce49658dda092aeddca7ef4227a3..4648aac301035fce9977062f365dbc6854a3fd7f 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//package org.sonar.api.issue.condition;
-//
-//import org.junit.Test;
-//import org.sonar.api.issue.Issue;
-//import org.sonar.api.issue.internal.DefaultIssue;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//public class HasResolutionTest {
-//
-//  DefaultIssue issue = new DefaultIssue();
-//
-//  @Test
-//  public void should_match() {
-//    HasResolution condition = new HasResolution(Issue.RESOLUTION_FIXED, Issue.RESOLUTION_FALSE_POSITIVE);
-//
-//    assertThat(condition.matches(issue.setResolution("FIXED"))).isTrue();
-//    assertThat(condition.matches(issue.setResolution("FALSE-POSITIVE"))).isTrue();
-//
-//    assertThat(condition.matches(issue.setResolution("Fixed"))).isFalse();
-//  }
-//}
+package org.sonar.api.issue.condition;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class HasResolutionTest {
+
+  Issue issue = mock(Issue.class);
+
+  @Test
+  public void should_match() {
+    HasResolution condition = new HasResolution(Issue.RESOLUTION_FIXED, Issue.RESOLUTION_FALSE_POSITIVE);
+
+    when(issue.resolution()).thenReturn("FIXED");
+    assertThat(condition.matches(issue)).isTrue();
+
+    when(issue.resolution()).thenReturn("FALSE-POSITIVE");
+    assertThat(condition.matches(issue)).isTrue();
+
+    when(issue.resolution()).thenReturn("Fixed");
+    assertThat(condition.matches(issue)).isFalse();
+  }
+}
index 5d9258fa07be3d6370f4c7dd77d8951bd932eace..68be04898ad4dcffa58d897012adcbe3038aeda2 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//
-//package org.sonar.api.issue.condition;
-//
-//import org.junit.Test;
-//import org.sonar.api.issue.internal.DefaultIssue;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//public class HasStatusTest {
-//
-//  DefaultIssue issue = new DefaultIssue();
-//
-//  @Test
-//  public void should_match() {
-//    HasStatus condition = new HasStatus("OPEN", "REOPENED", "CONFIRMED");
-//
-//    assertThat(condition.matches(issue.setStatus("OPEN"))).isTrue();
-//    assertThat(condition.matches(issue.setStatus("REOPENED"))).isTrue();
-//    assertThat(condition.matches(issue.setStatus("CONFIRMED"))).isTrue();
-//
-//    assertThat(condition.matches(issue.setStatus("open"))).isFalse();
-//    assertThat(condition.matches(issue.setStatus("CLOSED"))).isFalse();
-//  }
-//
-//}
+package org.sonar.api.issue.condition;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class HasStatusTest {
+
+  Issue issue = mock(Issue.class);
+
+  @Test
+  public void should_match() {
+    HasStatus condition = new HasStatus("OPEN", "REOPENED", "CONFIRMED");
+
+    when(issue.status()).thenReturn("OPEN");
+    assertThat(condition.matches(issue)).isTrue();
+
+    when(issue.status()).thenReturn("REOPENED");
+    assertThat(condition.matches(issue)).isTrue();
+
+    when(issue.status()).thenReturn("CONFIRMED");
+    assertThat(condition.matches(issue)).isTrue();
+
+    when(issue.status()).thenReturn("open");
+    assertThat(condition.matches(issue)).isFalse();
+
+    when(issue.status()).thenReturn("CLOSED");
+    assertThat(condition.matches(issue)).isFalse();
+  }
+
+}
index 84159f3f2421a6a35e2f40f9063b03d21dbfc7d8..65303bd24722fd220f46bd79d5dca7759592139c 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//
-//package org.sonar.api.issue.condition;
-//
-//import org.junit.Test;
-//import org.sonar.api.issue.internal.DefaultIssue;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//public class IsUnResolvedTest {
-//
-//  DefaultIssue issue = new DefaultIssue();
-//
-//  @Test
-//  public void should_match() {
-//    IsUnResolved condition = new IsUnResolved();
-//
-//    assertThat(condition.matches(issue)).isTrue();
-//    assertThat(condition.matches(issue.setResolution("FIXED"))).isFalse();
-//  }
-//}
+package org.sonar.api.issue.condition;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class IsUnResolvedTest {
+
+  Issue issue = mock(Issue.class);
+
+  @Test
+  public void should_match() {
+    IsUnResolved condition = new IsUnResolved();
+
+    assertThat(condition.matches(issue)).isTrue();
+
+    when(issue.resolution()).thenReturn("FIXED");
+    assertThat(condition.matches(issue)).isFalse();
+  }
+}
index ae2c76de3702c3efe9c199dc9e16cd68bbf9f2f1..aa646d14514edba5801b48ab4199e90e208726c2 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//package org.sonar.api.issue.condition;
-//
-//import org.junit.Test;
-//import org.mockito.Mockito;
-//import org.sonar.api.issue.Issue;
-//import org.sonar.api.issue.internal.DefaultIssue;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//import static org.mockito.Matchers.any;
-//import static org.mockito.Mockito.when;
-//
-//public class NotConditionTest {
-//
-//  Condition target = Mockito.mock(Condition.class);
-//
-//  @Test
-//  public void should_match_opposite() {
-//    NotCondition condition = new NotCondition(target);
-//
-//    when(target.matches(any(Issue.class))).thenReturn(true);
-//    assertThat(condition.matches(new DefaultIssue())).isFalse();
-//
-//    when(target.matches(any(Issue.class))).thenReturn(false);
-//    assertThat(condition.matches(new DefaultIssue())).isTrue();
-//  }
-//}
+package org.sonar.api.issue.condition;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.issue.Issue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class NotConditionTest {
+
+  Condition target = Mockito.mock(Condition.class);
+  Issue issue = mock(Issue.class);
+
+  @Test
+  public void should_match_opposite() {
+    NotCondition condition = new NotCondition(target);
+
+    when(target.matches(any(Issue.class))).thenReturn(true);
+    assertThat(condition.matches(issue)).isFalse();
+
+    when(target.matches(any(Issue.class))).thenReturn(false);
+    assertThat(condition.matches(issue)).isTrue();
+  }
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java
deleted file mode 100644 (file)
index 7db2fa9..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//package org.sonar.api.issue.internal;
-//
-//import com.google.common.collect.ImmutableMap;
-//import org.apache.commons.lang.StringUtils;
-//import org.junit.Test;
-//import org.sonar.api.issue.Issue;
-//import org.sonar.api.issue.IssueComment;
-//import org.sonar.api.rule.RuleKey;
-//import org.sonar.api.utils.Duration;
-//
-//import java.text.SimpleDateFormat;
-//import java.util.List;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//import static org.assertj.core.api.Assertions.entry;
-//import static org.junit.Assert.fail;
-//import static org.mockito.Mockito.mock;
-//
-//public class DefaultIssueTest {
-//
-//  DefaultIssue issue = new DefaultIssue();
-//
-//  @Test
-//  public void test_setters_and_getters() throws Exception {
-//    issue.setKey("ABCD")
-//      .setComponentKey("org.sample.Sample")
-//      .setProjectKey("Sample")
-//      .setRuleKey(RuleKey.of("squid", "S100"))
-//      .setLanguage("xoo")
-//      .setSeverity("MINOR")
-//      .setManualSeverity(true)
-//      .setMessage("a message")
-//      .setLine(7)
-//      .setEffortToFix(1.2d)
-//      .setDebt(Duration.create(28800L))
-//      .setActionPlanKey("BCDE")
-//      .setStatus(Issue.STATUS_CLOSED)
-//      .setResolution(Issue.RESOLUTION_FIXED)
-//      .setReporter("simon")
-//      .setAssignee("julien")
-//      .setAuthorLogin("steph")
-//      .setChecksum("c7b5db46591806455cf082bb348631e8")
-//      .setNew(true)
-//      .setBeingClosed(true)
-//      .setOnDisabledRule(true)
-//      .setChanged(true)
-//      .setSendNotifications(true)
-//      .setCreationDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"))
-//      .setUpdateDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-20"))
-//      .setCloseDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-21"))
-//      .setSelectedAt(1400000000000L);
-//
-//    assertThat(issue.key()).isEqualTo("ABCD");
-//    assertThat(issue.componentKey()).isEqualTo("org.sample.Sample");
-//    assertThat(issue.projectKey()).isEqualTo("Sample");
-//    assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("squid", "S100"));
-//    assertThat(issue.language()).isEqualTo("xoo");
-//    assertThat(issue.severity()).isEqualTo("MINOR");
-//    assertThat(issue.manualSeverity()).isTrue();
-//    assertThat(issue.message()).isEqualTo("a message");
-//    assertThat(issue.line()).isEqualTo(7);
-//    assertThat(issue.effortToFix()).isEqualTo(1.2d);
-//    assertThat(issue.debt()).isEqualTo(Duration.create(28800L));
-//    assertThat(issue.actionPlanKey()).isEqualTo("BCDE");
-//    assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
-//    assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
-//    assertThat(issue.reporter()).isEqualTo("simon");
-//    assertThat(issue.assignee()).isEqualTo("julien");
-//    assertThat(issue.authorLogin()).isEqualTo("steph");
-//    assertThat(issue.checksum()).isEqualTo("c7b5db46591806455cf082bb348631e8");
-//    assertThat(issue.isNew()).isTrue();
-//    assertThat(issue.isBeingClosed()).isTrue();
-//    assertThat(issue.isOnDisabledRule()).isTrue();
-//    assertThat(issue.isChanged()).isTrue();
-//    assertThat(issue.mustSendNotifications()).isTrue();
-//    assertThat(issue.creationDate()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"));
-//    assertThat(issue.updateDate()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-20"));
-//    assertThat(issue.closeDate()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-21"));
-//    assertThat(issue.selectedAt()).isEqualTo(1400000000000L);
-//  }
-//
-//  @Test
-//  public void set_empty_dates() {
-//    issue
-//      .setCreationDate(null)
-//      .setUpdateDate(null)
-//      .setCloseDate(null)
-//      .setSelectedAt(null);
-//
-//    assertThat(issue.creationDate()).isNull();
-//    assertThat(issue.updateDate()).isNull();
-//    assertThat(issue.closeDate()).isNull();
-//    assertThat(issue.selectedAt()).isNull();
-//  }
-//
-//  @Test
-//  public void test_attributes() throws Exception {
-//    assertThat(issue.attribute("foo")).isNull();
-//    issue.setAttribute("foo", "bar");
-//    assertThat(issue.attribute("foo")).isEqualTo("bar");
-//    issue.setAttribute("foo", "newbar");
-//    assertThat(issue.attribute("foo")).isEqualTo("newbar");
-//    issue.setAttribute("foo", null);
-//    assertThat(issue.attribute("foo")).isNull();
-//  }
-//
-//  @Test
-//  public void setAttributes_should_not_clear_existing_values() {
-//    issue.setAttributes(ImmutableMap.of("1", "one"));
-//    assertThat(issue.attribute("1")).isEqualTo("one");
-//
-//    issue.setAttributes(ImmutableMap.of("2", "two"));
-//    assertThat(issue.attributes()).containsOnly(entry("1", "one"), entry("2", "two"));
-//
-//    issue.setAttributes(null);
-//    assertThat(issue.attributes()).containsOnly(entry("1", "one"), entry("2", "two"));
-//  }
-//
-//  @Test
-//  public void fail_on_empty_status() {
-//    try {
-//      issue.setStatus("");
-//      fail();
-//    } catch (IllegalArgumentException e) {
-//      assertThat(e).hasMessage("Status must be set");
-//    }
-//  }
-//
-//  @Test
-//  public void fail_on_bad_severity() {
-//    try {
-//      issue.setSeverity("FOO");
-//      fail();
-//    } catch (IllegalArgumentException e) {
-//      assertThat(e).hasMessage("Not a valid severity: FOO");
-//    }
-//  }
-//
-//  @Test
-//  public void message_should_be_abbreviated_if_too_long() {
-//    issue.setMessage(StringUtils.repeat("a", 5000));
-//    assertThat(issue.message()).hasSize(4000);
-//  }
-//
-//  @Test
-//  public void message_should_be_trimmed() {
-//    issue.setMessage("    foo     ");
-//    assertThat(issue.message()).isEqualTo("foo");
-//  }
-//
-//  @Test
-//  public void message_could_be_null() {
-//    issue.setMessage(null);
-//    assertThat(issue.message()).isNull();
-//  }
-//
-//  @Test
-//  public void test_nullable_fields() throws Exception {
-//    issue.setEffortToFix(null).setSeverity(null).setLine(null);
-//    assertThat(issue.effortToFix()).isNull();
-//    assertThat(issue.severity()).isNull();
-//    assertThat(issue.line()).isNull();
-//  }
-//
-//  @Test
-//  public void test_equals_and_hashCode() throws Exception {
-//    DefaultIssue a1 = new DefaultIssue().setKey("AAA");
-//    DefaultIssue a2 = new DefaultIssue().setKey("AAA");
-//    DefaultIssue b = new DefaultIssue().setKey("BBB");
-//    assertThat(a1).isEqualTo(a1);
-//    assertThat(a1).isEqualTo(a2);
-//    assertThat(a1).isNotEqualTo(b);
-//    assertThat(a1.hashCode()).isEqualTo(a1.hashCode());
-//  }
-//
-//  @Test
-//  public void comments_should_not_be_modifiable() {
-//    DefaultIssue issue = new DefaultIssue().setKey("AAA");
-//
-//    List<IssueComment> comments = issue.comments();
-//    assertThat(comments).isEmpty();
-//
-//    try {
-//      comments.add(new DefaultIssueComment());
-//      fail();
-//    } catch (UnsupportedOperationException e) {
-//      // ok
-//    } catch (Exception e) {
-//      fail("Unexpected exception: " + e);
-//    }
-//  }
-//
-//  @Test
-//  public void all_changes_contain_current_change() {
-//    IssueChangeContext issueChangeContext = mock(IssueChangeContext.class);
-//    DefaultIssue issue = new DefaultIssue().setKey("AAA").setFieldChange(issueChangeContext, "actionPlan", "1.0", "1.1");
-//
-//    assertThat(issue.changes()).hasSize(1);
-//  }
-//}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/FieldDiffsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/FieldDiffsTest.java
deleted file mode 100644 (file)
index 031b3e9..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube is free software; you can redistribute it and/or
-// * modify it under the terms of the GNU Lesser General Public
-// * License as published by the Free Software Foundation; either
-// * version 3 of the License, or (at your option) any later version.
-// *
-// * SonarQube is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Lesser General Public License for more details.
-// *
-// * You should have received a copy of the GNU Lesser General Public License
-// * along with this program; if not, write to the Free Software Foundation,
-// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-// */
-//package org.sonar.api.issue.internal;
-//
-//import org.junit.Test;
-//
-//import static org.assertj.core.api.Assertions.assertThat;
-//
-//public class FieldDiffsTest {
-//
-//  FieldDiffs diffs = new FieldDiffs();
-//
-//  @Test
-//  public void diffs_should_be_empty_by_default() {
-//    assertThat(diffs.diffs()).isEmpty();
-//  }
-//
-//  @Test
-//  public void test_diff() throws Exception {
-//    diffs.setDiff("severity", "BLOCKER", "INFO");
-//    diffs.setDiff("resolution", "OPEN", "FIXED");
-//
-//    assertThat(diffs.diffs()).hasSize(2);
-//
-//    FieldDiffs.Diff diff = diffs.diffs().get("severity");
-//    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
-//    assertThat(diff.newValue()).isEqualTo("INFO");
-//
-//    diff = diffs.diffs().get("resolution");
-//    assertThat(diff.oldValue()).isEqualTo("OPEN");
-//    assertThat(diff.newValue()).isEqualTo("FIXED");
-//  }
-//
-//  @Test
-//  public void diff_with_long_values() {
-//    diffs.setDiff("technicalDebt", 50l, "100");
-//
-//    FieldDiffs.Diff diff = diffs.diffs().get("technicalDebt");
-//    assertThat(diff.oldValueLong()).isEqualTo(50l);
-//    assertThat(diff.newValueLong()).isEqualTo(100l);
-//  }
-//
-//  @Test
-//  public void diff_with_empty_long_values() {
-//    diffs.setDiff("technicalDebt", null, "");
-//
-//    FieldDiffs.Diff diff = diffs.diffs().get("technicalDebt");
-//    assertThat(diff.oldValueLong()).isNull();
-//    assertThat(diff.newValueLong()).isNull();
-//  }
-//
-//  @Test
-//  public void test_diff_by_key() throws Exception {
-//    diffs.setDiff("severity", "BLOCKER", "INFO");
-//    diffs.setDiff("resolution", "OPEN", "FIXED");
-//
-//    assertThat(diffs.diffs()).hasSize(2);
-//
-//    FieldDiffs.Diff diff = diffs.diffs().get("severity");
-//    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
-//    assertThat(diff.newValue()).isEqualTo("INFO");
-//
-//    diff = diffs.diffs().get("resolution");
-//    assertThat(diff.oldValue()).isEqualTo("OPEN");
-//    assertThat(diff.newValue()).isEqualTo("FIXED");
-//  }
-//
-//  @Test
-//  public void should_keep_old_value() {
-//    diffs.setDiff("severity", "BLOCKER", "INFO");
-//    diffs.setDiff("severity", null, "MAJOR");
-//    FieldDiffs.Diff diff = diffs.diffs().get("severity");
-//    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
-//    assertThat(diff.newValue()).isEqualTo("MAJOR");
-//  }
-//
-//  @Test
-//  public void test_toString() throws Exception {
-//    diffs.setDiff("severity", "BLOCKER", "INFO");
-//    diffs.setDiff("resolution", "OPEN", "FIXED");
-//
-//    assertThat(diffs.toString()).isEqualTo("severity=BLOCKER|INFO,resolution=OPEN|FIXED");
-//  }
-//
-//  @Test
-//  public void test_toString_with_null_values() throws Exception {
-//    diffs.setDiff("severity", null, "INFO");
-//    diffs.setDiff("assignee", "user1", null);
-//
-//    assertThat(diffs.toString()).isEqualTo("severity=INFO,assignee=");
-//  }
-//
-//  @Test
-//  public void test_parse() throws Exception {
-//    diffs = FieldDiffs.parse("severity=BLOCKER|INFO,resolution=OPEN|FIXED");
-//    assertThat(diffs.diffs()).hasSize(2);
-//
-//    FieldDiffs.Diff diff = diffs.diffs().get("severity");
-//    assertThat(diff.oldValue()).isEqualTo("BLOCKER");
-//    assertThat(diff.newValue()).isEqualTo("INFO");
-//
-//    diff = diffs.diffs().get("resolution");
-//    assertThat(diff.oldValue()).isEqualTo("OPEN");
-//    assertThat(diff.newValue()).isEqualTo("FIXED");
-//  }
-//
-//  @Test
-//  public void test_parse_empty_values() throws Exception {
-//    diffs = FieldDiffs.parse("severity=INFO,resolution=");
-//    assertThat(diffs.diffs()).hasSize(2);
-//
-//    FieldDiffs.Diff diff = diffs.diffs().get("severity");
-//    assertThat(diff.oldValue()).isEqualTo("");
-//    assertThat(diff.newValue()).isEqualTo("INFO");
-//
-//    diff = diffs.diffs().get("resolution");
-//    assertThat(diff.oldValue()).isEqualTo("");
-//    assertThat(diff.newValue()).isEqualTo("");
-//  }
-//
-//  @Test
-//  public void test_parse_null() throws Exception {
-//    diffs = FieldDiffs.parse(null);
-//    assertThat(diffs.diffs()).isEmpty();
-//  }
-//
-//  @Test
-//  public void test_parse_empty() throws Exception {
-//    diffs = FieldDiffs.parse("");
-//    assertThat(diffs.diffs()).isEmpty();
-//  }
-//}