--- /dev/null
+/*
+ * 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.DepthTraversalTypeAwareVisitor.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";
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}