diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-06-23 16:06:31 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-07-02 16:07:03 +0200 |
commit | 08cdf04215f4b0b3a4ccfe7902c36df4906688c3 (patch) | |
tree | 21d9f9f41edc21e323c55e5ab46c5f973d0b8570 | |
parent | bf4118d6a9ceb9ad24274cdc6537d4a607121815 (diff) | |
download | sonarqube-08cdf04215f4b0b3a4ccfe7902c36df4906688c3.tar.gz sonarqube-08cdf04215f4b0b3a4ccfe7902c36df4906688c3.zip |
SONAR-6623 count issues
17 files changed, 576 insertions, 865 deletions
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 f4c31846f1f..4bd929e44e1 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 @@ -45,6 +45,7 @@ import org.sonar.server.computation.debt.DebtModelHolderImpl; import org.sonar.server.computation.event.EventRepositoryImpl; import org.sonar.server.computation.formula.CoreFormulaRepositoryImpl; import org.sonar.server.computation.issue.BaseIssuesLoader; +import org.sonar.server.computation.issue.CountIssuesListener; import org.sonar.server.computation.issue.DebtCalculator; import org.sonar.server.computation.issue.DefaultAssignee; import org.sonar.server.computation.issue.IssueAssigner; @@ -176,6 +177,7 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co DebtCalculator.class, IssueListeners.class, IssueLifecycle.class, + CountIssuesListener.class, UpdateConflictResolver.class, TrackerBaseInputFactory.class, TrackerRawInputFactory.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/CountIssuesListener.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/CountIssuesListener.java new file mode 100644 index 00000000000..ff6034f8a08 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/CountIssuesListener.java @@ -0,0 +1,273 @@ +/* + * 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 CountIssuesListener extends IssueListener { + + private final static 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 final static 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 CountIssuesListener(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); + } + + @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) { + // 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); + } + + addMeasuresByStatus(component); + addMeasuresBySeverity(component); + addMeasuresByPeriod(component); + } + + 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]; + boolean set = false; + for (Period period : periodsHolder.getPeriods()) { + Multiset<String> bag = currentCounters.counterForPeriod(period.getIndex()).severityBag; + if (bag.contains(severity)) { + variations[period.getIndex() - 1] = new Double(bag.count(severity)); + set = true; + } + } + if (set) { + Metric metric = metricRepository.getByKey(metricKey); + measureRepository.add(component, metric, Measure.newMeasureBuilder() + .setVariations(new MeasureVariations(variations)) + .createNoValue()); + } + } + } + } + + /** + * 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/DebtCalculator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java index 8dbd6aaa96e..afe30d47c87 100644 --- 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 @@ -25,7 +25,7 @@ import org.sonar.server.computation.component.Component; public class DebtCalculator extends IssueListener { @Override - public void beforeIssue(Component component, DefaultIssue issue) { + public void onOpenIssueInitialization(Component component, DefaultIssue issue) { if (issue.resolution() == null) { // TODO } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListener.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListener.java index 8ad03714432..ebd3d4eb098 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListener.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListener.java @@ -25,11 +25,23 @@ import org.sonar.server.computation.component.Component; public abstract class IssueListener { + /** + * 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) { } - public void beforeIssue(Component component, DefaultIssue issue) { + /** + * This method is called when initializing an open issue. At that time + * any information related to tracking step are not available (line, assignee, + * resolution, status, creation date, uuid, ...). + * <p/> + * The need for this method is for example to calculate the issue debt + * before merging with base issue + */ + public void onOpenIssueInitialization(Component component, DefaultIssue issue) { } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListeners.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListeners.java index 9da6c8d4c97..7c92c0e4ad3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListeners.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListeners.java @@ -37,9 +37,9 @@ public class IssueListeners { } } - public void beforeIssue(Component component, DefaultIssue issue) { + public void onOpenIssueInitialization(Component component, DefaultIssue issue) { for (IssueListener listener : listeners) { - listener.beforeIssue(component, issue); + listener.onOpenIssueInitialization(component, issue); } } 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..f671559674f 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"); - checkState(this.periods == null, "Periods have already been initialized"); + 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 003d8de45b2..49643fa299a 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 @@ -24,7 +24,6 @@ import com.google.common.collect.Iterables; import java.util.Arrays; import java.util.List; import org.sonar.server.computation.container.ComputeEngineContainer; -import org.sonar.server.computation.issue.IntegrateIssuesStep; /** * Ordered list of steps to be executed @@ -48,14 +47,14 @@ public class ComputationSteps { FeedDebtModelStep.class, // load project related stuffs - IntegrateIssuesStep.class, QualityGateLoadingStep.class, FeedPeriodsStep.class, // data computation - ComputeFormulaMeasuresStep.class, - CustomMeasuresCopyStep.class, + IntegrateIssuesStep.class, ComputeIssueMeasuresStep.class, + CustomMeasuresCopyStep.class, + ComputeFormulaMeasuresStep.class, 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/issue/IntegrateIssuesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java index da507bb7ad2..fed2f288295 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IntegrateIssuesStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java @@ -17,7 +17,7 @@ * 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; +package org.sonar.server.computation.step; import com.google.common.collect.Sets; import java.util.List; @@ -28,7 +28,11 @@ 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.step.ComputationStep; +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.IssueListeners; +import org.sonar.server.computation.issue.TrackerExecution; import org.sonar.server.util.cache.DiskCache; import static org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor.Order.POST_ORDER; @@ -87,7 +91,7 @@ public class IntegrateIssuesStep implements ComputationStep { Set<DefaultIssue> issues = tracking.getUnmatchedRaws(); for (DefaultIssue issue : issues) { issueLifecycle.initNewOpenIssue(issue); - issueListeners.beforeIssue(component, issue); + issueListeners.onOpenIssueInitialization(component, issue); process(component, issue, cacheAppender); } } @@ -96,7 +100,7 @@ public class IntegrateIssuesStep implements ComputationStep { for (Map.Entry<DefaultIssue, DefaultIssue> entry : tracking.getMatchedRaws().entrySet()) { DefaultIssue raw = entry.getKey(); DefaultIssue base = entry.getValue(); - issueListeners.beforeIssue(component, raw); + issueListeners.onOpenIssueInitialization(component, raw); issueLifecycle.mergeExistingOpenIssue(raw, base); process(component, raw, cacheAppender); } @@ -104,7 +108,7 @@ public class IntegrateIssuesStep implements ComputationStep { int line = entry.getKey(); DefaultIssue manualIssue = entry.getValue(); manualIssue.setLine(line == 0 ? null : line); - issueListeners.beforeIssue(component, manualIssue); + issueListeners.onOpenIssueInitialization(component, manualIssue); process(component, manualIssue, cacheAppender); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CountIssuesListenerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CountIssuesListenerTest.java new file mode 100644 index 00000000000..56b65390a89 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CountIssuesListenerTest.java @@ -0,0 +1,267 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.issue; + +import java.util.Date; +import javax.annotation.Nullable; +import org.assertj.core.data.Offset; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.tracking.Tracking; +import org.sonar.server.computation.batch.BatchReportReaderRule; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.measure.MeasureRepositoryImpl; +import org.sonar.server.computation.measure.MeasureVariations; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricImpl; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolderRule; +import org.sonar.server.rule.RuleTesting; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; +import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; +import static org.sonar.api.issue.Issue.STATUS_CLOSED; +import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; +import static org.sonar.api.issue.Issue.STATUS_OPEN; +import static org.sonar.api.issue.Issue.STATUS_RESOLVED; +import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; +import static org.sonar.api.rule.Severity.BLOCKER; +import static org.sonar.api.rule.Severity.CRITICAL; +import static org.sonar.api.rule.Severity.MAJOR; +import static org.sonar.server.computation.component.DumbComponent.builder; +import static org.sonar.server.computation.metric.Metric.MetricType.INT; + +public class CountIssuesListenerTest { + + static final Component FILE1 = builder(Component.Type.FILE, 1).build(); + static final Component FILE2 = builder(Component.Type.FILE, 2).build(); + static final Component FILE3 = builder(Component.Type.FILE, 3).build(); + static final Component PROJECT = builder(Component.Type.PROJECT, 4).addChildren(FILE1, FILE2, FILE3).build(); + + static final Metric ISSUES_METRIC = new MetricImpl(1, VIOLATIONS_KEY, VIOLATIONS_KEY, INT); + static final Metric OPEN_ISSUES_METRIC = new MetricImpl(2, OPEN_ISSUES_KEY, OPEN_ISSUES_KEY, INT); + static final Metric REOPENED_ISSUES_METRIC = new MetricImpl(3, REOPENED_ISSUES_KEY, REOPENED_ISSUES_KEY, INT); + static final Metric CONFIRMED_ISSUES_METRIC = new MetricImpl(4, CONFIRMED_ISSUES_KEY, CONFIRMED_ISSUES_KEY, INT); + static final Metric BLOCKER_ISSUES_METRIC = new MetricImpl(5, BLOCKER_VIOLATIONS_KEY, BLOCKER_VIOLATIONS_KEY, INT); + static final Metric CRITICAL_ISSUES_METRIC = new MetricImpl(6, CRITICAL_VIOLATIONS_KEY, CRITICAL_VIOLATIONS_KEY, INT); + static final Metric MAJOR_ISSUES_METRIC = new MetricImpl(7, MAJOR_VIOLATIONS_KEY, MAJOR_VIOLATIONS_KEY, INT); + static final Metric MINOR_ISSUES_METRIC = new MetricImpl(8, MINOR_VIOLATIONS_KEY, MINOR_VIOLATIONS_KEY, INT); + static final Metric INFO_ISSUES_METRIC = new MetricImpl(9, INFO_VIOLATIONS_KEY, INFO_VIOLATIONS_KEY, INT); + static final Metric NEW_ISSUES_METRIC = new MetricImpl(10, NEW_VIOLATIONS_KEY, NEW_VIOLATIONS_KEY, INT); + static final Metric NEW_BLOCKER_ISSUES_METRIC = new MetricImpl(11, NEW_BLOCKER_VIOLATIONS_KEY, NEW_BLOCKER_VIOLATIONS_KEY, INT); + static final Metric NEW_CRITICAL_ISSUES_METRIC = new MetricImpl(12, NEW_CRITICAL_VIOLATIONS_KEY, NEW_CRITICAL_VIOLATIONS_KEY, INT); + static final Metric NEW_MAJOR_ISSUES_METRIC = new MetricImpl(13, NEW_MAJOR_VIOLATIONS_KEY, NEW_MAJOR_VIOLATIONS_KEY, INT); + static final Metric NEW_MINOR_ISSUES_METRIC = new MetricImpl(14, NEW_MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY, INT); + static final Metric NEW_INFO_ISSUES_METRIC = new MetricImpl(15, NEW_INFO_VIOLATIONS_KEY, NEW_INFO_VIOLATIONS_KEY, INT); + static final Metric FALSE_POSITIVE_ISSUES_METRIC = new MetricImpl(16, FALSE_POSITIVE_ISSUES_KEY, FALSE_POSITIVE_ISSUES_KEY, INT); + + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + @Rule + public PeriodsHolderRule periodsHolder = new PeriodsHolderRule(); + + Tracking tracking = mock(Tracking.class); + MetricRepository metricRepository = mock(MetricRepository.class); + MeasureRepository measureRepository; + RuleCache ruleCache = mock(RuleCache.class); + CountIssuesListener sut; + + @Before + public void setUp() throws Exception { + initMetrics(); + measureRepository = new MeasureRepositoryImpl(null, reportReader, metricRepository, ruleCache); + + sut = new CountIssuesListener(periodsHolder, metricRepository, measureRepository); + } + + @Test + public void count_issues_by_status() throws Exception { + periodsHolder.setPeriods(); + + // bottom-up traversal -> from files to project + sut.beforeComponent(FILE1, tracking); + sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER)); + sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR)); + sut.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR)); + sut.afterComponent(FILE1); + + sut.beforeComponent(FILE2, tracking); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER)); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR)); + sut.afterComponent(FILE2); + + sut.beforeComponent(FILE3, tracking); + sut.afterComponent(FILE3); + + sut.beforeComponent(PROJECT, tracking); + sut.afterComponent(PROJECT); + + // count by status + assertThat(measureRepository.getRawMeasure(FILE1, ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + + assertThat(measureRepository.getRawMeasure(FILE2, ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + assertThat(measureRepository.getRawMeasure(FILE2, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE2, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE2, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + + assertThat(measureRepository.getRawMeasure(FILE3, ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + + assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(3); + assertThat(measureRepository.getRawMeasure(PROJECT, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(PROJECT, FALSE_POSITIVE_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(PROJECT, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + } + + @Test + public void count_unresolved_issues_by_severity() throws Exception { + periodsHolder.setPeriods(); + + // bottom-up traversal -> from files to project + sut.beforeComponent(FILE1, tracking); + sut.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER)); + // this resolved issue is ignored + sut.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR)); + sut.afterComponent(FILE1); + + sut.beforeComponent(FILE2, tracking); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER)); + sut.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR)); + sut.afterComponent(FILE2); + + sut.beforeComponent(PROJECT, tracking); + sut.afterComponent(PROJECT); + + assertThat(measureRepository.getRawMeasure(FILE1, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE1, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE1, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + + assertThat(measureRepository.getRawMeasure(FILE2, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + assertThat(measureRepository.getRawMeasure(FILE2, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(FILE2, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + + assertThat(measureRepository.getRawMeasure(PROJECT, BLOCKER_ISSUES_METRIC).get().getIntValue()).isEqualTo(2); + assertThat(measureRepository.getRawMeasure(PROJECT, CRITICAL_ISSUES_METRIC).get().getIntValue()).isEqualTo(0); + assertThat(measureRepository.getRawMeasure(PROJECT, MAJOR_ISSUES_METRIC).get().getIntValue()).isEqualTo(1); + } + + @Test + public void count_new_issues() throws Exception { + Period period = newPeriod(3, 1500000000000L); + periodsHolder.setPeriods(period); + + sut.beforeComponent(FILE1, tracking); + // created before -> existing issues + sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate() - 1000000L)); + // created during the first analysis starting the period -> existing issues + sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate())); + // created after -> new issues + sut.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L)); + sut.onIssue(FILE1, createIssueAt(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getSnapshotDate() + 200000L)); + sut.afterComponent(FILE1); + + sut.beforeComponent(FILE2, tracking); + sut.afterComponent(FILE2); + + sut.beforeComponent(PROJECT, tracking); + sut.afterComponent(PROJECT); + + assertVariation(FILE1, NEW_ISSUES_METRIC, period.getIndex(), 1); + assertVariation(FILE1, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1); + assertThat(measureRepository.getRawMeasure(FILE1, NEW_BLOCKER_ISSUES_METRIC).isPresent()).isFalse(); + assertThat(measureRepository.getRawMeasure(FILE1, NEW_MAJOR_ISSUES_METRIC).isPresent()).isFalse(); + + assertVariation(PROJECT, NEW_ISSUES_METRIC, period.getIndex(), 1); + assertVariation(PROJECT, NEW_CRITICAL_ISSUES_METRIC, period.getIndex(), 1); + assertThat(measureRepository.getRawMeasure(PROJECT, NEW_BLOCKER_ISSUES_METRIC).isPresent()).isFalse(); + assertThat(measureRepository.getRawMeasure(PROJECT, NEW_MAJOR_ISSUES_METRIC).isPresent()).isFalse(); + } + + private void assertVariation(Component component, Metric metric, int periodIndex, int expectedVariation) { + MeasureVariations variations = measureRepository.getRawMeasure(component, metric).get().getVariations(); + assertThat(variations.getVariation(periodIndex)).isEqualTo((double) expectedVariation, Offset.offset(0.01)); + } + + private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) { + return new DefaultIssue() + .setResolution(resolution).setStatus(status) + .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1) + .setCreationDate(new Date()); + } + + private static DefaultIssue createIssueAt(@Nullable String resolution, String status, String severity, long creationDate) { + return new DefaultIssue() + .setResolution(resolution).setStatus(status) + .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1) + .setCreationDate(new Date(creationDate)); + } + + private static Period newPeriod(int index, long date) { + return new Period(index, "mode", null, date, 42l); + } + + private void initMetrics() { + when(metricRepository.getByKey(ISSUES_METRIC.getKey())).thenReturn(ISSUES_METRIC); + when(metricRepository.getByKey(OPEN_ISSUES_METRIC.getKey())).thenReturn(OPEN_ISSUES_METRIC); + when(metricRepository.getByKey(REOPENED_ISSUES_METRIC.getKey())).thenReturn(REOPENED_ISSUES_METRIC); + when(metricRepository.getByKey(CONFIRMED_ISSUES_METRIC.getKey())).thenReturn(CONFIRMED_ISSUES_METRIC); + when(metricRepository.getByKey(BLOCKER_ISSUES_METRIC.getKey())).thenReturn(BLOCKER_ISSUES_METRIC); + when(metricRepository.getByKey(CRITICAL_ISSUES_METRIC.getKey())).thenReturn(CRITICAL_ISSUES_METRIC); + when(metricRepository.getByKey(MAJOR_ISSUES_METRIC.getKey())).thenReturn(MAJOR_ISSUES_METRIC); + when(metricRepository.getByKey(MINOR_ISSUES_METRIC.getKey())).thenReturn(MINOR_ISSUES_METRIC); + when(metricRepository.getByKey(INFO_ISSUES_METRIC.getKey())).thenReturn(INFO_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_ISSUES_METRIC.getKey())).thenReturn(NEW_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_BLOCKER_ISSUES_METRIC.getKey())).thenReturn(NEW_BLOCKER_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_CRITICAL_ISSUES_METRIC.getKey())).thenReturn(NEW_CRITICAL_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_MAJOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MAJOR_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_MINOR_ISSUES_METRIC.getKey())).thenReturn(NEW_MINOR_ISSUES_METRIC); + when(metricRepository.getByKey(NEW_INFO_ISSUES_METRIC.getKey())).thenReturn(NEW_INFO_ISSUES_METRIC); + when(metricRepository.getByKey(FALSE_POSITIVE_ISSUES_METRIC.getKey())).thenReturn(FALSE_POSITIVE_ISSUES_METRIC); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/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/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/sonar-batch/src/main/java/org/sonar/batch/compute/SeverityUtils.java b/sonar-batch/src/main/java/org/sonar/batch/compute/SeverityUtils.java deleted file mode 100644 index fd92039ce98..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/compute/SeverityUtils.java +++ /dev/null @@ -1,66 +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.batch.compute; - -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Metric; -import org.sonar.api.rules.RulePriority; - -final class SeverityUtils { - private SeverityUtils() { - // only static methods - } - - static Metric severityToIssueMetric(RulePriority severity) { - Metric metric; - if (severity.equals(RulePriority.BLOCKER)) { - metric = CoreMetrics.BLOCKER_VIOLATIONS; - } else if (severity.equals(RulePriority.CRITICAL)) { - metric = CoreMetrics.CRITICAL_VIOLATIONS; - } else if (severity.equals(RulePriority.MAJOR)) { - metric = CoreMetrics.MAJOR_VIOLATIONS; - } else if (severity.equals(RulePriority.MINOR)) { - metric = CoreMetrics.MINOR_VIOLATIONS; - } else if (severity.equals(RulePriority.INFO)) { - metric = CoreMetrics.INFO_VIOLATIONS; - } else { - throw new IllegalArgumentException("Unsupported severity: " + severity); - } - return metric; - } - - static Metric severityToNewMetricIssue(RulePriority severity) { - Metric metric; - if (severity.equals(RulePriority.BLOCKER)) { - metric = CoreMetrics.NEW_BLOCKER_VIOLATIONS; - } else if (severity.equals(RulePriority.CRITICAL)) { - metric = CoreMetrics.NEW_CRITICAL_VIOLATIONS; - } else if (severity.equals(RulePriority.MAJOR)) { - metric = CoreMetrics.NEW_MAJOR_VIOLATIONS; - } else if (severity.equals(RulePriority.MINOR)) { - metric = CoreMetrics.NEW_MINOR_VIOLATIONS; - } else if (severity.equals(RulePriority.INFO)) { - metric = CoreMetrics.NEW_INFO_VIOLATIONS; - } else { - throw new IllegalArgumentException("Unsupported severity: " + severity); - } - return metric; - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingTest.java index ae678c34bd3..616c7fb6ed4 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingTest.java @@ -246,7 +246,7 @@ public class IssueTrackingTest { DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", 200, RuleKey.of("squid", "AvoidCycle"), null); thrown - .expectMessage("Invalid line number for issue DefaultIssue[key=<null>,componentUuid=<null>,componentKey=<null>,moduleUuid=<null>,moduleUuidPath=<null>,projectUuid=<null>,projectKey=<null>,ruleKey=squid:AvoidCycle,language=<null>,severity=<null>,manualSeverity=false,message=1 branch need to be covered,line=200,effortToFix=<null>,debt=<null>,status=OPEN,resolution=<null>,reporter=<null>,assignee=<null>,checksum=<null>,attributes=<null>,authorLogin=<null>,actionPlanKey=<null>,comments=<null>,tags=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true,endOfLife=false,onDisabledRule=false,isChanged=false,sendNotifications=false,selectedAt=<null>]. File has only 17 line(s)"); + .expectMessage("Invalid line number for issue"); tracking.track(sourceHashHolder, Collections.<ServerIssue>emptyList(), newArrayList(newIssue)); } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetLine.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetLine.java deleted file mode 100644 index 8073e179280..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetLine.java +++ /dev/null @@ -1,29 +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.core.issue.workflow; - -enum UnsetLine implements Function { - INSTANCE; - @Override - public void execute(Context context) { - context.setLine(null); - } - -} |