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;
DefaultAssignee.class,
IssueVisitors.class,
IssueLifecycle.class,
+ ComponentsWithUnprocessedIssues.class,
// common rules
CommonRuleEngineImpl.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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();
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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");
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.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);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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());
+ }
+}
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;
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
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.computation.step;
-
-import com.google.common.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";
- }
-
-}
UnitTestMeasuresStep.class,
ComplexityMeasuresStep.class,
- // must be executed after the measures required for common rules (coverage, comment density, duplications)
- IntegrateIssuesStep.class,
-
FeedMeasureComputers.class,
ExecuteVisitorsStep.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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);
+ }
+}