]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3755 exclude false-positives from issue counters
authorSimon Brandhof <simon.brandhof@gmail.com>
Mon, 20 May 2013 18:53:29 +0000 (20:53 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Mon, 20 May 2013 18:53:52 +0000 (20:53 +0200)
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/CountFalsePositivesDecorator.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/CountOpenIssuesDecorator.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueCountersDecorator.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/CountFalsePositivesDecoratorTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/CountOpenIssuesDecoratorTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueCountersDecoratorTest.java [deleted file]

index 2fa1725d52aed07a58240fe8268b1e31baec7925..cc3b9e380d26d3fbaa68826e67a8800882d5cdf3 100644 (file)
@@ -399,7 +399,8 @@ public final class CorePlugin extends SonarPlugin {
       // issues
       IssueHandlers.class,
       IssueFilters.class,
-      IssueCountersDecorator.class,
+      CountOpenIssuesDecorator.class,
+      CountFalsePositivesDecorator.class,
       WeightedIssuesDecorator.class,
       IssuesDensityDecorator.class,
       InitialOpenIssuesSensor.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/CountFalsePositivesDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/CountFalsePositivesDecorator.java
new file mode 100644 (file)
index 0000000..6fab0f8
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.issue;
+
+import org.sonar.api.batch.*;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+/**
+ * Computes the number of false-positives
+ *
+ * @since 3.6
+ */
+@DependsUpon(DecoratorBarriers.END_OF_ISSUES_UPDATES)
+public class CountFalsePositivesDecorator implements Decorator {
+
+  private final ResourcePerspectives perspectives;
+
+  public CountFalsePositivesDecorator(ResourcePerspectives perspectives) {
+    this.perspectives = perspectives;
+  }
+
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  @DependedUpon
+  public Metric generatesFalsePositiveMeasure() {
+    return CoreMetrics.FALSE_POSITIVE_ISSUES;
+  }
+
+  public void decorate(Resource resource, DecoratorContext context) {
+    Issuable issuable = perspectives.as(Issuable.class, resource);
+    if (issuable != null) {
+      int falsePositives = 0;
+      for (Issue issue : issuable.issues()) {
+        if (Issue.RESOLUTION_FALSE_POSITIVE.equals(issue.resolution())) {
+          falsePositives++;
+        }
+      }
+      saveMeasure(context, CoreMetrics.FALSE_POSITIVE_ISSUES, falsePositives);
+    }
+  }
+
+  private void saveMeasure(DecoratorContext context, Metric metric, int value) {
+    context.saveMeasure(metric, (double) (value + sumChildren(context, metric)));
+  }
+
+  private int sumChildren(DecoratorContext context, Metric metric) {
+    return MeasureUtils.sum(true, context.getChildrenMeasures(metric)).intValue();
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/CountOpenIssuesDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/CountOpenIssuesDecorator.java
new file mode 100644 (file)
index 0000000..4a4a51f
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.issue;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+import com.google.common.collect.*;
+import org.sonar.api.batch.*;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.*;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.TimeMachineConfiguration;
+
+import javax.annotation.Nullable;
+
+import java.util.*;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * Computes metrics related to number of issues.
+ *
+ * @since 3.6
+ */
+@DependsUpon(DecoratorBarriers.END_OF_ISSUES_UPDATES)
+public class CountOpenIssuesDecorator implements Decorator {
+
+  private final ResourcePerspectives perspectives;
+  private final RuleFinder rulefinder;
+  private final TimeMachineConfiguration timeMachineConfiguration;
+
+  public CountOpenIssuesDecorator(ResourcePerspectives perspectives, RuleFinder rulefinder, TimeMachineConfiguration timeMachineConfiguration) {
+    this.perspectives = perspectives;
+    this.rulefinder = rulefinder;
+    this.timeMachineConfiguration = timeMachineConfiguration;
+  }
+
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  @DependedUpon
+  public List<Metric> generatesIssuesMetrics() {
+    return ImmutableList.of(
+      CoreMetrics.ISSUES,
+      CoreMetrics.BLOCKER_ISSUES,
+      CoreMetrics.CRITICAL_ISSUES,
+      CoreMetrics.MAJOR_ISSUES,
+      CoreMetrics.MINOR_ISSUES,
+      CoreMetrics.INFO_ISSUES,
+      CoreMetrics.NEW_ISSUES,
+      CoreMetrics.NEW_BLOCKER_ISSUES,
+      CoreMetrics.NEW_CRITICAL_ISSUES,
+      CoreMetrics.NEW_MAJOR_ISSUES,
+      CoreMetrics.NEW_MINOR_ISSUES,
+      CoreMetrics.NEW_INFO_ISSUES,
+      CoreMetrics.UNASSIGNED_ISSUES
+    );
+  }
+
+  public void decorate(Resource resource, DecoratorContext context) {
+    Issuable issuable = perspectives.as(Issuable.class, resource);
+    if (issuable != null) {
+      Collection<Issue> issues = getOpenIssues(issuable.issues());
+      boolean shouldSaveNewMetrics = shouldSaveNewMetrics(context);
+
+      Multiset<RulePriority> severityBag = HashMultiset.create();
+      Map<RulePriority, Multiset<Rule>> rulesPerSeverity = Maps.newHashMap();
+      ListMultimap<RulePriority, Issue> issuesPerSeverity = ArrayListMultimap.create();
+      int countUnassigned = 0;
+
+      for (Issue issue : issues) {
+        severityBag.add(RulePriority.valueOf(issue.severity()));
+        Multiset<Rule> rulesBag = initRules(rulesPerSeverity, RulePriority.valueOf(issue.severity()));
+        rulesBag.add(rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule()));
+        issuesPerSeverity.put(RulePriority.valueOf(issue.severity()), issue);
+
+        if (issue.assignee() == null) {
+          countUnassigned++;
+        }
+      }
+
+      for (RulePriority ruleSeverity : RulePriority.values()) {
+        saveIssuesForSeverity(context, ruleSeverity, severityBag);
+        saveIssuesPerRules(context, ruleSeverity, rulesPerSeverity);
+        saveNewIssuesForSeverity(context, ruleSeverity, issuesPerSeverity, shouldSaveNewMetrics);
+        saveNewIssuesPerRule(context, ruleSeverity, issues, shouldSaveNewMetrics);
+      }
+
+      saveTotalIssues(context, issues);
+      saveNewIssues(context, issues, shouldSaveNewMetrics);
+
+      saveMeasure(context, CoreMetrics.UNASSIGNED_ISSUES, countUnassigned);
+    }
+  }
+
+  private void saveTotalIssues(DecoratorContext context, Collection<Issue> issues) {
+    if (context.getMeasure(CoreMetrics.ISSUES) == null) {
+      Collection<Measure> childrenIssues = context.getChildrenMeasures(CoreMetrics.ISSUES);
+      Double sum = MeasureUtils.sum(true, childrenIssues);
+      context.saveMeasure(CoreMetrics.ISSUES, sum + issues.size());
+    }
+  }
+
+  private void saveNewIssues(DecoratorContext context, Collection<Issue> issues, boolean shouldSaveNewMetrics) {
+    if (shouldSaveNewMetrics) {
+      Measure measure = new Measure(CoreMetrics.NEW_ISSUES);
+      saveNewIssues(context, measure, issues);
+    }
+  }
+
+  private void saveIssuesForSeverity(DecoratorContext context, RulePriority ruleSeverity, Multiset<RulePriority> severitiesBag) {
+    Metric metric = SeverityUtils.severityToIssueMetric(ruleSeverity);
+    if (context.getMeasure(metric) == null) {
+      Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric));
+      int sum = MeasureUtils.sum(true, children).intValue() + severitiesBag.count(ruleSeverity);
+      context.saveMeasure(metric, (double) sum);
+    }
+  }
+
+  private void saveNewIssuesForSeverity(DecoratorContext context, RulePriority severity, ListMultimap<RulePriority, Issue> issuesPerSeverities, boolean shouldSaveNewMetrics) {
+    if (shouldSaveNewMetrics) {
+      Metric metric = SeverityUtils.severityToNewMetricIssue(severity);
+      Measure measure = new Measure(metric);
+      saveNewIssues(context, measure, issuesPerSeverities.get(severity));
+    }
+  }
+
+  private void saveIssuesPerRules(DecoratorContext context, RulePriority severity, Map<RulePriority, Multiset<Rule>> rulesPerSeverity) {
+    Metric metric = SeverityUtils.severityToIssueMetric(severity);
+
+    Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
+    for (Measure child : children) {
+      RuleMeasure childRuleMeasure = (RuleMeasure) child;
+      Rule rule = childRuleMeasure.getRule();
+      if (rule != null && MeasureUtils.hasValue(childRuleMeasure)) {
+        Multiset<Rule> rulesBag = initRules(rulesPerSeverity, severity);
+        rulesBag.add(rule, childRuleMeasure.getIntValue());
+      }
+    }
+
+    Multiset<Rule> rulesBag = rulesPerSeverity.get(severity);
+    if (rulesBag != null) {
+      for (Multiset.Entry<Rule> entry : rulesBag.entrySet()) {
+        RuleMeasure measure = RuleMeasure.createForRule(metric, entry.getElement(), (double) entry.getCount());
+        measure.setSeverity(severity);
+        context.saveMeasure(measure);
+      }
+    }
+  }
+
+  private void saveNewIssuesPerRule(DecoratorContext context, RulePriority severity, Collection<Issue> issues, boolean shouldSaveNewMetrics) {
+    if (shouldSaveNewMetrics) {
+      Metric metric = SeverityUtils.severityToNewMetricIssue(severity);
+      ListMultimap<Rule, Measure> childMeasuresPerRule = ArrayListMultimap.create();
+      ListMultimap<Rule, Issue> issuesPerRule = ArrayListMultimap.create();
+      Set<Rule> rules = Sets.newHashSet();
+
+      Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
+      for (Measure child : children) {
+        RuleMeasure childRuleMeasure = (RuleMeasure) child;
+        Rule rule = childRuleMeasure.getRule();
+        if (rule != null) {
+          childMeasuresPerRule.put(rule, childRuleMeasure);
+          rules.add(rule);
+        }
+      }
+
+      for (Issue issue : issues) {
+        if (RulePriority.valueOf(issue.severity()).equals(severity)) {
+          Rule rule = rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule());
+          rules.add(rule);
+          issuesPerRule.put(rule, issue);
+        }
+      }
+
+      for (Rule rule : rules) {
+        RuleMeasure measure = RuleMeasure.createForRule(metric, rule, null);
+        measure.setSeverity(severity);
+        for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
+          int variationIndex = pastSnapshot.getIndex();
+          int count = countIssuesAfterDate(issuesPerRule.get(rule), pastSnapshot.getTargetDate());
+          double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRule.get(rule)) + count;
+          measure.setVariation(variationIndex, sum);
+        }
+        context.saveMeasure(measure);
+      }
+    }
+  }
+
+  private void saveNewIssues(DecoratorContext context, Measure measure, Collection<Issue> issues) {
+    for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
+      int variationIndex = pastSnapshot.getIndex();
+      Collection<Measure> children = context.getChildrenMeasures(measure.getMetric());
+      int count = countIssuesAfterDate(issues, pastSnapshot.getTargetDate());
+      double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count;
+      measure.setVariation(variationIndex, sum);
+    }
+    context.saveMeasure(measure);
+  }
+
+  private void saveMeasure(DecoratorContext context, Metric metric, int value) {
+    context.saveMeasure(metric, (double) (value + sumChildren(context, metric)));
+  }
+
+  private int sumChildren(DecoratorContext context, Metric metric) {
+    int sum = 0;
+    if (!ResourceUtils.isFile(context.getResource())) {
+      sum = MeasureUtils.sum(true, context.getChildrenMeasures(metric)).intValue();
+    }
+    return sum;
+  }
+
+  private Multiset<Rule> initRules(Map<RulePriority, Multiset<Rule>> rulesPerSeverity, RulePriority severity) {
+    Multiset<Rule> rulesBag = rulesPerSeverity.get(severity);
+    if (rulesBag == null) {
+      rulesBag = HashMultiset.create();
+      rulesPerSeverity.put(severity, rulesBag);
+    }
+    return rulesBag;
+  }
+
+  @VisibleForTesting
+  int countIssuesAfterDate(Collection<Issue> issues, Date targetDate) {
+    if (issues == null) {
+      return 0;
+    }
+    int count = 0;
+    for (Issue issue : issues) {
+      if (isAfter(issue, targetDate)) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  private boolean isAfter(Issue issue, @Nullable Date date) {
+    return date == null || issue.creationDate() != null && issue.creationDate().after(date);
+  }
+
+  private boolean shouldSaveNewMetrics(DecoratorContext context) {
+    return context.getProject().isLatestAnalysis() && context.getMeasure(CoreMetrics.NEW_ISSUES) == null;
+  }
+
+  private Collection<Issue> getOpenIssues(Collection<Issue> issues) {
+    return newArrayList(Iterables.filter(issues, new Predicate<Issue>() {
+      @Override
+      public boolean apply(final Issue issue) {
+        return issue.resolution()==null;
+      }
+    }));
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueCountersDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueCountersDecorator.java
deleted file mode 100644 (file)
index 3f82358..0000000
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.plugins.core.issue;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicate;
-import com.google.common.collect.*;
-import org.sonar.api.batch.*;
-import org.sonar.api.component.ResourcePerspectives;
-import org.sonar.api.issue.Issuable;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.measures.*;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.resources.ResourceUtils;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
-
-import javax.annotation.Nullable;
-
-import java.util.*;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-/**
- * Computes metrics related to number of issues.
- *
- * @since 3.6
- */
-@DependsUpon(DecoratorBarriers.END_OF_ISSUES_UPDATES)
-public class IssueCountersDecorator implements Decorator {
-
-  private final ResourcePerspectives perspectives;
-  private final RuleFinder rulefinder;
-  private final TimeMachineConfiguration timeMachineConfiguration;
-
-  public IssueCountersDecorator(ResourcePerspectives perspectives, RuleFinder rulefinder, TimeMachineConfiguration timeMachineConfiguration) {
-    this.perspectives = perspectives;
-    this.rulefinder = rulefinder;
-    this.timeMachineConfiguration = timeMachineConfiguration;
-  }
-
-  public boolean shouldExecuteOnProject(Project project) {
-    return true;
-  }
-
-  @DependedUpon
-  public List<Metric> generatesIssuesMetrics() {
-    return ImmutableList.of(
-      CoreMetrics.ISSUES,
-      CoreMetrics.BLOCKER_ISSUES,
-      CoreMetrics.CRITICAL_ISSUES,
-      CoreMetrics.MAJOR_ISSUES,
-      CoreMetrics.MINOR_ISSUES,
-      CoreMetrics.INFO_ISSUES,
-      CoreMetrics.NEW_ISSUES,
-      CoreMetrics.NEW_BLOCKER_ISSUES,
-      CoreMetrics.NEW_CRITICAL_ISSUES,
-      CoreMetrics.NEW_MAJOR_ISSUES,
-      CoreMetrics.NEW_MINOR_ISSUES,
-      CoreMetrics.NEW_INFO_ISSUES,
-      CoreMetrics.FALSE_POSITIVE_ISSUES,
-      CoreMetrics.UNASSIGNED_ISSUES
-    );
-  }
-
-  public void decorate(Resource resource, DecoratorContext context) {
-    Issuable issuable = perspectives.as(Issuable.class, resource);
-    if (issuable != null) {
-      Collection<Issue> issues = getOpenIssues(issuable.issues());
-      boolean shouldSaveNewMetrics = shouldSaveNewMetrics(context);
-
-      Multiset<RulePriority> severitiesBag = HashMultiset.create();
-      Map<RulePriority, Multiset<Rule>> rulesPerSeverity = Maps.newHashMap();
-      ListMultimap<RulePriority, Issue> issuesPerSeverities = ArrayListMultimap.create();
-      int countUnassigned = 0;
-      int falsePositives = 0;
-
-      for (Issue issue : issues) {
-        severitiesBag.add(RulePriority.valueOf(issue.severity()));
-        Multiset<Rule> rulesBag = initRules(rulesPerSeverity, RulePriority.valueOf(issue.severity()));
-        rulesBag.add(rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule()));
-        issuesPerSeverities.put(RulePriority.valueOf(issue.severity()), issue);
-
-        if (issue.assignee() == null) {
-          countUnassigned++;
-        }
-        if (Issue.RESOLUTION_FALSE_POSITIVE.equals(issue.resolution())) {
-          falsePositives++;
-        }
-      }
-
-      for (RulePriority ruleSeverity : RulePriority.values()) {
-        saveIssuesForSeverity(context, ruleSeverity, severitiesBag);
-        saveIssuesPerRules(context, ruleSeverity, rulesPerSeverity);
-        saveNewIssuesForSeverity(context, ruleSeverity, issuesPerSeverities, shouldSaveNewMetrics);
-        saveNewIssuesPerRule(context, ruleSeverity, issues, shouldSaveNewMetrics);
-      }
-
-      saveTotalIssues(context, issues);
-      saveNewIssues(context, issues, shouldSaveNewMetrics);
-
-      saveMeasure(context, CoreMetrics.UNASSIGNED_ISSUES, countUnassigned);
-      saveMeasure(context, CoreMetrics.FALSE_POSITIVE_ISSUES, falsePositives);
-    }
-  }
-
-  private void saveTotalIssues(DecoratorContext context, Collection<Issue> issues) {
-    if (context.getMeasure(CoreMetrics.ISSUES) == null) {
-      Collection<Measure> childrenIssues = context.getChildrenMeasures(CoreMetrics.ISSUES);
-      Double sum = MeasureUtils.sum(true, childrenIssues);
-      context.saveMeasure(CoreMetrics.ISSUES, sum + issues.size());
-    }
-  }
-
-  private void saveNewIssues(DecoratorContext context, Collection<Issue> issues, boolean shouldSaveNewMetrics) {
-    if (shouldSaveNewMetrics) {
-      Measure measure = new Measure(CoreMetrics.NEW_ISSUES);
-      saveNewIssues(context, measure, issues);
-    }
-  }
-
-  private void saveIssuesForSeverity(DecoratorContext context, RulePriority ruleSeverity, Multiset<RulePriority> severitiesBag) {
-    Metric metric = SeverityUtils.severityToIssueMetric(ruleSeverity);
-    if (context.getMeasure(metric) == null) {
-      Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric));
-      int sum = MeasureUtils.sum(true, children).intValue() + severitiesBag.count(ruleSeverity);
-      context.saveMeasure(metric, (double) sum);
-    }
-  }
-
-  private void saveNewIssuesForSeverity(DecoratorContext context, RulePriority severity, ListMultimap<RulePriority, Issue> issuesPerSeverities, boolean shouldSaveNewMetrics) {
-    if (shouldSaveNewMetrics) {
-      Metric metric = SeverityUtils.severityToNewMetricIssue(severity);
-      Measure measure = new Measure(metric);
-      saveNewIssues(context, measure, issuesPerSeverities.get(severity));
-    }
-  }
-
-  private void saveIssuesPerRules(DecoratorContext context, RulePriority severity, Map<RulePriority, Multiset<Rule>> rulesPerSeverity) {
-    Metric metric = SeverityUtils.severityToIssueMetric(severity);
-
-    Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
-    for (Measure child : children) {
-      RuleMeasure childRuleMeasure = (RuleMeasure) child;
-      Rule rule = childRuleMeasure.getRule();
-      if (rule != null && MeasureUtils.hasValue(childRuleMeasure)) {
-        Multiset<Rule> rulesBag = initRules(rulesPerSeverity, severity);
-        rulesBag.add(rule, childRuleMeasure.getIntValue());
-      }
-    }
-
-    Multiset<Rule> rulesBag = rulesPerSeverity.get(severity);
-    if (rulesBag != null) {
-      for (Multiset.Entry<Rule> entry : rulesBag.entrySet()) {
-        RuleMeasure measure = RuleMeasure.createForRule(metric, entry.getElement(), (double) entry.getCount());
-        measure.setSeverity(severity);
-        context.saveMeasure(measure);
-      }
-    }
-  }
-
-  private void saveNewIssuesPerRule(DecoratorContext context, RulePriority severity, Collection<Issue> issues, boolean shouldSaveNewMetrics) {
-    if (shouldSaveNewMetrics) {
-      Metric metric = SeverityUtils.severityToNewMetricIssue(severity);
-      ListMultimap<Rule, Measure> childMeasuresPerRule = ArrayListMultimap.create();
-      ListMultimap<Rule, Issue> issuesPerRule = ArrayListMultimap.create();
-      Set<Rule> rules = Sets.newHashSet();
-
-      Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
-      for (Measure child : children) {
-        RuleMeasure childRuleMeasure = (RuleMeasure) child;
-        Rule rule = childRuleMeasure.getRule();
-        if (rule != null) {
-          childMeasuresPerRule.put(rule, childRuleMeasure);
-          rules.add(rule);
-        }
-      }
-
-      for (Issue issue : issues) {
-        if (RulePriority.valueOf(issue.severity()).equals(severity)) {
-          Rule rule = rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule());
-          rules.add(rule);
-          issuesPerRule.put(rule, issue);
-        }
-      }
-
-      for (Rule rule : rules) {
-        RuleMeasure measure = RuleMeasure.createForRule(metric, rule, null);
-        measure.setSeverity(severity);
-        for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
-          int variationIndex = pastSnapshot.getIndex();
-          int count = countIssuesAfterDate(issuesPerRule.get(rule), pastSnapshot.getTargetDate());
-          double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRule.get(rule)) + count;
-          measure.setVariation(variationIndex, sum);
-        }
-        context.saveMeasure(measure);
-      }
-    }
-  }
-
-  private void saveNewIssues(DecoratorContext context, Measure measure, Collection<Issue> issues) {
-    for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
-      int variationIndex = pastSnapshot.getIndex();
-      Collection<Measure> children = context.getChildrenMeasures(measure.getMetric());
-      int count = countIssuesAfterDate(issues, pastSnapshot.getTargetDate());
-      double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count;
-      measure.setVariation(variationIndex, sum);
-    }
-    context.saveMeasure(measure);
-  }
-
-  private void saveMeasure(DecoratorContext context, Metric metric, int value) {
-    context.saveMeasure(metric, (double) (value + sumChildren(context, metric)));
-  }
-
-  private int sumChildren(DecoratorContext context, Metric metric) {
-    int sum = 0;
-    if (!ResourceUtils.isFile(context.getResource())) {
-      sum = MeasureUtils.sum(true, context.getChildrenMeasures(metric)).intValue();
-    }
-    return sum;
-  }
-
-  private Multiset<Rule> initRules(Map<RulePriority, Multiset<Rule>> rulesPerSeverity, RulePriority severity) {
-    Multiset<Rule> rulesBag = rulesPerSeverity.get(severity);
-    if (rulesBag == null) {
-      rulesBag = HashMultiset.create();
-      rulesPerSeverity.put(severity, rulesBag);
-    }
-    return rulesBag;
-  }
-
-  @VisibleForTesting
-  int countIssuesAfterDate(Collection<Issue> issues, Date targetDate) {
-    if (issues == null) {
-      return 0;
-    }
-    int count = 0;
-    for (Issue issue : issues) {
-      if (isAfter(issue, targetDate)) {
-        count++;
-      }
-    }
-    return count;
-  }
-
-  private boolean isAfter(Issue issue, @Nullable Date date) {
-    return date == null || issue.creationDate() != null && issue.creationDate().after(date);
-  }
-
-  private boolean shouldSaveNewMetrics(DecoratorContext context) {
-    return context.getProject().isLatestAnalysis() && context.getMeasure(CoreMetrics.NEW_ISSUES) == null;
-  }
-
-  private Collection<Issue> getOpenIssues(Collection<Issue> issues) {
-    return newArrayList(Iterables.filter(issues, new Predicate<Issue>() {
-      @Override
-      public boolean apply(final Issue issue) {
-        return !Issue.STATUS_CLOSED.equals(issue.status());
-      }
-    }));
-  }
-
-  @Override
-  public String toString() {
-    return getClass().getSimpleName();
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/CountFalsePositivesDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/CountFalsePositivesDecoratorTest.java
new file mode 100644 (file)
index 0000000..714c8ac
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.issue;
+
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.java.api.JavaClass;
+
+import java.util.Arrays;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CountFalsePositivesDecoratorTest {
+
+  ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+  CountFalsePositivesDecorator decorator = new CountFalsePositivesDecorator(perspectives);
+
+  @Test
+  public void should_count_false_positives() {
+    DefaultIssue falsePositive = new DefaultIssue().setRuleKey(RuleKey.parse("squid:AvoidCycles"))
+      .setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_OPEN);
+    DefaultIssue open = new DefaultIssue().setRuleKey(RuleKey.parse("squid:AvoidCycles"))
+      .setResolution(null).setStatus(Issue.STATUS_OPEN);
+
+    File file = new File("foo.c");
+    Issuable issuable = mock(Issuable.class);
+    when(perspectives.as(Issuable.class, file)).thenReturn(issuable);
+    when(issuable.issues()).thenReturn(Arrays.<Issue>asList(falsePositive, open));
+
+    DecoratorContext context = mock(DecoratorContext.class);
+    decorator.decorate(file, context);
+
+    verify(context).saveMeasure(CoreMetrics.FALSE_POSITIVE_ISSUES, 1.0);
+  }
+
+  @Test
+  public void should_declare_metadata() {
+    assertThat(decorator.shouldExecuteOnProject(new Project("foo"))).isTrue();
+    assertThat(decorator.generatesFalsePositiveMeasure()).isEqualTo(CoreMetrics.FALSE_POSITIVE_ISSUES);
+    assertThat(decorator.toString()).isEqualTo("CountFalsePositivesDecorator");
+  }
+
+  @Test
+  public void should_ignore_classes_and_methods() {
+    JavaClass javaClass = JavaClass.create("Foo.java");
+    when(perspectives.as(Issuable.class, javaClass)).thenReturn(null);
+
+    DecoratorContext context = mock(DecoratorContext.class);
+    decorator.decorate(javaClass, context);
+
+    verifyZeroInteractions(context);
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/CountOpenIssuesDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/CountOpenIssuesDecoratorTest.java
new file mode 100644 (file)
index 0000000..109aa46
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.issue;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.*;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.test.IsRuleMeasure;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.core.issue.DefaultIssue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CountOpenIssuesDecoratorTest {
+
+  CountOpenIssuesDecorator decorator;
+  TimeMachineConfiguration timeMachineConfiguration;
+  RuleFinder ruleFinder;
+  Issuable issuable;
+  DecoratorContext context;
+  Resource resource;
+  Project project;
+  Rule ruleA1;
+  Rule ruleA2;
+  Rule ruleB1;
+  Date rightNow;
+  Date tenDaysAgo;
+  Date fiveDaysAgo;
+
+  @Before
+  public void before() {
+    ruleA1 = Rule.create().setRepositoryKey("ruleA1").setKey("ruleA1").setName("nameA1");
+    ruleA2 = Rule.create().setRepositoryKey("ruleA2").setKey("ruleA2").setName("nameA2");
+    ruleB1 = Rule.create().setRepositoryKey("ruleB1").setKey("ruleB1").setName("nameB1");
+
+    ruleFinder = mock(RuleFinder.class);
+    when(ruleFinder.findByKey(ruleA1.getRepositoryKey(), ruleA1.getKey())).thenReturn(ruleA1);
+    when(ruleFinder.findByKey(ruleA2.getRepositoryKey(), ruleA2.getKey())).thenReturn(ruleA2);
+    when(ruleFinder.findByKey(ruleB1.getRepositoryKey(), ruleB1.getKey())).thenReturn(ruleB1);
+
+    rightNow = new Date();
+    tenDaysAgo = DateUtils.addDays(rightNow, -10);
+    fiveDaysAgo = DateUtils.addDays(rightNow, -5);
+
+    PastSnapshot pastSnapshot = mock(PastSnapshot.class);
+    when(pastSnapshot.getIndex()).thenReturn(1);
+    when(pastSnapshot.getTargetDate()).thenReturn(fiveDaysAgo);
+
+    PastSnapshot pastSnapshot2 = mock(PastSnapshot.class);
+    when(pastSnapshot2.getIndex()).thenReturn(2);
+    when(pastSnapshot2.getTargetDate()).thenReturn(tenDaysAgo);
+
+    timeMachineConfiguration = mock(TimeMachineConfiguration.class);
+    when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Arrays.asList(pastSnapshot, pastSnapshot2));
+
+    project = mock(Project.class);
+    when(project.isLatestAnalysis()).thenReturn(true);
+
+    resource = mock(Resource.class);
+    context = mock(DecoratorContext.class);
+    when(context.getResource()).thenReturn(resource);
+    when(context.getProject()).thenReturn(project);
+    when(context.getMeasure(CoreMetrics.NEW_ISSUES)).thenReturn(null);
+
+    issuable = mock(Issuable.class);
+    ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+    when(perspectives.as(Issuable.class, resource)).thenReturn(issuable);
+    decorator = new CountOpenIssuesDecorator(perspectives, ruleFinder, timeMachineConfiguration);
+  }
+
+  @Test
+  public void should_be_depended_upon_metric() {
+    assertThat(decorator.generatesIssuesMetrics()).hasSize(13);
+  }
+
+  @Test
+  public void should_count_issues() {
+    when(resource.getScope()).thenReturn(Scopes.PROJECT);
+    when(issuable.issues()).thenReturn(createIssues());
+    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+    decorator.decorate(resource, context);
+
+    verify(context).saveMeasure(CoreMetrics.ISSUES, 4.0);
+  }
+
+  @Test
+  public void should_do_nothing_when_issuable_is_null() {
+    ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+    when(perspectives.as(Issuable.class, resource)).thenReturn(null);
+    CountOpenIssuesDecorator decorator = new CountOpenIssuesDecorator(perspectives, ruleFinder, timeMachineConfiguration);
+
+    decorator.decorate(resource, context);
+
+    verifyZeroInteractions(context);
+  }
+
+  /**
+   * See http://jira.codehaus.org/browse/SONAR-1729
+   */
+  @Test
+  public void should_not_count_issues_if_measure_already_exists() {
+    when(resource.getScope()).thenReturn(Scopes.PROJECT);
+    when(issuable.issues()).thenReturn(createIssues());
+    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+    when(context.getMeasure(CoreMetrics.ISSUES)).thenReturn(new Measure(CoreMetrics.ISSUES, 3000.0));
+    when(context.getMeasure(CoreMetrics.MAJOR_ISSUES)).thenReturn(new Measure(CoreMetrics.MAJOR_ISSUES, 500.0));
+
+    decorator.decorate(resource, context);
+
+    verify(context, never()).saveMeasure(eq(CoreMetrics.ISSUES), anyDouble());// not changed
+    verify(context, never()).saveMeasure(eq(CoreMetrics.MAJOR_ISSUES), anyDouble());// not changed
+    verify(context, times(1)).saveMeasure(eq(CoreMetrics.CRITICAL_ISSUES), anyDouble());// did not exist
+  }
+
+  @Test
+  public void should_save_zero_on_projects() {
+    when(resource.getScope()).thenReturn(Scopes.PROJECT);
+    when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList());
+    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+    decorator.decorate(resource, context);
+
+    verify(context).saveMeasure(CoreMetrics.ISSUES, 0.0);
+  }
+
+  @Test
+  public void should_save_zero_on_directories() {
+    when(resource.getScope()).thenReturn(Scopes.DIRECTORY);
+    when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList());
+    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+    decorator.decorate(resource, context);
+
+    verify(context).saveMeasure(CoreMetrics.ISSUES, 0.0);
+  }
+
+  @Test
+  public void should_count_issues_by_severity() {
+    when(resource.getScope()).thenReturn(Scopes.PROJECT);
+    when(issuable.issues()).thenReturn(createIssues());
+    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+    decorator.decorate(resource, context);
+
+    verify(context).saveMeasure(CoreMetrics.BLOCKER_ISSUES, 0.0);
+    verify(context).saveMeasure(CoreMetrics.CRITICAL_ISSUES, 2.0);
+    verify(context).saveMeasure(CoreMetrics.MAJOR_ISSUES, 1.0);
+    verify(context).saveMeasure(CoreMetrics.MINOR_ISSUES, 1.0);
+    verify(context).saveMeasure(CoreMetrics.INFO_ISSUES, 0.0);
+  }
+
+  @Test
+  public void should_count_issues_per_rule() {
+    List<Issue> issues = newArrayList();
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()));
+    when(issuable.issues()).thenReturn(issues);
+
+    decorator.decorate(resource, context);
+
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.CRITICAL_ISSUES, ruleA1, 2.0)));
+    verify(context, never()).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MAJOR_ISSUES, ruleA1, 0.0)));
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MAJOR_ISSUES, ruleA2, 1.0)));
+  }
+
+  @Test
+  public void should_save_unassigned_issues() {
+    List<Issue> issues = newArrayList();
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setStatus(Issue.STATUS_OPEN).setSeverity(RulePriority.CRITICAL.name()));
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setStatus(Issue.STATUS_REOPENED).setSeverity(RulePriority.CRITICAL.name()));
+    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setStatus(Issue.STATUS_OPEN).setAssignee("arthur").setSeverity(RulePriority.CRITICAL.name()));
+    when(issuable.issues()).thenReturn(issues);
+
+    decorator.decorate(resource, context);
+
+    verify(context).saveMeasure(CoreMetrics.UNASSIGNED_ISSUES, 2.0);
+  }
+
+  @Test
+  public void same_rule_should_have_different_severities() {
+    List<Issue> issues = newArrayList();
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.MINOR.name()));
+    when(issuable.issues()).thenReturn(issues);
+
+    decorator.decorate(resource, context);
+
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.CRITICAL_ISSUES, ruleA1, 2.0)));
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MINOR_ISSUES, ruleA1, 1.0)));
+  }
+
+  @Test
+  public void should_count_issues_after_date() {
+    List<Issue> issues = createIssuesForNewMetrics();
+
+    assertThat(decorator.countIssuesAfterDate(null, fiveDaysAgo)).isEqualTo(0);
+    assertThat(decorator.countIssuesAfterDate(issues, fiveDaysAgo)).isEqualTo(1); // 1 rightNow
+    assertThat(decorator.countIssuesAfterDate(issues, tenDaysAgo)).isEqualTo(3); // 1 rightNow + 2 fiveDaysAgo
+  }
+
+  @Test
+  public void should_clear_cache_after_execution() {
+    Issue issue1 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA1.getRepositoryKey(), ruleA1.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow);
+    Issue issue2 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA2.getRepositoryKey(), ruleA2.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow);
+    when(issuable.issues()).thenReturn(newArrayList(issue1)).thenReturn(newArrayList(issue2));
+
+    decorator.decorate(resource, context);
+    decorator.decorate(resource, context);
+
+    verify(context, times(2)).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0)));
+    verify(context, never()).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 2.0, 2.0)));
+  }
+
+  @Test
+  public void should_save_severity_new_issues() {
+    when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
+
+    decorator.decorate(resource, context);
+
+    // remember : period1 is 5daysAgo, period2 is 10daysAgo
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_BLOCKER_ISSUES, 0.0, 0.0)));
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0)));
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MAJOR_ISSUES, 0.0, 1.0)));
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MINOR_ISSUES, 0.0, 1.0)));
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_INFO_ISSUES, 0.0, 0.0)));
+  }
+
+  @Test
+  public void should_save_rule_new_issues() {
+    when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
+
+    decorator.decorate(resource, context);
+
+    // remember : period1 is 5daysAgo, period2 is 10daysAgo
+    verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, ruleA1, 1.0, 1.0)));
+    verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MAJOR_ISSUES, ruleA2, 0.0, 1.0)));
+    verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MINOR_ISSUES, ruleB1, 0.0, 1.0)));
+  }
+
+  @Test
+  public void should_not_save_new_issues_if_not_last_analysis() {
+    when(project.isLatestAnalysis()).thenReturn(false);
+    when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
+
+    decorator.decorate(resource, context);
+
+    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_BLOCKER_ISSUES)));
+    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_ISSUES)));
+    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MAJOR_ISSUES)));
+    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MINOR_ISSUES)));
+    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_INFO_ISSUES)));
+    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_ISSUES)));
+  }
+
+  List<Issue> createIssues() {
+    List<Issue> issues = newArrayList();
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setStatus(Issue.STATUS_OPEN));
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setStatus(Issue.STATUS_REOPENED));
+    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setStatus(Issue.STATUS_REOPENED));
+    issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setStatus(Issue.STATUS_OPEN));
+
+    // resolved
+    issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setResolution(Issue.RESOLUTION_FIXED).setStatus(Issue.STATUS_RESOLVED));
+    return issues;
+  }
+
+  List<Issue> createIssuesForNewMetrics() {
+    List<Issue> issues = newArrayList();
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow).setStatus(Issue.STATUS_OPEN));
+    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_OPEN));
+    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreationDate(fiveDaysAgo).setStatus(Issue.STATUS_REOPENED));
+    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_REOPENED));
+    issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreationDate(fiveDaysAgo).setStatus(Issue.STATUS_OPEN));
+    issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_OPEN));
+    return issues;
+  }
+
+  class IsVariationRuleMeasure extends ArgumentMatcher<Measure> {
+    Metric metric = null;
+    Rule rule = null;
+    Double var1 = null;
+    Double var2 = null;
+
+    public IsVariationRuleMeasure(Metric metric, Rule rule, Double var1, Double var2) {
+      this.metric = metric;
+      this.rule = rule;
+      this.var1 = var1;
+      this.var2 = var2;
+    }
+
+    public boolean matches(Object o) {
+      if (!(o instanceof RuleMeasure)) {
+        return false;
+      }
+      RuleMeasure m = (RuleMeasure) o;
+      return ObjectUtils.equals(metric, m.getMetric()) &&
+        ObjectUtils.equals(rule, m.getRule()) &&
+        ObjectUtils.equals(var1, m.getVariation1()) &&
+        ObjectUtils.equals(var2, m.getVariation2());
+    }
+  }
+
+  class IsVariationMeasure extends ArgumentMatcher<Measure> {
+    Metric metric = null;
+    Double var1 = null;
+    Double var2 = null;
+
+    public IsVariationMeasure(Metric metric, Double var1, Double var2) {
+      this.metric = metric;
+      this.var1 = var1;
+      this.var2 = var2;
+    }
+
+    public boolean matches(Object o) {
+      if (!(o instanceof Measure)) {
+        return false;
+      }
+      Measure m = (Measure) o;
+      return ObjectUtils.equals(metric, m.getMetric()) &&
+        ObjectUtils.equals(var1, m.getVariation1()) &&
+        ObjectUtils.equals(var2, m.getVariation2()) &&
+        !(m instanceof RuleMeasure);
+    }
+  }
+
+  class IsMetricMeasure extends ArgumentMatcher<Measure> {
+    Metric metric = null;
+
+    public IsMetricMeasure(Metric metric) {
+      this.metric = metric;
+    }
+
+    public boolean matches(Object o) {
+      if (!(o instanceof Measure)) {
+        return false;
+      }
+      Measure m = (Measure) o;
+      return ObjectUtils.equals(metric, m.getMetric());
+    }
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueCountersDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueCountersDecoratorTest.java
deleted file mode 100644 (file)
index 572f983..0000000
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.plugins.core.issue;
-
-import com.google.common.collect.Lists;
-import org.apache.commons.lang.ObjectUtils;
-import org.apache.commons.lang.time.DateUtils;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentMatcher;
-import org.sonar.api.batch.DecoratorContext;
-import org.sonar.api.component.ResourcePerspectives;
-import org.sonar.api.issue.Issuable;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.measures.*;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.resources.Scopes;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.test.IsRuleMeasure;
-import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
-import org.sonar.core.issue.DefaultIssue;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class IssueCountersDecoratorTest {
-
-  private IssueCountersDecorator decorator;
-  private TimeMachineConfiguration timeMachineConfiguration;
-  private RuleFinder rulefinder;
-  private Issuable issuable;
-  private DecoratorContext context;
-  private Resource resource;
-  private Project project;
-  private Rule ruleA1;
-  private Rule ruleA2;
-  private Rule ruleB1;
-  private Date rightNow;
-  private Date tenDaysAgo;
-  private Date fiveDaysAgo;
-
-  @Before
-  public void before() {
-    ruleA1 = Rule.create().setRepositoryKey("ruleA1").setKey("ruleA1").setName("nameA1");
-    ruleA2 = Rule.create().setRepositoryKey("ruleA2").setKey("ruleA2").setName("nameA2");
-    ruleB1 = Rule.create().setRepositoryKey("ruleB1").setKey("ruleB1").setName("nameB1");
-
-    rulefinder = mock(RuleFinder.class);
-    when(rulefinder.findByKey(ruleA1.getRepositoryKey(), ruleA1.getKey())).thenReturn(ruleA1);
-    when(rulefinder.findByKey(ruleA2.getRepositoryKey(), ruleA2.getKey())).thenReturn(ruleA2);
-    when(rulefinder.findByKey(ruleB1.getRepositoryKey(), ruleB1.getKey())).thenReturn(ruleB1);
-
-    rightNow = new Date();
-    tenDaysAgo = DateUtils.addDays(rightNow, -10);
-    fiveDaysAgo = DateUtils.addDays(rightNow, -5);
-
-    PastSnapshot pastSnapshot = mock(PastSnapshot.class);
-    when(pastSnapshot.getIndex()).thenReturn(1);
-    when(pastSnapshot.getTargetDate()).thenReturn(fiveDaysAgo);
-
-    PastSnapshot pastSnapshot2 = mock(PastSnapshot.class);
-    when(pastSnapshot2.getIndex()).thenReturn(2);
-    when(pastSnapshot2.getTargetDate()).thenReturn(tenDaysAgo);
-
-    timeMachineConfiguration = mock(TimeMachineConfiguration.class);
-    when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Arrays.asList(pastSnapshot, pastSnapshot2));
-
-    project = mock(Project.class);
-    when(project.isLatestAnalysis()).thenReturn(true);
-
-    resource = mock(Resource.class);
-    context = mock(DecoratorContext.class);
-    when(context.getResource()).thenReturn(resource);
-    when(context.getProject()).thenReturn(project);
-    when(context.getMeasure(CoreMetrics.NEW_ISSUES)).thenReturn(null);
-
-    issuable = mock(Issuable.class);
-    ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
-    when(perspectives.as(Issuable.class, resource)).thenReturn(issuable);
-    decorator = new IssueCountersDecorator(perspectives, rulefinder, timeMachineConfiguration);
-  }
-
-  @Test
-  public void should_be_depended_upon_metric() {
-    assertThat(decorator.generatesIssuesMetrics()).hasSize(14);
-  }
-
-  @Test
-  public void should_count_issues() {
-    when(resource.getScope()).thenReturn(Scopes.PROJECT);
-    when(issuable.issues()).thenReturn(createIssues());
-    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(CoreMetrics.ISSUES, 4.0);
-  }
-
-  @Test
-  public void should_do_nothing_when_issuable_is_null() {
-    ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
-    when(perspectives.as(Issuable.class, resource)).thenReturn(null);
-    IssueCountersDecorator decorator = new IssueCountersDecorator(perspectives, rulefinder, timeMachineConfiguration);
-
-    decorator.decorate(resource, context);
-
-    verifyZeroInteractions(context);
-  }
-
-  /**
-   * See http://jira.codehaus.org/browse/SONAR-1729
-   */
-  @Test
-  public void should_not_count_issues_if_measure_already_exists() {
-    when(resource.getScope()).thenReturn(Scopes.PROJECT);
-    when(issuable.issues()).thenReturn(createIssues());
-    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
-    when(context.getMeasure(CoreMetrics.ISSUES)).thenReturn(new Measure(CoreMetrics.ISSUES, 3000.0));
-    when(context.getMeasure(CoreMetrics.MAJOR_ISSUES)).thenReturn(new Measure(CoreMetrics.MAJOR_ISSUES, 500.0));
-
-    decorator.decorate(resource, context);
-
-    verify(context, never()).saveMeasure(eq(CoreMetrics.ISSUES), anyDouble());// not changed
-    verify(context, never()).saveMeasure(eq(CoreMetrics.MAJOR_ISSUES), anyDouble());// not changed
-    verify(context, times(1)).saveMeasure(eq(CoreMetrics.CRITICAL_ISSUES), anyDouble());// did not exist
-  }
-
-  @Test
-  public void should_save_zero_on_projects() {
-    when(resource.getScope()).thenReturn(Scopes.PROJECT);
-    when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList());
-    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(CoreMetrics.ISSUES, 0.0);
-  }
-
-  @Test
-  public void should_save_zero_on_directories() {
-    when(resource.getScope()).thenReturn(Scopes.DIRECTORY);
-    when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList());
-    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(CoreMetrics.ISSUES, 0.0);
-  }
-
-  @Test
-  public void should_count_issues_by_severity() {
-    when(resource.getScope()).thenReturn(Scopes.PROJECT);
-    when(issuable.issues()).thenReturn(createIssues());
-    when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(CoreMetrics.BLOCKER_ISSUES, 0.0);
-    verify(context).saveMeasure(CoreMetrics.CRITICAL_ISSUES, 2.0);
-    verify(context).saveMeasure(CoreMetrics.MAJOR_ISSUES, 1.0);
-    verify(context).saveMeasure(CoreMetrics.MINOR_ISSUES, 1.0);
-    verify(context).saveMeasure(CoreMetrics.INFO_ISSUES, 0.0);
-  }
-
-  @Test
-  public void should_count_issues_per_rule() {
-    List<Issue> issues = newArrayList();
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
-    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()));
-    when(issuable.issues()).thenReturn(issues);
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.CRITICAL_ISSUES, ruleA1, 2.0)));
-    verify(context, never()).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MAJOR_ISSUES, ruleA1, 0.0)));
-    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MAJOR_ISSUES, ruleA2, 1.0)));
-  }
-
-  @Test
-  public void should_save_unassigned_issues() {
-    List<Issue> issues = newArrayList();
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setStatus(Issue.STATUS_OPEN).setSeverity(RulePriority.CRITICAL.name()));
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setStatus(Issue.STATUS_REOPENED).setSeverity(RulePriority.CRITICAL.name()));
-    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setStatus(Issue.STATUS_OPEN).setAssignee("arthur").setSeverity(RulePriority.CRITICAL.name()));
-    when(issuable.issues()).thenReturn(issues);
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(CoreMetrics.UNASSIGNED_ISSUES, 2.0);
-  }
-
-  @Test
-  public void should_save_false_positive_issues() {
-    List<Issue> issues = newArrayList();
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_OPEN).setSeverity(RulePriority.CRITICAL.name()));
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setResolution(Issue.RESOLUTION_FIXED).setStatus(Issue.STATUS_OPEN).setSeverity(RulePriority.CRITICAL.name()));
-    when(issuable.issues()).thenReturn(issues);
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(CoreMetrics.FALSE_POSITIVE_ISSUES, 1.0);
-  }
-
-  @Test
-  public void same_rule_should_have_different_severities() {
-    List<Issue> issues = newArrayList();
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.MINOR.name()));
-    when(issuable.issues()).thenReturn(issues);
-
-    decorator.decorate(resource, context);
-
-    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.CRITICAL_ISSUES, ruleA1, 2.0)));
-    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MINOR_ISSUES, ruleA1, 1.0)));
-  }
-
-  @Test
-  public void should_count_issues_after_date() {
-    List<Issue> issues = createIssuesForNewMetrics();
-
-    assertThat(decorator.countIssuesAfterDate(null, fiveDaysAgo)).isEqualTo(0);
-    assertThat(decorator.countIssuesAfterDate(issues, fiveDaysAgo)).isEqualTo(1); // 1 rightNow
-    assertThat(decorator.countIssuesAfterDate(issues, tenDaysAgo)).isEqualTo(3); // 1 rightNow + 2 fiveDaysAgo
-  }
-
-  @Test
-  public void should_clear_cache_after_execution() {
-    Issue issue1 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA1.getRepositoryKey(), ruleA1.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow);
-    Issue issue2 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA2.getRepositoryKey(), ruleA2.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow);
-    when(issuable.issues()).thenReturn(newArrayList(issue1)).thenReturn(newArrayList(issue2));
-
-    decorator.decorate(resource, context);
-    decorator.decorate(resource, context);
-
-    verify(context, times(2)).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0)));
-    verify(context, never()).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 2.0, 2.0)));
-  }
-
-  @Test
-  public void should_save_severity_new_issues() {
-    when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
-
-    decorator.decorate(resource, context);
-
-    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_BLOCKER_ISSUES, 0.0, 0.0)));
-    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0)));
-    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MAJOR_ISSUES, 0.0, 1.0)));
-    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MINOR_ISSUES, 0.0, 1.0)));
-    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_INFO_ISSUES, 0.0, 0.0)));
-  }
-
-  @Test
-  public void should_save_rule_new_issues() {
-    when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
-
-    decorator.decorate(resource, context);
-
-    // remember : period1 is 5daysAgo, period2 is 10daysAgo
-    verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, ruleA1, 1.0, 1.0)));
-    verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MAJOR_ISSUES, ruleA2, 0.0, 1.0)));
-    verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MINOR_ISSUES, ruleB1, 0.0, 1.0)));
-  }
-
-  @Test
-  public void should_not_save_new_issues_if_not_last_analysis() {
-    when(project.isLatestAnalysis()).thenReturn(false);
-    when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
-
-    decorator.decorate(resource, context);
-
-    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_BLOCKER_ISSUES)));
-    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_ISSUES)));
-    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MAJOR_ISSUES)));
-    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MINOR_ISSUES)));
-    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_INFO_ISSUES)));
-    verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_ISSUES)));
-  }
-
-  private List<Issue> createIssues() {
-    List<Issue> issues = newArrayList();
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setStatus(Issue.STATUS_OPEN));
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setStatus(Issue.STATUS_REOPENED));
-    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setStatus(Issue.STATUS_REOPENED));
-    issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setStatus(Issue.STATUS_OPEN));
-    return issues;
-  }
-
-  private List<Issue> createIssuesForNewMetrics() {
-    List<Issue> issues = newArrayList();
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow).setStatus(Issue.STATUS_OPEN));
-    issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_OPEN));
-    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreationDate(fiveDaysAgo).setStatus(Issue.STATUS_REOPENED));
-    issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_REOPENED));
-    issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreationDate(fiveDaysAgo).setStatus(Issue.STATUS_OPEN));
-    issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_OPEN));
-    return issues;
-  }
-
-  private class IsVariationRuleMeasure extends ArgumentMatcher<Measure> {
-    private Metric metric = null;
-    private Rule rule = null;
-    private Double var1 = null;
-    private Double var2 = null;
-
-    public IsVariationRuleMeasure(Metric metric, Rule rule, Double var1, Double var2) {
-      this.metric = metric;
-      this.rule = rule;
-      this.var1 = var1;
-      this.var2 = var2;
-    }
-
-    public boolean matches(Object o) {
-      if (!(o instanceof RuleMeasure)) {
-        return false;
-      }
-      RuleMeasure m = (RuleMeasure) o;
-      return ObjectUtils.equals(metric, m.getMetric()) &&
-        ObjectUtils.equals(rule, m.getRule()) &&
-        ObjectUtils.equals(var1, m.getVariation1()) &&
-        ObjectUtils.equals(var2, m.getVariation2());
-    }
-  }
-
-  private class IsVariationMeasure extends ArgumentMatcher<Measure> {
-    private Metric metric = null;
-    private Double var1 = null;
-    private Double var2 = null;
-
-    public IsVariationMeasure(Metric metric, Double var1, Double var2) {
-      this.metric = metric;
-      this.var1 = var1;
-      this.var2 = var2;
-    }
-
-    public boolean matches(Object o) {
-      if (!(o instanceof Measure)) {
-        return false;
-      }
-      Measure m = (Measure) o;
-      return ObjectUtils.equals(metric, m.getMetric()) &&
-        ObjectUtils.equals(var1, m.getVariation1()) &&
-        ObjectUtils.equals(var2, m.getVariation2()) &&
-        !(m instanceof RuleMeasure);
-    }
-  }
-
-  private class IsMetricMeasure extends ArgumentMatcher<Measure> {
-    private Metric metric = null;
-
-    public IsMetricMeasure(Metric metric) {
-      this.metric = metric;
-    }
-
-    public boolean matches(Object o) {
-      if (!(o instanceof Measure)) {
-        return false;
-      }
-      Measure m = (Measure) o;
-      return ObjectUtils.equals(metric, m.getMetric());
-    }
-  }
-}