]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6730 Split IntegrateIssuesStep into 3 visitors
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 25 Aug 2015 15:09:49 +0000 (17:09 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 31 Aug 2015 07:49:14 +0000 (09:49 +0200)
12 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java
server/sonar-server/src/main/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitor.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssues.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IntegrateIssuesVisitor.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitor.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java
server/sonar-server/src/test/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssuesTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitorTest.java [new file with mode: 0644]

index 046d7cefcc1a5b30cc161c3f02571600d06f27cd..4c16f1ed92e2cb4a2d7b74c1a4217530203a2875 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.server.computation.component.SettingsRepositoryImpl;
 import org.sonar.server.computation.debt.DebtModelHolderImpl;
 import org.sonar.server.computation.event.EventRepositoryImpl;
 import org.sonar.server.computation.issue.BaseIssuesLoader;
+import org.sonar.server.computation.issue.ComponentsWithUnprocessedIssues;
 import org.sonar.server.computation.issue.DebtAggregator;
 import org.sonar.server.computation.issue.DebtCalculator;
 import org.sonar.server.computation.issue.DefaultAssignee;
@@ -139,6 +140,7 @@ public final class ReportComputeEngineContainerPopulator implements ContainerPop
         DefaultAssignee.class,
         IssueVisitors.class,
         IssueLifecycle.class,
+        ComponentsWithUnprocessedIssues.class,
 
         // common rules
         CommonRuleEngineImpl.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitor.java
new file mode 100644 (file)
index 0000000..87075b4
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.issue;
+
+import java.util.List;
+import java.util.Set;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.CrawlerDepthLimit;
+import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
+import org.sonar.server.util.cache.DiskCache;
+
+import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
+
+/**
+ * Close issues on removed components
+ */
+public class CloseIssuesOnRemovedComponentsVisitor extends TypeAwareVisitorAdapter {
+
+  private final BaseIssuesLoader baseIssuesLoader;
+  private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
+  private final IssueCache issueCache;
+  private final IssueLifecycle issueLifecycle;
+
+  public CloseIssuesOnRemovedComponentsVisitor(BaseIssuesLoader baseIssuesLoader, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, IssueCache issueCache, IssueLifecycle issueLifecycle) {
+    super(CrawlerDepthLimit.PROJECT, POST_ORDER);
+    this.baseIssuesLoader = baseIssuesLoader;
+    this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
+    this.issueCache = issueCache;
+    this.issueLifecycle = issueLifecycle;
+  }
+
+  @Override
+  public void visitProject(Component project) {
+    closeIssuesForDeletedComponentUuids(componentsWithUnprocessedIssues.getUuids());
+  }
+
+  private void closeIssuesForDeletedComponentUuids(Set<String> deletedComponentUuids) {
+    DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender();
+    try {
+      for (String deletedComponentUuid : deletedComponentUuids) {
+        List<DefaultIssue> issues = baseIssuesLoader.loadForComponentUuid(deletedComponentUuid);
+        for (DefaultIssue issue : issues) {
+          issue.setBeingClosed(true);
+          // TODO should be renamed
+          issue.setOnDisabledRule(false);
+          issueLifecycle.doAutomaticTransition(issue);
+          cacheAppender.append(issue);
+        }
+      }
+    } finally {
+      cacheAppender.close();
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssues.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssues.java
new file mode 100644 (file)
index 0000000..4a54a9e
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.issue;
+
+import java.util.Set;
+import javax.annotation.CheckForNull;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Objects.requireNonNull;
+
+public class ComponentsWithUnprocessedIssues {
+
+  @CheckForNull
+  private Set<String> uuids;
+
+  public void setUuids(Set<String> uuids) {
+    requireNonNull(uuids, "Uuids cannot be null");
+    checkState(this.uuids == null, "Uuids have already been initialized");
+    this.uuids = newHashSet(uuids);
+  }
+
+  public void remove(String uuid) {
+    checkIssuesAreInitialized();
+    uuids.remove(uuid);
+  }
+
+  public Set<String> getUuids() {
+    checkIssuesAreInitialized();
+    return uuids;
+  }
+
+  private void checkIssuesAreInitialized() {
+    checkState(this.uuids != null, "Uuids have not been initialized yet");
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IntegrateIssuesVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IntegrateIssuesVisitor.java
new file mode 100644 (file)
index 0000000..4cbcf06
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.issue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.CrawlerDepthLimit;
+import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
+import org.sonar.server.util.cache.DiskCache;
+
+import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
+
+public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
+
+  private final TrackerExecution tracker;
+  private final IssueCache issueCache;
+  private final IssueLifecycle issueLifecycle;
+  private final IssueVisitors issueVisitors;
+  private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
+
+  private final List<DefaultIssue> componentIssues = new ArrayList<>();
+
+  public IntegrateIssuesVisitor(TrackerExecution tracker, IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors,
+                                ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues) {
+    super(CrawlerDepthLimit.FILE, POST_ORDER);
+    this.tracker = tracker;
+    this.issueCache = issueCache;
+    this.issueLifecycle = issueLifecycle;
+    this.issueVisitors = issueVisitors;
+    this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
+  }
+
+  @Override
+  public void visitAny(Component component) {
+    componentIssues.clear();
+    processIssues(component);
+    componentsWithUnprocessedIssues.remove(component.getUuid());
+  }
+
+  private void processIssues(Component component) {
+    DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender();
+    try {
+      Tracking<DefaultIssue, DefaultIssue> tracking = tracker.track(component);
+      issueVisitors.beforeComponent(component);
+      fillNewOpenIssues(component, tracking, cacheAppender);
+      fillExistingOpenIssues(component, tracking, cacheAppender);
+      closeUnmatchedBaseIssues(component, tracking, cacheAppender);
+      issueVisitors.afterComponent(component);
+    } catch (Exception e) {
+      Loggers.get(getClass()).error(String.format("Fail to process issues of %s", component.getKey()), e);
+    } finally {
+      cacheAppender.close();
+    }
+  }
+
+  private void fillNewOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+    for (DefaultIssue issue : tracking.getUnmatchedRaws()) {
+      issueLifecycle.initNewOpenIssue(issue);
+      process(component, issue, cacheAppender);
+    }
+  }
+
+  private void fillExistingOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+    for (Map.Entry<DefaultIssue, DefaultIssue> entry : tracking.getMatchedRaws().entrySet()) {
+      DefaultIssue raw = entry.getKey();
+      DefaultIssue base = entry.getValue();
+      issueLifecycle.mergeExistingOpenIssue(raw, base);
+      process(component, raw, cacheAppender);
+    }
+    for (Map.Entry<Integer, DefaultIssue> entry : tracking.getOpenManualIssuesByLine().entries()) {
+      Integer line = entry.getKey();
+      DefaultIssue manualIssue = entry.getValue();
+      issueLifecycle.moveOpenManualIssue(manualIssue, line);
+      process(component, manualIssue, cacheAppender);
+    }
+  }
+
+  private void closeUnmatchedBaseIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+    for (DefaultIssue issue : tracking.getUnmatchedBases()) {
+      // TODO should replace flag "beingClosed" by express call to transition "automaticClose"
+      issue.setBeingClosed(true);
+      // TODO manual issues -> was updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);. Is it a problem ?
+      process(component, issue, cacheAppender);
+    }
+  }
+
+  private void process(Component component, DefaultIssue issue, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+    issueLifecycle.doAutomaticTransition(issue);
+    issueVisitors.onIssue(component, issue);
+    cacheAppender.append(issue);
+    componentIssues.add(issue);
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitor.java
new file mode 100644 (file)
index 0000000..5c9907b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.issue;
+
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.CrawlerDepthLimit;
+import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
+
+import static org.sonar.server.computation.component.ComponentVisitor.Order.PRE_ORDER;
+
+/**
+ * Load all open components having open issues of the project
+ */
+public class LoadComponentUuidsHavingOpenIssuesVisitor extends TypeAwareVisitorAdapter {
+
+  private final BaseIssuesLoader baseIssuesLoader;
+  private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
+
+  public LoadComponentUuidsHavingOpenIssuesVisitor(BaseIssuesLoader baseIssuesLoader, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues) {
+    super(CrawlerDepthLimit.PROJECT, PRE_ORDER);
+    this.baseIssuesLoader = baseIssuesLoader;
+    this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
+  }
+
+  @Override
+  public void visitProject(Component project) {
+    componentsWithUnprocessedIssues.setUuids(baseIssuesLoader.loadUuidsOfComponentsWithOpenIssues());
+  }
+}
index cd0542a859379b21cd3d121d66cd2072e7babe95..b3d92cc82736a37403c479714d55ad7837489d09 100644 (file)
@@ -28,6 +28,9 @@ import java.util.List;
 import javax.annotation.Nonnull;
 import org.sonar.server.computation.component.ComponentVisitor;
 import org.sonar.server.computation.container.ComputeEngineContainer;
+import org.sonar.server.computation.issue.CloseIssuesOnRemovedComponentsVisitor;
+import org.sonar.server.computation.issue.IntegrateIssuesVisitor;
+import org.sonar.server.computation.issue.LoadComponentUuidsHavingOpenIssuesVisitor;
 import org.sonar.server.computation.measure.MeasureComputersVisitor;
 import org.sonar.server.computation.sqale.SqaleMeasuresVisitor;
 
@@ -37,6 +40,10 @@ import org.sonar.server.computation.sqale.SqaleMeasuresVisitor;
 public class ComponentVisitors {
 
   private static final List<Class<? extends ComponentVisitor>> ORDERED_VISITOR_CLASSES = ImmutableList.of(
+    LoadComponentUuidsHavingOpenIssuesVisitor.class,
+    IntegrateIssuesVisitor.class,
+    CloseIssuesOnRemovedComponentsVisitor.class,
+
     SqaleMeasuresVisitor.class,
 
     // Must be after all other visitors as it requires measures computed by previous visitors
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java
deleted file mode 100644 (file)
index 4f9a33a..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.step;
-
-import com.google.common.collect.Sets;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Tracking;
-import org.sonar.server.computation.component.Component;
-import org.sonar.server.computation.component.CrawlerDepthLimit;
-import org.sonar.server.computation.component.DepthTraversalTypeAwareCrawler;
-import org.sonar.server.computation.component.TreeRootHolder;
-import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
-import org.sonar.server.computation.issue.BaseIssuesLoader;
-import org.sonar.server.computation.issue.IssueCache;
-import org.sonar.server.computation.issue.IssueLifecycle;
-import org.sonar.server.computation.issue.IssueVisitors;
-import org.sonar.server.computation.issue.TrackerExecution;
-import org.sonar.server.util.cache.DiskCache;
-
-import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
-
-public class IntegrateIssuesStep implements ComputationStep {
-
-  private final TreeRootHolder treeRootHolder;
-  private final TrackerExecution tracker;
-  private final IssueCache issueCache;
-  private final BaseIssuesLoader baseIssuesLoader;
-  private final IssueLifecycle issueLifecycle;
-  private final IssueVisitors issueVisitors;
-
-  public IntegrateIssuesStep(TreeRootHolder treeRootHolder, TrackerExecution tracker, IssueCache issueCache,
-    BaseIssuesLoader baseIssuesLoader, IssueLifecycle issueLifecycle,
-    IssueVisitors issueVisitors) {
-    this.treeRootHolder = treeRootHolder;
-    this.tracker = tracker;
-    this.issueCache = issueCache;
-    this.baseIssuesLoader = baseIssuesLoader;
-    this.issueLifecycle = issueLifecycle;
-    this.issueVisitors = issueVisitors;
-  }
-
-  @Override
-  public void execute() {
-    // all the components that had issues before this analysis
-    final Set<String> unprocessedComponentUuids = Sets.newHashSet(baseIssuesLoader.loadUuidsOfComponentsWithOpenIssues());
-
-    new DepthTraversalTypeAwareCrawler(
-      new TypeAwareVisitorAdapter(CrawlerDepthLimit.FILE, POST_ORDER) {
-        @Override
-        public void visitAny(Component component) {
-          processIssues(component);
-          unprocessedComponentUuids.remove(component.getUuid());
-        }
-      }).visit(treeRootHolder.getRoot());
-
-    closeIssuesForDeletedComponentUuids(unprocessedComponentUuids);
-  }
-
-  private void processIssues(Component component) {
-    DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender();
-    try {
-      Tracking<DefaultIssue, DefaultIssue> tracking = tracker.track(component);
-      issueVisitors.beforeComponent(component);
-      fillNewOpenIssues(component, tracking, cacheAppender);
-      fillExistingOpenIssues(component, tracking, cacheAppender);
-      closeUnmatchedBaseIssues(component, tracking, cacheAppender);
-      issueVisitors.afterComponent(component);
-    } catch (Exception e) {
-      Loggers.get(getClass()).error(String.format("Fail to process issues of %s", component.getKey()), e);
-    } finally {
-      cacheAppender.close();
-    }
-  }
-
-  private void fillNewOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
-    for (DefaultIssue issue : tracking.getUnmatchedRaws()) {
-      issueLifecycle.initNewOpenIssue(issue);
-      process(component, issue, cacheAppender);
-    }
-  }
-
-  private void fillExistingOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
-    for (Map.Entry<DefaultIssue, DefaultIssue> entry : tracking.getMatchedRaws().entrySet()) {
-      DefaultIssue raw = entry.getKey();
-      DefaultIssue base = entry.getValue();
-      issueLifecycle.mergeExistingOpenIssue(raw, base);
-      process(component, raw, cacheAppender);
-    }
-    for (Map.Entry<Integer, DefaultIssue> entry : tracking.getOpenManualIssuesByLine().entries()) {
-      Integer line = entry.getKey();
-      DefaultIssue manualIssue = entry.getValue();
-      issueLifecycle.moveOpenManualIssue(manualIssue, line);
-      process(component, manualIssue, cacheAppender);
-    }
-  }
-
-  private void closeUnmatchedBaseIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
-    for (DefaultIssue issue : tracking.getUnmatchedBases()) {
-      // TODO should replace flag "beingClosed" by express call to transition "automaticClose"
-      issue.setBeingClosed(true);
-      // TODO manual issues -> was updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);. Is it a problem ?
-      process(component, issue, cacheAppender);
-    }
-  }
-
-  private void process(Component component, DefaultIssue issue, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
-    issueLifecycle.doAutomaticTransition(issue);
-    issueVisitors.onIssue(component, issue);
-    cacheAppender.append(issue);
-  }
-
-  private void closeIssuesForDeletedComponentUuids(Set<String> deletedComponentUuids) {
-    DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender();
-    try {
-      for (String deletedComponentUuid : deletedComponentUuids) {
-        List<DefaultIssue> issues = baseIssuesLoader.loadForComponentUuid(deletedComponentUuid);
-        for (DefaultIssue issue : issues) {
-          issue.setBeingClosed(true);
-          // TODO should be renamed
-          issue.setOnDisabledRule(false);
-          issueLifecycle.doAutomaticTransition(issue);
-          cacheAppender.append(issue);
-        }
-      }
-    } finally {
-      cacheAppender.close();
-    }
-  }
-
-  @Override
-  public String getDescription() {
-    return "Integrate issues";
-  }
-
-}
index 06cea673d41350ba113cb49ac4b7c74d6e8dccd4..d65b2d96add2293e1cf34f49e3cdc6fcf903cab7 100644 (file)
@@ -63,9 +63,6 @@ public class ReportComputationSteps implements ComputationSteps {
       UnitTestMeasuresStep.class,
       ComplexityMeasuresStep.class,
 
-      // must be executed after the measures required for common rules (coverage, comment density, duplications)
-      IntegrateIssuesStep.class,
-
       FeedMeasureComputers.class,
       ExecuteVisitorsStep.class,
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitorTest.java
new file mode 100644 (file)
index 0000000..3305924
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.issue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.System2;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.server.computation.component.ComponentVisitor;
+import org.sonar.server.computation.component.ReportComponent;
+import org.sonar.server.computation.component.VisitorsCrawler;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+
+public class CloseIssuesOnRemovedComponentsVisitorTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  BaseIssuesLoader baseIssuesLoader = mock(BaseIssuesLoader.class);
+  ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues = mock(ComponentsWithUnprocessedIssues.class);
+  IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
+  IssueCache issueCache;
+  VisitorsCrawler underTest;
+
+  @Before
+  public void setUp() throws Exception {
+    issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
+    underTest = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new CloseIssuesOnRemovedComponentsVisitor(baseIssuesLoader, componentsWithUnprocessedIssues, issueCache, issueLifecycle)));
+  }
+
+  @Test
+  public void close_issue() throws Exception {
+    String fileUuid = "FILE1";
+    String issueUuid = "ABCD";
+
+    when(componentsWithUnprocessedIssues.getUuids()).thenReturn(newHashSet(fileUuid));
+    DefaultIssue issue = new DefaultIssue().setKey(issueUuid);
+    when(baseIssuesLoader.loadForComponentUuid(fileUuid)).thenReturn(Collections.singletonList(issue));
+
+    underTest.visit(ReportComponent.builder(PROJECT, 1).build());
+
+    verify(issueLifecycle).doAutomaticTransition(issue);
+    CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+    assertThat(issues.hasNext()).isTrue();
+
+    DefaultIssue result = issues.next();
+    assertThat(result.key()).isEqualTo(issueUuid);
+    assertThat(result.isBeingClosed()).isTrue();
+    assertThat(result.isOnDisabledRule()).isFalse();
+  }
+
+  @Test
+  public void nothing_to_do_when_no_uuid_in_queue() throws Exception {
+    when(componentsWithUnprocessedIssues.getUuids()).thenReturn(Collections.<String>emptySet());
+
+    underTest.visit(ReportComponent.builder(PROJECT, 1).build());
+
+    verifyZeroInteractions(issueLifecycle);
+    CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+    assertThat(issues.hasNext()).isFalse();
+  }
+
+  @Test
+  public void do_nothing_on_module() throws Exception {
+    underTest.visit(ReportComponent.builder(MODULE, 1).build());
+
+    verifyZeroInteractions(issueLifecycle);
+    CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+    assertThat(issues.hasNext()).isFalse();
+  }
+
+  @Test
+  public void do_nothing_on_directory() throws Exception {
+    underTest.visit(ReportComponent.builder(DIRECTORY, 1).build());
+
+    verifyZeroInteractions(issueLifecycle);
+    CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+    assertThat(issues.hasNext()).isFalse();
+  }
+
+  @Test
+  public void do_nothing_on_file() throws Exception {
+    underTest.visit(ReportComponent.builder(FILE, 1).build());
+
+    verifyZeroInteractions(issueLifecycle);
+    CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+    assertThat(issues.hasNext()).isFalse();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssuesTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssuesTest.java
new file mode 100644 (file)
index 0000000..86a4d35
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.issue;
+
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentsWithUnprocessedIssuesTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  ComponentsWithUnprocessedIssues sut = new ComponentsWithUnprocessedIssues();
+
+  @Test
+  public void set_uuids() throws Exception {
+    sut.setUuids(newHashSet("ABCD", "EFGH"));
+
+    assertThat(sut.getUuids()).containsOnly("ABCD", "EFGH");
+  }
+
+  @Test
+  public void set_uuids_makes_a_copy_of_input_issues() throws Exception {
+    Set<String> issues = newHashSet("ABCD", "EFGH");
+    sut.setUuids(issues);
+
+    assertThat(sut.getUuids()).containsOnly("ABCD", "EFGH");
+
+    // Remove a element from the list, number of issues from the queue should remain the same
+    issues.remove("ABCD");
+    assertThat(sut.getUuids()).containsOnly("ABCD", "EFGH");
+  }
+
+  @Test
+  public void fail_with_NPE_when_setting_null_uuids() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Uuids cannot be null");
+
+    sut.setUuids(null);
+  }
+
+  @Test
+  public void fail_with_ISE_when_setting_uuids_twice() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Uuids have already been initialized");
+
+    sut.setUuids(newHashSet("ABCD"));
+    sut.setUuids(newHashSet("EFGH"));
+  }
+
+  @Test
+  public void remove_uuid() throws Exception {
+    sut.setUuids(newHashSet("ABCD", "EFGH"));
+    sut.remove("ABCD");
+
+    assertThat(sut.getUuids()).containsOnly("EFGH");
+  }
+
+  @Test
+  public void fail_with_ISE_when_removing_uuid_and_not_initialized() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Uuids have not been initialized yet");
+
+    sut.remove("ABCD");
+  }
+
+  @Test
+  public void fail_with_ISE_when_getting_uuid_and_not_initialized() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Uuids have not been initialized yet");
+
+    sut.getUuids();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java
new file mode 100644 (file)
index 0000000..7a81077
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.issue;
+
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.protocol.Constants;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracker;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.server.computation.batch.BatchReportReaderRule;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.TypeAwareVisitor;
+import org.sonar.server.computation.issue.commonrule.CommonRuleEngineImpl;
+import org.sonar.server.computation.qualityprofile.ActiveRulesHolderRule;
+import org.sonar.server.issue.IssueTesting;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.sonar.server.computation.component.ReportComponent.builder;
+
+public class IntegrateIssuesVisitorTest {
+
+  static final String FILE_UUID = "FILE_UUID";
+  static final String FILE_KEY = "FILE_KEY";
+  static final int FILE_REF = 2;
+  static final Component FILE = builder(Component.Type.FILE, FILE_REF)
+    .setKey(FILE_KEY)
+    .setUuid(FILE_UUID)
+    .build();
+
+  static final String PROJECT_KEY = "PROJECT_KEY";
+  static final String PROJECT_UUID = "PROJECT_UUID";
+  static final int PROJECT_REF = 1;
+  static final Component PROJECT = builder(Component.Type.PROJECT, PROJECT_REF)
+    .setKey(PROJECT_KEY)
+    .setUuid(PROJECT_UUID)
+    .addChildren(FILE)
+    .build();
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
+  @Rule
+  public ActiveRulesHolderRule activeRulesHolderRule = new ActiveRulesHolderRule();
+
+  @Rule
+  public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
+
+  ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+
+  BaseIssuesLoader baseIssuesLoader = new BaseIssuesLoader(treeRootHolder, dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule);
+  TrackerExecution tracker = new TrackerExecution(new TrackerBaseInputFactory(baseIssuesLoader, dbTester.getDbClient()), new TrackerRawInputFactory(treeRootHolder, reportReader,
+    new CommonRuleEngineImpl()), new Tracker<DefaultIssue, DefaultIssue>());
+  IssueCache issueCache;
+
+  IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
+  IssueVisitor issueVisitor = mock(IssueVisitor.class);
+  IssueVisitors issueVisitors = new IssueVisitors(new IssueVisitor[]{issueVisitor});
+  ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues = new ComponentsWithUnprocessedIssues();
+
+  TypeAwareVisitor underTest;
+
+  @Before
+  public void setUp() throws Exception {
+    treeRootHolder.setRoot(PROJECT);
+    issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
+    underTest = new IntegrateIssuesVisitor(tracker, issueCache, issueLifecycle, issueVisitors, componentsWithUnprocessedIssues);
+  }
+
+  @Test
+  public void process_new_issue() throws Exception {
+    componentsWithUnprocessedIssues.setUuids(Collections.<String>emptySet());
+
+    BatchReport.Issue reportIssue = BatchReport.Issue.newBuilder()
+      .setMsg("the message")
+      .setRuleRepository("xoo")
+      .setRuleKey("S001")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, asList(reportIssue));
+    reportReader.putFileSourceLines(FILE_REF, "line1");
+
+    underTest.visitAny(FILE);
+
+    verify(issueLifecycle).initNewOpenIssue(defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo("S001");
+
+    verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo("S001");
+
+    assertThat(newArrayList(issueCache.traverse())).hasSize(1);
+    assertThat(componentsWithUnprocessedIssues.getUuids()).isEmpty();
+  }
+
+  @Test
+  public void process_existing_issue() throws Exception {
+    componentsWithUnprocessedIssues.setUuids(newHashSet(FILE_UUID));
+
+    RuleKey ruleKey = RuleTesting.XOO_X1;
+    // Issue from db has severity major
+    addBaseIssue(ruleKey);
+
+    // Issue from report has severity blocker
+    BatchReport.Issue reportIssue = BatchReport.Issue.newBuilder()
+      .setMsg("the message")
+      .setRuleRepository(ruleKey.repository())
+      .setRuleKey(ruleKey.rule())
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, asList(reportIssue));
+    reportReader.putFileSourceLines(FILE_REF, "line1");
+
+    underTest.visitAny(FILE);
+
+    ArgumentCaptor<DefaultIssue> rawIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+    ArgumentCaptor<DefaultIssue> baseIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueLifecycle).mergeExistingOpenIssue(rawIssueCaptor.capture(), baseIssueCaptor.capture());
+    assertThat(rawIssueCaptor.getValue().severity()).isEqualTo(Severity.BLOCKER);
+    assertThat(baseIssueCaptor.getValue().severity()).isEqualTo(Severity.MAJOR);
+
+    verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().ruleKey()).isEqualTo(ruleKey);
+    List<DefaultIssue> issues = newArrayList(issueCache.traverse());
+    assertThat(issues).hasSize(1);
+    assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
+
+    assertThat(componentsWithUnprocessedIssues.getUuids()).isEmpty();
+  }
+
+  @Test
+  public void process_manual_issue() throws Exception {
+    componentsWithUnprocessedIssues.setUuids(newHashSet(FILE_UUID));
+
+    RuleKey ruleKey = RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "architecture");
+    addBaseIssue(ruleKey);
+
+    underTest.visitAny(FILE);
+
+    verify(issueLifecycle).moveOpenManualIssue(defaultIssueCaptor.capture(), eq((Integer) null));
+    assertThat(defaultIssueCaptor.getValue().ruleKey()).isEqualTo(ruleKey);
+
+    verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().ruleKey()).isEqualTo(ruleKey);
+    List<DefaultIssue> issues = newArrayList(issueCache.traverse());
+    assertThat(issues).hasSize(1);
+
+    assertThat(componentsWithUnprocessedIssues.getUuids()).isEmpty();
+  }
+
+  @Test
+  public void execute_issue_visitors() throws Exception {
+    componentsWithUnprocessedIssues.setUuids(Collections.<String>emptySet());
+    BatchReport.Issue reportIssue = BatchReport.Issue.newBuilder()
+      .setMsg("the message")
+      .setRuleRepository("xoo")
+      .setRuleKey("S001")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, asList(reportIssue));
+    reportReader.putFileSourceLines(FILE_REF, "line1");
+
+    underTest.visitAny(FILE);
+
+    verify(issueVisitor).beforeComponent(FILE);
+    verify(issueVisitor).afterComponent(FILE);
+    verify(issueVisitor).onIssue(eq(FILE), defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo("S001");
+  }
+
+  @Test
+  public void close_unmatched_base_issue() throws Exception {
+    componentsWithUnprocessedIssues.setUuids(newHashSet(FILE_UUID));
+    RuleKey ruleKey = RuleTesting.XOO_X1;
+    addBaseIssue(ruleKey);
+
+    // No issue in the report
+
+    underTest.visitAny(FILE);
+
+    verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().isBeingClosed()).isTrue();
+    List<DefaultIssue> issues = newArrayList(issueCache.traverse());
+    assertThat(issues).hasSize(1);
+
+    assertThat(componentsWithUnprocessedIssues.getUuids()).isEmpty();
+  }
+
+  private void addBaseIssue(RuleKey ruleKey) {
+    ComponentDto project = ComponentTesting.newProjectDto(PROJECT_UUID).setKey(PROJECT_KEY);
+    ComponentDto file = ComponentTesting.newFileDto(project, FILE_UUID).setKey(FILE_KEY);
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), project, file);
+
+    RuleDto ruleDto = RuleTesting.newDto(ruleKey);
+    dbTester.getDbClient().ruleDao().insert(dbTester.getSession(), ruleDto);
+    ruleRepositoryRule.add(ruleKey);
+
+    IssueDto issue = IssueTesting.newDto(ruleDto, file, project)
+      .setKee("ISSUE")
+      .setStatus(Issue.STATUS_OPEN)
+      .setSeverity(Severity.MAJOR);
+    dbTester.getDbClient().issueDao().insert(dbTester.getSession(), issue);
+    dbTester.getSession().commit();
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitorTest.java
new file mode 100644 (file)
index 0000000..b2497e4
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.issue;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.sonar.server.computation.component.ComponentVisitor;
+import org.sonar.server.computation.component.ReportComponent;
+import org.sonar.server.computation.component.VisitorsCrawler;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+
+public class LoadComponentUuidsHavingOpenIssuesVisitorTest {
+
+  BaseIssuesLoader baseIssuesLoader = mock(BaseIssuesLoader.class);
+  ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues = mock(ComponentsWithUnprocessedIssues.class);
+  VisitorsCrawler underTest = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new LoadComponentUuidsHavingOpenIssuesVisitor(baseIssuesLoader, componentsWithUnprocessedIssues)));
+
+  @Test
+  public void set_issues_when_visiting_project() throws Exception {
+    when(baseIssuesLoader.loadUuidsOfComponentsWithOpenIssues()).thenReturn(newHashSet("FILE1", "FILE2"));
+
+    underTest.visit(ReportComponent.builder(PROJECT, 1).build());
+
+    verify(componentsWithUnprocessedIssues).setUuids(newHashSet("FILE1", "FILE2"));
+  }
+
+  @Test
+  public void do_nothing_on_not_project_level() throws Exception {
+    when(baseIssuesLoader.loadUuidsOfComponentsWithOpenIssues()).thenReturn(newHashSet("FILE1", "FILE2"));
+
+    underTest.visit(ReportComponent.builder(MODULE, 1).build());
+    underTest.visit(ReportComponent.builder(DIRECTORY, 1).build());
+    underTest.visit(ReportComponent.builder(FILE, 1).build());
+
+    verifyZeroInteractions(componentsWithUnprocessedIssues);
+  }
+}