From: Julien Lancelot Date: Tue, 25 Aug 2015 15:09:49 +0000 (+0200) Subject: SONAR-6730 Split IntegrateIssuesStep into 3 visitors X-Git-Tag: 5.2-RC1~553 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=62def73057ecf234e2e066231364e57193b005f0;p=sonarqube.git SONAR-6730 Split IntegrateIssuesStep into 3 visitors --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java index 046d7cefcc1..4c16f1ed92e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java @@ -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 index 00000000000..87075b4b47e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitor.java @@ -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 deletedComponentUuids) { + DiskCache.DiskAppender cacheAppender = issueCache.newAppender(); + try { + for (String deletedComponentUuid : deletedComponentUuids) { + List 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 index 00000000000..4a54a9e1d0c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssues.java @@ -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 uuids; + + public void setUuids(Set 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 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 index 00000000000..4cbcf06ed33 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IntegrateIssuesVisitor.java @@ -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 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.DiskAppender cacheAppender = issueCache.newAppender(); + try { + Tracking 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 tracking, DiskCache.DiskAppender cacheAppender) { + for (DefaultIssue issue : tracking.getUnmatchedRaws()) { + issueLifecycle.initNewOpenIssue(issue); + process(component, issue, cacheAppender); + } + } + + private void fillExistingOpenIssues(Component component, Tracking tracking, DiskCache.DiskAppender cacheAppender) { + for (Map.Entry entry : tracking.getMatchedRaws().entrySet()) { + DefaultIssue raw = entry.getKey(); + DefaultIssue base = entry.getValue(); + issueLifecycle.mergeExistingOpenIssue(raw, base); + process(component, raw, cacheAppender); + } + for (Map.Entry 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 tracking, DiskCache.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.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 index 00000000000..5c9907bf50a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitor.java @@ -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()); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java index cd0542a8593..b3d92cc8273 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java @@ -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> 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 index 4f9a33aba58..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/IntegrateIssuesStep.java +++ /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 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.DiskAppender cacheAppender = issueCache.newAppender(); - try { - Tracking 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 tracking, DiskCache.DiskAppender cacheAppender) { - for (DefaultIssue issue : tracking.getUnmatchedRaws()) { - issueLifecycle.initNewOpenIssue(issue); - process(component, issue, cacheAppender); - } - } - - private void fillExistingOpenIssues(Component component, Tracking tracking, DiskCache.DiskAppender cacheAppender) { - for (Map.Entry entry : tracking.getMatchedRaws().entrySet()) { - DefaultIssue raw = entry.getKey(); - DefaultIssue base = entry.getValue(); - issueLifecycle.mergeExistingOpenIssue(raw, base); - process(component, raw, cacheAppender); - } - for (Map.Entry 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 tracking, DiskCache.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.DiskAppender cacheAppender) { - issueLifecycle.doAutomaticTransition(issue); - issueVisitors.onIssue(component, issue); - cacheAppender.append(issue); - } - - private void closeIssuesForDeletedComponentUuids(Set deletedComponentUuids) { - DiskCache.DiskAppender cacheAppender = issueCache.newAppender(); - try { - for (String deletedComponentUuid : deletedComponentUuids) { - List 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"; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java index 06cea673d41..d65b2d96add 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java @@ -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 index 00000000000..330592480fa --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/CloseIssuesOnRemovedComponentsVisitorTest.java @@ -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.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 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.emptySet()); + + underTest.visit(ReportComponent.builder(PROJECT, 1).build()); + + verifyZeroInteractions(issueLifecycle); + CloseableIterator 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 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 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 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 index 00000000000..86a4d3554a0 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ComponentsWithUnprocessedIssuesTest.java @@ -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 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 index 00000000000..7a810775bda --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java @@ -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 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()); + 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.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 rawIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class); + ArgumentCaptor 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 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 issues = newArrayList(issueCache.traverse()); + assertThat(issues).hasSize(1); + + assertThat(componentsWithUnprocessedIssues.getUuids()).isEmpty(); + } + + @Test + public void execute_issue_visitors() throws Exception { + componentsWithUnprocessedIssues.setUuids(Collections.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 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 index 00000000000..b2497e4551a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/LoadComponentUuidsHavingOpenIssuesVisitorTest.java @@ -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.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); + } +}