diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-07-02 20:01:59 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-07-02 20:01:59 +0200 |
commit | be9b5a2ea93b7959cbaa4a630bff79467b3ba8d0 (patch) | |
tree | 03fad10de15f1cf9d64199e32bfa3b90158035ea /server/sonar-server | |
parent | e154f3cf3e73c0ba447fffef1d447dc729f8bf9b (diff) | |
parent | 1c0b9a97e3bc6d58c4697b4350b39adc81a6efc1 (diff) | |
download | sonarqube-be9b5a2ea93b7959cbaa4a630bff79467b3ba8d0.tar.gz sonarqube-be9b5a2ea93b7959cbaa4a630bff79467b3ba8d0.zip |
Merge branch 'feature/ce_issue_tracking'
Diffstat (limited to 'server/sonar-server')
122 files changed, 3650 insertions, 2141 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReader.java index dc57d0aa6ae..2568cb4ed9a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReader.java @@ -36,8 +36,6 @@ public interface BatchReportReader { List<BatchReport.Issue> readComponentIssues(int componentRef); - BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef); - List<BatchReport.Duplication> readComponentDuplications(int componentRef); List<BatchReport.Symbols.Symbol> readComponentSymbols(int componentRef); @@ -47,7 +45,8 @@ public interface BatchReportReader { CloseableIterator<BatchReport.Coverage> readComponentCoverage(int fileRef); /** - * Reads file source line by line. + * Reads file source line by line. Throws an exception if the ref does not relate + * to a file */ CloseableIterator<String> readFileSource(int fileRef); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReaderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReaderImpl.java index c48e0289b1a..8f1f476e2ec 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReaderImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReaderImpl.java @@ -74,11 +74,6 @@ public class BatchReportReaderImpl implements BatchReportReader { } @Override - public BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef) { - return delegate.readDeletedComponentIssues(deletedComponentRef); - } - - @Override public List<BatchReport.Duplication> readComponentDuplications(int componentRef) { return delegate.readComponentDuplications(componentRef); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/component/ComponentVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/component/ComponentVisitor.java index 5804e0b2b55..4039933865c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/component/ComponentVisitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/component/ComponentVisitor.java @@ -23,6 +23,16 @@ public interface ComponentVisitor { void visit(Component tree); enum Order { - PRE_ORDER, POST_ORDER + /** + * Each component is visited BEFORE its children. Top-down traversal of + * tree of components. + */ + PRE_ORDER, + + /** + * Each component is visited AFTER its children. Bottom-up traversal of + * tree of components. + */ + POST_ORDER } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/component/ProjectSettingsRepository.java b/server/sonar-server/src/main/java/org/sonar/server/computation/component/ProjectSettingsRepository.java index 467049c0e00..2a12477b8de 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/component/ProjectSettingsRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/component/ProjectSettingsRepository.java @@ -26,7 +26,7 @@ import org.sonar.server.properties.ProjectSettingsFactory; /** * Lazy loading of project settings. * - * Should be refactored to be able to load settings from any components. + * TODO Should be refactored to be able to load settings from any components. */ public class ProjectSettingsRepository { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java index 81b8cd7fd97..ec4863798e0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java @@ -31,6 +31,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; import org.sonar.core.component.Module; import org.sonar.core.issue.db.UpdateConflictResolver; +import org.sonar.core.issue.tracking.Tracker; import org.sonar.core.platform.ComponentContainer; import org.sonar.server.computation.ComputationService; import org.sonar.server.computation.ReportQueue; @@ -43,13 +44,25 @@ import org.sonar.server.computation.component.TreeRootHolderImpl; import org.sonar.server.computation.debt.DebtModelHolderImpl; import org.sonar.server.computation.event.EventRepositoryImpl; import org.sonar.server.computation.formula.FormulaRepositoryImpl; +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; +import org.sonar.server.computation.issue.IssueAssigner; import org.sonar.server.computation.issue.IssueCache; -import org.sonar.server.computation.issue.IssueComputation; -import org.sonar.server.computation.issue.RuleCache; +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.RuleCacheLoader; -import org.sonar.server.computation.issue.ScmAccountCache; -import org.sonar.server.computation.issue.ScmAccountCacheLoader; -import org.sonar.server.computation.issue.SourceLinesCache; +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; +import org.sonar.server.computation.issue.TrackerBaseInputFactory; +import org.sonar.server.computation.issue.TrackerExecution; +import org.sonar.server.computation.issue.TrackerRawInputFactory; import org.sonar.server.computation.language.LanguageRepositoryImpl; import org.sonar.server.computation.measure.MeasureRepositoryImpl; import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeysModule; @@ -156,14 +169,30 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co NewCoverageMetricKeysModule.class, // issues - ScmAccountCacheLoader.class, - ScmAccountCache.class, - SourceLinesCache.class, - IssueComputation.class, - RuleCache.class, RuleCacheLoader.class, + RuleRepositoryImpl.class, + ScmAccountToUserLoader.class, + ScmAccountToUser.class, IssueCache.class, + DefaultAssignee.class, + IssueVisitors.class, + IssueLifecycle.class, + + // order is important: DebtAggregator then NewDebtAggregator (new debt requires debt) + DebtCalculator.class, + DebtAggregator.class, + NewDebtCalculator.class, + NewDebtAggregator.class, + IssueAssigner.class, + RuleTagsCopier.class, + IssueCounter.class, + UpdateConflictResolver.class, + TrackerBaseInputFactory.class, + TrackerRawInputFactory.class, + Tracker.class, + TrackerExecution.class, + BaseIssuesLoader.class, // views ViewIndex.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/Characteristic.java b/server/sonar-server/src/main/java/org/sonar/server/computation/debt/Characteristic.java index 5c1224c8df9..0606b2533ee 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/Characteristic.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/debt/Characteristic.java @@ -20,6 +20,7 @@ package org.sonar.server.computation.debt; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -29,11 +30,13 @@ import static java.util.Objects.requireNonNull; public class Characteristic { private final int id; private final String key; + private final Integer parentId; - public Characteristic(int id, String key) { + public Characteristic(int id, String key, @Nullable Integer parentId) { requireNonNull(key, "key cannot be null"); this.id = id; this.key = key; + this.parentId = parentId; } public int getId() { @@ -44,6 +47,11 @@ public class Characteristic { return key; } + @CheckForNull + public Integer getParentId() { + return parentId; + } + @Override public boolean equals(@Nullable Object o) { if (this == o) { @@ -66,10 +74,11 @@ public class Characteristic { @Override public String toString() { - return "Characteristic{" + - "id=" + id + - ", key='" + key + '\'' + - '}'; + StringBuilder sb = new StringBuilder("Characteristic{"); + sb.append("id=").append(id); + sb.append(", key='").append(key).append('\''); + sb.append(", parentId=").append(parentId); + sb.append('}'); + return sb.toString(); } - } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolder.java index c79b7cb2860..0290164b18b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolder.java @@ -20,30 +20,17 @@ package org.sonar.server.computation.debt; -import com.google.common.base.Optional; -import java.util.Set; +import java.util.List; public interface DebtModelHolder { /** - * Return a characteristic by its key + * Return a characteristic by its id * - * @throws IllegalStateException if the holder is empty + * @throws IllegalStateException if no Characteristic with the specified id is found + * @throws IllegalStateException if the holder is not initialized yet */ - Optional<Characteristic> getCharacteristicByKey(String key); - - /** - * Return the set of root characteristics - * - * @throws IllegalStateException if the holder is empty - */ - Set<Characteristic> findRootCharacteristics(); - - /** - * Return the collection of sub characteristics from a root characteristic key - * - * @throws IllegalStateException if the holder is empty - */ - Set<Characteristic> findSubCharacteristicsByRootKey(String rootCharacteristicKey); + Characteristic getCharacteristicById(int id); + List<Characteristic> getRootCharacteristics(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolderImpl.java index 6d0e0d1fbf1..a66b8354271 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolderImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/debt/DebtModelHolderImpl.java @@ -20,61 +20,51 @@ package org.sonar.server.computation.debt; -import com.google.common.base.Optional; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; -import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; public class DebtModelHolderImpl implements MutableDebtModelHolder { - private final Multimap<Characteristic, Characteristic> subCharacteristicsByRootCharacteristic = ArrayListMultimap.create(); - private final Map<String, Characteristic> characteristicByKey = new HashMap<>(); + 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"); - subCharacteristicsByRootCharacteristic.putAll(rootCharacteristic, subCharacteristics); - characteristicByKey.put(rootCharacteristic.getKey(), rootCharacteristic); + rootCharacteristics.add(rootCharacteristic); + characteristicById.put(rootCharacteristic.getId(), rootCharacteristic); for (Characteristic characteristic : subCharacteristics) { - characteristicByKey.put(characteristic.getKey(), characteristic); + characteristicById.put(characteristic.getId(), characteristic); } + return this; } @Override - public Set<Characteristic> findRootCharacteristics() { - checkCharacteristicsAreInitialized(); - return ImmutableSet.copyOf(subCharacteristicsByRootCharacteristic.keySet()); - } - - @Override - public Set<Characteristic> findSubCharacteristicsByRootKey(String rootCharacteristicKey) { - checkCharacteristicsAreInitialized(); - Characteristic rootCharacteristic = characteristicByKey.get(rootCharacteristicKey); - if (rootCharacteristic == null) { - return Collections.emptySet(); + public Characteristic getCharacteristicById(int id) { + checkInitialized(); + Characteristic characteristic = characteristicById.get(id); + if (characteristic == null) { + throw new IllegalStateException("Debt characteristic with id [" + id + "] does not exist"); } - return ImmutableSet.copyOf(subCharacteristicsByRootCharacteristic.get(rootCharacteristic)); + return characteristic; } @Override - public Optional<Characteristic> getCharacteristicByKey(String key) { - checkCharacteristicsAreInitialized(); - return fromNullable(characteristicByKey.get(key)); + public List<Characteristic> getRootCharacteristics() { + checkInitialized(); + return rootCharacteristics; } - private void checkCharacteristicsAreInitialized() { - checkState(!characteristicByKey.isEmpty(), "Characteristics have not been initialized yet"); + private void checkInitialized() { + checkState(!characteristicById.isEmpty(), "Characteristics have not been initialized yet"); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/MutableDebtModelHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/debt/MutableDebtModelHolder.java index 665986425bf..45ab2d84fa2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/debt/MutableDebtModelHolder.java +++ b/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); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java new file mode 100644 index 00000000000..8ebfb64479e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java @@ -0,0 +1,104 @@ +/* + * 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.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +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.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.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. + * + */ +public class BaseIssuesLoader { + + private final Set<RuleKey> activeRuleKeys; + private final TreeRootHolder treeRootHolder; + private final DbClient dbClient; + private final RuleRepository ruleRepository; + + public BaseIssuesLoader(BatchReportReader reportReader, TreeRootHolder treeRootHolder, + DbClient dbClient, RuleRepository ruleRepository) { + this.activeRuleKeys = from(reportReader.readMetadata().getActiveRuleKeyList()).transform(stringToRuleKey()).toSet(); + this.treeRootHolder = treeRootHolder; + this.dbClient = dbClient; + this.ruleRepository = ruleRepository; + } + + public List<DefaultIssue> loadForComponentUuid(String componentUuid) { + DbSession session = dbClient.openSession(false); + final List<DefaultIssue> result = new ArrayList<>(); + try { + Map<String, String> params = ImmutableMap.of("componentUuid", componentUuid); + session.select(IssueMapper.class.getName() + ".selectNonClosedByComponentUuid", params, new ResultHandler() { + @Override + public void handleResult(ResultContext resultContext) { + DefaultIssue issue = ((IssueDto) resultContext.getResultObject()).toDefaultIssue(); + + // TODO this field should be set outside this class + 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()); + result.add(issue); + } + }); + return result; + } finally { + MyBatis.closeQuietly(session); + } + } + + private boolean isActive(RuleKey ruleKey) { + return ruleKey.isManual() || activeRuleKeys.contains(ruleKey); + } + + /** + * Uuids of all the components that have open issues on this project. + */ + public Set<String> loadUuidsOfComponentsWithOpenIssues() { + DbSession session = dbClient.openSession(false); + try { + return dbClient.issueDao().selectComponentUuidsOfOpenIssuesForProjectUuid(session, treeRootHolder.getRoot().getUuid()); + } finally { + MyBatis.closeQuietly(session); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtAggregator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtAggregator.java new file mode 100644 index 00000000000..c8c410a6503 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtAggregator.java @@ -0,0 +1,171 @@ +/* + * 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 java.util.Set; +import javax.annotation.CheckForNull; +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.server.computation.component.Component; +import org.sonar.server.computation.debt.Characteristic; +import org.sonar.server.computation.debt.DebtModelHolder; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; + +import static com.google.common.collect.Maps.newHashMap; + +public class DebtAggregator extends IssueVisitor { + + private final RuleRepository ruleRepository; + private final DebtModelHolder debtModelHolder; + private final MetricRepository metricRepository; + private final MeasureRepository measureRepository; + + private final Map<Integer, Debt> debtsByComponentRef = new HashMap<>(); + private Debt currentDebt; + + public DebtAggregator(RuleRepository ruleRepository, DebtModelHolder debtModelHolder, + MetricRepository metricRepository, MeasureRepository measureRepository) { + this.ruleRepository = ruleRepository; + this.debtModelHolder = debtModelHolder; + this.metricRepository = metricRepository; + this.measureRepository = measureRepository; + } + + @Override + public void beforeComponent(Component component, Tracking tracking) { + currentDebt = new Debt(); + debtsByComponentRef.put(component.getRef(), currentDebt); + + // aggregate children counters + for (Component child : component.getChildren()) { + // no need to keep the children in memory. They can be garbage-collected. + Debt childDebt = debtsByComponentRef.remove(child.getRef()); + if (childDebt != null) { + currentDebt.add(childDebt); + } + } + } + + @Override + public void onIssue(Component component, DefaultIssue issue) { + if (issue.resolution() == null) { + currentDebt.add(issue); + } + } + + @Override + public void afterComponent(Component component) { + Metric metric = metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY); + + // 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(); + // 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)); + } + + 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; + } + + private class Debt { + private long minutes = 0L; + private final SumMap<Integer> minutesByRuleId = new SumMap<>(); + private final SumMap<Integer> minutesByCharacteristicId = new SumMap<>(); + + void add(DefaultIssue issue) { + Long issueMinutes = issue.debtInMinutes(); + if (issueMinutes != null && issueMinutes != 0L) { + this.minutes += issueMinutes; + + Rule rule = ruleRepository.getByKey(issue.ruleKey()); + this.minutesByRuleId.add(rule.getId(), issueMinutes); + + Integer subCharacteristicId = rule.getSubCharacteristicId(); + if (subCharacteristicId != null) { + Characteristic characteristic = debtModelHolder.getCharacteristicById(subCharacteristicId); + this.minutesByCharacteristicId.add(characteristic.getId(), issueMinutes); + Integer characteristicParentId = characteristic.getParentId(); + if (characteristicParentId != null) { + this.minutesByCharacteristicId.add(characteristicParentId, issueMinutes); + } + } + } + } + + public void add(Debt debt) { + this.minutes += debt.minutes; + this.minutesByRuleId.add(debt.minutesByRuleId); + this.minutesByCharacteristicId.add(debt.minutesByCharacteristicId); + } + } + + private static class SumMap<E> { + private final Map<E, Long> sumByKeys = newHashMap(); + + void add(SumMap<E> other) { + for (Map.Entry<E, Long> entry : other.entrySet()) { + add(entry.getKey(), entry.getValue()); + } + } + + void add(@Nullable E key, Long value) { + if (key != null) { + Long currentValue = sumByKeys.get(key); + sumByKeys.put(key, currentValue != null ? (currentValue + value) : value); + } + } + + @CheckForNull + Long get(E key) { + return sumByKeys.get(key); + } + + Set<Map.Entry<E, Long>> entrySet() { + return sumByKeys.entrySet(); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java new file mode 100644 index 00000000000..0a7e5274809 --- /dev/null +++ b/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."); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.java new file mode 100644 index 00000000000..7cdd0a5da73 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.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 com.google.common.base.Strings; +import javax.annotation.CheckForNull; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.server.computation.component.ProjectSettingsRepository; +import org.sonar.server.computation.component.TreeRootHolder; +import org.sonar.server.user.index.UserDoc; +import org.sonar.server.user.index.UserIndex; + +/** + * The user who is optionally declared as being the assignee + * of all the issues which SCM author is not associated with any SonarQube user. + */ +public class DefaultAssignee { + + private static final Logger LOG = Loggers.get(DefaultAssignee.class); + + private final TreeRootHolder treeRootHolder; + private final UserIndex userIndex; + private final ProjectSettingsRepository settings; + + private boolean loaded = false; + private String login = null; + + public DefaultAssignee(TreeRootHolder treeRootHolder, UserIndex userIndex, ProjectSettingsRepository settings) { + this.treeRootHolder = treeRootHolder; + this.userIndex = userIndex; + this.settings = settings; + } + + @CheckForNull + public String getLogin() { + if (!loaded) { + String configuredLogin = settings.getProjectSettings(treeRootHolder.getRoot().getKey()).getString(CoreProperties.DEFAULT_ISSUE_ASSIGNEE); + if (!Strings.isNullOrEmpty(configuredLogin) && isValidLogin(configuredLogin)) { + this.login = configuredLogin; + } + loaded = true; + } + return login; + } + + private boolean isValidLogin(String s) { + UserDoc user = userIndex.getNullableByLogin(s); + if (user == null) { + LOG.info("Property {} is set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, s); + return false; + } + return true; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/SourceLinesCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueAssigner.java index f8654fefeb8..7f49d983154 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/SourceLinesCache.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueAssigner.java @@ -27,102 +27,103 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset; -import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset.Builder; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Tracking; import org.sonar.server.computation.batch.BatchReportReader; +import org.sonar.server.computation.component.Component; import org.sonar.server.source.index.SourceLineDoc; import org.sonar.server.source.index.SourceLineIndex; /** - * Cache of the lines of the currently processed file. Only a <strong>single</strong> file - * is kept in memory at a time. Data is loaded <strong>on demand</strong> (to avoid non necessary - * loading).<br /> + * Detect the SCM author and SQ assignee. + * <p/> * It relies on: * <ul> * <li>the SCM information sent in the report for modified files</li> - * <li>the source line index for non-modified files</li> + * <li>the Elasticsearch index of source lines for non-modified files</li> * </ul> - * */ -public class SourceLinesCache { - - private final SourceLineIndex index; - private BatchReportReader reportReader; +public class IssueAssigner extends IssueVisitor { - private boolean loaded = false; - private BatchReport.Changesets scm; - private String currentFileUuid; - private Integer currentFileReportRef; + private final SourceLineIndex sourceLineIndex; + private final BatchReportReader reportReader; + private final DefaultAssignee defaultAssigne; + private final ScmAccountToUser scmAccountToUser; private long lastCommitDate = 0L; private String lastCommitAuthor = null; + private BatchReport.Changesets scmChangesets = null; - public SourceLinesCache(SourceLineIndex index) { - this.index = index; + public IssueAssigner(SourceLineIndex sourceLineIndex, BatchReportReader reportReader, + ScmAccountToUser scmAccountToUser, DefaultAssignee defaultAssigne) { + this.sourceLineIndex = sourceLineIndex; + this.reportReader = reportReader; + this.scmAccountToUser = scmAccountToUser; + this.defaultAssigne = defaultAssigne; } - /** - * Marks the currently processed component - */ - void init(String fileUuid, @Nullable Integer fileReportRef, BatchReportReader reportReader) { - this.loaded = false; - this.currentFileUuid = fileUuid; - this.currentFileReportRef = fileReportRef; - this.lastCommitDate = 0L; - this.lastCommitAuthor = null; - this.reportReader = reportReader; - clear(); + @Override + public void beforeComponent(Component component, Tracking tracking) { + // optimization - do not load data if there are no new issues + if (!tracking.getUnmatchedRaws().isEmpty()) { + scmChangesets = loadScmChangesetsFromReport(component); + if (scmChangesets == null) { + scmChangesets = loadScmChangesetsFromIndex(component); + } + computeLastCommitDateAndAuthor(); + } + } + + @Override + public void onIssue(Component component, DefaultIssue issue) { + if (issue.isNew()) { + String scmAuthor = guessScmAuthor(issue.line()); + issue.setAuthorLogin(scmAuthor); + if (scmAuthor != null) { + String assigneeLogin = scmAccountToUser.getNullable(scmAuthor); + if (assigneeLogin == null) { + issue.setAssignee(defaultAssigne.getLogin()); + } else { + issue.setAssignee(assigneeLogin); + } + } + } + } + + @Override + public void afterComponent(Component component) { + lastCommitDate = 0L; + lastCommitAuthor = null; + scmChangesets = null; } /** - * Last committer of the line, can be null. + * Get the SCM login of the last committer on the line. When line is zero, + * then get the last committer on the file. */ @CheckForNull - public String lineAuthor(@Nullable Integer lineNumber) { - loadIfNeeded(); - - if (lineNumber == null) { - // issue on file, approximately estimate that author is the last committer on the file - return lastCommitAuthor; - } + private String guessScmAuthor(@Nullable Integer line) { String author = null; - if (lineNumber <= scm.getChangesetIndexByLineCount()) { - BatchReport.Changesets.Changeset changeset = scm.getChangeset(scm.getChangesetIndexByLine(lineNumber - 1)); + if (line != null && line <= scmChangesets.getChangesetIndexByLineCount()) { + BatchReport.Changesets.Changeset changeset = scmChangesets.getChangeset(scmChangesets.getChangesetIndexByLine(line - 1)); author = changeset.hasAuthor() ? changeset.getAuthor() : null; } - return StringUtils.defaultIfEmpty(author, lastCommitAuthor); } - private void loadIfNeeded() { - checkState(); - - if (!loaded) { - scm = loadScmFromReport(); - loaded = scm != null; - } - - if (!loaded) { - scm = loadLinesFromIndexAndBuildScm(); - loaded = true; - } - - computeLastCommitDateAndAuthor(); - } - - private BatchReport.Changesets loadScmFromReport() { - return reportReader.readChangesets(currentFileReportRef); + private BatchReport.Changesets loadScmChangesetsFromReport(Component component) { + return reportReader.readChangesets(component.getRef()); } - private BatchReport.Changesets loadLinesFromIndexAndBuildScm() { - List<SourceLineDoc> lines = index.getLines(currentFileUuid); + private BatchReport.Changesets loadScmChangesetsFromIndex(Component component) { + List<SourceLineDoc> lines = sourceLineIndex.getLines(component.getUuid()); Map<String, BatchReport.Changesets.Changeset> changesetByRevision = new HashMap<>(); BatchReport.Changesets.Builder scmBuilder = BatchReport.Changesets.newBuilder() - .setComponentRef(currentFileReportRef); + .setComponentRef(component.getRef()); for (SourceLineDoc sourceLine : lines) { String scmRevision = sourceLine.scmRevision(); if (scmRevision == null || changesetByRevision.get(scmRevision) == null) { - Builder changeSetBuilder = BatchReport.Changesets.Changeset.newBuilder(); + BatchReport.Changesets.Changeset.Builder changeSetBuilder = BatchReport.Changesets.Changeset.newBuilder(); String scmAuthor = sourceLine.scmAuthor(); if (scmAuthor != null) { changeSetBuilder.setAuthor(scmAuthor); @@ -135,7 +136,7 @@ public class SourceLinesCache { changeSetBuilder.setRevision(scmRevision); } - Changeset changeset = changeSetBuilder.build(); + BatchReport.Changesets.Changeset changeset = changeSetBuilder.build(); scmBuilder.addChangeset(changeset); scmBuilder.addChangesetIndexByLine(scmBuilder.getChangesetCount() - 1); if (scmRevision != null) { @@ -149,24 +150,11 @@ public class SourceLinesCache { } private void computeLastCommitDateAndAuthor() { - for (BatchReport.Changesets.Changeset changeset : scm.getChangesetList()) { + for (BatchReport.Changesets.Changeset changeset : scmChangesets.getChangesetList()) { if (changeset.hasAuthor() && changeset.hasDate() && changeset.getDate() > lastCommitDate) { lastCommitDate = changeset.getDate(); lastCommitAuthor = changeset.getAuthor(); } } } - - private void checkState() { - if (currentFileReportRef == null) { - throw new IllegalStateException("Report component reference must not be null to use the cache"); - } - } - - /** - * Makes cache eligible to GC - */ - public void clear() { - scm = null; - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java index 50fab5b12b3..916a68d4118 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java @@ -19,18 +19,16 @@ */ package org.sonar.server.computation.issue; -import org.sonar.api.issue.internal.DefaultIssue; +import java.io.File; +import java.io.IOException; import org.sonar.api.utils.System2; import org.sonar.api.utils.TempFolder; +import org.sonar.core.issue.DefaultIssue; import org.sonar.server.util.cache.DiskCache; -import java.io.File; -import java.io.IOException; - /** * 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> { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java deleted file mode 100644 index 76079a64a85..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.computation.issue; - -import com.google.common.base.Strings; -import com.google.common.collect.Sets; -import java.util.Date; -import javax.annotation.Nullable; -import org.sonar.api.CoreProperties; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.FieldDiffs; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.utils.Duration; -import org.sonar.api.utils.KeyValueFormat; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.core.rule.RuleDto; -import org.sonar.server.computation.batch.BatchReportReader; -import org.sonar.server.computation.component.ProjectSettingsRepository; -import org.sonar.server.user.index.UserDoc; -import org.sonar.server.user.index.UserIndex; -import org.sonar.server.util.cache.DiskCache; - -public class IssueComputation { - - private static final Logger LOG = Loggers.get(IssueComputation.class); - - private final RuleCache ruleCache; - private final ScmAccountCache scmAccountCache; - private final SourceLinesCache linesCache; - private final DiskCache<DefaultIssue>.DiskAppender diskIssuesAppender; - private final UserIndex userIndex; - private final ProjectSettingsRepository projectSettingsRepository; - private final BatchReportReader reportReader; - private boolean hasAssigneeBeenComputed = false; - private String defaultAssignee = null; - - public IssueComputation(RuleCache ruleCache, SourceLinesCache linesCache, ScmAccountCache scmAccountCache, - IssueCache issueCache, UserIndex userIndex, ProjectSettingsRepository projectSettingsRepository, BatchReportReader reportReader) { - this.ruleCache = ruleCache; - this.linesCache = linesCache; - this.scmAccountCache = scmAccountCache; - this.userIndex = userIndex; - this.reportReader = reportReader; - this.projectSettingsRepository = projectSettingsRepository; - this.diskIssuesAppender = issueCache.newAppender(); - } - - public void processComponentIssues(Iterable<BatchReport.Issue> issues, String componentUuid, @Nullable Integer componentReportRef, - String projectKey, String projectUuid) { - linesCache.init(componentUuid, componentReportRef, reportReader); - computeDefaultAssignee(projectSettingsRepository.getProjectSettings(projectKey).getString(CoreProperties.DEFAULT_ISSUE_ASSIGNEE)); - for (BatchReport.Issue reportIssue : issues) { - DefaultIssue issue = toDefaultIssue(componentUuid, reportIssue, projectKey, projectUuid); - if (issue.isNew()) { - guessAuthor(issue); - autoAssign(issue, defaultAssignee); - copyRuleTags(issue); - } - diskIssuesAppender.append(issue); - } - linesCache.clear(); - } - - private DefaultIssue toDefaultIssue(String componentUuid, BatchReport.Issue issue, String projectKey, String projectUuid) { - DefaultIssue target = new DefaultIssue(); - target.setKey(issue.getUuid()); - target.setComponentUuid(componentUuid); - target.setRuleKey(RuleKey.of(issue.getRuleRepository(), issue.getRuleKey())); - target.setSeverity(issue.getSeverity().name()); - target.setManualSeverity(issue.getManualSeverity()); - target.setMessage(issue.hasMsg() ? issue.getMsg() : null); - target.setLine(issue.hasLine() ? issue.getLine() : null); - target.setProjectUuid(projectUuid); - target.setProjectKey(projectKey); - target.setEffortToFix(issue.hasEffortToFix() ? issue.getEffortToFix() : null); - target.setDebt(issue.hasDebtInMinutes() ? Duration.create(issue.getDebtInMinutes()) : null); - if (issue.hasDiffFields()) { - FieldDiffs fieldDiffs = FieldDiffs.parse(issue.getDiffFields()); - fieldDiffs.setCreationDate(new Date(reportReader.readMetadata().getAnalysisDate())); - target.setCurrentChange(fieldDiffs); - } - target.setStatus(issue.getStatus()); - target.setTags(issue.getTagList()); - target.setResolution(issue.hasResolution() ? issue.getResolution() : null); - target.setReporter(issue.hasReporter() ? issue.getReporter() : null); - target.setAssignee(issue.hasAssignee() ? issue.getAssignee() : null); - target.setChecksum(issue.hasChecksum() ? issue.getChecksum() : null); - target.setAttributes(issue.hasAttributes() ? KeyValueFormat.parse(issue.getAttributes()) : null); - target.setAuthorLogin(issue.hasAuthorLogin() ? issue.getAuthorLogin() : null); - target.setActionPlanKey(issue.hasActionPlanKey() ? issue.getActionPlanKey() : null); - target.setCreationDate(issue.hasCreationDate() ? new Date(issue.getCreationDate()) : null); - target.setUpdateDate(issue.hasUpdateDate() ? new Date(issue.getUpdateDate()) : null); - target.setCloseDate(issue.hasCloseDate() ? new Date(issue.getCloseDate()) : null); - target.setChanged(issue.getIsChanged()); - target.setNew(issue.getIsNew()); - target.setSelectedAt(issue.hasSelectedAt() ? issue.getSelectedAt() : null); - target.setSendNotifications(issue.getMustSendNotification()); - return target; - } - - public void afterReportProcessing() { - diskIssuesAppender.close(); - } - - private void guessAuthor(DefaultIssue issue) { - // issue.authorLogin() can be not-null when old developer cockpit plugin (or other plugin) - // is still installed and executed during analysis - if (issue.authorLogin() == null) { - issue.setAuthorLogin(linesCache.lineAuthor(issue.line())); - } - } - - private void autoAssign(DefaultIssue issue, @Nullable String defaultAssignee) { - // issue.assignee() can be not-null if the issue-assign-plugin is - // still installed and executed during analysis - if (issue.assignee() == null) { - String scmAccount = issue.authorLogin(); - if (scmAccount != null) { - issue.setAssignee(scmAccountCache.getNullable(scmAccount)); - } - if (issue.assignee() == null && defaultAssignee != null) { - issue.setAssignee(defaultAssignee); - } - } - } - - private void copyRuleTags(DefaultIssue issue) { - RuleDto rule = ruleCache.get(issue.ruleKey()); - issue.setTags(Sets.union(rule.getTags(), rule.getSystemTags())); - } - - private void computeDefaultAssignee(@Nullable String login) { - if (hasAssigneeBeenComputed) { - return; - } - - hasAssigneeBeenComputed = true; - if (!Strings.isNullOrEmpty(login)) { - UserDoc user = userIndex.getNullableByLogin(login); - if (user == null) { - LOG.info("the {} property was set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, login); - } else { - defaultAssignee = login; - } - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCounter.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCounter.java new file mode 100644 index 00000000000..8c27aefac35 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCounter.java @@ -0,0 +1,268 @@ +/* + * 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.HashMultiset; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multiset; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.sonar.api.issue.Issue; +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.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.measure.MeasureVariations; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolder; + +import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; +import static org.sonar.api.issue.Issue.STATUS_OPEN; +import static org.sonar.api.issue.Issue.STATUS_REOPENED; +import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; +import static org.sonar.api.rule.Severity.BLOCKER; +import static org.sonar.api.rule.Severity.CRITICAL; +import static org.sonar.api.rule.Severity.INFO; +import static org.sonar.api.rule.Severity.MAJOR; +import static org.sonar.api.rule.Severity.MINOR; + +/** + * For each component, computes the measures related to number of issues: + * <ul> + * <li>unresolved issues</li> + * <li>false-positives</li> + * <li>open issues</li> + * <li>issues per status (open, reopen, confirmed)</li> + * <li>issues per severity (from info to blocker)</li> + * </ul> + * For each value, the variation on configured periods is also computed. + */ +public class IssueCounter extends IssueVisitor { + + private static final Map<String, String> SEVERITY_TO_METRIC_KEY = ImmutableMap.of( + BLOCKER, BLOCKER_VIOLATIONS_KEY, + CRITICAL, CRITICAL_VIOLATIONS_KEY, + MAJOR, MAJOR_VIOLATIONS_KEY, + MINOR, MINOR_VIOLATIONS_KEY, + INFO, INFO_VIOLATIONS_KEY + ); + + 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, + MINOR, NEW_MINOR_VIOLATIONS_KEY, + INFO, NEW_INFO_VIOLATIONS_KEY + ); + + private final PeriodsHolder periodsHolder; + private final MetricRepository metricRepository; + private final MeasureRepository measureRepository; + + private final Map<Integer, Counters> countersByComponentRef = new HashMap<>(); + private Counters currentCounters; + + public IssueCounter(PeriodsHolder periodsHolder, + MetricRepository metricRepository, MeasureRepository measureRepository) { + this.periodsHolder = periodsHolder; + this.metricRepository = metricRepository; + this.measureRepository = measureRepository; + } + + @Override + public void beforeComponent(Component component, Tracking tracking) { + // TODO optimization no need to instantiate counter if no open issues + currentCounters = new Counters(); + countersByComponentRef.put(component.getRef(), currentCounters); + + // aggregate children counters + for (Component child : component.getChildren()) { + // no need to keep the children in memory. They can be garbage-collected. + Counters childCounters = countersByComponentRef.remove(child.getRef()); + currentCounters.add(childCounters); + } + } + + @Override + public void onIssue(Component component, DefaultIssue issue) { + currentCounters.add(issue); + for (Period period : periodsHolder.getPeriods()) { + // Add one second to not take into account issues created during current analysis + if (issue.creationDate().getTime() >= period.getSnapshotDate() + 1000L) { + currentCounters.addOnPeriod(issue, period.getIndex()); + } + } + } + + @Override + public void afterComponent(Component component) { + addMeasuresByStatus(component); + addMeasuresBySeverity(component); + addMeasuresByPeriod(component); + currentCounters = null; + } + + private void addMeasuresBySeverity(Component component) { + for (Map.Entry<String, String> entry : SEVERITY_TO_METRIC_KEY.entrySet()) { + String severity = entry.getKey(); + String metricKey = entry.getValue(); + addMeasure(component, metricKey, currentCounters.counter().severityBag.count(severity)); + } + } + + private void addMeasuresByStatus(Component component) { + addMeasure(component, VIOLATIONS_KEY, currentCounters.counter().unresolved); + addMeasure(component, OPEN_ISSUES_KEY, currentCounters.counter().open); + addMeasure(component, REOPENED_ISSUES_KEY, currentCounters.counter().reopened); + addMeasure(component, CONFIRMED_ISSUES_KEY, currentCounters.counter().confirmed); + addMeasure(component, FALSE_POSITIVE_ISSUES_KEY, currentCounters.counter().falsePositives); + } + + private void addMeasure(Component component, String metricKey, int value) { + Metric metric = metricRepository.getByKey(metricKey); + measureRepository.add(component, metric, Measure.newMeasureBuilder().create(value)); + } + + private void addMeasuresByPeriod(Component component) { + if (!periodsHolder.getPeriods().isEmpty()) { + Double[] unresolvedVariations = new Double[PeriodsHolder.MAX_NUMBER_OF_PERIODS]; + for (Period period : periodsHolder.getPeriods()) { + unresolvedVariations[period.getIndex() - 1] = new Double(currentCounters.counterForPeriod(period.getIndex()).unresolved); + } + measureRepository.add(component, metricRepository.getByKey(NEW_VIOLATIONS_KEY), Measure.newMeasureBuilder() + .setVariations(new MeasureVariations(unresolvedVariations)) + .createNoValue()); + + for (Map.Entry<String, String> entry : SEVERITY_TO_NEW_METRIC_KEY.entrySet()) { + String severity = entry.getKey(); + String metricKey = entry.getValue(); + Double[] variations = new Double[PeriodsHolder.MAX_NUMBER_OF_PERIODS]; + for (Period period : periodsHolder.getPeriods()) { + Multiset<String> bag = currentCounters.counterForPeriod(period.getIndex()).severityBag; + 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()); + } + } + } + + /** + * Count issues by status, resolutions, rules and severities + */ + private static class Counter { + private int unresolved = 0; + private int open = 0; + private int reopened = 0; + private int confirmed = 0; + private int falsePositives = 0; + private Multiset<String> severityBag = HashMultiset.create(); + + void add(Counter counter) { + unresolved += counter.unresolved; + open += counter.open; + reopened += counter.reopened; + confirmed += counter.confirmed; + falsePositives += counter.falsePositives; + severityBag.addAll(counter.severityBag); + } + + void add(Issue issue) { + if (issue.resolution() == null) { + unresolved++; + severityBag.add(issue.severity()); + } else if (Issue.RESOLUTION_FALSE_POSITIVE.equals(issue.resolution())) { + falsePositives++; + } + switch (issue.status()) { + case STATUS_OPEN: + open++; + break; + case STATUS_REOPENED: + reopened++; + break; + case STATUS_CONFIRMED: + confirmed++; + break; + default: + // Other statuses are ignored + } + } + } + + /** + * List of {@link Counter} for regular value and periods. + */ + private static class Counters { + private final Counter[] array = new Counter[1 + PeriodsHolder.MAX_NUMBER_OF_PERIODS]; + + Counters() { + array[0] = new Counter(); + for (int i = 1; i <= PeriodsHolder.MAX_NUMBER_OF_PERIODS; i++) { + array[i] = new Counter(); + } + } + + void add(@Nullable Counters other) { + if (other != null) { + for (int i = 0; i < array.length; i++) { + array[i].add(other.array[i]); + } + } + } + + void addOnPeriod(Issue issue, int periodIndex) { + array[periodIndex].add(issue); + } + + void add(Issue issue) { + array[0].add(issue); + } + + Counter counter() { + return array[0]; + } + + Counter counterForPeriod(int periodIndex) { + return array[periodIndex]; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java new file mode 100644 index 00000000000..31856ec5b6f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java @@ -0,0 +1,100 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import java.util.Date; +import javax.annotation.Nullable; +import org.sonar.api.issue.Issue; +import org.sonar.api.utils.internal.Uuids; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; +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, DebtCalculator debtCalculator) { + this.workflow = workflow; + this.updater = updater; + this.debtCalculator = debtCalculator; + this.changeContext = IssueChangeContext.createScan(new Date(reportReader.readMetadata().getAnalysisDate())); + } + + public void initNewOpenIssue(DefaultIssue issue) { + issue.setKey(Uuids.create()); + 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) { + raw.setNew(false); + raw.setKey(base.key()); + raw.setCreationDate(base.creationDate()); + raw.setUpdateDate(base.updateDate()); + raw.setCloseDate(base.closeDate()); + raw.setActionPlanKey(base.actionPlanKey()); + raw.setResolution(base.resolution()); + raw.setStatus(base.status()); + 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()); + } else { + updater.setPastSeverity(raw, base.severity(), changeContext); + } + + // TODO attributes + + // fields coming from raw + updater.setPastLine(raw, base.getLine()); + updater.setPastMessage(raw, base.getMessage(), changeContext); + updater.setPastEffortToFix(raw, base.effortToFix(), changeContext); + updater.setPastTechnicalDebt(raw, base.debt(), changeContext); + 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); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitor.java new file mode 100644 index 00000000000..6b3768dc26b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitor.java @@ -0,0 +1,48 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Tracking; +import org.sonar.server.computation.component.Component; + +public abstract class IssueVisitor { + + /** + * This method is called for each component before processing its issues. + * The component does not necessarily have issues. + */ + public void beforeComponent(Component component, Tracking tracking) { + + } + + /** + * 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) { + + } + + public void afterComponent(Component component) { + + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitors.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitors.java new file mode 100644 index 00000000000..51ddee18a05 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueVisitors.java @@ -0,0 +1,51 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Tracking; +import org.sonar.server.computation.component.Component; + +public class IssueVisitors { + + private final IssueVisitor[] visitors; + + public IssueVisitors(IssueVisitor[] visitors) { + this.visitors = visitors; + } + + public void beforeComponent(Component component, Tracking tracking) { + for (IssueVisitor visitor : visitors) { + visitor.beforeComponent(component, tracking); + } + } + + public void onIssue(Component component, DefaultIssue issue) { + for (IssueVisitor visitor : visitors) { + visitor.onIssue(component, issue); + } + } + + public void afterComponent(Component component) { + for (IssueVisitor visitor : visitors) { + visitor.afterComponent(component); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtAggregator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtAggregator.java new file mode 100644 index 00000000000..d26b324ff14 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtAggregator.java @@ -0,0 +1,120 @@ +/* + * 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.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.db.IssueChangeDto; +import org.sonar.core.issue.tracking.Tracking; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.measure.MeasureVariations; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolder; +import org.sonar.server.db.DbClient; + +public class NewDebtAggregator extends IssueVisitor { + + private final NewDebtCalculator calculator; + private final PeriodsHolder periodsHolder; + private final DbClient dbClient; + private final MetricRepository metricRepository; + private final MeasureRepository measureRepository; + + private ListMultimap<String, IssueChangeDto> changesByIssueUuid = ArrayListMultimap.create(); + private Map<Integer, DebtSum> sumsByComponentRef = new HashMap<>(); + private DebtSum currentSum = null; + + public NewDebtAggregator(NewDebtCalculator calculator, PeriodsHolder periodsHolder, DbClient dbClient, + MetricRepository metricRepository, MeasureRepository measureRepository) { + this.calculator = calculator; + this.periodsHolder = periodsHolder; + this.dbClient = dbClient; + this.metricRepository = metricRepository; + this.measureRepository = measureRepository; + } + + @Override + public void beforeComponent(Component component, Tracking tracking) { + currentSum = new DebtSum(); + sumsByComponentRef.put(component.getRef(), currentSum); + 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.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); + } + } + } + + @Override + public void afterComponent(Component component) { + if (!currentSum.isEmpty) { + MeasureVariations variations = new MeasureVariations(currentSum.sums); + Metric metric = metricRepository.getByKey(CoreMetrics.NEW_TECHNICAL_DEBT_KEY); + measureRepository.add(component, metric, Measure.newMeasureBuilder().setVariations(variations).createNoValue()); + } + changesByIssueUuid.clear(); + currentSum = null; + } + + private static class DebtSum { + private final Double[] sums = new Double[PeriodsHolder.MAX_NUMBER_OF_PERIODS]; + private boolean isEmpty = true; + + void add(int periodIndex, long newDebt) { + double previous = Objects.firstNonNull(sums[periodIndex - 1], 0d); + sums[periodIndex - 1] = previous + newDebt; + isEmpty = false; + } + + void add(DebtSum other) { + for (int i = 0; i < sums.length; i++) { + Double otherValue = other.sums[i]; + if (otherValue != null) { + add(i + 1, otherValue.longValue()); + } + } + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java new file mode 100644 index 00000000000..ed7502e7b22 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java @@ -0,0 +1,130 @@ +/* + * 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.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; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang.time.DateUtils; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; +import org.sonar.core.issue.IssueUpdater; +import org.sonar.core.issue.db.IssueChangeDto; +import org.sonar.server.computation.period.Period; + +import static com.google.common.collect.FluentIterable.from; + +/** + * Gets the issue debt that was introduced on a period. The algorithm + * is based on the issue changelog. + */ +public class NewDebtCalculator { + + 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, Collection<IssueChangeDto> debtChangelog, long periodDate) { + List<FieldDiffs> debtDiffs = from(debtChangelog).transform(ToFieldDiffs.INSTANCE).filter(HasDebtChange.INSTANCE).toSortedList(CHANGE_ORDERING); + FieldDiffs currentChange = issue.currentChange(); + if (currentChange != null && HasDebtChange.INSTANCE.apply(currentChange)) { + debtDiffs = Lists.newArrayList(debtDiffs); + debtDiffs.add(currentChange); + } + long newDebt = issue.debtInMinutes(); + + for (Iterator<FieldDiffs> it = debtDiffs.iterator(); it.hasNext();) { + FieldDiffs diffs = it.next(); + Date date = diffs.creationDate(); + // TODO use longs + if (isBeforeOrEqual(date, new Date(periodDate))) { + // return new value from the change that is just before the period date + return subtract(newDebt, debtDiff(diffs).newValueLong()); + } + if (!it.hasNext()) { + // return old value from the change that is just after the period date when there's no more element in changelog + return subtract(newDebt, debtDiff(diffs).oldValueLong()); + } + } + // no changelog + return 0L; + } + + /** + * SONAR-5059 + */ + @CheckForNull + private static long subtract(long newDebt, @Nullable Long with) { + if (with != null) { + return Math.max(0L, newDebt - with); + } + return newDebt; + } + + private static boolean isBeforeOrEqual(@Nullable Date changeDate, Date periodDate) { + return (changeDate != null) && (DateUtils.truncatedCompareTo(changeDate, periodDate, Calendar.SECOND) <= 0); + } + + private static FieldDiffs.Diff debtDiff(FieldDiffs diffs) { + return diffs.diffs().get(IssueUpdater.TECHNICAL_DEBT); + } + + /** + * Changelog have to be sorted from newest to oldest. + * Null date should be the first as this happen when technical debt has changed since previous analysis. + */ + private static final Comparator<FieldDiffs> CHANGE_ORDERING = Ordering.natural().reverse().nullsFirst().onResultOf(new Function<FieldDiffs, Date>() { + @Override + public Date apply(@Nonnull FieldDiffs dto) { + return dto.creationDate(); + } + }); + + private enum ToFieldDiffs implements Function<IssueChangeDto, FieldDiffs> { + INSTANCE; + @Override + public FieldDiffs apply(@Nonnull IssueChangeDto dto) { + return dto.toFieldDiffs(); + } + } + + private enum HasDebtChange implements Predicate<FieldDiffs> { + INSTANCE; + @Override + public boolean apply(@Nonnull FieldDiffs diffs) { + return diffs.diffs().containsKey(IssueUpdater.TECHNICAL_DEBT); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/Rule.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/Rule.java new file mode 100644 index 00000000000..acf8bd333a5 --- /dev/null +++ b/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(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java index e6439fd5baa..f655769e55d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java +++ b/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"); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleImpl.java new file mode 100644 index 00000000000..07548d5148f --- /dev/null +++ b/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; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java new file mode 100644 index 00000000000..3b86d2c24e5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepository.java @@ -0,0 +1,28 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import org.sonar.api.rule.RuleKey; + +public interface RuleRepository { + + Rule getByKey(RuleKey key); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleRepositoryImpl.java index 6b6a07a681d..ee3314cee5a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/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); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java new file mode 100644 index 00000000000..c44f4710b9c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java @@ -0,0 +1,43 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.computation.component.Component; + +import static com.google.common.collect.Sets.union; + +public class RuleTagsCopier extends IssueVisitor { + + private final RuleRepository ruleRepository; + + 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 + Rule rule = ruleRepository.getByKey(issue.ruleKey()); + issue.setTags(union(issue.tags(), rule.getTags())); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUser.java index 1170c2dd6a4..53babcf7bc1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCache.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUser.java @@ -22,10 +22,11 @@ package org.sonar.server.computation.issue; import org.sonar.server.util.cache.MemoryCache; /** - * Cache of dictionary {SCM account -> SQ user login} + * Cache of dictionary {SCM account -> SQ user login}. Kept in memory + * during the execution of Compute Engine. */ -public class ScmAccountCache extends MemoryCache<String,String> { - public ScmAccountCache(ScmAccountCacheLoader loader) { +public class ScmAccountToUser extends MemoryCache<String,String> { + public ScmAccountToUser(ScmAccountToUserLoader loader) { super(loader); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCacheLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUserLoader.java index 2d51c5b5526..6eb42c29ba8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCacheLoader.java +++ b/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 ScmAccountCacheLoader implements CacheLoader<String, String> { +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 ScmAccountCacheLoader(UserIndex index) { - this(index, Loggers.get(ScmAccountCacheLoader.class)); - } - - @VisibleForTesting - ScmAccountCacheLoader(UserIndex index, Logger log) { - this.log = log; + public ScmAccountToUserLoader(UserIndex index) { this.index = index; } @@ -60,12 +54,7 @@ public class ScmAccountCacheLoader 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 ScmAccountCacheLoader 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(); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerBaseInputFactory.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerBaseInputFactory.java new file mode 100644 index 00000000000..8f5feb2573c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerBaseInputFactory.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 java.util.List; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; +import org.sonar.core.issue.tracking.LazyInput; +import org.sonar.core.issue.tracking.LineHashSequence; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.computation.component.Component; +import org.sonar.server.db.DbClient; + +/** + * Factory of {@link Input} of base data for issue tracking. Data are lazy-loaded. + */ +public class TrackerBaseInputFactory { + + private final BaseIssuesLoader baseIssuesLoader; + private final DbClient dbClient; + + public TrackerBaseInputFactory(BaseIssuesLoader baseIssuesLoader, DbClient dbClient) { + this.baseIssuesLoader = baseIssuesLoader; + this.dbClient = dbClient; + } + + public Input<DefaultIssue> create(Component component) { + return new BaseLazyInput(component); + } + + private class BaseLazyInput extends LazyInput<DefaultIssue> { + private final Component component; + + private BaseLazyInput(Component component) { + this.component = component; + } + + @Override + protected LineHashSequence loadLineHashSequence() { + DbSession session = dbClient.openSession(false); + try { + List<String> hashes = dbClient.fileSourceDao().selectLineHashes(session, component.getUuid()); + return new LineHashSequence(hashes); + } finally { + MyBatis.closeQuietly(session); + } + } + + @Override + protected List<DefaultIssue> loadIssues() { + return baseIssuesLoader.loadForComponentUuid(component.getUuid()); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerExecution.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerExecution.java new file mode 100644 index 00000000000..86e0f7c8417 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerExecution.java @@ -0,0 +1,43 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Tracker; +import org.sonar.core.issue.tracking.Tracking; +import org.sonar.server.computation.component.Component; + +public class TrackerExecution { + + private final TrackerBaseInputFactory baseInputFactory; + private final TrackerRawInputFactory rawInputFactory; + private final Tracker<DefaultIssue, DefaultIssue> tracker; + + public TrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, + Tracker<DefaultIssue, DefaultIssue> tracker) { + this.baseInputFactory = baseInputFactory; + this.rawInputFactory = rawInputFactory; + this.tracker = tracker; + } + + public Tracking<DefaultIssue, DefaultIssue> track(Component component) { + return tracker.track(rawInputFactory.create(component), baseInputFactory.create(component)); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java new file mode 100644 index 00000000000..6f7cc4640cf --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.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 com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.util.ArrayList; +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.KeyValueFormat; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Input; +import org.sonar.core.issue.tracking.LazyInput; +import org.sonar.core.issue.tracking.LineHashSequence; +import org.sonar.server.computation.batch.BatchReportReader; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.TreeRootHolder; + +public class TrackerRawInputFactory { + + private final TreeRootHolder treeRootHolder; + private final BatchReportReader reportReader; + + public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader) { + this.treeRootHolder = treeRootHolder; + this.reportReader = reportReader; + } + + public Input<DefaultIssue> create(Component component) { + return new RawLazyInput(component); + } + + private class RawLazyInput extends LazyInput<DefaultIssue> { + private final Component component; + + private RawLazyInput(Component component) { + this.component = component; + } + + @Override + protected LineHashSequence loadLineHashSequence() { + Iterable<String> lines; + if (component.getType() == Component.Type.FILE) { + lines = Lists.newArrayList(reportReader.readFileSource(component.getRef())); + } else { + lines = Collections.emptyList(); + } + return LineHashSequence.createForLines(lines); + } + + @Override + protected List<DefaultIssue> loadIssues() { + List<BatchReport.Issue> reportIssues = reportReader.readComponentIssues(component.getRef()); + List<DefaultIssue> issues = new ArrayList<>(); + if (!reportIssues.isEmpty()) { + // optimization - do not load line hashes if there are no issues + LineHashSequence lineHashSeq = getLineHashSequence(); + for (BatchReport.Issue reportIssue : reportIssues) { + DefaultIssue issue = toIssue(lineHashSeq, reportIssue); + issues.add(issue); + } + } + return issues; + } + + private DefaultIssue toIssue(LineHashSequence lineHashSeq, BatchReport.Issue reportIssue) { + DefaultIssue issue = new DefaultIssue(); + issue.setRuleKey(RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey())); + issue.setResolution(null); + issue.setStatus(Issue.STATUS_OPEN); + issue.setComponentUuid(component.getUuid()); + issue.setComponentKey(component.getKey()); + issue.setProjectUuid(treeRootHolder.getRoot().getUuid()); + issue.setProjectKey(treeRootHolder.getRoot().getKey()); + + if (reportIssue.hasLine()) { + issue.setLine(reportIssue.getLine()); + issue.setChecksum(lineHashSeq.getHashForLine(reportIssue.getLine())); + } else { + issue.setChecksum(""); + } + if (reportIssue.hasMsg()) { + issue.setMessage(reportIssue.getMsg()); + } + if (reportIssue.hasSeverity()) { + issue.setSeverity(reportIssue.getSeverity().name()); + } + if (reportIssue.hasEffortToFix()) { + issue.setEffortToFix(reportIssue.getEffortToFix()); + } + issue.setTags(Sets.newHashSet(reportIssue.getTagList())); + if (reportIssue.hasAttributes()) { + issue.setAttributes(KeyValueFormat.parse(reportIssue.getAttributes())); + } + return issue; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/BatchMeasureToMeasure.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/BatchMeasureToMeasure.java index bdaeae33186..41a4e49cb77 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/BatchMeasureToMeasure.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/BatchMeasureToMeasure.java @@ -22,20 +22,12 @@ package org.sonar.server.computation.measure; import com.google.common.base.Optional; import java.util.Objects; import javax.annotation.Nullable; -import org.sonar.api.rule.RuleKey; import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.core.rule.RuleDto; -import org.sonar.server.computation.issue.RuleCache; import org.sonar.server.computation.metric.Metric; import static com.google.common.base.Optional.of; public class BatchMeasureToMeasure { - private final RuleCache ruleCache; - - public BatchMeasureToMeasure(RuleCache ruleCache) { - this.ruleCache = ruleCache; - } public Optional<Measure> toMeasure(@Nullable BatchReport.Measure batchMeasure, Metric metric) { Objects.requireNonNull(metric); @@ -43,7 +35,7 @@ public class BatchMeasureToMeasure { return Optional.absent(); } - Measure.NewMeasureBuilder builder = createBuilder(batchMeasure); + Measure.NewMeasureBuilder builder = Measure.newMeasureBuilder(); String data = batchMeasure.hasStringValue() ? batchMeasure.getStringValue() : null; switch (metric.getType().getValueType()) { case INT: @@ -65,20 +57,6 @@ public class BatchMeasureToMeasure { } } - private Measure.NewMeasureBuilder createBuilder(BatchReport.Measure batchMeasure) { - if (batchMeasure.hasCharactericId() && batchMeasure.hasRuleKey()) { - throw new IllegalArgumentException("Measure with both characteristicId and ruleKey are not supported"); - } - if (batchMeasure.hasCharactericId()) { - return Measure.newMeasureBuilder().forCharacteristic(batchMeasure.getCharactericId()); - } - if (batchMeasure.hasRuleKey()) { - RuleDto ruleDto = ruleCache.get(RuleKey.parse(batchMeasure.getRuleKey())); - return Measure.newMeasureBuilder().forRule(ruleDto.getId()); - } - return Measure.newMeasureBuilder(); - } - private static Optional<Measure> toIntegerMeasure(Measure.NewMeasureBuilder builder, BatchReport.Measure batchMeasure, @Nullable String data) { if (!batchMeasure.hasIntValue()) { return toNoValueMeasure(builder, batchMeasure); @@ -113,7 +91,7 @@ public class BatchMeasureToMeasure { } return of(setCommonProperties(builder, batchMeasure).create(batchMeasure.getStringValue())); } - + private static Optional<Measure> toLevelMeasure(Measure.NewMeasureBuilder builder, BatchReport.Measure batchMeasure) { if (!batchMeasure.hasStringValue()) { return toNoValueMeasure(builder, batchMeasure); @@ -130,14 +108,7 @@ public class BatchMeasureToMeasure { } private static Measure.NewMeasureBuilder setCommonProperties(Measure.NewMeasureBuilder builder, BatchReport.Measure batchMeasure) { - if (batchMeasure.hasAlertStatus()) { - Optional<Measure.Level> qualityGateStatus = Measure.Level.toLevel(batchMeasure.getAlertStatus()); - if (qualityGateStatus.isPresent()) { - String text = batchMeasure.hasAlertText() ? batchMeasure.getAlertText() : null; - builder.setQualityGateStatus(new QualityGateStatus(qualityGateStatus.get(), text)); - } - } - if (hasAnyVariation(batchMeasure)) { + if (hasAnyVariation(batchMeasure)) { builder.setVariations(createVariations(batchMeasure)); } return builder; @@ -145,20 +116,19 @@ public class BatchMeasureToMeasure { private static boolean hasAnyVariation(BatchReport.Measure batchMeasure) { return batchMeasure.hasVariationValue1() - || batchMeasure.hasVariationValue2() - || batchMeasure.hasVariationValue3() - || batchMeasure.hasVariationValue4() - || batchMeasure.hasVariationValue5(); + || batchMeasure.hasVariationValue2() + || batchMeasure.hasVariationValue3() + || batchMeasure.hasVariationValue4() + || batchMeasure.hasVariationValue5(); } private static MeasureVariations createVariations(BatchReport.Measure batchMeasure) { return new MeasureVariations( - batchMeasure.hasVariationValue1() ? batchMeasure.getVariationValue1() : null, - batchMeasure.hasVariationValue2() ? batchMeasure.getVariationValue2() : null, - batchMeasure.hasVariationValue3() ? batchMeasure.getVariationValue3() : null, - batchMeasure.hasVariationValue4() ? batchMeasure.getVariationValue4() : null, - batchMeasure.hasVariationValue5() ? batchMeasure.getVariationValue5() : null - ); + batchMeasure.hasVariationValue1() ? batchMeasure.getVariationValue1() : null, + batchMeasure.hasVariationValue2() ? batchMeasure.getVariationValue2() : null, + batchMeasure.hasVariationValue3() ? batchMeasure.getVariationValue3() : null, + batchMeasure.hasVariationValue4() ? batchMeasure.getVariationValue4() : null, + batchMeasure.hasVariationValue5() ? batchMeasure.getVariationValue5() : null); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureRepositoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureRepositoryImpl.java index 26a486c58bd..8cdb33bcfb5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureRepositoryImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureRepositoryImpl.java @@ -36,7 +36,6 @@ import org.sonar.core.rule.RuleDto; import org.sonar.server.computation.batch.BatchReportReader; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.debt.Characteristic; -import org.sonar.server.computation.issue.RuleCache; import org.sonar.server.computation.metric.Metric; import org.sonar.server.computation.metric.MetricRepository; import org.sonar.server.db.DbClient; @@ -55,10 +54,10 @@ public class MeasureRepositoryImpl implements MeasureRepository { private final Map<Integer, Map<MeasureKey, Measure>> measures = new HashMap<>(); public MeasureRepositoryImpl(DbClient dbClient, BatchReportReader reportReader, - final MetricRepository metricRepository, final RuleCache ruleCache) { + MetricRepository metricRepository) { this.dbClient = dbClient; this.reportReader = reportReader; - this.batchMeasureToMeasure = new BatchMeasureToMeasure(ruleCache); + this.batchMeasureToMeasure = new BatchMeasureToMeasure(); this.metricRepository = metricRepository; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolder.java index 706d6d86fe7..f901194dce4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolder.java @@ -32,6 +32,8 @@ import org.sonar.api.CoreProperties; */ public interface PeriodsHolder { + int MAX_NUMBER_OF_PERIODS = 5; + /** * Return the list of differential periods, ordered by increasing index. * diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolderImpl.java index 1d6346ad0c1..8c4cb7c8f8f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolderImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/period/PeriodsHolderImpl.java @@ -50,10 +50,10 @@ public class PeriodsHolderImpl implements PeriodsHolder { */ public void setPeriods(Iterable<Period> periods) { requireNonNull(periods, "Periods cannot be null"); - checkArgument(Iterables.size(periods) <= 5, "There can not be more than 5 periods"); + 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"); - Period[] newPeriods = new Period[5]; + Period[] newPeriods = new Period[MAX_NUMBER_OF_PERIODS]; for (Period period : from(periods).filter(CheckNotNull.INSTANCE)) { int arrayIndex = period.getIndex() - 1; checkArgument(newPeriods[arrayIndex] == null, "More than one period has the index " + period.getIndex()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java index f22c71b0d5b..1d7f0e161e5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java @@ -46,17 +46,16 @@ public class ComputationSteps { FeedDebtModelStep.class, - // Read report - ParseReportStep.class, - // load project related stuffs QualityGateLoadingStep.class, FeedPeriodsStep.class, // data computation - ComputeFormulaMeasuresStep.class, + IntegrateIssuesStep.class, CustomMeasuresCopyStep.class, - ComputeIssueMeasuresStep.class, + ComputeFormulaMeasuresStep.class, + + // SQALE measures depend on issues SqaleMeasuresStep.class, NewCoverageMeasuresStep.class, NewCoverageAggregationStep.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeIssueMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeIssueMeasuresStep.java deleted file mode 100644 index f7060ab065b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeIssueMeasuresStep.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.sonar.server.computation.step; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Multiset; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.sonar.api.rule.Severity; -import org.sonar.batch.protocol.output.BatchReport.Issue; -import org.sonar.server.computation.batch.BatchReportReader; -import org.sonar.server.computation.component.Component; -import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; -import org.sonar.server.computation.component.TreeRootHolder; -import org.sonar.server.computation.measure.Measure; -import org.sonar.server.computation.measure.MeasureRepository; -import org.sonar.server.computation.measure.MeasureVariations; -import org.sonar.server.computation.metric.Metric; -import org.sonar.server.computation.metric.MetricRepository; -import org.sonar.server.computation.period.Period; -import org.sonar.server.computation.period.PeriodsHolder; - -import static com.google.common.collect.FluentIterable.from; -import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; -import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; -import static org.sonar.api.issue.Issue.STATUS_OPEN; -import static org.sonar.api.issue.Issue.STATUS_REOPENED; -import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; -import static org.sonar.server.computation.component.Component.Type.FILE; -import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; - -/** - * Computes metrics related to number of issues. - * - Total number of issues and new issues - * - Number of issues by severity, and new issues by severity - * - Number of false-positives - */ -public class ComputeIssueMeasuresStep implements ComputationStep { - - private final BatchReportReader reportReader; - private final TreeRootHolder treeRootHolder; - private final PeriodsHolder periodsHolder; - private final MeasureRepository measureRepository; - private final MetricRepository metricRepository; - - private final static Map<String, String> SEVERITY_METRIC_KEY_BY_SEVERITY = ImmutableMap.of( - Severity.BLOCKER, BLOCKER_VIOLATIONS_KEY, - Severity.CRITICAL, CRITICAL_VIOLATIONS_KEY, - Severity.MAJOR, MAJOR_VIOLATIONS_KEY, - Severity.MINOR, MINOR_VIOLATIONS_KEY, - Severity.INFO, INFO_VIOLATIONS_KEY - ); - - private final static Map<String, String> NEW_SEVERITY_METRIC_KEY_BY_SEVERITY = ImmutableMap.of( - Severity.BLOCKER, NEW_BLOCKER_VIOLATIONS_KEY, - Severity.CRITICAL, NEW_CRITICAL_VIOLATIONS_KEY, - Severity.MAJOR, NEW_MAJOR_VIOLATIONS_KEY, - Severity.MINOR, NEW_MINOR_VIOLATIONS_KEY, - Severity.INFO, NEW_INFO_VIOLATIONS_KEY - ); - - public ComputeIssueMeasuresStep(PeriodsHolder periodsHolder, BatchReportReader reportReader, TreeRootHolder treeRootHolder, MeasureRepository measureRepository, - MetricRepository metricRepository) { - this.periodsHolder = periodsHolder; - this.reportReader = reportReader; - this.treeRootHolder = treeRootHolder; - this.measureRepository = measureRepository; - this.metricRepository = metricRepository; - } - - @Override - public void execute() { - new DepthTraversalTypeAwareVisitor(FILE, POST_ORDER) { - @Override - public void visitAny(Component component) { - List<Issue> issues = reportReader.readComponentIssues(component.getRef()); - List<Issue> unresolvedIssues = from(issues) - .filter(UnresolvedIssue.INSTANCE) - .toList(); - CountIssues countIssues = new CountIssues(unresolvedIssues); - addIssuesMeasures(component, unresolvedIssues); - addIssuesStatusMeasures(component, countIssues); - addIssuesSeverityMeasures(component, countIssues); - addFalsePositiveMeasures(component, issues); - } - }.visit(treeRootHolder.getRoot()); - } - - private void addIssuesMeasures(Component component, List<Issue> unresolvedIssues) { - addMeasure(component, VIOLATIONS_KEY, unresolvedIssues.size()); - addNewMeasures(component, NEW_VIOLATIONS_KEY, unresolvedIssues); - } - - private void addIssuesStatusMeasures(Component component, CountIssues countIssues) { - addMeasure(component, OPEN_ISSUES_KEY, countIssues.openIssues); - addMeasure(component, REOPENED_ISSUES_KEY, countIssues.reopenedIssues); - addMeasure(component, CONFIRMED_ISSUES_KEY, countIssues.confirmedIssues); - } - - private void addIssuesSeverityMeasures(Component component, CountIssues countIssues) { - for (Map.Entry<String, String> entry : SEVERITY_METRIC_KEY_BY_SEVERITY.entrySet()) { - String severity = entry.getKey(); - String metricKey = entry.getValue(); - addMeasure(component, metricKey, countIssues.severityBag.count(severity)); - } - for (Map.Entry<String, String> entry : NEW_SEVERITY_METRIC_KEY_BY_SEVERITY.entrySet()) { - String severity = entry.getKey(); - String metricKey = entry.getValue(); - addNewMeasures(component, metricKey, countIssues.issuesPerSeverity.get(severity)); - } - } - - private void addFalsePositiveMeasures(Component component, List<Issue> issues) { - addMeasure(component, FALSE_POSITIVE_ISSUES_KEY, from(issues).filter(FalsePositiveIssue.INSTANCE).size()); - } - - private void addNewMeasures(Component component, String metricKey, List<Issue> issues) { - if (periodsHolder.getPeriods().isEmpty()) { - return; - } - Metric metric = metricRepository.getByKey(metricKey); - Double[] periodValues = new Double[]{null, null, null, null, null}; - for (Period period : periodsHolder.getPeriods()) { - Collection<Measure> childrenMeasures = getChildrenMeasures(component, metric); - double periodValue = sumIssuesOnPeriod(issues, period.getSnapshotDate()) + sumChildrenMeasuresOnPeriod(childrenMeasures, period.getIndex()); - periodValues[period.getIndex() - 1] = periodValue; - } - measureRepository.add(component, metric, Measure.newMeasureBuilder() - .setVariations(new MeasureVariations(periodValues)) - .createNoValue()); - } - - private void addMeasure(Component component, String metricKey, int value) { - Metric metric = metricRepository.getByKey(metricKey); - int totalValue = value + sumChildrenMeasures(getChildrenMeasures(component, metric)); - measureRepository.add(component, metric, Measure.newMeasureBuilder().create(totalValue, null)); - } - - private Collection<Measure> getChildrenMeasures(Component component, final Metric metric) { - return from(component.getChildren()).transform(new ComponentChildrenMeasures(metric)).toList(); - } - - private static int sumChildrenMeasures(Collection<Measure> measures) { - SumMeasure sumMeasures = new SumMeasure(); - from(measures).filter(sumMeasures).size(); - return sumMeasures.getSum(); - } - - private static double sumChildrenMeasuresOnPeriod(Collection<Measure> measures, int periodIndex) { - SumVariationMeasure sumMeasures = new SumVariationMeasure(periodIndex); - from(measures).filter(sumMeasures).size(); - return sumMeasures.getSum(); - } - - private static int sumIssuesOnPeriod(Collection<Issue> issues, long periodDate) { - // Add one second to not take into account issues created during current analysis - long datePlusOneSecond = periodDate + 1000L; - SumIssueAfterDate sumIssues = new SumIssueAfterDate(datePlusOneSecond); - from(issues).filter(sumIssues).toList(); - return sumIssues.getSum(); - } - - private static class CountIssues { - int openIssues = 0; - int reopenedIssues = 0; - int confirmedIssues = 0; - Multiset<String> severityBag = HashMultiset.create(); - ListMultimap<String, Issue> issuesPerSeverity = ArrayListMultimap.create(); - - public CountIssues(Iterable<Issue> issues) { - for (Issue issue : issues) { - countByStatus(issue.getStatus()); - severityBag.add(issue.getSeverity().name()); - issuesPerSeverity.put(issue.getSeverity().name(), issue); - } - } - - private void countByStatus(String status) { - switch (status) { - case STATUS_OPEN: - openIssues++; - break; - case STATUS_REOPENED: - reopenedIssues++; - break; - case STATUS_CONFIRMED: - confirmedIssues++; - break; - default: - // Other statuses are ignored - } - } - } - - private static class SumMeasure implements Predicate<Measure> { - - private int sum = 0; - - @Override - public boolean apply(@Nonnull Measure input) { - sum += input.getIntValue(); - return true; - } - - public int getSum() { - return sum; - } - } - - private static class SumVariationMeasure implements Predicate<Measure> { - - private final int periodIndex; - private double sum = 0d; - - public SumVariationMeasure(int periodIndex) { - this.periodIndex = periodIndex; - } - - @Override - public boolean apply(@Nonnull Measure input) { - if (input.hasVariations() && input.getVariations().hasVariation(periodIndex)) { - sum += input.getVariations().getVariation(periodIndex); - } - return true; - } - - public double getSum() { - return sum; - } - } - - private static class SumIssueAfterDate implements Predicate<Issue> { - - private final long date; - private int sum = 0; - - public SumIssueAfterDate(long date) { - this.date = date; - } - - @Override - public boolean apply(@Nonnull Issue issue) { - if (isAfter(issue, date)) { - sum++; - } - return true; - } - - public int getSum() { - return sum; - } - - private static boolean isAfter(Issue issue, long date) { - // TODO should we truncate the date to the second as it was done in batch ? - return issue.getCreationDate() > date; - } - } - - private enum FalsePositiveIssue implements Predicate<Issue> { - INSTANCE; - - @Override - public boolean apply(@Nonnull Issue issue) { - return issue.hasResolution() && issue.getResolution().equals(RESOLUTION_FALSE_POSITIVE); - } - } - - private enum UnresolvedIssue implements Predicate<Issue> { - INSTANCE; - - @Override - public boolean apply(@Nonnull Issue issue) { - return !issue.hasResolution(); - } - } - - private class ComponentChildrenMeasures implements Function<Component, Measure> { - private final Metric metric; - - public ComponentChildrenMeasures(Metric metric) { - this.metric = metric; - } - - @Nullable - @Override - public Measure apply(@Nonnull Component input) { - Optional<Measure> childMeasure = measureRepository.getRawMeasure(input, metric); - if (childMeasure.isPresent()) { - return childMeasure.get(); - } - return null; - } - } - - @Override - public String getDescription() { - return "Compute measures on issues"; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedDebtModelStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedDebtModelStep.java index ca491bc53c7..80e7899eb5e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedDebtModelStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedDebtModelStep.java @@ -22,8 +22,6 @@ package org.sonar.server.computation.step; import com.google.common.base.Function; import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.FluentIterable; import java.util.Collection; import java.util.List; import java.util.Map; @@ -35,6 +33,9 @@ import org.sonar.server.computation.debt.Characteristic; import org.sonar.server.computation.debt.MutableDebtModelHolder; import org.sonar.server.db.DbClient; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.FluentIterable.from; + /** * Populates the {@link org.sonar.server.computation.debt.DebtModelHolder} */ @@ -60,23 +61,23 @@ public class FeedDebtModelStep implements ComputationStep { private void feedDebtModel(DbSession session) { List<CharacteristicDto> characteristicDtos = dbClient.debtCharacteristicDao().selectEnabledCharacteristics(session); - Map<Integer, CharacteristicDto> rootCharacteristicsById = FluentIterable.from(characteristicDtos) + Map<Integer, CharacteristicDto> rootCharacteristicsById = from(characteristicDtos) .filter(IsRootPredicate.INSTANCE) .uniqueIndex(CharacteristicDtoToId.INSTANCE); - for (Map.Entry<Integer, Collection<CharacteristicDto>> entry : FluentIterable.from(characteristicDtos) - .filter(Predicates.not(IsRootPredicate.INSTANCE)) + for (Map.Entry<Integer, Collection<CharacteristicDto>> entry : from(characteristicDtos) + .filter(not(IsRootPredicate.INSTANCE)) .index(CharacteristicDtoToParentId.INSTANCE) .asMap().entrySet()) { mutableDebtModelHolder.addCharacteristics( toCharacteristic(rootCharacteristicsById.get(entry.getKey())), - FluentIterable.from(entry.getValue()).transform(CharacteristicDtoToCharacteristic.INSTANCE) + from(entry.getValue()).transform(CharacteristicDtoToCharacteristic.INSTANCE) ); } } private static Characteristic toCharacteristic(CharacteristicDto dto) { - return new Characteristic(dto.getId(), dto.getKey()); + return new Characteristic(dto.getId(), dto.getKey(), dto.getParentId()); } private enum CharacteristicDtoToId implements Function<CharacteristicDto, Integer> { @@ -84,8 +85,8 @@ public class FeedDebtModelStep implements ComputationStep { @Nullable @Override - public Integer apply(@Nonnull CharacteristicDto characteristicDto) { - return characteristicDto.getId(); + public Integer apply(@Nonnull CharacteristicDto dto) { + return dto.getId(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java new file mode 100644 index 00000000000..c1c234d32e2 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java @@ -0,0 +1,153 @@ +/* + * 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 com.google.common.collect.Sets; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.sonar.api.utils.log.Loggers; +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.DepthTraversalTypeAwareVisitor; +import org.sonar.server.computation.component.TreeRootHolder; +import org.sonar.server.computation.issue.BaseIssuesLoader; +import org.sonar.server.computation.issue.IssueCache; +import org.sonar.server.computation.issue.IssueLifecycle; +import org.sonar.server.computation.issue.IssueVisitors; +import org.sonar.server.computation.issue.TrackerExecution; +import org.sonar.server.util.cache.DiskCache; + +import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; + +public class IntegrateIssuesStep implements ComputationStep { + + private final TreeRootHolder treeRootHolder; + private final TrackerExecution tracker; + private final IssueCache issueCache; + private final BaseIssuesLoader baseIssuesLoader; + private final IssueLifecycle issueLifecycle; + private final IssueVisitors issueVisitors; + + public IntegrateIssuesStep(TreeRootHolder treeRootHolder, TrackerExecution tracker, IssueCache issueCache, + BaseIssuesLoader baseIssuesLoader, IssueLifecycle issueLifecycle, + IssueVisitors issueVisitors) { + this.treeRootHolder = treeRootHolder; + this.tracker = tracker; + this.issueCache = issueCache; + this.baseIssuesLoader = baseIssuesLoader; + this.issueLifecycle = issueLifecycle; + this.issueVisitors = issueVisitors; + } + + @Override + public void execute() { + // all the components that had issues before this analysis + final Set<String> unprocessedComponentUuids = Sets.newHashSet(baseIssuesLoader.loadUuidsOfComponentsWithOpenIssues()); + + new DepthTraversalTypeAwareVisitor(Component.Type.FILE, POST_ORDER) { + @Override + public void visitAny(Component component) { + processIssues(component); + unprocessedComponentUuids.remove(component.getUuid()); + } + }.visit(treeRootHolder.getRoot()); + + closeIssuesForDeletedComponentUuids(unprocessedComponentUuids); + } + + private void processIssues(Component component) { + Tracking<DefaultIssue, DefaultIssue> tracking = tracker.track(component); + DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender(); + try { + issueVisitors.beforeComponent(component, tracking); + fillNewOpenIssues(component, tracking, cacheAppender); + fillExistingOpenIssues(component, tracking, cacheAppender); + closeUnmatchedBaseIssues(component, tracking, cacheAppender); + issueVisitors.afterComponent(component); + } finally { + cacheAppender.close(); + } + } + + private void fillNewOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { + Set<DefaultIssue> issues = tracking.getUnmatchedRaws(); + for (DefaultIssue issue : issues) { + issueLifecycle.initNewOpenIssue(issue); + process(component, issue, cacheAppender); + } + } + + private void fillExistingOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { + for (Map.Entry<DefaultIssue, DefaultIssue> entry : tracking.getMatchedRaws().entrySet()) { + DefaultIssue raw = entry.getKey(); + DefaultIssue base = entry.getValue(); + issueLifecycle.mergeExistingOpenIssue(raw, base); + process(component, raw, cacheAppender); + } + for (Map.Entry<Integer, DefaultIssue> entry : tracking.getOpenManualIssuesByLine().entries()) { + Integer line = entry.getKey(); + DefaultIssue manualIssue = entry.getValue(); + issueLifecycle.moveOpenManualIssue(manualIssue, line); + process(component, manualIssue, cacheAppender); + } + } + + private void closeUnmatchedBaseIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { + 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); + // TODO manual issues -> was updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);. Is it a problem ? + process(component, issue, cacheAppender); + } + } + + private void process(Component component, DefaultIssue issue, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { + issueLifecycle.doAutomaticTransition(issue); + issueVisitors.onIssue(component, issue); + cacheAppender.append(issue); + } + + private void closeIssuesForDeletedComponentUuids(Set<String> deletedComponentUuids) { + DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender(); + try { + for (String deletedComponentUuid : deletedComponentUuids) { + List<DefaultIssue> issues = baseIssuesLoader.loadForComponentUuid(deletedComponentUuid); + for (DefaultIssue issue : issues) { + issue.setBeingClosed(true); + // TODO should be renamed + issue.setOnDisabledRule(false); + issueLifecycle.doAutomaticTransition(issue); + cacheAppender.append(issue); + } + } + } finally { + cacheAppender.close(); + } + } + + @Override + public String getDescription() { + return "Integrate issues"; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ParseReportStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ParseReportStep.java deleted file mode 100644 index 8170712bc6b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ParseReportStep.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.sonar.server.computation.step; - -import java.util.List; -import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.server.computation.batch.BatchReportReader; -import org.sonar.server.computation.component.Component; -import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; -import org.sonar.server.computation.component.TreeRootHolder; -import org.sonar.server.computation.issue.IssueComputation; - -public class ParseReportStep implements ComputationStep { - - private final IssueComputation issueComputation; - private final BatchReportReader reportReader; - private final TreeRootHolder treeRootHolder; - - public ParseReportStep(IssueComputation issueComputation, BatchReportReader reportReader, TreeRootHolder treeRootHolder) { - this.issueComputation = issueComputation; - this.reportReader = reportReader; - this.treeRootHolder = treeRootHolder; - } - - @Override - public void execute() { - IssueDepthTraversalTypeAwareVisitor visitor = new IssueDepthTraversalTypeAwareVisitor(); - visitor.visit(treeRootHolder.getRoot()); - processDeletedComponents(visitor); - issueComputation.afterReportProcessing(); - } - - private void processDeletedComponents(IssueDepthTraversalTypeAwareVisitor visitor) { - int deletedComponentsCount = reportReader.readMetadata().getDeletedComponentsCount(); - for (int componentRef = 1; componentRef <= deletedComponentsCount; componentRef++) { - BatchReport.Issues issues = reportReader.readDeletedComponentIssues(componentRef); - issueComputation.processComponentIssues(issues.getIssueList(), issues.getComponentUuid(), null, visitor.projectKey, visitor.projectUuid); - } - } - - @Override - public String getDescription() { - return "Digest analysis report"; - } - - private class IssueDepthTraversalTypeAwareVisitor extends DepthTraversalTypeAwareVisitor { - private String projectKey; - private String projectUuid; - - public IssueDepthTraversalTypeAwareVisitor() { - super(Component.Type.FILE, Order.PRE_ORDER); - } - - @Override - public void visitProject(Component tree) { - projectKey = tree.getKey(); - projectUuid = tree.getUuid(); - executeForComponent(tree); - } - - @Override - public void visitModule(Component module) { - executeForComponent(module); - } - - @Override - public void visitDirectory(Component directory) { - executeForComponent(directory); - } - - @Override - public void visitFile(Component file) { - executeForComponent(file); - } - - private void executeForComponent(Component component) { - int componentRef = component.getRef(); - List<BatchReport.Issue> issues = reportReader.readComponentIssues(componentRef); - issueComputation.processComponentIssues(issues, component.getUuid(), componentRef, projectKey, projectUuid); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java index df107b009c2..6ffda565d47 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java @@ -19,12 +19,11 @@ */ package org.sonar.server.computation.step; -import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueComment; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.FieldDiffs; import org.sonar.api.utils.System2; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.FieldDiffs; import org.sonar.core.issue.db.IssueChangeDto; import org.sonar.core.issue.db.IssueChangeMapper; import org.sonar.core.issue.db.IssueDto; @@ -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,21 +65,17 @@ public class PersistIssuesStep implements ComputationStep { DefaultIssue issue = issues.next(); boolean saved = false; if (issue.isNew()) { - Integer ruleId = ruleCache.get(issue.ruleKey()).getId(); - mapper.insert(IssueDto.toDtoForComputationInsert(issue, ruleId, system2.now())); + Integer ruleId = ruleRepository.getByKey(issue.ruleKey()).getId(); + IssueDto dto = IssueDto.toDtoForComputationInsert(issue, ruleId, system2.now()); + mapper.insert(dto); saved = true; } else if (issue.isChanged()) { IssueDto dto = IssueDto.toDtoForUpdate(issue, system2.now()); - if (Issue.STATUS_CLOSED.equals(issue.status()) || issue.selectedAt() == null) { - // Issue is closed by scan or changed by end-user - mapper.update(dto); - } else { - int updateCount = mapper.updateIfBeforeSelectedDate(dto); - if (updateCount == 0) { - // End-user and scan changed the issue at the same time. - // See https://jira.sonarsource.com/browse/SONAR-4309 - conflictResolver.resolve(issue, mapper); - } + int updateCount = mapper.updateIfBeforeSelectedDate(dto); + if (updateCount == 0) { + // 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; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java index 5129e8a4959..f55a33ac0ad 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java @@ -23,12 +23,12 @@ import com.google.common.collect.ImmutableSet; import java.util.Date; import java.util.Map; import java.util.Set; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; 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); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java index aa1542a659d..73475e26ed8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java @@ -26,7 +26,7 @@ import com.google.common.collect.Sets; import org.sonar.api.server.ServerSide; import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.IsUnResolved; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.server.rule.RuleTagFormat; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.user.UserSession; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java b/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java index 40f6d03988b..57c4c21fd76 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java @@ -26,7 +26,7 @@ import com.google.common.collect.ImmutableList; import org.sonar.api.server.ServerSide; import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.Condition; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.server.user.UserSession; import java.util.Collection; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java index b5660789cad..f24fa6159da 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java @@ -31,8 +31,8 @@ import org.sonar.api.issue.Issue; import org.sonar.api.issue.action.Action; import org.sonar.api.issue.action.Actions; import org.sonar.api.issue.action.Function; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; import org.sonar.core.issue.db.IssueStorage; import org.sonar.core.persistence.DbSession; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java index 0c15344639a..41e8b91708c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java @@ -24,7 +24,7 @@ import com.google.common.base.Strings; import org.sonar.api.server.ServerSide; import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.IsUnResolved; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.user.User; import org.sonar.api.user.UserFinder; import org.sonar.core.issue.IssueUpdater; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java index 83356b5a1c1..d37a746aff9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java @@ -24,7 +24,7 @@ import com.google.common.base.Strings; import java.util.Collection; import java.util.Map; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.server.ServerSide; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.user.UserSession; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index 9f01f119669..fc42a798539 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -45,9 +45,9 @@ import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueComment; import org.sonar.api.issue.action.Action; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.rule.RuleKey; import org.sonar.api.server.ServerSide; import org.sonar.api.user.User; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java index 53c4539b16f..65ba2386cf4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java @@ -35,8 +35,8 @@ import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.Rule; import org.sonar.api.utils.log.Logger; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelog.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelog.java index cd089010083..3d8ee6d3b66 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelog.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelog.java @@ -20,7 +20,7 @@ package org.sonar.server.issue; import com.google.common.collect.Maps; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.user.User; import javax.annotation.CheckForNull; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogFormatter.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogFormatter.java index aebc35d8529..0dc55507993 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogFormatter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogFormatter.java @@ -25,7 +25,7 @@ import java.util.Locale; import java.util.Map; import org.sonar.api.server.ServerSide; import org.sonar.api.i18n.I18n; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.core.issue.IssueUpdater; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogService.java index 62c85672fd5..2009dfb10b8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogService.java @@ -23,7 +23,7 @@ import java.util.Collection; import java.util.List; import org.sonar.api.server.ServerSide; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.user.User; import org.sonar.api.user.UserFinder; import org.sonar.core.issue.db.IssueChangeDao; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java index 4a1bb7c307d..8b1da808880 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java @@ -24,9 +24,9 @@ import com.google.common.base.Strings; import org.apache.commons.lang.StringUtils; import org.sonar.api.server.ServerSide; import org.sonar.api.issue.IssueComment; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.api.utils.System2; import org.sonar.core.issue.IssueUpdater; import org.sonar.core.issue.db.IssueChangeDto; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java index 134a9414a4b..15c65e354cd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java +++ b/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; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java index 0f342ce585a..35e97cf20da 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java @@ -24,8 +24,8 @@ import com.google.common.base.Strings; import org.apache.commons.lang.StringUtils; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.server.notification.NotificationManager; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java index 690dca8de77..bf2993a12e2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java @@ -25,7 +25,7 @@ import org.sonar.api.server.ServerSide; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.IsUnResolved; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.user.UserSession; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueStorage.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueStorage.java index eabaeb44089..74c9aedce06 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueStorage.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueStorage.java @@ -20,7 +20,7 @@ package org.sonar.server.issue; import org.sonar.api.server.ServerSide; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.rules.RuleFinder; import org.sonar.core.component.ComponentDto; import org.sonar.core.issue.db.IssueDto; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java index 174f943f7cd..6c75b9c1f01 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java @@ -26,7 +26,7 @@ import java.util.Map; import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.Condition; import org.sonar.api.issue.condition.IsUnResolved; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.server.ServerSide; import org.sonar.api.web.UserRole; import org.sonar.core.issue.IssueUpdater; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java index 6683c79382d..1f4c4b283ed 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java @@ -27,7 +27,7 @@ import java.util.Collection; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.server.ServerSide; import org.sonar.core.issue.workflow.IssueWorkflow; import org.sonar.core.issue.workflow.Transition; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/actionplan/ActionPlanService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/actionplan/ActionPlanService.java index 7d9b9259753..a2dc02852a2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/actionplan/ActionPlanService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/actionplan/ActionPlanService.java @@ -28,8 +28,8 @@ import java.util.Date; import java.util.List; import javax.annotation.CheckForNull; import org.sonar.api.issue.ActionPlan; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.api.server.ServerSide; import org.sonar.api.web.UserRole; import org.sonar.core.issue.ActionPlanDeadlineComparator; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java b/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java index 958135fb50d..348e9464d6a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java @@ -19,7 +19,9 @@ */ package org.sonar.server.issue.db; -import org.apache.ibatis.session.ResultHandler; +import java.util.List; +import java.util.Set; +import javax.annotation.CheckForNull; import org.sonar.core.issue.db.IssueDto; import org.sonar.core.issue.db.IssueMapper; import org.sonar.core.persistence.DaoComponent; @@ -27,9 +29,6 @@ import org.sonar.core.persistence.DbSession; import org.sonar.core.persistence.MyBatis; import org.sonar.server.exceptions.NotFoundException; -import javax.annotation.CheckForNull; -import java.util.List; - public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoComponent { public IssueDao(MyBatis mybatis) { @@ -57,12 +56,8 @@ public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoCom return mapper(session).selectByKeys(keys); } - public void selectNonClosedIssuesByModuleUuid(DbSession session, String moduleUuid, ResultHandler handler) { - session.select("org.sonar.core.issue.db.IssueMapper.selectNonClosedIssuesByModuleUuid", moduleUuid, handler); - } - - public void selectNonClosedIssuesByProjectUuid(DbSession session, String projectUuid, ResultHandler handler) { - session.select("org.sonar.core.issue.db.IssueMapper.selectNonClosedIssuesByProjectUuid", projectUuid, handler); + public Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(DbSession session, String projectUuid) { + return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid); } public void insert(DbSession session, IssueDto dto) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java index 15b0d435f66..91e0ae311b1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java @@ -21,8 +21,8 @@ package org.sonar.server.issue.notification; import com.google.common.base.Strings; import org.sonar.api.component.Component; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.notifications.Notification; import javax.annotation.CheckForNull; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java index 8e6da6672af..11c94a2cce0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java @@ -35,13 +35,13 @@ import javax.annotation.Nullable; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueComment; -import org.sonar.api.issue.internal.DefaultIssueComment; import org.sonar.api.user.User; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.component.ComponentDto; +import org.sonar.core.issue.DefaultIssueComment; import org.sonar.markdown.Markdown; import org.sonar.server.user.UserSession; import org.sonar.server.ws.JsonWriterUtils; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 13860cdbaba..fa78cb3f86a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -38,7 +38,6 @@ import org.apache.commons.lang.BooleanUtils; import org.sonar.api.i18n.I18n; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssueComment; import org.sonar.api.resources.Language; import org.sonar.api.resources.Languages; import org.sonar.api.rule.RuleKey; @@ -52,6 +51,7 @@ import org.sonar.api.user.UserFinder; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.component.ComponentDto; +import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.persistence.DbSession; import org.sonar.server.component.ws.ComponentJsonWriter; import org.sonar.server.db.DbClient; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ShowAction.java index 7ab69a45769..aa13668a4b6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ShowAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ShowAction.java @@ -29,8 +29,8 @@ import org.sonar.api.i18n.I18n; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueComment; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.server.debt.DebtCharacteristic; import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic; import org.sonar.api.server.ws.Request; diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/db/FileSourceDao.java b/server/sonar-server/src/main/java/org/sonar/server/source/db/FileSourceDao.java index e17ae11c323..c1d8f0e6002 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/db/FileSourceDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/db/FileSourceDao.java @@ -21,11 +21,13 @@ package org.sonar.server.source.db; import com.google.common.base.Function; +import com.google.common.base.Splitter; import java.io.Reader; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import javax.annotation.CheckForNull; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.io.IOUtils; @@ -40,6 +42,7 @@ import org.sonar.core.source.db.FileSourceMapper; @ServerSide public class FileSourceDao implements DaoComponent { + private static final Splitter END_OF_LINE_SPLITTER = Splitter.on('\n'); private final MyBatis mybatis; public FileSourceDao(MyBatis myBatis) { @@ -66,6 +69,27 @@ public class FileSourceDao implements DaoComponent { } } + @CheckForNull + public List<String> selectLineHashes(DbSession dbSession, String fileUuid) { + Connection connection = dbSession.getConnection(); + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + pstmt = connection.prepareStatement("SELECT line_hashes FROM file_sources WHERE file_uuid=? AND data_type=?"); + pstmt.setString(1, fileUuid); + pstmt.setString(2, Type.SOURCE); + rs = pstmt.executeQuery(); + if (rs.next()) { + return END_OF_LINE_SPLITTER.splitToList(rs.getString(1)); + } + return null; + } catch (SQLException e) { + throw new IllegalStateException("Fail to read FILE_SOURCES.LINE_HASHES of file " + fileUuid, e); + } finally { + DbUtils.closeQuietly(connection, pstmt, rs); + } + } + public <T> void readLineHashesStream(DbSession dbSession, String fileUuid, Function<Reader, T> function) { Connection connection = dbSession.getConnection(); PreparedStatement pstmt = null; diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderImplTest.java index 8286bcfc9fd..f09ed4bf395 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderImplTest.java @@ -42,7 +42,7 @@ public class BatchReportReaderImplTest { private static final BatchReport.Measure MEASURE = BatchReport.Measure.newBuilder().build(); private static final BatchReport.Component COMPONENT = BatchReport.Component.newBuilder().setRef(COMPONENT_REF).build(); private static final BatchReport.Issue ISSUE = BatchReport.Issue.newBuilder().build(); - private static final BatchReport.Issues ISSUES = BatchReport.Issues.newBuilder().setComponentRef(COMPONENT_REF).setComponentUuid(COMPONENT_UUID).addIssue(ISSUE).build(); + private static final BatchReport.Issues ISSUES = BatchReport.Issues.newBuilder().setComponentRef(COMPONENT_REF).addIssue(ISSUE).build(); private static final BatchReport.Duplication DUPLICATION = BatchReport.Duplication.newBuilder().build(); private static final BatchReport.Symbols.Symbol SYMBOL = BatchReport.Symbols.Symbol.newBuilder().build(); private static final BatchReport.SyntaxHighlighting SYNTAX_HIGHLIGHTING_1 = BatchReport.SyntaxHighlighting.newBuilder().build(); @@ -166,25 +166,6 @@ public class BatchReportReaderImplTest { assertThat(underTest.readComponentIssues(COMPONENT_REF)).isNotSameAs(underTest.readComponentIssues(COMPONENT_REF)); } - @Test(expected = IllegalStateException.class) - public void readDeletedComponentIssues_throws_ISE_if_file_does_not_exist() { - underTest.readDeletedComponentIssues(COMPONENT_REF); - } - - @Test - public void verify_readDeletedComponentIssues_returns_Issues() { - writer.writeDeletedComponentIssues(COMPONENT_REF, COMPONENT_UUID, of(ISSUE)); - - assertThat(underTest.readDeletedComponentIssues(COMPONENT_REF)).isEqualTo(ISSUES); - } - - @Test - public void readDeletedComponentIssues_it_not_cached() { - writer.writeDeletedComponentIssues(COMPONENT_REF, COMPONENT_UUID, of(ISSUE)); - - assertThat(underTest.readDeletedComponentIssues(COMPONENT_REF)).isNotSameAs(underTest.readDeletedComponentIssues(COMPONENT_REF)); - } - @Test public void readComponentDuplications_returns_empty_list_if_file_does_not_exist() { assertThat(underTest.readComponentDuplications(COMPONENT_REF)).isEmpty(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderRule.java index 6a6baba1ed7..77a62c5610f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderRule.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderRule.java @@ -39,7 +39,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { private Map<Integer, BatchReport.Changesets> changesets = new HashMap<>(); private Map<Integer, BatchReport.Component> components = new HashMap<>(); private Map<Integer, List<BatchReport.Issue>> issues = new HashMap<>(); - private Map<Integer, BatchReport.Issues> deletedIssues = new HashMap<>(); private Map<Integer, List<BatchReport.Duplication>> duplications = new HashMap<>(); private Map<Integer, List<BatchReport.Symbols.Symbol>> symbols = new HashMap<>(); private Map<Integer, List<BatchReport.SyntaxHighlighting>> syntaxHighlightings = new HashMap<>(); @@ -55,8 +54,7 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { public void evaluate() throws Throwable { try { statement.evaluate(); - } - finally { + } finally { clear(); } } @@ -69,7 +67,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { this.changesets.clear(); this.components.clear(); this.issues.clear(); - this.deletedIssues.clear(); this.duplications.clear(); this.symbols.clear(); this.syntaxHighlightings.clear(); @@ -133,19 +130,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { } @Override - public BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef) { - BatchReport.Issues issues = this.deletedIssues.get(deletedComponentRef); - if (issues == null) { - throw new IllegalStateException("Unable to issues for deleted component #" + deletedComponentRef); - } - return issues; - } - - public void putDeletedIssues(int componentRef, BatchReport.Issues issues) { - this.deletedIssues.put(componentRef, issues); - } - - @Override public List<BatchReport.Duplication> readComponentDuplications(int componentRef) { return nonNull(this.duplications.get(componentRef)); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/debt/CharacteristicTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/debt/CharacteristicTest.java index 1499001ec1d..c1deba15921 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/debt/CharacteristicTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/debt/CharacteristicTest.java @@ -33,14 +33,15 @@ public class CharacteristicTest { @Test public void test_getter_and_setter() throws Exception { - Characteristic characteristic = new Characteristic(1, "PORTABILITY"); + Characteristic characteristic = new Characteristic(1, "PORTABILITY", null); assertThat(characteristic.getId()).isEqualTo(1); assertThat(characteristic.getKey()).isEqualTo("PORTABILITY"); + assertThat(characteristic.getParentId()).isNull(); } @Test public void test_to_string() throws Exception { - assertThat(new Characteristic(1, "PORTABILITY").toString()).isEqualTo("Characteristic{id=1, key='PORTABILITY'}"); + assertThat(new Characteristic(1, "PORTABILITY", null).toString()).isEqualTo("Characteristic{id=1, key='PORTABILITY', parentId=null}"); } @Test @@ -48,6 +49,6 @@ public class CharacteristicTest { thrown.expect(NullPointerException.class); thrown.expectMessage("key cannot be null"); - new Characteristic(1, null); + new Characteristic(1, null, null); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/debt/DebtModelHolderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/debt/DebtModelHolderImplTest.java index 49917ee013f..09556a13363 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/debt/DebtModelHolderImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/debt/DebtModelHolderImplTest.java @@ -17,7 +17,6 @@ * 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; @@ -34,22 +33,23 @@ public class DebtModelHolderImplTest { @Rule public ExpectedException thrown = ExpectedException.none(); - private static final Characteristic PORTABILITY = new Characteristic(1, "PORTABILITY"); - private static final Characteristic COMPILER_RELATED_PORTABILITY = new Characteristic(2, "COMPILER_RELATED_PORTABILITY"); - private static final Characteristic HARDWARE_RELATED_PORTABILITY = new Characteristic(3, "HARDWARE_RELATED_PORTABILITY"); + 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"); - private static final Characteristic READABILITY = new Characteristic(5, "READABILITY"); + 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 { + 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.findRootCharacteristics()).hasSize(2); - assertThat(sut.findSubCharacteristicsByRootKey("PORTABILITY")).hasSize(2); + 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 @@ -77,51 +77,26 @@ public class DebtModelHolderImplTest { } @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); + assertThat(sut.getRootCharacteristics()).hasSize(2); } @Test - public void get_root_characteristics_throws_ISE_when_not_initialized() throws Exception { + public void getCharacteristicById_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(); + sut.getCharacteristicById(1); } @Test - public void get_sub_characteristics_by_root_key_throws_a_ISE_when_not_initialized() throws Exception { + public void getRootCharacteristics_throws_ISE_when_not_initialized() throws Exception { thrown.expect(IllegalStateException.class); thrown.expectMessage("Characteristics have not been initialized yet"); - sut.findSubCharacteristicsByRootKey("PORTABILITY"); + sut.getRootCharacteristics(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtAggregatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtAggregatorTest.java new file mode 100644 index 00000000000..2542ac0c175 --- /dev/null +++ b/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); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtCalculatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DebtCalculatorTest.java new file mode 100644 index 00000000000..cf57d9aa682 --- /dev/null +++ b/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)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DefaultAssigneeTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DefaultAssigneeTest.java new file mode 100644 index 00000000000..e1e846bb729 --- /dev/null +++ b/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(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DumbRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/DumbRule.java new file mode 100644 index 00000000000..79be4f1dafa --- /dev/null +++ b/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; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueAssignerTest.java index 4638933135b..d5f8ab5a2e8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueAssignerTest.java @@ -24,32 +24,36 @@ 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.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 org.sonar.test.DbTests; -import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; -@Category(DbTests.class) -public class SourceLinesCacheTest { +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(); - SourceLinesCache sut; + IssueAssigner underTest; @Before public void setUp() throws Exception { esTester.truncateIndices(); - sut = new SourceLinesCache(new SourceLineIndex(esTester.client())); + underTest = new IssueAssigner(new SourceLineIndex(esTester.client()), reportReader, scmAccountToUser, defaultAssignee); } @Test @@ -63,41 +67,43 @@ public class SourceLinesCacheTest { .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"); + 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(expected = IllegalStateException.class) - public void fail_when_component_ref_is_not_filled() { - sut.init("ANY_UUID", null, reportReader); - sut.lineAuthor(0); - } +// @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() diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java index e84f8688413..f07db4e1b6d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java @@ -17,210 +17,229 @@ * 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.ImmutableSet; -import com.google.common.collect.Iterators; -import java.io.IOException; -import java.util.Arrays; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.Settings; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.log.LogTester; -import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.core.rule.RuleDto; -import org.sonar.server.computation.batch.BatchReportReaderRule; -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.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class IssueComputationTest { - - private static final RuleKey RULE_KEY = RuleKey.of("squid", "R1"); - private static final String PROJECT_KEY = "PROJECT_KEY"; - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - @Rule - public LogTester logTester = new LogTester(); - @Rule - public BatchReportReaderRule reportReader = new BatchReportReaderRule(); - - IssueComputation sut; - - // inputs - RuleCache ruleCache = mock(RuleCache.class); - SourceLinesCache lineCache = mock(SourceLinesCache.class); - ScmAccountCache scmAccountCache = mock(ScmAccountCache.class); - RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule()); - BatchReport.Issue.Builder inputIssue = BatchReport.Issue.newBuilder() - .setUuid("ISSUE_A") - .setRuleRepository(RULE_KEY.repository()) - .setRuleKey(RULE_KEY.rule()) - .setStatus(Issue.STATUS_OPEN); - Settings projectSettings; - ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.class); - UserIndex userIndex = mock(UserIndex.class); - - // output - IssueCache outputIssues; - - @Before - public void setUp() throws IOException { - when(ruleCache.get(RULE_KEY)).thenReturn(rule); - outputIssues = new IssueCache(temp.newFile(), System2.INSTANCE); - projectSettings = new Settings(); - when(projectSettingsRepository.getProjectSettings(PROJECT_KEY)).thenReturn(projectSettings); - sut = new IssueComputation(ruleCache, lineCache, scmAccountCache, outputIssues, userIndex, projectSettingsRepository, reportReader); - } - - @After - public void after() { - sut.afterReportProcessing(); - } - - @Test - public void store_issues_on_disk() { - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).key()).isEqualTo("ISSUE_A"); - } - - @Test - public void copy_rule_tags_on_new_issues() { - inputIssue.setIsNew(true); - rule.setTags(ImmutableSet.of("bug", "performance")); - rule.setSystemTags(ImmutableSet.of("blocker")); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).containsOnly("blocker", "bug", "performance"); - } - - @Test - public void do_not_copy_rule_tags_on_existing_issues() { - inputIssue.setIsNew(false); - rule.setTags(ImmutableSet.of("bug", "performance")); - rule.setSystemTags(ImmutableSet.of("blocker")); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).isEmpty(); - } - - @Test - public void guess_author_of_new_issues() { - inputIssue.setIsNew(true); - inputIssue.setLine(3); - when(lineCache.lineAuthor(3)).thenReturn("charlie"); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isEqualTo("charlie"); - } - - @Test - public void do_not_fail_if_missing_author_for_new_issues() { - inputIssue.setIsNew(true); - inputIssue.setLine(3); - when(lineCache.lineAuthor(3)).thenReturn(null); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); - } - - @Test - public void do_not_guess_author_of_existing_issues() { - inputIssue.setIsNew(false); - inputIssue.setLine(3); - when(lineCache.lineAuthor(3)).thenReturn("charlie"); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); - } - - @Test - public void auto_assign_new_issues() { - inputIssue.setIsNew(true); - inputIssue.setAuthorLogin("charlie"); - when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie"); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo("char.lie"); - } - - @Test - public void do_not_auto_assign_existing_issues() { - inputIssue.setIsNew(false); - inputIssue.setAuthorLogin("charlie"); - when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie"); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); - } - - @Test - public void do_not_override_author_and_assignee_set_by_old_batch_plugins() { - inputIssue.setIsNew(true); - - // these fields were provided during project analysis, for instance - // by developer cockpit or issue-assign plugins - inputIssue.setAuthorLogin("charlie"); - inputIssue.setAssignee("cabu"); - - process(); - - // keep the values, without trying to update them - DefaultIssue cachedIssue = Iterators.getOnlyElement(outputIssues.traverse()); - assertThat(cachedIssue.assignee()).isEqualTo("cabu"); - assertThat(cachedIssue.authorLogin()).isEqualTo("charlie"); - verifyZeroInteractions(scmAccountCache); - } - - @Test - public void assign_default_assignee_when_available() { - inputIssue.setIsNew(true); - String wolinski = "wolinski"; - projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); - when(userIndex.getNullableByLogin(wolinski)).thenReturn(new UserDoc()); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo(wolinski); - assertThat(logTester.logs()).doesNotContain(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); - } - - @Test - public void do_not_assign_default_assignee_when_not_found_in_index() { - inputIssue.setIsNew(true); - String wolinski = "wolinski"; - projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); - when(userIndex.getNullableByLogin(wolinski)).thenReturn(null); - - process(); - - assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); - assertThat(logTester.logs()).contains(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); - } - - private void process() { - sut.processComponentIssues(Arrays.asList(inputIssue.build()), "FILE_A", 1, PROJECT_KEY, "PROJECT_UUID"); - } -} +///* +// * 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.ImmutableSet; +//import com.google.common.collect.Iterators; +//import java.io.IOException; +//import java.util.Arrays; +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.rules.TemporaryFolder; +//import org.sonar.api.CoreProperties; +//import org.sonar.api.config.Settings; +//import org.sonar.api.issue.Issue; +//import org.sonar.core.issue.DefaultIssue; +//import org.sonar.api.rule.RuleKey; +//import org.sonar.api.utils.System2; +//import org.sonar.api.utils.log.LogTester; +//import org.sonar.batch.protocol.output.BatchReport; +//import org.sonar.core.rule.RuleDto; +//import org.sonar.server.computation.batch.BatchReportReaderRule; +//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.verifyZeroInteractions; +//import static org.mockito.Mockito.when; +// +//public class IssueComputationTest { +// +// private static final RuleKey RULE_KEY = RuleKey.of("squid", "R1"); +// private static final String PROJECT_KEY = "PROJECT_KEY"; +// +// @Rule +// public TemporaryFolder temp = new TemporaryFolder(); +// @Rule +// public LogTester logTester = new LogTester(); +// @Rule +// public BatchReportReaderRule reportReader = new BatchReportReaderRule(); +// +// IssueComputation sut; +// +// // inputs +// RuleCache ruleCache = mock(RuleCache.class); +// SourceAuthorsHolder lineCache = mock(SourceAuthorsHolder.class); +// ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class); +// RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule()); +// BatchReport.Issue.Builder inputIssue = BatchReport.Issue.newBuilder() +// .setUuid("ISSUE_A") +// .setRuleRepository(RULE_KEY.repository()) +// .setRuleKey(RULE_KEY.rule()) +// .setStatus(Issue.STATUS_OPEN); +// Settings projectSettings; +// ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.class); +// UserIndex userIndex = mock(UserIndex.class); +// +// // output +// DeprecatedIssueCache outputIssues; +// +// @Before +// public void setUp() throws IOException { +// when(ruleCache.get(RULE_KEY)).thenReturn(rule); +// outputIssues = new DeprecatedIssueCache(temp.newFile(), System2.INSTANCE); +// projectSettings = new Settings(); +// when(projectSettingsRepository.getProjectSettings(PROJECT_KEY)).thenReturn(projectSettings); +// sut = new IssueComputation(ruleCache, lineCache, scmAccountToUser, outputIssues, userIndex, projectSettingsRepository, reportReader); +// } +// +// @After +// public void after() { +// sut.afterReportProcessing(); +// } +// +// @Test +// public void store_issues_on_disk() { +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).key()).isEqualTo("ISSUE_A"); +// } +// +// @Test +// public void copy_rule_tags_on_new_issues() { +// inputIssue.setIsNew(true); +// rule.setTags(ImmutableSet.of("bug", "performance")); +// rule.setSystemTags(ImmutableSet.of("blocker")); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).containsOnly("blocker", "bug", "performance"); +// } +// +// @Test +// public void do_not_copy_rule_tags_on_existing_issues() { +// inputIssue.setIsNew(false); +// rule.setTags(ImmutableSet.of("bug", "performance")); +// rule.setSystemTags(ImmutableSet.of("blocker")); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).isEmpty(); +// } +// +// @Test +// public void guess_author_of_new_issues() { +// inputIssue.setIsNew(true); +// inputIssue.setLine(3); +// when(lineCache.lineAuthor(3)).thenReturn("charlie"); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isEqualTo("charlie"); +// } +// +// @Test +// public void do_not_fail_if_missing_author_for_new_issues() { +// inputIssue.setIsNew(true); +// inputIssue.setLine(3); +// when(lineCache.lineAuthor(3)).thenReturn(null); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); +// } +// +// @Test +// public void do_not_guess_author_of_existing_issues() { +// inputIssue.setIsNew(false); +// inputIssue.setLine(3); +// when(lineCache.lineAuthor(3)).thenReturn("charlie"); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); +// } +// +// @Test +// public void auto_assign_new_issues() { +// inputIssue.setIsNew(true); +// inputIssue.setAuthorLogin("charlie"); +// when(scmAccountToUser.getNullable("charlie")).thenReturn("char.lie"); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo("char.lie"); +// } +// +// @Test +// public void do_not_auto_assign_existing_issues() { +// inputIssue.setIsNew(false); +// inputIssue.setAuthorLogin("charlie"); +// when(scmAccountToUser.getNullable("charlie")).thenReturn("char.lie"); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); +// } +// +// @Test +// public void do_not_override_author_and_assignee_set_by_old_batch_plugins() { +// inputIssue.setIsNew(true); +// +// // these fields were provided during project analysis, for instance +// // by developer cockpit or issue-assign plugins +// inputIssue.setAuthorLogin("charlie"); +// inputIssue.setAssignee("cabu"); +// +// process(); +// +// // keep the values, without trying to update them +// DefaultIssue cachedIssue = Iterators.getOnlyElement(outputIssues.traverse()); +// assertThat(cachedIssue.assignee()).isEqualTo("cabu"); +// assertThat(cachedIssue.authorLogin()).isEqualTo("charlie"); +// verifyZeroInteractions(scmAccountToUser); +// } +// +// @Test +// public void assign_default_assignee_when_available() { +// inputIssue.setIsNew(true); +// String wolinski = "wolinski"; +// projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); +// when(userIndex.getNullableByLogin(wolinski)).thenReturn(new UserDoc()); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo(wolinski); +// assertThat(logTester.logs()).doesNotContain(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); +// } +// +// @Test +// public void do_not_assign_default_assignee_when_not_found_in_index() { +// inputIssue.setIsNew(true); +// String wolinski = "wolinski"; +// projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); +// when(userIndex.getNullableByLogin(wolinski)).thenReturn(null); +// +// process(); +// +// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); +// assertThat(logTester.logs()).contains(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); +// } +// +// private void process() { +// sut.processComponentIssues(Arrays.asList(inputIssue.build()), "FILE_A", 1, PROJECT_KEY, "PROJECT_UUID"); +// } +//} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java new file mode 100644 index 00000000000..67988ec1f5a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueCounterTest.java @@ -0,0 +1,266 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import java.util.Date; +import javax.annotation.Nullable; +import org.assertj.core.data.Offset; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Tracking; +import org.sonar.server.computation.batch.BatchReportReaderRule; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.measure.MeasureRepositoryImpl; +import org.sonar.server.computation.measure.MeasureVariations; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricImpl; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolderRule; +import org.sonar.server.rule.RuleTesting; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; +import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; +import static org.sonar.api.issue.Issue.STATUS_CLOSED; +import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; +import static org.sonar.api.issue.Issue.STATUS_OPEN; +import static org.sonar.api.issue.Issue.STATUS_RESOLVED; +import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; +import static org.sonar.api.rule.Severity.BLOCKER; +import static org.sonar.api.rule.Severity.CRITICAL; +import static org.sonar.api.rule.Severity.MAJOR; +import static org.sonar.server.computation.component.DumbComponent.builder; +import static org.sonar.server.computation.metric.Metric.MetricType.INT; + +public class IssueCounterTest { + + static final Component FILE1 = builder(Component.Type.FILE, 1).build(); + static final Component FILE2 = builder(Component.Type.FILE, 2).build(); + static final Component FILE3 = builder(Component.Type.FILE, 3).build(); + static final Component PROJECT = builder(Component.Type.PROJECT, 4).addChildren(FILE1, FILE2, FILE3).build(); + + static final Metric ISSUES_METRIC = new MetricImpl(1, VIOLATIONS_KEY, VIOLATIONS_KEY, INT); + static final Metric OPEN_ISSUES_METRIC = new MetricImpl(2, OPEN_ISSUES_KEY, OPEN_ISSUES_KEY, INT); + static final Metric REOPENED_ISSUES_METRIC = new MetricImpl(3, REOPENED_ISSUES_KEY, REOPENED_ISSUES_KEY, INT); + static final Metric CONFIRMED_ISSUES_METRIC = new MetricImpl(4, CONFIRMED_ISSUES_KEY, CONFIRMED_ISSUES_KEY, INT); + static final Metric BLOCKER_ISSUES_METRIC = new MetricImpl(5, BLOCKER_VIOLATIONS_KEY, BLOCKER_VIOLATIONS_KEY, INT); + static final Metric CRITICAL_ISSUES_METRIC = new MetricImpl(6, CRITICAL_VIOLATIONS_KEY, CRITICAL_VIOLATIONS_KEY, INT); + static final Metric MAJOR_ISSUES_METRIC = new MetricImpl(7, MAJOR_VIOLATIONS_KEY, MAJOR_VIOLATIONS_KEY, INT); + static final Metric MINOR_ISSUES_METRIC = new MetricImpl(8, MINOR_VIOLATIONS_KEY, MINOR_VIOLATIONS_KEY, INT); + static final Metric INFO_ISSUES_METRIC = new MetricImpl(9, INFO_VIOLATIONS_KEY, INFO_VIOLATIONS_KEY, INT); + static final Metric NEW_ISSUES_METRIC = new MetricImpl(10, NEW_VIOLATIONS_KEY, NEW_VIOLATIONS_KEY, INT); + static final Metric NEW_BLOCKER_ISSUES_METRIC = new MetricImpl(11, NEW_BLOCKER_VIOLATIONS_KEY, NEW_BLOCKER_VIOLATIONS_KEY, INT); + static final Metric NEW_CRITICAL_ISSUES_METRIC = new MetricImpl(12, NEW_CRITICAL_VIOLATIONS_KEY, NEW_CRITICAL_VIOLATIONS_KEY, INT); + static final Metric NEW_MAJOR_ISSUES_METRIC = new MetricImpl(13, NEW_MAJOR_VIOLATIONS_KEY, NEW_MAJOR_VIOLATIONS_KEY, INT); + static final Metric NEW_MINOR_ISSUES_METRIC = new MetricImpl(14, NEW_MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY, INT); + static final Metric NEW_INFO_ISSUES_METRIC = new MetricImpl(15, NEW_INFO_VIOLATIONS_KEY, NEW_INFO_VIOLATIONS_KEY, INT); + static final Metric FALSE_POSITIVE_ISSUES_METRIC = new MetricImpl(16, FALSE_POSITIVE_ISSUES_KEY, FALSE_POSITIVE_ISSUES_KEY, INT); + + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + @Rule + public PeriodsHolderRule periodsHolder = new PeriodsHolderRule(); + + Tracking tracking = mock(Tracking.class); + MetricRepository metricRepository = mock(MetricRepository.class); + MeasureRepository measureRepository; + IssueCounter sut; + + @Before + public void setUp() throws Exception { + initMetrics(); + measureRepository = new MeasureRepositoryImpl(null, reportReader, metricRepository); + + sut = new IssueCounter(periodsHolder, metricRepository, measureRepository); + } + + @Test + public void count_issues_by_status() throws Exception { + periodsHolder.setPeriods(); + + // bottom-up traversal -> from files to project + sut.beforeComponent(FILE1, tracking); + sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER)); + sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR)); + sut.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR)); + sut.afterComponent(FILE1); + + sut.beforeComponent(FILE2, tracking); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER)); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR)); + sut.afterComponent(FILE2); + + sut.beforeComponent(FILE3, tracking); + sut.afterComponent(FILE3); + + sut.beforeComponent(PROJECT, tracking); + sut.afterComponent(PROJECT); + + // count by status + assertThat(measureRepository.getRawMeasure(FILE1, ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + + assertThat(measureRepository.getRawMeasure(FILE2, ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + assertThat(measureRepository.getRawMeasure(FILE2, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE2, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE2, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + + assertThat(measureRepository.getRawMeasure(FILE3, ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + + assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(3); + assertThat(measureRepository.getRawMeasure(PROJECT, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(PROJECT, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(PROJECT, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + } + + @Test + public void count_unresolved_issues_by_severity() throws Exception { + periodsHolder.setPeriods(); + + // bottom-up traversal -> from files to project + sut.beforeComponent(FILE1, tracking); + sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER)); + // this resolved issue is ignored + sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR)); + sut.afterComponent(FILE1); + + sut.beforeComponent(FILE2, tracking); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER)); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR)); + sut.afterComponent(FILE2); + + sut.beforeComponent(PROJECT, tracking); + sut.afterComponent(PROJECT); + + assertThat(measureRepository.getRawMeasure(FILE1, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE1, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + + assertThat(measureRepository.getRawMeasure(FILE2, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE2, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE2, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + + assertThat(measureRepository.getRawMeasure(PROJECT, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + assertThat(measureRepository.getRawMeasure(PROJECT, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(PROJECT, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + } + + @Test + public void count_new_issues() throws Exception { + Period period = newPeriod(3, 1500000000000L); + periodsHolder.setPeriods(period); + + sut.beforeComponent(FILE1, tracking); + // created before -> existing issues + sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate() - 1000000L)); + // created during the first analysis starting the period -> existing issues + sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate())); + // created after -> new issues + sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L)); + sut.onIssue(FILE1, createIssueAt(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getSnapshotDate() + 200000L)); + sut.afterComponent(FILE1); + + sut.beforeComponent(FILE2, tracking); + sut.afterComponent(FILE2); + + sut.beforeComponent(PROJECT, tracking); + sut.afterComponent(PROJECT); + + assertVariation(FILE1, NEW_ISSUES_METRIC, period.getIndex(), 1); + assertVariation(FILE1, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1); + assertVariation(FILE1, NEW_BLOCKER_ISSUES_METRIC, period.getIndex(), 0); + assertVariation(FILE1, NEW_MAJOR_ISSUES_METRIC, period.getIndex(), 0); + + assertVariation(PROJECT, NEW_ISSUES_METRIC, period.getIndex(), 1); + assertVariation(PROJECT, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1); + assertVariation(PROJECT, NEW_BLOCKER_ISSUES_METRIC, period.getIndex(), 0); + assertVariation(PROJECT, NEW_MAJOR_ISSUES_METRIC, period.getIndex(), 0); + } + + private void assertVariation(Component component, Metric metric, int periodIndex, int expectedVariation) { + MeasureVariations variations = measureRepository.getRawMeasure(component, metric).get().getVariations(); + assertThat(variations.getVariation(periodIndex)).isEqualTo((double) expectedVariation, Offset.offset(0.01)); + } + + private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) { + return new DefaultIssue() + .setResolution(resolution).setStatus(status) + .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1) + .setCreationDate(new Date()); + } + + private static DefaultIssue createIssueAt(@Nullable String resolution, String status, String severity, long creationDate) { + return new DefaultIssue() + .setResolution(resolution).setStatus(status) + .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1) + .setCreationDate(new Date(creationDate)); + } + + private static Period newPeriod(int index, long date) { + return new Period(index, "mode", null, date, 42l); + } + + private void initMetrics() { + when(metricRepository.getByKey(ISSUES_METRIC.getKey())).thenReturn(ISSUES_METRIC); + when(metricRepository.getByKey(OPEN_ISSUES_METRIC.getKey())).thenReturn(OPEN_ISSUES_METRIC); + when(metricRepository.getByKey(REOPENED_ISSUES_METRIC.getKey())).thenReturn(REOPENED_ISSUES_METRIC); + when(metricRepository.getByKey(CONFIRMED_ISSUES_METRIC.getKey())).thenReturn(CONFIRMED_ISSUES_METRIC); + when(metricRepository.getByKey(BLOCKER_ISSUES_METRIC.getKey())).thenReturn(BLOCKER_ISSUES_METRIC); + when(metricRepository.getByKey(CRITICAL_ISSUES_METRIC.getKey())).thenReturn(CRITICAL_ISSUES_METRIC); + when(metricRepository.getByKey(MAJOR_ISSUES_METRIC.getKey())).thenReturn(MAJOR_ISSUES_METRIC); + when(metricRepository.getByKey(MINOR_ISSUES_METRIC.getKey())).thenReturn(MINOR_ISSUES_METRIC); + when(metricRepository.getByKey(INFO_ISSUES_METRIC.getKey())).thenReturn(INFO_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_ISSUES_METRIC.getKey())).thenReturn(NEW_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_BLOCKER_ISSUES_METRIC.getKey())).thenReturn(NEW_BLOCKER_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_CRITICAL_ISSUES_METRIC.getKey())).thenReturn(NEW_CRITICAL_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_MAJOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MAJOR_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_MINOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MINOR_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_INFO_ISSUES_METRIC.getKey())).thenReturn(NEW_INFO_ISSUES_METRIC); + when(metricRepository.getByKey(FALSE_POSITIVE_ISSUES_METRIC.getKey())).thenReturn(FALSE_POSITIVE_ISSUES_METRIC); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtAggregatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtAggregatorTest.java new file mode 100644 index 00000000000..cf864b8a8e0 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtAggregatorTest.java @@ -0,0 +1,114 @@ +/* + * 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 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(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtCalculatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtCalculatorTest.java new file mode 100644 index 00000000000..37add915d09 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/NewDebtCalculatorTest.java @@ -0,0 +1,125 @@ +/* + * 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.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()); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java index d65db7e5307..0457c5dc0f2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java +++ b/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(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryImplTest.java index 57172b9b9dc..a7f5c5fba82 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/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); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleRepositoryRule.java new file mode 100644 index 00000000000..860ab256f05 --- /dev/null +++ b/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; + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleTagsCopierTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleTagsCopierTest.java new file mode 100644 index 00000000000..df0a3d4c9d9 --- /dev/null +++ b/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(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest.java index 6f0fd8ae6fc..40c58734445 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest.java @@ -19,27 +19,28 @@ */ package org.sonar.server.computation.issue; +import java.util.Collections; 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 java.util.Collections; - 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 ScmAccountCacheLoaderTest { +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(); @@ -49,7 +50,7 @@ public class ScmAccountCacheLoaderTest { public void load_login_for_scm_account() throws Exception { esTester.putDocuments("users", "user", getClass(), "charlie.json"); UserIndex index = new UserIndex(esTester.client()); - ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index); + ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index); assertThat(loader.load("missing")).isNull(); assertThat(loader.load("jesuis@charlie.com")).isEqualTo("charlie"); @@ -59,17 +60,16 @@ public class ScmAccountCacheLoaderTest { 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); - ScmAccountCacheLoader loader = new ScmAccountCacheLoader(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 public void load_by_multiple_scm_accounts_is_not_supported_yet() { UserIndex index = new UserIndex(esTester.client()); - ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index); + ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index); try { loader.loadAll(Collections.<String>emptyList()); fail(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/BatchMeasureToMeasureTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/BatchMeasureToMeasureTest.java index fbf88dbdf0d..c00155eb02f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/BatchMeasureToMeasureTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/BatchMeasureToMeasureTest.java @@ -23,20 +23,14 @@ import com.google.common.base.Optional; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.sonar.api.rule.RuleKey; import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.core.rule.RuleDto; -import org.sonar.server.computation.issue.RuleCache; import org.sonar.server.computation.metric.Metric; import org.sonar.server.computation.metric.MetricImpl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.guava.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @RunWith(DataProviderRunner.class) public class BatchMeasureToMeasureTest { @@ -48,19 +42,9 @@ public class BatchMeasureToMeasureTest { private static final Metric SOME_LEVEL_METRIC = new MetricImpl(42, "level", "name", Metric.MetricType.LEVEL); private static final String SOME_DATA = "some_data man!"; - private static final String SOME_ALERT_TEXT = "some alert text_be_careFul!"; private static final BatchReport.Measure EMPTY_BATCH_MEASURE = BatchReport.Measure.newBuilder().build(); - private static final RuleKey SOME_RULE_KEY = RuleKey.of("A", "B"); - private static final int SOME_RULE_ID = 9513; - private static final RuleDto SOME_RULE = new RuleDto().setRuleKey(SOME_RULE_KEY.toString()).setId(SOME_RULE_ID); - private RuleCache ruleCache = mock(RuleCache.class); - private BatchMeasureToMeasure underTest = new BatchMeasureToMeasure(ruleCache); - - @Before - public void setUp() throws Exception { - when(ruleCache.get(SOME_RULE_KEY)).thenReturn(SOME_RULE); - } + private BatchMeasureToMeasure underTest = new BatchMeasureToMeasure(); @Test public void toMeasure_returns_absent_for_null_argument() { @@ -77,25 +61,6 @@ public class BatchMeasureToMeasureTest { underTest.toMeasure(null, null); } - @Test(expected = IllegalArgumentException.class) - public void toMeasure_throws_IAE_if_batch_Measure_has_both_ruleKey_and_characteristicId() { - underTest.toMeasure(BatchReport.Measure.newBuilder().setRuleKey(SOME_RULE_KEY.toString()).setCharactericId(42).build(), SOME_STRING_METRIC); - } - - @Test - public void toMeasure_maps_characteristicId_if_present() { - BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder().setCharactericId(42).build(); - - assertThat(underTest.toMeasure(batchMeasure, SOME_STRING_METRIC).get().getCharacteristicId()).isEqualTo(42); - } - - @Test - public void toMeasure_maps_ruleKey_if_present() { - BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder().setRuleKey(SOME_RULE_KEY.toString()).build(); - - assertThat(underTest.toMeasure(batchMeasure, SOME_STRING_METRIC).get().getRuleId()).isEqualTo(SOME_RULE_ID); - } - @Test public void toMeasure_returns_no_value_if_dto_has_no_string_value_for_LEVEL_Metric() { Optional<Measure> measure = underTest.toMeasure(EMPTY_BATCH_MEASURE, SOME_LEVEL_METRIC); @@ -138,8 +103,6 @@ public class BatchMeasureToMeasureTest { public void toMeasure_for_LEVEL_Metric_maps_QualityGateStatus() { BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder() .setStringValue(Measure.Level.OK.name()) - .setAlertStatus(Measure.Level.ERROR.name()) - .setAlertText(SOME_ALERT_TEXT) .build(); Optional<Measure> measure = underTest.toMeasure(batchMeasure, SOME_LEVEL_METRIC); @@ -147,8 +110,6 @@ public class BatchMeasureToMeasureTest { assertThat(measure).isPresent(); assertThat(measure.get().getValueType()).isEqualTo(Measure.ValueType.LEVEL); assertThat(measure.get().getLevelValue()).isEqualTo(Measure.Level.OK); - assertThat(measure.get().getQualityGateStatus().getStatus()).isEqualTo(Measure.Level.ERROR); - assertThat(measure.get().getQualityGateStatus().getText()).isEqualTo(SOME_ALERT_TEXT); } @Test @@ -182,7 +143,6 @@ public class BatchMeasureToMeasureTest { BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder() .setIntValue(10) .setStringValue(SOME_DATA) - .setAlertStatus(Measure.Level.OK.name()).setAlertText(SOME_ALERT_TEXT) .build(); Optional<Measure> measure = underTest.toMeasure(batchMeasure, SOME_INT_METRIC); @@ -191,8 +151,6 @@ public class BatchMeasureToMeasureTest { assertThat(measure.get().getValueType()).isEqualTo(Measure.ValueType.INT); assertThat(measure.get().getIntValue()).isEqualTo(10); assertThat(measure.get().getData()).isEqualTo(SOME_DATA); - assertThat(measure.get().getQualityGateStatus().getStatus()).isEqualTo(Measure.Level.OK); - assertThat(measure.get().getQualityGateStatus().getText()).isEqualTo(SOME_ALERT_TEXT); } @Test @@ -217,7 +175,6 @@ public class BatchMeasureToMeasureTest { BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder() .setLongValue(10l) .setStringValue(SOME_DATA) - .setAlertStatus(Measure.Level.OK.name()).setAlertText(SOME_ALERT_TEXT) .build(); Optional<Measure> measure = underTest.toMeasure(batchMeasure, SOME_LONG_METRIC); @@ -226,8 +183,6 @@ public class BatchMeasureToMeasureTest { assertThat(measure.get().getValueType()).isEqualTo(Measure.ValueType.LONG); assertThat(measure.get().getLongValue()).isEqualTo(10); assertThat(measure.get().getData()).isEqualTo(SOME_DATA); - assertThat(measure.get().getQualityGateStatus().getStatus()).isEqualTo(Measure.Level.OK); - assertThat(measure.get().getQualityGateStatus().getText()).isEqualTo(SOME_ALERT_TEXT); } @Test @@ -243,7 +198,6 @@ public class BatchMeasureToMeasureTest { BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder() .setDoubleValue(10.6395d) .setStringValue(SOME_DATA) - .setAlertStatus(Measure.Level.OK.name()).setAlertText(SOME_ALERT_TEXT) .build(); Optional<Measure> measure = underTest.toMeasure(batchMeasure, SOME_DOUBLE_METRIC); @@ -252,8 +206,6 @@ public class BatchMeasureToMeasureTest { assertThat(measure.get().getValueType()).isEqualTo(Measure.ValueType.DOUBLE); assertThat(measure.get().getDoubleValue()).isEqualTo(10.6d); assertThat(measure.get().getData()).isEqualTo(SOME_DATA); - assertThat(measure.get().getQualityGateStatus().getStatus()).isEqualTo(Measure.Level.OK); - assertThat(measure.get().getQualityGateStatus().getText()).isEqualTo(SOME_ALERT_TEXT); } @Test @@ -281,7 +233,7 @@ public class BatchMeasureToMeasureTest { @Test public void toMeasure_maps_data_and_alert_properties_in_dto_for_Boolean_metric() { BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder() - .setBooleanValue(true).setStringValue(SOME_DATA).setAlertStatus(Measure.Level.OK.name()).setAlertText(SOME_ALERT_TEXT).build(); + .setBooleanValue(true).setStringValue(SOME_DATA).build(); Optional<Measure> measure = underTest.toMeasure(batchMeasure, SOME_BOOLEAN_METRIC); @@ -289,8 +241,6 @@ public class BatchMeasureToMeasureTest { assertThat(measure.get().getValueType()).isEqualTo(Measure.ValueType.BOOLEAN); assertThat(measure.get().getBooleanValue()).isTrue(); assertThat(measure.get().getData()).isEqualTo(SOME_DATA); - assertThat(measure.get().getQualityGateStatus().getStatus()).isEqualTo(Measure.Level.OK); - assertThat(measure.get().getQualityGateStatus().getText()).isEqualTo(SOME_ALERT_TEXT); } @Test @@ -305,7 +255,6 @@ public class BatchMeasureToMeasureTest { public void toMeasure_maps_alert_properties_in_dto_for_String_Metric() { BatchReport.Measure batchMeasure = BatchReport.Measure.newBuilder() .setStringValue(SOME_DATA) - .setAlertStatus(Measure.Level.OK.name()).setAlertText(SOME_ALERT_TEXT) .build(); Optional<Measure> measure = underTest.toMeasure(batchMeasure, SOME_STRING_METRIC); @@ -314,8 +263,6 @@ public class BatchMeasureToMeasureTest { assertThat(measure.get().getValueType()).isEqualTo(Measure.ValueType.STRING); assertThat(measure.get().getStringValue()).isEqualTo(SOME_DATA); assertThat(measure.get().getData()).isEqualTo(SOME_DATA); - assertThat(measure.get().getQualityGateStatus().getStatus()).isEqualTo(Measure.Level.OK); - assertThat(measure.get().getQualityGateStatus().getText()).isEqualTo(SOME_ALERT_TEXT); } @DataProvider diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryImplTest.java index ad6f55545e4..67d0a10e685 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryImplTest.java @@ -49,7 +49,6 @@ 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.computation.debt.Characteristic; -import org.sonar.server.computation.issue.RuleCache; import org.sonar.server.computation.metric.Metric; import org.sonar.server.computation.metric.MetricImpl; import org.sonar.server.computation.metric.MetricRepository; @@ -89,16 +88,15 @@ public class MeasureRepositoryImplTest { private static final Measure SOME_MEASURE = Measure.newMeasureBuilder().create("some value"); private static final String SOME_DATA = "some data"; private static final RuleDto SOME_RULE = RuleDto.createFor(RuleKey.of("A", "1")).setId(963); - private static final Characteristic SOME_CHARACTERISTIC = new Characteristic(741, "key"); + private static final Characteristic SOME_CHARACTERISTIC = new Characteristic(741, "key", null); private DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new MeasureDao(), new SnapshotDao(), new MetricDao(), new ComponentDao()); private MetricRepository metricRepository = mock(MetricRepository.class); - private RuleCache ruleCache = mock(RuleCache.class); - private MeasureRepositoryImpl underTest = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository, ruleCache); + private MeasureRepositoryImpl underTest = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository); private DbClient mockedDbClient = mock(DbClient.class); private BatchReportReader mockBatchReportReader = mock(BatchReportReader.class); - private MeasureRepositoryImpl underTestWithMock = new MeasureRepositoryImpl(mockedDbClient, mockBatchReportReader, metricRepository, ruleCache); + private MeasureRepositoryImpl underTestWithMock = new MeasureRepositoryImpl(mockedDbClient, mockBatchReportReader, metricRepository); @CheckForNull private DbSession dbSession; @@ -227,7 +225,7 @@ public class MeasureRepositoryImplTest { @Nullable @Override public Object[] apply(Measure input) { - return new Measure[] { input }; + return new Measure[] {input}; } }).toArray(Object[].class); } @@ -254,8 +252,8 @@ public class MeasureRepositoryImplTest { fail("An IllegalArgumentException should have been raised"); } catch (IllegalArgumentException e) { assertThat(e).hasMessage(format( - "Measure's ValueType (%s) is not consistent with the Measure's ValueType (%s)", - measure.getValueType(), metricType.getValueType())); + "Measure's ValueType (%s) is not consistent with the Measure's ValueType (%s)", + measure.getValueType(), metricType.getValueType())); } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java index 381579a9e1e..28e2332d115 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java +++ b/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(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeIssueMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeIssueMeasuresStepTest.java deleted file mode 100644 index 3e09534b789..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeIssueMeasuresStepTest.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.sonar.server.computation.step; - -import java.util.Arrays; -import javax.annotation.Nullable; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.utils.internal.Uuids; -import org.sonar.batch.protocol.Constants; -import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.batch.protocol.output.BatchReport.Issue; -import org.sonar.core.rule.RuleDto; -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.issue.RuleCache; -import org.sonar.server.computation.measure.MeasureRepository; -import org.sonar.server.computation.measure.MeasureRepositoryImpl; -import org.sonar.server.computation.metric.Metric; -import org.sonar.server.computation.metric.MetricImpl; -import org.sonar.server.computation.metric.MetricRepository; -import org.sonar.server.computation.period.Period; -import org.sonar.server.computation.period.PeriodsHolderRule; -import org.sonar.server.rule.RuleTesting; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; -import static org.sonar.api.issue.Issue.STATUS_CLOSED; -import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; -import static org.sonar.api.issue.Issue.STATUS_OPEN; -import static org.sonar.api.issue.Issue.STATUS_REOPENED; -import static org.sonar.api.issue.Issue.STATUS_RESOLVED; -import static org.sonar.api.issue.internal.DefaultIssue.RESOLUTION_FIXED; -import static org.sonar.api.issue.internal.DefaultIssue.RESOLUTION_REMOVED; -import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; -import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY; -import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; -import static org.sonar.api.rule.Severity.BLOCKER; -import static org.sonar.api.rule.Severity.CRITICAL; -import static org.sonar.api.rule.Severity.INFO; -import static org.sonar.api.rule.Severity.MAJOR; -import static org.sonar.api.rule.Severity.MINOR; -import static org.sonar.server.computation.component.DumbComponent.builder; -import static org.sonar.server.computation.metric.Metric.MetricType.INT; - -public class ComputeIssueMeasuresStepTest { - - static final Component FILE = builder(Component.Type.FILE, 2).build(); - static final Component PROJECT = builder(Component.Type.PROJECT, 1).addChildren(FILE).build(); - - static final Metric ISSUES_METRIC = new MetricImpl(1, VIOLATIONS_KEY, VIOLATIONS_KEY, INT); - static final Metric OPEN_ISSUES_METRIC = new MetricImpl(2, OPEN_ISSUES_KEY, OPEN_ISSUES_KEY, INT); - static final Metric REOPENED_ISSUES_METRIC = new MetricImpl(3, REOPENED_ISSUES_KEY, REOPENED_ISSUES_KEY, INT); - static final Metric CONFIRMED_ISSUES_METRIC = new MetricImpl(4, CONFIRMED_ISSUES_KEY, CONFIRMED_ISSUES_KEY, INT); - static final Metric BLOCKER_ISSUES_METRIC = new MetricImpl(5, BLOCKER_VIOLATIONS_KEY, BLOCKER_VIOLATIONS_KEY, INT); - static final Metric CRITICAL_ISSUES_METRIC = new MetricImpl(6, CRITICAL_VIOLATIONS_KEY, CRITICAL_VIOLATIONS_KEY, INT); - static final Metric MAJOR_ISSUES_METRIC = new MetricImpl(7, MAJOR_VIOLATIONS_KEY, MAJOR_VIOLATIONS_KEY, INT); - static final Metric MINOR_ISSUES_METRIC = new MetricImpl(8, MINOR_VIOLATIONS_KEY, MINOR_VIOLATIONS_KEY, INT); - static final Metric INFO_ISSUES_METRIC = new MetricImpl(9, INFO_VIOLATIONS_KEY, INFO_VIOLATIONS_KEY, INT); - static final Metric NEW_ISSUES_METRIC = new MetricImpl(10, NEW_VIOLATIONS_KEY, NEW_VIOLATIONS_KEY, INT); - static final Metric NEW_BLOCKER_ISSUES_METRIC = new MetricImpl(11, NEW_BLOCKER_VIOLATIONS_KEY, NEW_BLOCKER_VIOLATIONS_KEY, INT); - static final Metric NEW_CRITICAL_ISSUES_METRIC = new MetricImpl(12, NEW_CRITICAL_VIOLATIONS_KEY, NEW_CRITICAL_VIOLATIONS_KEY, INT); - static final Metric NEW_MAJOR_ISSUES_METRIC = new MetricImpl(13, NEW_MAJOR_VIOLATIONS_KEY, NEW_MAJOR_VIOLATIONS_KEY, INT); - static final Metric NEW_MINOR_ISSUES_METRIC = new MetricImpl(14, NEW_MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY, INT); - static final Metric NEW_INFO_ISSUES_METRIC = new MetricImpl(15, NEW_INFO_VIOLATIONS_KEY, NEW_INFO_VIOLATIONS_KEY, INT); - static final Metric FALSE_POSITIVE_ISSUES_METRIC = new MetricImpl(16, FALSE_POSITIVE_ISSUES_KEY, FALSE_POSITIVE_ISSUES_KEY, INT); - - static final RuleDto RULE_1 = RuleTesting.newDto(RuleKey.of("xoo", "x1")).setId(1); - static final RuleDto RULE_2 = RuleTesting.newDto(RuleKey.of("xoo", "x2")).setId(2); - static final RuleDto RULE_3 = RuleTesting.newDto(RuleKey.of("xoo", "x3")).setId(3); - static final RuleDto RULE_4 = RuleTesting.newDto(RuleKey.of("xoo", "x4")).setId(4); - static final RuleDto RULE_5 = RuleTesting.newDto(RuleKey.of("xoo", "x5")).setId(5); - static final RuleDto RULE_6 = RuleTesting.newDto(RuleKey.of("xoo", "x6")).setId(6); - - @Rule - public BatchReportReaderRule reportReader = new BatchReportReaderRule(); - - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - - @Rule - public PeriodsHolderRule periodsHolder = new PeriodsHolderRule(); - - MetricRepository metricRepository = mock(MetricRepository.class); - RuleCache ruleCache = mock(RuleCache.class); - MeasureRepository measureRepository; - - ComputeIssueMeasuresStep sut; - - @Before - public void setUp() throws Exception { - initMetrics(); - measureRepository = new MeasureRepositoryImpl(null, reportReader, metricRepository, ruleCache); - - sut = new ComputeIssueMeasuresStep(periodsHolder, reportReader, treeRootHolder, measureRepository, metricRepository); - } - - @Test - public void compute_total_issues_measure() throws Exception { - treeRootHolder.setRoot(PROJECT); - periodsHolder.setPeriods(); - addIssues(FILE.getRef(), createIssue(STATUS_OPEN, BLOCKER, RULE_1.getKey())); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - } - - @Test - public void compute_measures_on_all_levels() throws Exception { - Component file1 = builder(Component.Type.FILE, 5).build(); - Component file2 = builder(Component.Type.FILE, 4).build(); - Component file3 = builder(Component.Type.FILE, 3).build(); - Component directory = builder(Component.Type.DIRECTORY, 2).addChildren(file1, file2, file3).build(); - Component project = builder(Component.Type.PROJECT, 1).addChildren(directory).build(); - treeRootHolder.setRoot(project); - periodsHolder.setPeriods(); - - addIssues(file1.getRef(), createIssue(STATUS_OPEN, BLOCKER, RULE_1.getKey())); - addIssues(file2.getRef(), createIssue(STATUS_REOPENED, CRITICAL, RULE_2.getKey())); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(file1, ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(file2, ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(directory, ISSUES_METRIC).get().getIntValue()).isEqualTo(2); - assertThat(measureRepository.getRawMeasure(project, ISSUES_METRIC).get().getIntValue()).isEqualTo(2); - } - - @Test - public void compute_measures_on_issue_statuses() throws Exception { - treeRootHolder.setRoot(PROJECT); - periodsHolder.setPeriods(); - addIssues(FILE.getRef(), - createIssue(STATUS_OPEN, BLOCKER, RULE_1.getKey()), - createIssue(STATUS_REOPENED, BLOCKER, RULE_2.getKey()), - createIssue(STATUS_CONFIRMED, BLOCKER, RULE_3.getKey()), - createIssue(STATUS_CONFIRMED, BLOCKER, RULE_4.getKey())); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, REOPENED_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); - } - - @Test - public void compute_measures_on_issue_severities() throws Exception { - treeRootHolder.setRoot(PROJECT); - periodsHolder.setPeriods(); - addIssues(FILE.getRef(), - createIssue(STATUS_OPEN, BLOCKER, RULE_1.getKey()), - createIssue(STATUS_OPEN, CRITICAL, RULE_2.getKey()), - createIssue(STATUS_OPEN, MAJOR, RULE_3.getKey()), - createIssue(STATUS_OPEN, MINOR, RULE_4.getKey()), - createIssue(STATUS_OPEN, INFO, RULE_5.getKey()), - createIssue(STATUS_OPEN, INFO, RULE_6.getKey())); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, MINOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, INFO_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); - } - - @Test - public void compute_measures_on_false_positive_issue() throws Exception { - treeRootHolder.setRoot(PROJECT); - periodsHolder.setPeriods(); - addIssues(FILE.getRef(), - createIssue(STATUS_OPEN, BLOCKER, RULE_1.getKey()), - createIssue(STATUS_CLOSED, BLOCKER, RESOLUTION_FALSE_POSITIVE, RULE_2.getKey()), - createIssue(STATUS_RESOLVED, BLOCKER, RESOLUTION_FIXED, RULE_3.getKey()), - createIssue(STATUS_RESOLVED, BLOCKER, RESOLUTION_REMOVED, RULE_4.getKey())); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); - } - - @Test - public void compute_measures_on_new_issue() throws Exception { - treeRootHolder.setRoot(PROJECT); - addIssues(FILE.getRef(), - // issue created before the period 3 - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1388552400000L), - // issue created after period 3 but before current analysis - createIssue(STATUS_OPEN, BLOCKER, null, RULE_1.getKey(), 1433131200000L)); - periodsHolder.setPeriods(newPeriod(3, 1420088400000L)); - - sut.execute(); - - // Only 1 new issues for period 3 - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().getVariation3()).isEqualTo(1); - - // No variation on other periods - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().hasVariation1()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().hasVariation2()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().hasVariation4()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().hasVariation5()).isFalse(); - } - - @Test - public void do_not_take_into_account_issue_from_current_analysis_when_computing_measures_on_new_issue() throws Exception { - treeRootHolder.setRoot(PROJECT); - addIssues(FILE.getRef(), - // issue created during current analysis -> should not be taking into account - createIssue(STATUS_OPEN, BLOCKER, null, RULE_1.getKey(), 1420088400000L)); - periodsHolder.setPeriods(newPeriod(1, 1420088400000L)); - - sut.execute(); - - // No new issues - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().getVariation1()).isEqualTo(0); - } - - @Test - public void compute_measures_on_new_issue_on_every_variations() throws Exception { - treeRootHolder.setRoot(PROJECT); - addIssues(FILE.getRef(), - // issue created the 2014-01-01, before all periods - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1388552400000L), - // issue created the 2015-01-15, before period 2 - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1421298000000L), - // issue created the 2015-02-15, before period 3 - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1423976400000L), - // issue created the 2015-03-15, before period 4 - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1426392000000L), - // issue created the 2015-04-15, before period 5 - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1429070400000L), - // issue created the 2015-06-01 -> Should not been taken into account by any period - createIssue(STATUS_OPEN, BLOCKER, null, RULE_1.getKey(), 1433131200000L)); - periodsHolder.setPeriods( - // 2015-01-01 - newPeriod(1, 1420088400000L), - // 2015-02-01 - newPeriod(2, 1422766800000L), - // 2015-03-01 - newPeriod(3, 1425186000000L), - // 2015-04-01 - newPeriod(4, 1427860800000L), - // 2015-05-01 - newPeriod(5, 1430452800000L)); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().getVariation1()).isEqualTo(5); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().getVariation2()).isEqualTo(4); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().getVariation3()).isEqualTo(3); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().getVariation4()).isEqualTo(2); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).get().getVariations().getVariation5()).isEqualTo(1); - } - - @Test - public void compute_measures_on_new_issue_severities() throws Exception { - treeRootHolder.setRoot(PROJECT); - addIssues(FILE.getRef(), - // issue created before the period 1 - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1388552400000L), - // issues created after period 1 but before current analysis - createIssue(STATUS_OPEN, BLOCKER, null, RULE_1.getKey(), 1433131200000L), - createIssue(STATUS_OPEN, BLOCKER, null, RULE_2.getKey(), 1433131200000L), - createIssue(STATUS_OPEN, CRITICAL, null, RULE_1.getKey(), 1433131200000L), - createIssue(STATUS_OPEN, MAJOR, null, RULE_1.getKey(), 1433131200000L), - createIssue(STATUS_OPEN, MINOR, null, RULE_1.getKey(), 1433131200000L), - createIssue(STATUS_OPEN, INFO, null, RULE_1.getKey(), 1433131200000L)); - periodsHolder.setPeriods(newPeriod(1, 1420088400000L)); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_BLOCKER_ISSUES_METRIC).get().getVariations().getVariation1()).isEqualTo(2); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_CRITICAL_ISSUES_METRIC).get().getVariations().getVariation1()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_MAJOR_ISSUES_METRIC).get().getVariations().getVariation1()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_MINOR_ISSUES_METRIC).get().getVariations().getVariation1()).isEqualTo(1); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_INFO_ISSUES_METRIC).get().getVariations().getVariation1()).isEqualTo(1); - } - - @Test - public void compute_no_new_measures_when_no_period() throws Exception { - treeRootHolder.setRoot(PROJECT); - periodsHolder.setPeriods(); - addIssues(FILE.getRef(), - createIssue(STATUS_CONFIRMED, BLOCKER, null, RULE_1.getKey(), 1388552400000L)); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_ISSUES_METRIC).isPresent()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_BLOCKER_ISSUES_METRIC).isPresent()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_CRITICAL_ISSUES_METRIC).isPresent()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_MAJOR_ISSUES_METRIC).isPresent()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_MINOR_ISSUES_METRIC).isPresent()).isFalse(); - assertThat(measureRepository.getRawMeasure(PROJECT, NEW_INFO_ISSUES_METRIC).isPresent()).isFalse(); - } - - @Test - public void compute_measures_having_zero_value_if_no_issue() throws Exception { - treeRootHolder.setRoot(PROJECT); - periodsHolder.setPeriods(); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - assertThat(measureRepository.getRawMeasure(PROJECT, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - assertThat(measureRepository.getRawMeasure(PROJECT, REOPENED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - assertThat(measureRepository.getRawMeasure(PROJECT, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - assertThat(measureRepository.getRawMeasure(PROJECT, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - assertThat(measureRepository.getRawMeasure(PROJECT, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - assertThat(measureRepository.getRawMeasure(PROJECT, MINOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - assertThat(measureRepository.getRawMeasure(PROJECT, INFO_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - } - - @Test - public void ignore_resolved_issues() throws Exception { - treeRootHolder.setRoot(PROJECT); - periodsHolder.setPeriods(); - addIssues(FILE.getRef(), - createIssue(STATUS_CLOSED, BLOCKER, RESOLUTION_FALSE_POSITIVE, RULE_1.getKey()), - createIssue(STATUS_RESOLVED, BLOCKER, RESOLUTION_FIXED, RULE_2.getKey()), - createIssue(STATUS_RESOLVED, BLOCKER, RESOLUTION_REMOVED, RULE_3.getKey())); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(0); - } - - private void addIssues(int componentRef, Issue... issues) { - reportReader.putIssues(componentRef, Arrays.asList(issues)); - } - - private static Issue createIssue(String status, String severity, RuleKey ruleKey) { - return createIssue(status, severity, null, ruleKey, 1000L); - } - - private static Issue createIssue(String status, String severity, @Nullable String resolution, RuleKey ruleKey) { - return createIssue(status, severity, resolution, ruleKey, 1000L); - } - - private static Issue createIssue(String status, String severity, @Nullable String resolution, RuleKey ruleKey, long creationDate) { - BatchReport.Issue.Builder issueBuilder = Issue.newBuilder() - .setUuid(Uuids.create()) - .setStatus(status) - .setRuleKey(ruleKey.rule()) - .setRuleRepository(ruleKey.repository()) - .setSeverity(Constants.Severity.valueOf(severity)) - .setCreationDate(creationDate); - if (resolution != null) { - issueBuilder.setResolution(resolution); - } - return issueBuilder.build(); - } - - private static Period newPeriod(int index, long date) { - return new Period(index, "mode", null, date, 42l); - } - - private void initMetrics() { - when(metricRepository.getByKey(ISSUES_METRIC.getKey())).thenReturn(ISSUES_METRIC); - when(metricRepository.getByKey(OPEN_ISSUES_METRIC.getKey())).thenReturn(OPEN_ISSUES_METRIC); - when(metricRepository.getByKey(REOPENED_ISSUES_METRIC.getKey())).thenReturn(REOPENED_ISSUES_METRIC); - when(metricRepository.getByKey(CONFIRMED_ISSUES_METRIC.getKey())).thenReturn(CONFIRMED_ISSUES_METRIC); - when(metricRepository.getByKey(BLOCKER_ISSUES_METRIC.getKey())).thenReturn(BLOCKER_ISSUES_METRIC); - when(metricRepository.getByKey(CRITICAL_ISSUES_METRIC.getKey())).thenReturn(CRITICAL_ISSUES_METRIC); - when(metricRepository.getByKey(MAJOR_ISSUES_METRIC.getKey())).thenReturn(MAJOR_ISSUES_METRIC); - when(metricRepository.getByKey(MINOR_ISSUES_METRIC.getKey())).thenReturn(MINOR_ISSUES_METRIC); - when(metricRepository.getByKey(INFO_ISSUES_METRIC.getKey())).thenReturn(INFO_ISSUES_METRIC); - when(metricRepository.getByKey(NEW_ISSUES_METRIC.getKey())).thenReturn(NEW_ISSUES_METRIC); - when(metricRepository.getByKey(NEW_BLOCKER_ISSUES_METRIC.getKey())).thenReturn(NEW_BLOCKER_ISSUES_METRIC); - when(metricRepository.getByKey(NEW_CRITICAL_ISSUES_METRIC.getKey())).thenReturn(NEW_CRITICAL_ISSUES_METRIC); - when(metricRepository.getByKey(NEW_MAJOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MAJOR_ISSUES_METRIC); - when(metricRepository.getByKey(NEW_MINOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MINOR_ISSUES_METRIC); - when(metricRepository.getByKey(NEW_INFO_ISSUES_METRIC.getKey())).thenReturn(NEW_INFO_ISSUES_METRIC); - when(metricRepository.getByKey(FALSE_POSITIVE_ISSUES_METRIC.getKey())).thenReturn(FALSE_POSITIVE_ISSUES_METRIC); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedDebtModelStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedDebtModelStepTest.java index e57a43636a8..429b2c3282f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedDebtModelStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedDebtModelStepTest.java @@ -17,7 +17,6 @@ * 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; @@ -76,12 +75,11 @@ public class FeedDebtModelStepTest extends BaseStepTest { sut.execute(); - Collection<Characteristic> rootChars = debtModelHolder.findRootCharacteristics(); + Collection<Characteristic> rootChars = debtModelHolder.getRootCharacteristics(); 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"); + Characteristic subChar = debtModelHolder.getCharacteristicById(1); + assertThat(subChar).isNotNull(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/FillMeasuresWithVariationsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/FillMeasuresWithVariationsStepTest.java index 9d4d6504b99..786fdcce131 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/FillMeasuresWithVariationsStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/FillMeasuresWithVariationsStepTest.java @@ -35,9 +35,7 @@ import org.sonar.core.measure.db.MeasureDto; 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.core.technicaldebt.db.CharacteristicDao; -import org.sonar.core.technicaldebt.db.CharacteristicDto; import org.sonar.server.component.ComponentTesting; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.component.db.SnapshotDao; @@ -45,9 +43,6 @@ 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.debt.Characteristic; -import org.sonar.server.computation.issue.RuleCache; -import org.sonar.server.computation.issue.RuleCacheLoader; import org.sonar.server.computation.measure.Measure; import org.sonar.server.computation.measure.MeasureRepository; import org.sonar.server.computation.measure.MeasureRepositoryImpl; @@ -60,7 +55,6 @@ import org.sonar.server.computation.period.PeriodsHolderRule; 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; @@ -119,7 +113,7 @@ public class FillMeasuresWithVariationsStepTest { metricRepository = new MetricRepositoryImpl(dbClient); metricRepository.start(); - measureRepository = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository, new RuleCache(new RuleCacheLoader(dbClient))); + measureRepository = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository); sut = new FillMeasuresWithVariationsStep(dbClient, treeRootHolder, periodsHolder, metricRepository, measureRepository); } @@ -258,66 +252,6 @@ public class FillMeasuresWithVariationsStepTest { } @Test - public void set_variation_on_rule_measure() throws Exception { - SnapshotDto period1ProjectSnapshot = createForProject(PROJECT_DTO); - dbClient.snapshotDao().insert(session, period1ProjectSnapshot); - - RuleDto rule1 = RuleTesting.newXooX1(); - RuleDto rule2 = RuleTesting.newXooX2(); - dbClient.ruleDao().insert(session, rule1, rule2); - - dbClient.measureDao().insert(session, newMeasureDto(ISSUES_METRIC.getId(), PROJECT_DTO.getId(), period1ProjectSnapshot.getId(), 60d)); - dbClient.measureDao().insert(session, newMeasureDto(ISSUES_METRIC.getId(), PROJECT_DTO.getId(), period1ProjectSnapshot.getId(), 40d).setRuleId(rule1.getId())); - dbClient.measureDao().insert(session, newMeasureDto(ISSUES_METRIC.getId(), PROJECT_DTO.getId(), period1ProjectSnapshot.getId(), 20d).setRuleId(rule2.getId())); - session.commit(); - - periodsHolder.setPeriods(newPeriod(1, period1ProjectSnapshot)); - - treeRootHolder.setRoot(PROJECT); - - measureRepository.add(PROJECT, toMetric(ISSUES_METRIC), Measure.newMeasureBuilder().create(80, null)); - measureRepository.add(PROJECT, toMetric(ISSUES_METRIC), Measure.newMeasureBuilder().forRule(rule1.getId()).create(45, null)); - measureRepository.add(PROJECT, toMetric(ISSUES_METRIC), Measure.newMeasureBuilder().forRule(rule2.getId()).create(35, null)); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, toMetric(ISSUES_METRIC)).get().getVariations().getVariation1()).isEqualTo(20d); - assertThat(measureRepository.getRawMeasure(PROJECT, toMetric(ISSUES_METRIC), rule1).get().getVariations().getVariation1()).isEqualTo(5d); - assertThat(measureRepository.getRawMeasure(PROJECT, toMetric(ISSUES_METRIC), rule2).get().getVariations().getVariation1()).isEqualTo(15d); - } - - @Test - public void set_variation_on_characteristic_measure() throws Exception { - SnapshotDto period1ProjectSnapshot = createForProject(PROJECT_DTO); - dbClient.snapshotDao().insert(session, period1ProjectSnapshot); - - CharacteristicDto char1 = new CharacteristicDto().setKey("PORTABILITY"); - CharacteristicDto char2 = new CharacteristicDto().setKey("MAINTAINABILITY"); - dbClient.debtCharacteristicDao().insert(session, char1, char2); - - dbClient.measureDao().insert(session, newMeasureDto(ISSUES_METRIC.getId(), PROJECT_DTO.getId(), period1ProjectSnapshot.getId(), 60d)); - dbClient.measureDao().insert(session, newMeasureDto(ISSUES_METRIC.getId(), PROJECT_DTO.getId(), period1ProjectSnapshot.getId(), 40d).setCharacteristicId(char1.getId())); - dbClient.measureDao().insert(session, newMeasureDto(ISSUES_METRIC.getId(), PROJECT_DTO.getId(), period1ProjectSnapshot.getId(), 20d).setCharacteristicId(char2.getId())); - session.commit(); - - periodsHolder.setPeriods(newPeriod(1, period1ProjectSnapshot)); - - treeRootHolder.setRoot(PROJECT); - - measureRepository.add(PROJECT, toMetric(ISSUES_METRIC), Measure.newMeasureBuilder().create(80, null)); - measureRepository.add(PROJECT, toMetric(ISSUES_METRIC), Measure.newMeasureBuilder().forCharacteristic(char1.getId()).create(45, null)); - measureRepository.add(PROJECT, toMetric(ISSUES_METRIC), Measure.newMeasureBuilder().forCharacteristic(char2.getId()).create(35, null)); - - sut.execute(); - - assertThat(measureRepository.getRawMeasure(PROJECT, toMetric(ISSUES_METRIC)).get().getVariations().getVariation1()).isEqualTo(20d); - assertThat(measureRepository.getRawMeasure(PROJECT, toMetric(ISSUES_METRIC), new Characteristic(char1.getId(), char1.getKey())).get().getVariations().getVariation1()) - .isEqualTo(5d); - assertThat(measureRepository.getRawMeasure(PROJECT, toMetric(ISSUES_METRIC), new Characteristic(char2.getId(), char2.getKey())).get().getVariations().getVariation1()) - .isEqualTo(15d); - } - - @Test public void read_measure_from_batch() throws Exception { // Project SnapshotDto period1ProjectSnapshot = createForProject(PROJECT_DTO); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ParseReportStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ParseReportStepTest.java deleted file mode 100644 index c1344850083..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ParseReportStepTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.computation.step; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.batch.protocol.Constants; -import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.core.persistence.DbTester; -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.IssueComputation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class ParseReportStepTest extends BaseStepTest { - - private static final String PROJECT_KEY = "PROJECT_KEY"; - - private static final List<BatchReport.Issue> ISSUES_ON_DELETED_COMPONENT = Arrays.asList(BatchReport.Issue.newBuilder() - .setUuid("DELETED_ISSUE_UUID") - .build()); - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - @Rule - public BatchReportReaderRule reportReader = new BatchReportReaderRule(); - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - - @ClassRule - public static DbTester dbTester = new DbTester(); - - IssueComputation issueComputation = mock(IssueComputation.class); - ParseReportStep sut = new ParseReportStep(issueComputation, reportReader, treeRootHolder); - - @Test - public void extract_report_from_db_and_browse_components() throws Exception { - DumbComponent root = DumbComponent.builder(Component.Type.PROJECT, 1).setUuid("PROJECT_UUID").setKey(PROJECT_KEY).addChildren( - DumbComponent.builder(Component.Type.FILE, 2).setUuid("FILE1_UUID").setKey("PROJECT_KEY:file1").build(), - DumbComponent.builder(Component.Type.FILE, 3).setUuid("FILE2_UUID").setKey("PROJECT_KEY:file2").build()) - .build(); - - generateReport(); - - treeRootHolder.setRoot(root); - - sut.execute(); - - assertThat(reportReader.readMetadata().getRootComponentRef()).isEqualTo(1); - assertThat(reportReader.readMetadata().getDeletedComponentsCount()).isEqualTo(1); - - // verify that all components are processed (currently only for issues) - verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "PROJECT_UUID", 1, PROJECT_KEY, "PROJECT_UUID"); - verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "FILE1_UUID", 2, PROJECT_KEY, "PROJECT_UUID"); - verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "FILE2_UUID", 3, PROJECT_KEY, "PROJECT_UUID"); - verify(issueComputation).processComponentIssues(ISSUES_ON_DELETED_COMPONENT, "DELETED_UUID", null, PROJECT_KEY, "PROJECT_UUID"); - verify(issueComputation).afterReportProcessing(); - } - - private void generateReport() throws IOException { - // project and 2 files - reportReader.setMetadata(BatchReport.Metadata.newBuilder() - .setRootComponentRef(1) - .setDeletedComponentsCount(1) - .build()); - - reportReader.putComponent(BatchReport.Component.newBuilder() - .setRef(1) - .setType(Constants.ComponentType.PROJECT) - .addChildRef(2) - .addChildRef(3) - .build()); - reportReader.putComponent(BatchReport.Component.newBuilder() - .setRef(2) - .setType(Constants.ComponentType.FILE) - .build()); - reportReader.putComponent(BatchReport.Component.newBuilder() - .setRef(3) - .setType(Constants.ComponentType.FILE) - .build()); - - // deleted components - BatchReport.Issues.Builder issuesBuilder = BatchReport.Issues.newBuilder(); - issuesBuilder.setComponentRef(1); - issuesBuilder.setComponentUuid("DELETED_UUID"); - issuesBuilder.addAllIssue(ISSUES_ON_DELETED_COMPONENT); - reportReader.putDeletedIssues(1, issuesBuilder.build()); - } - - @Override - protected ComputationStep step() { - return sut; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java index f50bc0e2cd9..fbb5aae1c1a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java @@ -27,18 +27,20 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.FieldDiffs; 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; @@ -48,12 +50,17 @@ import static org.mockito.Mockito.when; public class PersistIssuesStepTest extends BaseStepTest { + public static final long NOW = 1400000000000L; + @Rule public TemporaryFolder temp = new TemporaryFolder(); @ClassRule public static DbTester dbTester = new DbTester(); + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + DbSession session; DbClient dbClient; @@ -74,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(1400000000000L); - step = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleCache(new RuleCacheLoader(dbClient)), issueCache); + when(system2.now()).thenReturn(NOW); + reportReader.setMetadata(BatchReport.Metadata.getDefaultInstance()); + + step = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleRepositoryImpl(new RuleCacheLoader(dbClient, reportReader)), issueCache); } @After @@ -117,6 +125,7 @@ public class PersistIssuesStepTest extends BaseStepTest { .setSeverity(Severity.BLOCKER) .setStatus(Issue.STATUS_CLOSED) .setResolution(Issue.RESOLUTION_FIXED) + .setSelectedAt(NOW) .setNew(false) .setChanged(true) ).close(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistMeasuresStepTest.java index 296dda1d018..627545565ae 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistMeasuresStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistMeasuresStepTest.java @@ -29,41 +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 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 { @@ -72,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; @@ -109,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); @@ -139,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) @@ -151,11 +136,8 @@ public class PersistMeasuresStepTest extends BaseStepTest { .setVariationValue3(3.3d) .setVariationValue4(4.4d) .setVariationValue5(5.5d) - .setAlertStatus("WARN") - .setAlertText("Open issues > 0") .setDescription("measure-description") .setMetricKey(STRING_METRIC_KEY) - .setCharactericId(123456) .build())); reportReader.putMeasures(FILE_REF, Arrays.asList( @@ -167,24 +149,21 @@ public class PersistMeasuresStepTest extends BaseStepTest { .setVariationValue3(3.3d) .setVariationValue4(4.4d) .setVariationValue5(5.5d) - .setAlertStatus("ERROR") - .setAlertText("Blocker issues variation > 0") .setDescription("measure-description") .setMetricKey(DOUBLE_METRIC_KEY) - .setRuleKey(RULE_KEY.toString()) .build())); 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(); @@ -192,8 +171,7 @@ public class PersistMeasuresStepTest extends BaseStepTest { dto = dtos.get(PROJECT_REF); assertThat(dto.get("snapshotId")).isEqualTo(4L); assertThat(dto.get("componentId")).isEqualTo(fileDto.getId()); - 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("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); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java index cbca0035cb0..b846287b270 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java +++ b/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.api.issue.internal.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()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java index 29f8b0d496d..1738484d0c8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java @@ -28,8 +28,8 @@ import org.sonar.api.issue.Issue; import org.sonar.api.issue.action.Actions; import org.sonar.api.issue.action.Function; import org.sonar.api.issue.condition.Condition; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.component.ComponentDto; import org.sonar.core.issue.IssueUpdater; import org.sonar.core.issue.db.IssueDto; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java index d525662371c..326a7fbb2e9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java @@ -26,8 +26,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Matchers; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; import java.util.Collection; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/AssignActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/AssignActionTest.java index cec5baa96ce..cd5bba96b08 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/AssignActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/AssignActionTest.java @@ -25,8 +25,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.api.user.User; import org.sonar.api.user.UserFinder; import org.sonar.core.issue.IssueUpdater; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java index 9453a822e98..09ad3c81e84 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java @@ -25,8 +25,8 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.tester.AnonymousMockUserSession; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java index 93d9c9d3696..392c7182df1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java @@ -33,8 +33,8 @@ import org.mockito.ArgumentCaptor; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; import org.sonar.api.issue.action.Action; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.user.User; import org.sonar.api.web.UserRole; import org.sonar.core.issue.DefaultActionPlan; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogFormatterTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogFormatterTest.java index fbdec123925..d678470bcbe 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogFormatterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogFormatterTest.java @@ -28,7 +28,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.i18n.I18n; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.server.tester.UserSessionRule; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogServiceTest.java index 7723f024513..f06bea4e57b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogServiceTest.java @@ -27,8 +27,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.user.User; import org.sonar.api.user.UserFinder; import org.sonar.core.issue.db.IssueChangeDao; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java index a343a039412..d0bdd68ea9d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java @@ -27,7 +27,7 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssueComment; +import org.sonar.core.issue.DefaultIssueComment; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; import org.sonar.api.security.DefaultGroups; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java index c4921fddb2a..51d64252b1f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java @@ -28,9 +28,9 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; import org.sonar.core.issue.db.IssueChangeDao; import org.sonar.core.issue.db.IssueChangeDto; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/PlanActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/PlanActionTest.java index 9da70d4d606..f47fe9ceb9d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/PlanActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/PlanActionTest.java @@ -26,8 +26,8 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.DefaultActionPlan; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.issue.actionplan.ActionPlanService; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java index 7280f972a34..1c966c06cb6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java @@ -26,8 +26,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Matchers; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; import java.util.Collection; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java index a6ea52cbaf3..e295b3c965f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java @@ -23,9 +23,9 @@ package org.sonar.server.issue; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java index 093e41fa9c1..07de711cfc3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java @@ -26,8 +26,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.api.web.UserRole; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.tester.UserSessionRule; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java index 86945151ade..99c17905fb7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java @@ -26,8 +26,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.workflow.IssueWorkflow; import org.sonar.core.issue.workflow.Transition; import org.sonar.server.tester.UserSessionRule; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/actionplan/ActionPlanServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/actionplan/ActionPlanServiceTest.java index 4d17c6e4667..500f85a8348 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/actionplan/ActionPlanServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/actionplan/ActionPlanServiceTest.java @@ -28,8 +28,8 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; import org.sonar.api.web.UserRole; import org.sonar.core.issue.ActionPlanStats; import org.sonar.core.issue.DefaultActionPlan; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java index acd55086472..49a57b9db6d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java @@ -20,8 +20,8 @@ package org.sonar.server.issue.notification; import org.junit.Test; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; import org.sonar.core.component.ComponentDto; import static org.assertj.core.api.Assertions.assertThat; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java index 63c35a2bdeb..61e2e63bf0b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java @@ -24,7 +24,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import org.junit.Test; import org.mockito.Mockito; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.api.utils.DateUtils; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java index 215297d17ed..08f6851849b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java @@ -22,7 +22,7 @@ package org.sonar.server.issue.notification; import com.google.common.collect.Lists; import org.junit.Test; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.api.utils.Duration; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java index 34aab044a41..d80a5470154 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java @@ -29,7 +29,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.issue.Issue; import org.sonar.api.issue.action.Action; -import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.core.issue.DefaultIssue; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.web.UserRole; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java index 61e7a768110..5d1268abc73 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java @@ -31,9 +31,9 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.i18n.I18n; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.DefaultIssueComment; -import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.FieldDiffs; import org.sonar.api.rule.RuleKey; import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic; import org.sonar.api.user.User; diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml index 97ee8157a48..075ab07d14f 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml +++ b/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]" diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/charlie.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest/charlie.json index f509e6b39a5..f509e6b39a5 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/charlie.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest/charlie.json diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/charlie_conflict.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest/charlie_conflict.json index 8f5af49f8ae..8f5af49f8ae 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/charlie_conflict.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest/charlie_conflict.json diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/close_issue-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/close_issue-result.xml index e15b08c3e25..a4c029b4606 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/close_issue-result.xml +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/close_issue-result.xml @@ -24,7 +24,7 @@ issue_creation_date="[null]" issue_update_date="[null]" issue_close_date="[null]" - created_at="1100000000000" + created_at="1300000000000" updated_at="1400000000000" /> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/insert_new_issue.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/insert_new_issue.xml index 0d56aa5ec07..9f3a7f46ea4 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/insert_new_issue.xml +++ b/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" diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/shared.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/shared.xml index 23f7ab20677..3cee2f8e087 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/shared.xml +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistIssuesStepTest/shared.xml @@ -36,8 +36,8 @@ issue_creation_date="[null]" issue_update_date="[null]" issue_close_date="[null]" - created_at="1100000000000" - updated_at="1100000000000" + created_at="1300000000000" + updated_at="1300000000000" /> </dataset> |