import org.sonar.server.computation.issue.IssueLifecycle;
import org.sonar.server.computation.issue.IssueVisitors;
import org.sonar.server.computation.issue.LoadComponentUuidsHavingOpenIssuesVisitor;
+import org.sonar.server.computation.issue.MovedIssueVisitor;
import org.sonar.server.computation.issue.NewEffortAggregator;
import org.sonar.server.computation.issue.NewEffortCalculator;
import org.sonar.server.computation.issue.RuleRepositoryImpl;
NewEffortAggregator.class,
IssueAssigner.class,
IssueCounter.class,
+ MovedIssueVisitor.class,
// visitors : order is important, measure computers must be executed at the end in order to access to every measures / issues
LoadComponentUuidsHavingOpenIssuesVisitor.class,
*/
package org.sonar.server.computation.issue;
+import com.google.common.base.Optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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.computation.filemove.MovedFilesRepository;
import org.sonar.server.util.cache.DiskCache;
import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
private final IssueVisitors issueVisitors;
private final MutableComponentIssuesRepository componentIssuesRepository;
private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
+ private final MovedFilesRepository movedFilesRepository;
private final List<DefaultIssue> componentIssues = new ArrayList<>();
public IntegrateIssuesVisitor(TrackerExecution tracker, IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors,
- ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, MutableComponentIssuesRepository componentIssuesRepository) {
+ ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, MutableComponentIssuesRepository componentIssuesRepository, MovedFilesRepository movedFilesRepository) {
super(CrawlerDepthLimit.FILE, POST_ORDER);
this.tracker = tracker;
this.issueCache = issueCache;
this.issueVisitors = issueVisitors;
this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
this.componentIssuesRepository = componentIssuesRepository;
+ this.movedFilesRepository = movedFilesRepository;
}
@Override
public void visitAny(Component component) {
componentIssues.clear();
processIssues(component);
+
componentsWithUnprocessedIssues.remove(component.getUuid());
+ Optional<MovedFilesRepository.OriginalFile> originalFile = movedFilesRepository.getOriginalFile(component);
+ if (originalFile.isPresent()) {
+ componentsWithUnprocessedIssues.remove(originalFile.get().getUuid());
+ }
componentIssuesRepository.setIssues(component, componentIssues);
}
} else {
updater.setPastSeverity(raw, base.severity(), changeContext);
}
+ // set component/module related fields from base in case current component has been moved
+ // (in which case base issue belongs to original file and raw issue to component)
+ raw.setComponentUuid(base.componentUuid());
+ raw.setComponentKey(base.componentKey());
+ raw.setModuleUuid(base.moduleUuid());
+ raw.setModuleUuidPath(base.moduleUuidPath());
// fields coming from raw
updater.setPastLine(raw, base.getLine());
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 com.google.common.base.Optional;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.filemove.MovedFilesRepository;
+import org.sonar.server.computation.filemove.MovedFilesRepository.OriginalFile;
+
+import static com.google.common.base.Preconditions.checkState;
+
+public class MovedIssueVisitor extends IssueVisitor {
+ private final MovedFilesRepository movedFilesRepository;
+
+ public MovedIssueVisitor(MovedFilesRepository movedFilesRepository) {
+ this.movedFilesRepository = movedFilesRepository;
+ }
+
+ @Override
+ public void onIssue(Component component, DefaultIssue issue) {
+ if (component.getType() != Component.Type.FILE || component.getUuid().equals(issue.componentUuid())) {
+ return;
+ }
+ Optional<OriginalFile> originalFileOptional = movedFilesRepository.getOriginalFile(component);
+ checkState(originalFileOptional.isPresent(),
+ "Issue %s for component %s has a different component key but no original file exist in MovedFilesRepository",
+ issue, component);
+ OriginalFile originalFile = originalFileOptional.get();
+ checkState(originalFile.getUuid().equals(issue.componentUuid()),
+ "Issue %s doesn't belong to file %s registered as original file of current file %s",
+ issue, originalFile.getUuid(), component);
+
+ // it's enough to change component uuid, only this field is written to table ISSUES
+ // other fields (such as module, modulePath, componentKey) are read-only and result of a join with other tables
+ issue.setComponentUuid(component.getUuid());
+ issue.setComponentKey(component.getKey());
+ issue.setModuleUuid(null);
+ issue.setModuleUuidPath(null);
+
+ // ensure issue is updated in DB
+ issue.setChanged(true);
+ }
+}
import java.util.Collections;
import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Input;
import org.sonar.core.issue.tracking.LazyInput;
import org.sonar.db.DbSession;
import org.sonar.db.MyBatis;
import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.filemove.MovedFilesRepository;
+import org.sonar.server.computation.filemove.MovedFilesRepository.OriginalFile;
/**
* Factory of {@link Input} of base data for issue tracking. Data are lazy-loaded.
private final BaseIssuesLoader baseIssuesLoader;
private final DbClient dbClient;
+ private final MovedFilesRepository movedFilesRepository;
- public TrackerBaseInputFactory(BaseIssuesLoader baseIssuesLoader, DbClient dbClient) {
+ public TrackerBaseInputFactory(BaseIssuesLoader baseIssuesLoader, DbClient dbClient, MovedFilesRepository movedFilesRepository) {
this.baseIssuesLoader = baseIssuesLoader;
this.dbClient = dbClient;
+ this.movedFilesRepository = movedFilesRepository;
}
public Input<DefaultIssue> create(Component component) {
- return new BaseLazyInput(component);
+ return new BaseLazyInput(component, movedFilesRepository.getOriginalFile(component).orNull());
}
private class BaseLazyInput extends LazyInput<DefaultIssue> {
private final Component component;
+ @CheckForNull
+ private final String effectiveUuid;
- private BaseLazyInput(Component component) {
+ private BaseLazyInput(Component component, @Nullable OriginalFile originalFile) {
this.component = component;
+ this.effectiveUuid = originalFile == null ? component.getUuid() : originalFile.getUuid();
}
@Override
DbSession session = dbClient.openSession(false);
try {
- List<String> hashes = dbClient.fileSourceDao().selectLineHashes(session, component.getUuid());
+ List<String> hashes = dbClient.fileSourceDao().selectLineHashes(session, effectiveUuid);
if (hashes == null || hashes.isEmpty()) {
return EMPTY_LINE_HASH_SEQUENCE;
}
@Override
protected List<DefaultIssue> loadIssues() {
- return baseIssuesLoader.loadForComponentUuid(component.getUuid());
+ return baseIssuesLoader.loadForComponentUuid(effectiveUuid);
}
}
}
*/
package org.sonar.server.computation.issue;
+import com.google.common.base.Optional;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
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.filemove.MovedFilesRepository;
import org.sonar.server.computation.issue.commonrule.CommonRuleEngineImpl;
import org.sonar.server.computation.issue.filter.IssueFilter;
import org.sonar.server.computation.qualityprofile.ActiveRulesHolderRule;
@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();
-
@Rule
public ComponentIssuesRepositoryRule componentIssuesRepository = new ComponentIssuesRepositoryRule(treeRootHolder);
-
@Rule
public SourceLinesRepositoryRule fileSourceRepository = new SourceLinesRepositoryRule();
IssueFilter issueFilter = mock(IssueFilter.class);
BaseIssuesLoader baseIssuesLoader = new BaseIssuesLoader(treeRootHolder, dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule);
- TrackerExecution tracker = new TrackerExecution(new TrackerBaseInputFactory(baseIssuesLoader, dbTester.getDbClient()), new TrackerRawInputFactory(treeRootHolder, reportReader,
- fileSourceRepository, new CommonRuleEngineImpl(), issueFilter), new Tracker<DefaultIssue, DefaultIssue>());
+ MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
+ TrackerExecution tracker = new TrackerExecution(new TrackerBaseInputFactory(baseIssuesLoader, dbTester.getDbClient(), movedFilesRepository),
+ new TrackerRawInputFactory(treeRootHolder, reportReader,
+ fileSourceRepository, new CommonRuleEngineImpl(), issueFilter),
+ new Tracker<>());
IssueCache issueCache;
IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
treeRootHolder.setRoot(PROJECT);
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
- underTest = new IntegrateIssuesVisitor(tracker, issueCache, issueLifecycle, issueVisitors, componentsWithUnprocessedIssues, componentIssuesRepository);
+ when(movedFilesRepository.getOriginalFile(any(Component.class))).thenReturn(Optional.<MovedFilesRepository.OriginalFile>absent());
+ underTest = new IntegrateIssuesVisitor(tracker, issueCache, issueLifecycle, issueVisitors, componentsWithUnprocessedIssues, componentIssuesRepository, movedFilesRepository);
}
@Test
assertThat(componentIssuesRepository.getIssues(PROJECT)).isEmpty();
}
+ @Test
+ public void remove_uuid_of_original_file_from_componentsWithUnprocessedIssues_if_component_has_one() {
+ String originalFileUuid = "original file uuid";
+ componentsWithUnprocessedIssues.setUuids(newHashSet(FILE_UUID, originalFileUuid));
+ when(movedFilesRepository.getOriginalFile(FILE))
+ .thenReturn(Optional.of(new MovedFilesRepository.OriginalFile(4851, originalFileUuid, "original file key")));
+
+ underTest.visitAny(FILE);
+
+ 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);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 com.google.common.base.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportComponent;
+import org.sonar.server.computation.filemove.MovedFilesRepository;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class MovedIssueVisitorTest {
+ private static final String FILE_UUID = "file uuid";
+ private static final Component FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid(FILE_UUID).build();
+
+ @org.junit.Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
+ private MovedIssueVisitor underTest = new MovedIssueVisitor(movedFilesRepository);
+
+ @Before
+ public void setUp() throws Exception {
+ when(movedFilesRepository.getOriginalFile(any(Component.class)))
+ .thenReturn(Optional.<MovedFilesRepository.OriginalFile>absent());
+ }
+
+ @Test
+ public void onIssue_does_not_alter_issue_if_component_is_not_a_file() {
+ DefaultIssue issue = mock(DefaultIssue.class);
+ underTest.onIssue(ReportComponent.builder(Component.Type.DIRECTORY, 1).build(), issue);
+
+ verifyZeroInteractions(issue);
+ }
+
+ @Test
+ public void onIssue_does_not_alter_issue_if_component_file_but_issue_has_the_same_component_uuid() {
+ DefaultIssue issue = mockIssue(FILE_UUID);
+ underTest.onIssue(FILE, issue);
+
+ verify(issue).componentUuid();
+ verifyNoMoreInteractions(issue);
+ }
+
+ @Test
+ public void onIssue_throws_ISE_if_issue_has_different_component_uuid_but_component_has_no_original_file() {
+ DefaultIssue issue = mockIssue("other component uuid");
+ when(issue.toString()).thenReturn("[bad issue, bad!]");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Issue [bad issue, bad!] for component ReportComponent{ref=1, key='key_1', type=FILE} " +
+ "has a different component key but no original file exist in MovedFilesRepository");
+
+ underTest.onIssue(FILE, issue);
+ }
+
+ @Test
+ public void onIssue_throws_ISE_if_issue_has_different_component_uuid_from_component_but_it_is_not_the_one_of_original_file() {
+ DefaultIssue issue = mockIssue("other component uuid");
+ when(issue.toString()).thenReturn("[bad issue, bad!]");
+ when(movedFilesRepository.getOriginalFile(FILE))
+ .thenReturn(Optional.of(new MovedFilesRepository.OriginalFile(6451, "original uuid", "original key")));
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Issue [bad issue, bad!] doesn't belong to file original uuid registered as original " +
+ "file of current file ReportComponent{ref=1, key='key_1', type=FILE}");
+
+ underTest.onIssue(FILE, issue);
+ }
+
+ @Test
+ public void onIssue_update_component_and_module_fields_to_component_and_flag_issue_has_changed() {
+ MovedFilesRepository.OriginalFile originalFile = new MovedFilesRepository.OriginalFile(6451, "original uuid", "original key");
+ DefaultIssue issue = mockIssue(originalFile.getUuid());
+ when(movedFilesRepository.getOriginalFile(FILE))
+ .thenReturn(Optional.of(originalFile));
+
+ underTest.onIssue(FILE, issue);
+
+ verify(issue).setComponentUuid(FILE.getUuid());
+ verify(issue).setComponentKey(FILE.getKey());
+ verify(issue).setModuleUuid(null);
+ verify(issue).setModuleUuidPath(null);
+
+ verify(issue).setChanged(true);
+ }
+
+ private DefaultIssue mockIssue(String fileUuid) {
+ DefaultIssue issue = mock(DefaultIssue.class);
+ when(issue.componentUuid()).thenReturn(fileUuid);
+ return issue;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 com.google.common.base.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.source.FileSourceDao;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportComponent;
+import org.sonar.server.computation.filemove.MovedFilesRepository;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TrackerBaseInputFactoryTest {
+ private static final String FILE_UUID = "uuid";
+ private static final ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid(FILE_UUID).build();
+
+ private BaseIssuesLoader baseIssuesLoader = mock(BaseIssuesLoader.class);
+ private DbClient dbClient = mock(DbClient.class);
+ private DbSession dbSession = mock(DbSession.class);
+ private FileSourceDao fileSourceDao = mock(FileSourceDao.class);
+
+ private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
+
+ private TrackerBaseInputFactory underTest = new TrackerBaseInputFactory(baseIssuesLoader, dbClient, movedFilesRepository);
+
+ @Before
+ public void setUp() throws Exception {
+ when(dbClient.openSession(false)).thenReturn(dbSession);
+ when(dbClient.fileSourceDao()).thenReturn(fileSourceDao);
+ when(movedFilesRepository.getOriginalFile(any(Component.class)))
+ .thenReturn(Optional.<MovedFilesRepository.OriginalFile>absent());
+ }
+
+ @Test
+ public void create_returns_Input_which_retrieves_lines_hashes_of_specified_file_component_when_it_has_no_original_file() {
+ underTest.create(FILE).getLineHashSequence();
+
+ verify(fileSourceDao).selectLineHashes(dbSession, FILE_UUID);
+ }
+
+ @Test
+ public void create_returns_Input_which_retrieves_lines_hashes_of_original_file_of_component_when_it_has_one() {
+ String originalUuid = "original uuid";
+
+ when(movedFilesRepository.getOriginalFile(FILE)).thenReturn(
+ Optional.of(new MovedFilesRepository.OriginalFile(6542, originalUuid, "original key"))
+ );
+
+ underTest.create(FILE).getLineHashSequence();
+
+ verify(fileSourceDao).selectLineHashes(dbSession, originalUuid);
+ verify(fileSourceDao, times(0)).selectLineHashes(dbSession, FILE_UUID);
+ }
+
+ @Test
+ public void create_returns_Input_which_retrieves_issues_of_specified_file_component_when_it_has_no_original_file() {
+ underTest.create(FILE).getIssues();
+
+ verify(baseIssuesLoader).loadForComponentUuid(FILE_UUID);
+ }
+
+ @Test
+ public void create_returns_Input_which_retrieves_issues_of_original_file_of_component_when_it_has_one() {
+ String originalUuid = "original uuid";
+
+ when(movedFilesRepository.getOriginalFile(FILE)).thenReturn(
+ Optional.of(new MovedFilesRepository.OriginalFile(6542, originalUuid, "original key"))
+ );
+
+ underTest.create(FILE).getIssues();
+
+ verify(baseIssuesLoader).loadForComponentUuid(originalUuid);
+ verify(baseIssuesLoader, times(0)).loadForComponentUuid(FILE_UUID);
+ }
+}