]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4689 Batch API to get issues of the current module
authorSimon Brandhof <simon.brandhof@gmail.com>
Wed, 18 Sep 2013 14:11:11 +0000 (16:11 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Wed, 18 Sep 2013 14:11:37 +0000 (16:11 +0200)
12 files changed:
sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java
sonar-batch/src/main/java/org/sonar/batch/issue/DefaultModuleIssues.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java
sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java
sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java
sonar-batch/src/test/java/org/sonar/batch/issue/DefaultModuleIssuesTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java
sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/issue/ModuleIssues.java [new file with mode: 0644]

index caa4a98b0b22f187023801820254a6488a525def..ca8cf60059c82d02960c0b2ec70fa33974f48eae 100644 (file)
@@ -49,7 +49,7 @@ import org.sonar.batch.DefaultResourceCreationLock;
 import org.sonar.batch.ProjectTree;
 import org.sonar.batch.ResourceFilters;
 import org.sonar.batch.issue.DeprecatedViolations;
-import org.sonar.batch.issue.ScanIssues;
+import org.sonar.batch.issue.DefaultModuleIssues;
 import org.sonar.core.component.ComponentKeys;
 import org.sonar.core.component.ScanGraph;
 
@@ -82,7 +82,7 @@ public class DefaultIndex extends SonarIndex {
   private Map<Resource, Map<Resource, Dependency>> incomingDependenciesByResource = Maps.newHashMap();
   private ProjectTree projectTree;
   private final DeprecatedViolations deprecatedViolations;
-  private ScanIssues scanIssues;
+  private DefaultModuleIssues moduleIssues;
 
   public DefaultIndex(PersistenceManager persistence, DefaultResourceCreationLock lock, ProjectTree projectTree, MetricFinder metricFinder,
                       ScanGraph graph, DeprecatedViolations deprecatedViolations) {
@@ -124,12 +124,12 @@ public class DefaultIndex extends SonarIndex {
     return currentProject;
   }
 
-  public void setCurrentProject(Project project, ResourceFilters resourceFilters, ScanIssues scanIssues) {
+  public void setCurrentProject(Project project, ResourceFilters resourceFilters, DefaultModuleIssues moduleIssues) {
     this.currentProject = project;
 
     // the following components depend on the current module, so they need to be reloaded.
     this.resourceFilters = resourceFilters;
-    this.scanIssues = scanIssues;
+    this.moduleIssues = moduleIssues;
   }
 
   /**
@@ -378,7 +378,7 @@ public class DefaultIndex extends SonarIndex {
     violation.setSeverity(null);
 
     violation.setResource(bucket.getResource());
-    scanIssues.initAndAddViolation(violation);
+    moduleIssues.initAndAddViolation(violation);
   }
 
 
index 0be18b5dff9ead3714ad4e86be035ea655144d62..5b7c4c8092cdfd6363e7457a05e41838aca6ffe3 100644 (file)
@@ -33,13 +33,13 @@ import java.util.List;
  */
 public class DefaultIssuable implements Issuable {
 
-  private final ScanIssues scanIssues;
+  private final DefaultModuleIssues moduleIssues;
   private final IssueCache cache;
   private final Component component;
 
-  DefaultIssuable(Component component, ScanIssues scanIssues, IssueCache cache) {
+  DefaultIssuable(Component component, DefaultModuleIssues moduleIssues, IssueCache cache) {
     this.component = component;
-    this.scanIssues = scanIssues;
+    this.moduleIssues = moduleIssues;
     this.cache = cache;
   }
 
@@ -50,7 +50,7 @@ public class DefaultIssuable implements Issuable {
 
   @Override
   public boolean addIssue(Issue issue) {
-    return scanIssues.initAndAddIssue((DefaultIssue) issue);
+    return moduleIssues.initAndAddIssue((DefaultIssue) issue);
   }
 
   @SuppressWarnings("unchecked")
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultModuleIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultModuleIssues.java
new file mode 100644 (file)
index 0000000..cdd9f22
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.batch.issue;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.ModuleIssues;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Violation;
+import org.sonar.core.issue.DefaultIssueBuilder;
+
+import javax.annotation.Nullable;
+
+/**
+ * Initialize the issues raised during scan.
+ */
+public class DefaultModuleIssues implements ModuleIssues {
+
+  private final RulesProfile qProfile;
+  private final IssueCache cache;
+  private final Project project;
+  private final IssueFilters filters;
+
+  public DefaultModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters) {
+    this.qProfile = qProfile;
+    this.cache = cache;
+    this.project = project;
+    this.filters = filters;
+  }
+
+  public boolean initAndAddIssue(DefaultIssue issue) {
+    return initAndAddIssue(issue, null);
+  }
+
+  public boolean initAndAddViolation(Violation violation) {
+    DefaultIssue issue = newIssue(violation);
+    return initAndAddIssue(issue, violation);
+  }
+
+  private DefaultIssue newIssue(Violation violation) {
+    return (DefaultIssue) new DefaultIssueBuilder()
+      .componentKey(violation.getResource().getEffectiveKey())
+      .ruleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
+      .effortToFix(violation.getCost())
+      .line(violation.getLineId())
+      .message(violation.getMessage())
+      .severity(violation.getSeverity() != null ? violation.getSeverity().name() : null)
+      .build();
+  }
+
+  private boolean initAndAddIssue(DefaultIssue issue, @Nullable Violation violation) {
+    // TODO fail fast : if rule does not exist
+
+    ActiveRule activeRule = qProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule());
+    if (activeRule == null || activeRule.getRule() == null) {
+      // rule does not exist or is not enabled -> ignore the issue
+      return false;
+    }
+    issue.setCreationDate(project.getAnalysisDate());
+    issue.setUpdateDate(project.getAnalysisDate());
+    if (issue.severity() == null) {
+      issue.setSeverity(activeRule.getSeverity().name());
+    }
+
+    if (filters.accept(issue, violation)) {
+      cache.put(issue);
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public Iterable<Issue> issues() {
+    return (Iterable) Iterables.filter(cache.all(), new ModulePredicate(false));
+  }
+
+  @Override
+  public Iterable<Issue> resolvedIssues() {
+    return (Iterable) Iterables.filter(cache.all(), new ModulePredicate(true));
+  }
+
+  private class ModulePredicate implements Predicate<DefaultIssue> {
+    private final boolean resolved;
+
+    private ModulePredicate(boolean resolved) {
+      this.resolved = resolved;
+    }
+
+    @Override
+    public boolean apply(@Nullable DefaultIssue issue) {
+      if (issue != null && (issue.componentKey().equals(project.getEffectiveKey()) || issue.componentKey().startsWith(project.getEffectiveKey() + ":"))) {
+        return resolved ? issue.resolution() != null : issue.resolution()==null;
+      }
+      return false;
+    }
+  }
+}
index 3e13158a761ad06f8ef4bc8c832a4c907a764d3f..cd34582c24c0b6c25bf0deb224bd8e25b0dc69f8 100644 (file)
@@ -33,12 +33,12 @@ import javax.annotation.CheckForNull;
  */
 public class IssuableFactory extends PerspectiveBuilder<Issuable> {
 
-  private final ScanIssues scanIssues;
+  private final DefaultModuleIssues moduleIssues;
   private final IssueCache cache;
 
-  public IssuableFactory(ScanIssues scanIssues, IssueCache cache) {
+  public IssuableFactory(DefaultModuleIssues moduleIssues, IssueCache cache) {
     super(Issuable.class);
-    this.scanIssues = scanIssues;
+    this.moduleIssues = moduleIssues;
     this.cache = cache;
   }
 
@@ -49,6 +49,6 @@ public class IssuableFactory extends PerspectiveBuilder<Issuable> {
     if (component instanceof ResourceComponent) {
       supported = Scopes.isHigherThanOrEquals(((ResourceComponent) component).scope(), Scopes.FILE);
     }
-    return supported ? new DefaultIssuable(component, scanIssues, cache) : null;
+    return supported ? new DefaultIssuable(component, moduleIssues, cache) : null;
   }
 }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java
deleted file mode 100644 (file)
index a3b5326..0000000
+++ /dev/null
@@ -1,91 +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.batch.issue;
-
-import org.sonar.api.BatchComponent;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.Project;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.ActiveRule;
-import org.sonar.api.rules.Violation;
-import org.sonar.core.issue.DefaultIssueBuilder;
-
-import javax.annotation.Nullable;
-
-/**
- * Initialize the issues raised during scan.
- */
-public class ScanIssues implements BatchComponent {
-
-  private final RulesProfile qProfile;
-  private final IssueCache cache;
-  private final Project project;
-  private final IssueFilters filters;
-
-  public ScanIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters) {
-    this.qProfile = qProfile;
-    this.cache = cache;
-    this.project = project;
-    this.filters = filters;
-  }
-
-  public boolean initAndAddIssue(DefaultIssue issue) {
-    return initAndAddIssue(issue, null);
-  }
-
-  public boolean initAndAddViolation(Violation violation) {
-    DefaultIssue issue = newIssue(violation);
-    return initAndAddIssue(issue, violation);
-  }
-
-  private DefaultIssue newIssue(Violation violation) {
-    return (DefaultIssue) new DefaultIssueBuilder()
-      .componentKey(violation.getResource().getEffectiveKey())
-      .ruleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
-      .effortToFix(violation.getCost())
-      .line(violation.getLineId())
-      .message(violation.getMessage())
-      .severity(violation.getSeverity() != null ? violation.getSeverity().name() : null)
-      .build();
-  }
-
-  private boolean initAndAddIssue(DefaultIssue issue, @Nullable Violation violation) {
-    // TODO fail fast : if rule does not exist
-
-    ActiveRule activeRule = qProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule());
-    if (activeRule == null || activeRule.getRule() == null) {
-      // rule does not exist or is not enabled -> ignore the issue
-      return false;
-    }
-    issue.setCreationDate(project.getAnalysisDate());
-    issue.setUpdateDate(project.getAnalysisDate());
-    if (issue.severity() == null) {
-      issue.setSeverity(activeRule.getSeverity().name());
-    }
-
-    if (filters.accept(issue, violation)) {
-      cache.put(issue);
-      return true;
-    }
-    return false;
-  }
-
-}
index 0075238fd4edda6aad46b5a5709a4b47dfa219c0..543f01e294015e6d0690f55adbc04b4a011548b1 100644 (file)
@@ -42,7 +42,7 @@ import org.sonar.batch.index.DefaultIndex;
 import org.sonar.batch.index.ResourcePersister;
 import org.sonar.batch.issue.IssuableFactory;
 import org.sonar.batch.issue.IssueFilters;
-import org.sonar.batch.issue.ScanIssues;
+import org.sonar.batch.issue.DefaultModuleIssues;
 import org.sonar.batch.phases.PhaseExecutor;
 import org.sonar.batch.phases.PhasesTimeProfiler;
 import org.sonar.batch.scan.filesystem.*;
@@ -113,7 +113,7 @@ public class ModuleScanContainer extends ComponentContainer {
       new ProfileProvider(),
 
       // issues
-      ScanIssues.class,
+      DefaultModuleIssues.class,
       IssuableFactory.class,
 
       ScanPerspectives.class
@@ -140,7 +140,7 @@ public class ModuleScanContainer extends ComponentContainer {
     DefaultIndex index = getComponentByType(DefaultIndex.class);
     index.setCurrentProject(module,
       getComponentByType(ResourceFilters.class),
-      getComponentByType(ScanIssues.class));
+      getComponentByType(DefaultModuleIssues.class));
 
     getComponentByType(PhaseExecutor.class).execute(module);
   }
index e3f2e17300a98549f180d925f20b77cb58aea303..2431cefbbb5038f35e7e9a0082a81b9df7c4f4ad 100644 (file)
@@ -39,7 +39,7 @@ import org.sonar.batch.DefaultResourceCreationLock;
 import org.sonar.batch.ProjectTree;
 import org.sonar.batch.ResourceFilters;
 import org.sonar.batch.issue.DeprecatedViolations;
-import org.sonar.batch.issue.ScanIssues;
+import org.sonar.batch.issue.DefaultModuleIssues;
 import org.sonar.core.component.ScanGraph;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -77,7 +77,7 @@ public class DefaultIndexTest {
     rule = Rule.create("repoKey", "ruleKey", "Rule");
     rule.setId(1);
     rulesProfile.activateRule(rule, null);
-    index.setCurrentProject(project, new ResourceFilters(new ResourceFilter[]{filter}), mock(ScanIssues.class));
+    index.setCurrentProject(project, new ResourceFilters(new ResourceFilter[]{filter}), mock(DefaultModuleIssues.class));
     index.doStart(project);
   }
 
index a4ffb41416e3434aa9a2a6fdfe9945ab7dbd0ea9..f66cc03aa162b37bba52b6a5fb70b55b178bc9d9 100644 (file)
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.when;
 
 public class DefaultIssuableTest {
 
-  ScanIssues scanIssues = mock(ScanIssues.class);
+  DefaultModuleIssues moduleIssues = mock(DefaultModuleIssues.class);
   IssueCache cache = mock(IssueCache.class);
   Component component = mock(Component.class);
 
@@ -44,7 +44,7 @@ public class DefaultIssuableTest {
     DefaultIssue unresolved = new DefaultIssue();
     when(cache.byComponent("struts:org.apache.Action")).thenReturn(Arrays.asList(resolved, unresolved));
 
-    DefaultIssuable perspective = new DefaultIssuable(component, scanIssues, cache);
+    DefaultIssuable perspective = new DefaultIssuable(component, moduleIssues, cache);
 
     List<Issue> issues = perspective.issues();
     assertThat(issues).containsOnly(unresolved);
@@ -57,7 +57,7 @@ public class DefaultIssuableTest {
     DefaultIssue unresolved = new DefaultIssue();
     when(cache.byComponent("struts:org.apache.Action")).thenReturn(Arrays.asList(resolved, unresolved));
 
-    DefaultIssuable perspective = new DefaultIssuable(component, scanIssues, cache);
+    DefaultIssuable perspective = new DefaultIssuable(component, moduleIssues, cache);
 
     List<Issue> issues = perspective.resolvedIssues();
     assertThat(issues).containsOnly(resolved);
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultModuleIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultModuleIssuesTest.java
new file mode 100644 (file)
index 0000000..27f14f4
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * 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.batch.issue;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.rules.Violation;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DefaultModuleIssuesTest {
+
+  static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle");
+
+  IssueCache cache = mock(IssueCache.class);
+  RulesProfile qProfile = mock(RulesProfile.class);
+  Project project = mock(Project.class);
+  IssueFilters filters = mock(IssueFilters.class);
+  DefaultModuleIssues moduleIssues = new DefaultModuleIssues(qProfile, cache, project, filters);
+
+  @Before
+  public void setUp() {
+    when(project.getAnalysisDate()).thenReturn(new Date());
+    when(project.getEffectiveKey()).thenReturn("org.apache:struts-core");
+  }
+
+  @Test
+  public void should_ignore_null_active_rule() throws Exception {
+    when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(null);
+
+    DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY);
+    boolean added = moduleIssues.initAndAddIssue(issue);
+
+    assertThat(added).isFalse();
+    verifyZeroInteractions(cache);
+  }
+
+  @Test
+  public void should_ignore_null_rule_of_active_rule() throws Exception {
+    ActiveRule activeRule = mock(ActiveRule.class);
+    when(activeRule.getRule()).thenReturn(null);
+    when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(activeRule);
+
+    DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY);
+    boolean added = moduleIssues.initAndAddIssue(issue);
+
+    assertThat(added).isFalse();
+    verifyZeroInteractions(cache);
+  }
+
+  @Test
+  public void should_add_issue_to_cache() throws Exception {
+    Rule rule = Rule.create("squid", "AvoidCycle");
+    ActiveRule activeRule = mock(ActiveRule.class);
+    when(activeRule.getRule()).thenReturn(rule);
+    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
+    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
+
+    Date analysisDate = new Date();
+    when(project.getAnalysisDate()).thenReturn(analysisDate);
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(SQUID_RULE_KEY)
+      .setSeverity(Severity.CRITICAL);
+    when(filters.accept(issue, null)).thenReturn(true);
+
+    boolean added = moduleIssues.initAndAddIssue(issue);
+
+    assertThat(added).isTrue();
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(cache).put(argument.capture());
+    assertThat(argument.getValue().severity()).isEqualTo(Severity.CRITICAL);
+    assertThat(argument.getValue().creationDate()).isEqualTo(DateUtils.truncate(analysisDate, Calendar.SECOND));
+  }
+
+  @Test
+  public void should_use_severity_from_active_rule_if_no_severity() throws Exception {
+    Rule rule = Rule.create("squid", "AvoidCycle");
+    ActiveRule activeRule = mock(ActiveRule.class);
+    when(activeRule.getRule()).thenReturn(rule);
+    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
+    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
+
+    Date analysisDate = new Date();
+    when(project.getAnalysisDate()).thenReturn(analysisDate);
+
+    DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY).setSeverity(null);
+    when(filters.accept(issue, null)).thenReturn(true);
+    moduleIssues.initAndAddIssue(issue);
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(cache).put(argument.capture());
+    assertThat(argument.getValue().severity()).isEqualTo(Severity.INFO);
+    assertThat(argument.getValue().creationDate()).isEqualTo(DateUtils.truncate(analysisDate, Calendar.SECOND));
+  }
+
+  @Test
+  public void should_add_deprecated_violation() throws Exception {
+    Rule rule = Rule.create("squid", "AvoidCycle");
+    Resource resource = new JavaFile("org.struts.Action").setEffectiveKey("struts:org.struts.Action");
+    Violation violation = new Violation(rule, resource);
+    violation.setLineId(42);
+    violation.setSeverity(RulePriority.CRITICAL);
+    violation.setMessage("the message");
+
+    ActiveRule activeRule = mock(ActiveRule.class);
+    when(activeRule.getRule()).thenReturn(rule);
+    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
+    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
+    when(filters.accept(any(DefaultIssue.class), eq(violation))).thenReturn(true);
+
+    boolean added = moduleIssues.initAndAddViolation(violation);
+    assertThat(added).isTrue();
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(cache).put(argument.capture());
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
+    assertThat(issue.line()).isEqualTo(42);
+    assertThat(issue.message()).isEqualTo("the message");
+    assertThat(issue.key()).isNotEmpty();
+    assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
+    assertThat(issue.componentKey().toString()).isEqualTo("struts:org.struts.Action");
+  }
+
+  @Test
+  public void should_filter_issue() throws Exception {
+    Rule rule = Rule.create("squid", "AvoidCycle");
+    ActiveRule activeRule = mock(ActiveRule.class);
+    when(activeRule.getRule()).thenReturn(rule);
+    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
+    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(SQUID_RULE_KEY)
+      .setSeverity(Severity.CRITICAL);
+
+    when(filters.accept(issue, null)).thenReturn(false);
+
+    boolean added = moduleIssues.initAndAddIssue(issue);
+
+    assertThat(added).isFalse();
+    verifyZeroInteractions(cache);
+  }
+
+  @Test
+  public void should_get_module_issues() throws Exception {
+    DefaultIssue issueOnModule = new DefaultIssue().setKey("1").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core");
+    DefaultIssue issueInModule = new DefaultIssue().setKey("2").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core:Action");
+    DefaultIssue resolvedIssueInModule = new DefaultIssue().setKey("3").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core:Action").setResolution(Issue.RESOLUTION_FIXED);
+
+    when(cache.all()).thenReturn(Arrays.<DefaultIssue>asList(
+      // issue on root module
+      new DefaultIssue().setKey("4").setRuleKey(SQUID_RULE_KEY).setSeverity(Severity.CRITICAL).setComponentKey("org.apache:struts"),
+
+      // issue in root module
+      new DefaultIssue().setKey("5").setRuleKey(SQUID_RULE_KEY).setSeverity(Severity.CRITICAL).setComponentKey("org.apache:struts:FileInRoot"),
+
+      issueOnModule, issueInModule, resolvedIssueInModule
+    ));
+
+    // unresolved issues
+    List<Issue> issues = Lists.newArrayList(moduleIssues.issues());
+    assertThat(issues).containsOnly(issueInModule, issueOnModule);
+
+    List<Issue> resolvedIssues = Lists.newArrayList(moduleIssues.resolvedIssues());
+    assertThat(resolvedIssues).containsOnly(resolvedIssueInModule);
+  }
+}
index 9bc450ac5fd904945f12589027dbc0568cba4110..65e4b5d706cb064a82ae8ac4a6946122b3566c21 100644 (file)
@@ -34,12 +34,12 @@ import static org.mockito.Mockito.mock;
 
 public class IssuableFactoryTest {
 
-  ScanIssues scanIssues = mock(ScanIssues.class);
+  DefaultModuleIssues moduleIssues = mock(DefaultModuleIssues.class);
   IssueCache cache = mock(IssueCache.class, Mockito.RETURNS_MOCKS);
 
   @Test
   public void file_should_be_issuable() throws Exception {
-    IssuableFactory factory = new IssuableFactory(scanIssues, cache);
+    IssuableFactory factory = new IssuableFactory(moduleIssues, cache);
     Component component = new ResourceComponent(new File("foo/bar.c").setEffectiveKey("foo/bar.c"));
     Issuable issuable = factory.loadPerspective(Issuable.class, component);
 
@@ -50,7 +50,7 @@ public class IssuableFactoryTest {
 
   @Test
   public void project_should_be_issuable() throws Exception {
-    IssuableFactory factory = new IssuableFactory(scanIssues, cache);
+    IssuableFactory factory = new IssuableFactory(moduleIssues, cache);
     Component component = new ResourceComponent(new Project("Foo").setEffectiveKey("foo"));
     Issuable issuable = factory.loadPerspective(Issuable.class, component);
 
@@ -61,7 +61,7 @@ public class IssuableFactoryTest {
 
   @Test
   public void java_file_should_be_issuable() throws Exception {
-    IssuableFactory factory = new IssuableFactory(scanIssues, cache);
+    IssuableFactory factory = new IssuableFactory(moduleIssues, cache);
     Component component = new ResourceComponent(new JavaFile("org.apache.Action").setEffectiveKey("struts:org.apache.Action"));
     Issuable issuable = factory.loadPerspective(Issuable.class, component);
 
@@ -72,7 +72,7 @@ public class IssuableFactoryTest {
 
   @Test
   public void java_class_should_not_be_issuable() throws Exception {
-    IssuableFactory factory = new IssuableFactory(scanIssues, cache);
+    IssuableFactory factory = new IssuableFactory(moduleIssues, cache);
     Component component = new ResourceComponent(JavaClass.create("org.apache.Action").setEffectiveKey("struts:org.apache.Action"));
     Issuable issuable = factory.loadPerspective(Issuable.class, component);
 
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java
deleted file mode 100644 (file)
index f67ad67..0000000
+++ /dev/null
@@ -1,176 +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.batch.issue;
-
-import org.apache.commons.lang.time.DateUtils;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.JavaFile;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.ActiveRule;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.rules.Violation;
-
-import java.util.Calendar;
-import java.util.Date;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class ScanIssuesTest {
-
-  IssueCache cache = mock(IssueCache.class);
-  RulesProfile qProfile = mock(RulesProfile.class);
-  Project project = mock(Project.class);
-  IssueFilters filters = mock(IssueFilters.class);
-  ScanIssues scanIssues = new ScanIssues(qProfile, cache, project, filters);
-
-  @Before
-  public void setUp() {
-    when(project.getAnalysisDate()).thenReturn(new Date());
-  }
-  @Test
-  public void should_ignore_null_active_rule() throws Exception {
-    when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(null);
-
-    DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle"));
-    boolean added = scanIssues.initAndAddIssue(issue);
-
-    assertThat(added).isFalse();
-    verifyZeroInteractions(cache);
-  }
-
-  @Test
-  public void should_ignore_null_rule_of_active_rule() throws Exception {
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(null);
-    when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(activeRule);
-
-    DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle"));
-    boolean added = scanIssues.initAndAddIssue(issue);
-
-    assertThat(added).isFalse();
-    verifyZeroInteractions(cache);
-  }
-
-  @Test
-  public void should_add_issue_to_cache() throws Exception {
-    Rule rule = Rule.create("squid", "AvoidCycle");
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-
-    Date analysisDate = new Date();
-    when(project.getAnalysisDate()).thenReturn(analysisDate);
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
-      .setSeverity(Severity.CRITICAL);
-    when(filters.accept(issue, null)).thenReturn(true);
-
-    boolean added = scanIssues.initAndAddIssue(issue);
-
-    assertThat(added).isTrue();
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(cache).put(argument.capture());
-    assertThat(argument.getValue().severity()).isEqualTo(Severity.CRITICAL);
-    assertThat(argument.getValue().creationDate()).isEqualTo(DateUtils.truncate(analysisDate, Calendar.SECOND));
-  }
-
-  @Test
-  public void should_use_severity_from_active_rule_if_no_severity() throws Exception {
-    Rule rule = Rule.create("squid", "AvoidCycle");
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-
-    Date analysisDate = new Date();
-    when(project.getAnalysisDate()).thenReturn(analysisDate);
-
-    DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle")).setSeverity(null);
-    when(filters.accept(issue, null)).thenReturn(true);
-    scanIssues.initAndAddIssue(issue);
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(cache).put(argument.capture());
-    assertThat(argument.getValue().severity()).isEqualTo(Severity.INFO);
-    assertThat(argument.getValue().creationDate()).isEqualTo(DateUtils.truncate(analysisDate, Calendar.SECOND));
-  }
-
-  @Test
-  public void should_add_deprecated_violation() throws Exception {
-    Rule rule = Rule.create("squid", "AvoidCycle");
-    Resource resource = new JavaFile("org.struts.Action").setEffectiveKey("struts:org.struts.Action");
-    Violation violation = new Violation(rule, resource);
-    violation.setLineId(42);
-    violation.setSeverity(RulePriority.CRITICAL);
-    violation.setMessage("the message");
-
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-    when(filters.accept(any(DefaultIssue.class), eq(violation))).thenReturn(true);
-
-    boolean added = scanIssues.initAndAddViolation(violation);
-    assertThat(added).isTrue();
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(cache).put(argument.capture());
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
-    assertThat(issue.line()).isEqualTo(42);
-    assertThat(issue.message()).isEqualTo("the message");
-    assertThat(issue.key()).isNotEmpty();
-    assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
-    assertThat(issue.componentKey().toString()).isEqualTo("struts:org.struts.Action");
-  }
-
-  @Test
-  public void should_filter_issue() throws Exception {
-    Rule rule = Rule.create("squid", "AvoidCycle");
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
-      .setSeverity(Severity.CRITICAL);
-
-    when(filters.accept(issue, null)).thenReturn(false);
-
-    boolean added = scanIssues.initAndAddIssue(issue);
-
-    assertThat(added).isFalse();
-    verifyZeroInteractions(cache);
-  }
-}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/ModuleIssues.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/ModuleIssues.java
new file mode 100644 (file)
index 0000000..2f1a974
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.api.issue;
+
+import org.sonar.api.BatchComponent;
+
+/**
+ * Used by batch components to get the issues of the current module. It does not allow
+ * to get issues of all project modules.
+ *
+ * @since 4.0
+ */
+public interface ModuleIssues extends BatchComponent {
+
+  /**
+   * All the unresolved issues of the current module, including the issues reported by end-users.
+   */
+  Iterable<Issue> issues();
+
+  /**
+   * All the issues of this module that have been marked as resolved during this scan
+   */
+  Iterable<Issue> resolvedIssues();
+}