浏览代码

SONAR-6588 move computation of debt to Compute Engine

tags/5.2-RC1
Simon Brandhof 9 年前
父节点
当前提交
1c0b9a97e3
共有 96 个文件被更改,包括 2624 次插入2354 次删除
  1. 6
    4
      server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
  2. 3
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolder.java
  3. 14
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolderImpl.java
  4. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/debt/MutableDebtModelHolder.java
  5. 7
    21
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java
  6. 30
    21
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtAggregator.java
  7. 69
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java
  8. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.java
  9. 0
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java
  10. 8
    14
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCounter.java
  11. 20
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java
  12. 3
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitor.java
  13. 9
    9
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtAggregator.java
  14. 13
    11
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java
  15. 53
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/Rule.java
  16. 18
    8
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java
  17. 155
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleImpl.java
  18. 7
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java
  19. 7
    12
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepositoryImpl.java
  20. 5
    8
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java
  21. 16
    18
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUserLoader.java
  22. 6
    26
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java
  23. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolderImpl.java
  24. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
  25. 6
    18
      server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java
  26. 5
    12
      server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java
  27. 6
    5
      server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
  28. 2
    6
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java
  29. 83
    126
      server/sonar-server/src/test/java/org/sonar/server/computation/debt/DebtModelHolderImplTest.java
  30. 155
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtAggregatorTest.java
  31. 98
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtCalculatorTest.java
  32. 73
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/DefaultAssigneeTest.java
  33. 118
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/DumbRule.java
  34. 124
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueAssignerTest.java
  35. 5
    5
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java
  36. 95
    348
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtAggregatorTest.java
  37. 106
    127
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtCalculatorTest.java
  38. 27
    6
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java
  39. 15
    9
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryImplTest.java
  40. 59
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryRule.java
  41. 72
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleTagsCopierTest.java
  42. 7
    6
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest.java
  43. 0
    137
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceAuthorsHolderTest.java
  44. 8
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java
  45. 66
    87
      server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedDebtModelStepTest.java
  46. 9
    3
      server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java
  47. 18
    125
      server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistMeasuresStepTest.java
  48. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
  49. 2
    2
      server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml
  50. 1
    1
      server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/insert_new_issue.xml
  51. 49
    144
      sonar-batch-protocol/src/main/gen-java/org/sonar/batch/protocol/output/BatchReport.java
  52. 0
    1
      sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java
  53. 2
    3
      sonar-batch-protocol/src/main/protobuf/batch_report.proto
  54. 0
    29
      sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java
  55. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java
  56. 0
    5
      sonar-batch/src/main/java/org/sonar/batch/report/IssuesPublisher.java
  57. 0
    1
      sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java
  58. 3
    106
      sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java
  59. 0
    2
      sonar-batch/src/test/java/org/sonar/batch/report/IssuesPublisherTest.java
  60. 2
    2
      sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java
  61. 1
    1
      sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java
  62. 1
    1
      sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java
  63. 5
    0
      sonar-core/src/main/java/org/sonar/core/issue/tracking/BlockHashSequence.java
  64. 10
    10
      sonar-core/src/main/java/org/sonar/core/issue/tracking/BlockRecognizer.java
  65. 28
    37
      sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java
  66. 2
    1
      sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java
  67. 2
    2
      sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java
  68. 0
    3
      sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java
  69. 26
    0
      sonar-core/src/main/java/org/sonar/core/measure/db/MeasureDto.java
  70. 49
    0
      sonar-core/src/main/java/org/sonar/core/rule/RuleKeyFunctions.java
  71. 2
    2
      sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml
  72. 219
    0
      sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java
  73. 149
    0
      sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java
  74. 4
    4
      sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java
  75. 62
    14
      sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
  76. 2
    3
      sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java
  77. 4
    16
      sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java
  78. 46
    0
      sonar-core/src/test/java/org/sonar/core/rule/RuleKeyFunctionsTest.java
  79. 45
    7
      sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml
  80. 10
    2
      sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java
  81. 6
    3
      sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java
  82. 3
    12
      sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java
  83. 1
    1
      sonar-plugin-api/src/main/java/org/sonar/api/issue/condition/HasIssuePropertyCondition.java
  84. 18
    8
      sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java
  85. 1
    1
      sonar-plugin-api/src/main/java/org/sonar/api/measures/MeasuresFilters.java
  86. 2
    0
      sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
  87. 17
    1
      sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtRemediationFunction.java
  88. 0
    5
      sonar-plugin-api/src/test/java/org/sonar/api/batch/rule/internal/RulesBuilderTest.java
  89. 74
    94
      sonar-plugin-api/src/test/java/org/sonar/api/issue/action/ActionTest.java
  90. 50
    63
      sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasIssuePropertyConditionTest.java
  91. 27
    41
      sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasResolutionTest.java
  92. 34
    44
      sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasStatusTest.java
  93. 23
    39
      sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/IsUnResolvedTest.java
  94. 27
    45
      sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/NotConditionTest.java
  95. 0
    238
      sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java
  96. 0
    168
      sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/FieldDiffsTest.java

+ 6
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java 查看文件

@@ -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,

+ 3
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolder.java 查看文件

@@ -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();
}

+ 14
- 3
server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolderImpl.java 查看文件

@@ -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");
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/debt/MutableDebtModelHolder.java 查看文件

@@ -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);
}

+ 7
- 21
server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java 查看文件

@@ -19,29 +19,26 @@
*/
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);
}
}
}

+ 30
- 21
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtAggregator.java 查看文件

@@ -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();

+ 69
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java 查看文件

@@ -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.");
}
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.java 查看文件

@@ -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;

+ 0
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java 查看文件

@@ -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> {


+ 8
- 14
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCounter.java 查看文件

@@ -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());
}
}
}

+ 20
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java 查看文件

@@ -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);
}

+ 3
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitor.java 查看文件

@@ -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) {


+ 9
- 9
server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtAggregator.java 查看文件

@@ -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) {

+ 13
- 11
server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java 查看文件

@@ -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();
}
}


+ 53
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/Rule.java 查看文件

@@ -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();
}

+ 18
- 8
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java 查看文件

@@ -19,36 +19,46 @@
*/
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");
}
}

+ 155
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleImpl.java 查看文件

@@ -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;
}
}

sonar-core/src/main/java/org/sonar/core/computation/package-info.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java 查看文件

@@ -17,8 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.computation.issue;

@ParametersAreNonnullByDefault
package org.sonar.core.computation;
import org.sonar.api.rule.RuleKey;

import javax.annotation.ParametersAreNonnullByDefault;
public interface RuleRepository {

Rule getByKey(RuleKey key);

}

server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepositoryImpl.java 查看文件

@@ -20,23 +20,18 @@
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;
public class RuleRepositoryImpl implements RuleRepository {

/**
* Cache of the rules involved during the current analysis
*/
public class RuleCache extends MemoryCache<RuleKey, RuleDto> {
private final MemoryCache<RuleKey, Rule> cache;

public RuleCache(RuleCacheLoader loader) {
super(loader);
public RuleRepositoryImpl(RuleCacheLoader cacheLoader) {
this.cache = new MemoryCache<>(cacheLoader);
}

@CheckForNull
public String ruleName(RuleKey key) {
RuleDto rule = getNullable(key);
return rule != null ? rule.getName() : null;
@Override
public Rule getByKey(RuleKey key) {
return cache.get(key);
}
}

+ 5
- 8
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java 查看文件

@@ -19,28 +19,25 @@
*/
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()));
}
}
}

+ 16
- 18
server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUserLoader.java 查看文件

@@ -19,35 +19,29 @@
*/
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();
}
}
}

+ 6
- 26
server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java 查看文件

@@ -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;
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolderImpl.java 查看文件

@@ -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)) {

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java 查看文件

@@ -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,

+ 6
- 18
server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java 查看文件

@@ -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);
}
}

+ 5
- 12
server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java 查看文件

@@ -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);

+ 6
- 5
server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java 查看文件

@@ -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);

+ 2
- 6
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java 查看文件

@@ -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;
}

+ 83
- 126
server/sonar-server/src/test/java/org/sonar/server/computation/debt/DebtModelHolderImplTest.java 查看文件

@@ -17,129 +17,86 @@
* 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();
}
}

+ 155
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtAggregatorTest.java 查看文件

@@ -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);
}
}

+ 98
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtCalculatorTest.java 查看文件

@@ -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));
}
}

+ 73
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DefaultAssigneeTest.java 查看文件

@@ -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();
}
}

+ 118
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/DumbRule.java 查看文件

@@ -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;
}
}

+ 124
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueAssignerTest.java 查看文件

@@ -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");
}
}

server/sonar-server/src/test/java/org/sonar/server/computation/issue/CountIssuesListenerTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java 查看文件

@@ -71,7 +71,7 @@ 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 {
public class IssueCounterTest {

static final Component FILE1 = builder(Component.Type.FILE, 1).build();
static final Component FILE2 = builder(Component.Type.FILE, 2).build();
@@ -213,13 +213,13 @@ public class CountIssuesListenerTest {

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(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);
assertThat(measureRepository.getRawMeasure(PROJECT, NEW_BLOCKER_ISSUES_METRIC).isPresent()).isFalse();
assertThat(measureRepository.getRawMeasure(PROJECT, NEW_MAJOR_ISSUES_METRIC).isPresent()).isFalse();
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) {

+ 95
- 348
server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtAggregatorTest.java 查看文件

@@ -17,351 +17,98 @@
* 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();
}
}

+ 106
- 127
server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtCalculatorTest.java 查看文件

@@ -17,130 +17,109 @@
* 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());
}

}

+ 27
- 6
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java 查看文件

@@ -19,26 +19,34 @@
*/
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();

server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryImplTest.java 查看文件

@@ -20,21 +20,27 @@
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.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 RuleCacheTest {
public class RuleRepositoryImplTest {

RuleCacheLoader cacheLoader = mock(RuleCacheLoader.class);
RuleRepositoryImpl underTest = new RuleRepositoryImpl(cacheLoader);

@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");
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);
}
}

+ 59
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryRule.java 查看文件

@@ -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;
}

}

+ 72
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleTagsCopierTest.java 查看文件

@@ -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();
}
}

+ 7
- 6
server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest.java 查看文件

@@ -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

+ 0
- 137
server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceAuthorsHolderTest.java 查看文件

@@ -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");
// }
//}

+ 8
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java 查看文件

@@ -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();

+ 66
- 87
server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedDebtModelStepTest.java 查看文件

@@ -17,90 +17,69 @@
* 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();
}
}

+ 9
- 3
server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java 查看文件

@@ -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

+ 18
- 125
server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistMeasuresStepTest.java 查看文件

@@ -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);

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java 查看文件

@@ -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());


+ 2
- 2
server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml 查看文件

@@ -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]"

+ 1
- 1
server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/insert_new_issue.xml 查看文件

@@ -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"

+ 49
- 144
sonar-batch-protocol/src/main/gen-java/org/sonar/batch/protocol/output/BatchReport.java 查看文件

@@ -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

+ 0
- 1
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java 查看文件

@@ -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 {


+ 2
- 3
sonar-batch-protocol/src/main/protobuf/batch_report.proto 查看文件

@@ -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;

+ 0
- 29
sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java 查看文件

@@ -19,10 +19,8 @@
*/
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;
}

}

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java 查看文件

@@ -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() {

+ 0
- 5
sonar-batch/src/main/java/org/sonar/batch/report/IssuesPublisher.java 查看文件

@@ -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();
}


+ 0
- 1
sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java 查看文件

@@ -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 {


+ 3
- 106
sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java 查看文件

@@ -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
*/

+ 0
- 2
sonar-batch/src/test/java/org/sonar/batch/report/IssuesPublisherTest.java 查看文件

@@ -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");

+ 2
- 2
sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java 查看文件

@@ -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);

+ 1
- 1
sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java 查看文件

@@ -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);
}

+ 1
- 1
sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java 查看文件

@@ -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) {

+ 5
- 0
sonar-core/src/main/java/org/sonar/core/issue/tracking/BlockHashSequence.java 查看文件

@@ -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;


+ 10
- 10
sonar-core/src/main/java/org/sonar/core/issue/tracking/BlockRecognizer.java 查看文件

@@ -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);
}
}

+ 28
- 37
sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java 查看文件

@@ -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

+ 2
- 1
sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java 查看文件

@@ -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);
}

+ 2
- 2
sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java 查看文件

@@ -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())


+ 0
- 3
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java 查看文件

@@ -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 {

+ 26
- 0
sonar-core/src/main/java/org/sonar/core/measure/db/MeasureDto.java 查看文件

@@ -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();
}
}

+ 49
- 0
sonar-core/src/main/java/org/sonar/core/rule/RuleKeyFunctions.java 查看文件

@@ -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);
}
}

}

+ 2
- 2
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml 查看文件

@@ -58,14 +58,14 @@
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>


+ 219
- 0
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java 查看文件

@@ -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);
}
}

+ 149
- 0
sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java 查看文件

@@ -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();
}
}

+ 4
- 4
sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java 查看文件

@@ -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

+ 62
- 14
sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java 查看文件

@@ -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;

+ 2
- 3
sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java 查看文件

@@ -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();


+ 4
- 16
sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java 查看文件

@@ -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);

+ 46
- 0
sonar-core/src/test/java/org/sonar/core/rule/RuleKeyFunctionsTest.java 查看文件

@@ -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();
}
}

sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfUnresolvedIssuesByComponent.xml → sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml 查看文件

@@ -1,6 +1,6 @@
<dataset>

<!-- Unresolved -->
<!-- Unresolved. To be included -->
<issues
id="1"
kee="UNRESOLVED_ON_FILE_1"
@@ -52,7 +52,7 @@
issue_change_creation_date="[null]"
/>

<!-- Resolved: to be ignored -->
<!-- Resolved but not closed. To be included -->
<issues
id="2"
kee="RESOLVED_ON_FILE_1"
@@ -90,9 +90,47 @@
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="3"
id="4"
kee="UNRESOLVED_ON_FILE_2"
component_uuid="FILE_2"
project_uuid="PROJECT_1"
@@ -118,8 +156,8 @@

<!-- diff -->
<issue_changes
id="104"
kee="104"
id="105"
kee="105"
issue_key="UNRESOLVED_ON_FILE_2"
user_login="arthur"
change_type="diff"
@@ -131,8 +169,8 @@

<!-- comment -->
<issue_changes
id="105"
kee="105"
id="106"
kee="106"
issue_key="UNRESOLVED_ON_FILE_2"
user_login="arthur"
change_type="comment"

+ 10
- 2
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java 查看文件

@@ -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();

}

+ 6
- 3
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java 查看文件

@@ -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

+ 3
- 12
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java 查看文件

@@ -19,18 +19,15 @@
*/
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));

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/issue/condition/HasIssuePropertyCondition.java 查看文件

@@ -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));
}
}

+ 18
- 8
sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java 查看文件

@@ -20,17 +20,19 @@
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;
}

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/measures/MeasuresFilters.java 查看文件

@@ -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> {

+ 2
- 0
sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java 查看文件

@@ -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";

+ 17
- 1
sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtRemediationFunction.java 查看文件

@@ -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();

+ 0
- 5
sonar-plugin-api/src/test/java/org/sonar/api/batch/rule/internal/RulesBuilderTest.java 查看文件

@@ -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();
}


+ 74
- 94
sonar-plugin-api/src/test/java/org/sonar/api/issue/action/ActionTest.java 查看文件

@@ -17,97 +17,77 @@
* 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");
}

}

+ 50
- 63
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasIssuePropertyConditionTest.java 查看文件

@@ -17,66 +17,53 @@
* 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("");
}

}

+ 27
- 41
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasResolutionTest.java 查看文件

@@ -17,44 +17,30 @@
* 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();
}
}

+ 34
- 44
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/HasStatusTest.java 查看文件

@@ -17,47 +17,37 @@
* 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();
}

}

+ 23
- 39
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/IsUnResolvedTest.java 查看文件

@@ -17,42 +17,26 @@
* 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();
}
}

+ 27
- 45
sonar-plugin-api/src/test/java/org/sonar/api/issue/condition/NotConditionTest.java 查看文件

@@ -17,48 +17,30 @@
* 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();
}
}

+ 0
- 238
sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java 查看文件

@@ -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);
// }
//}

+ 0
- 168
sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/FieldDiffsTest.java 查看文件

@@ -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();
// }
//}

正在加载...
取消
保存