@@ -43,6 +43,7 @@ import org.sonar.ce.task.projectanalysis.duplication.DuplicationMeasures; | |||
import org.sonar.ce.task.projectanalysis.duplication.DuplicationRepositoryImpl; | |||
import org.sonar.ce.task.projectanalysis.duplication.IntegrateCrossProjectDuplications; | |||
import org.sonar.ce.task.projectanalysis.event.EventRepositoryImpl; | |||
import org.sonar.ce.task.projectanalysis.filemove.AddedFileRepositoryImpl; | |||
import org.sonar.ce.task.projectanalysis.filemove.FileSimilarityImpl; | |||
import org.sonar.ce.task.projectanalysis.filemove.MutableMovedFilesRepositoryImpl; | |||
import org.sonar.ce.task.projectanalysis.filemove.ScoreMatrixDumperImpl; | |||
@@ -284,6 +285,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop | |||
SourceSimilarityImpl.class, | |||
FileSimilarityImpl.class, | |||
MutableMovedFilesRepositoryImpl.class, | |||
AddedFileRepositoryImpl.class, | |||
// duplication | |||
IntegrateCrossProjectDuplications.class, |
@@ -0,0 +1,30 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.ce.task.projectanalysis.filemove; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
public interface AddedFileRepository { | |||
/** | |||
* @return {@code true} for any component on first analysis, otherwise {@code true} only if the specified component is | |||
* a {@link Component.Type#FILE file} registered to the repository. | |||
*/ | |||
boolean isAdded(Component component); | |||
} |
@@ -0,0 +1,60 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.ce.task.projectanalysis.filemove; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static com.google.common.base.Preconditions.checkNotNull; | |||
import static com.google.common.base.Preconditions.checkState; | |||
public class AddedFileRepositoryImpl implements MutableAddedFileRepository { | |||
private final Set<Component> addedComponents = new HashSet<>(); | |||
private final AnalysisMetadataHolder analysisMetadataHolder; | |||
public AddedFileRepositoryImpl(AnalysisMetadataHolder analysisMetadataHolder) { | |||
this.analysisMetadataHolder = analysisMetadataHolder; | |||
} | |||
@Override | |||
public boolean isAdded(Component component) { | |||
checkComponent(component); | |||
if (analysisMetadataHolder.isFirstAnalysis()) { | |||
return true; | |||
} | |||
return addedComponents.contains(component); | |||
} | |||
@Override | |||
public void register(Component component) { | |||
checkComponent(component); | |||
checkArgument(component.getType() == Component.Type.FILE, "component must be a file"); | |||
checkState(!analysisMetadataHolder.isFirstAnalysis(), "No file can be registered on first analysis"); | |||
addedComponents.add(component); | |||
} | |||
private static void checkComponent(Component component) { | |||
checkNotNull(component, "component can't be null"); | |||
} | |||
} |
@@ -22,7 +22,6 @@ package org.sonar.ce.task.projectanalysis.filemove; | |||
import com.google.common.collect.ArrayListMultimap; | |||
import com.google.common.collect.ImmutableList; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.ImmutableSet; | |||
import com.google.common.collect.Multimap; | |||
import com.google.common.collect.Sets; | |||
import java.util.ArrayList; | |||
@@ -62,7 +61,7 @@ import static com.google.common.collect.FluentIterable.from; | |||
import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; | |||
public class FileMoveDetectionStep implements ComputationStep { | |||
protected static final int MIN_REQUIRED_SCORE = 85; | |||
static final int MIN_REQUIRED_SCORE = 85; | |||
private static final Logger LOG = Loggers.get(FileMoveDetectionStep.class); | |||
private static final Comparator<ScoreMatrix.ScoreFile> SCORE_FILE_COMPARATOR = (o1, o2) -> -1 * Integer.compare(o1.getLineCount(), o2.getLineCount()); | |||
private static final double LOWER_BOUND_RATIO = 0.84; | |||
@@ -75,10 +74,11 @@ public class FileMoveDetectionStep implements ComputationStep { | |||
private final MutableMovedFilesRepository movedFilesRepository; | |||
private final SourceLinesHashRepository sourceLinesHash; | |||
private final ScoreMatrixDumper scoreMatrixDumper; | |||
private final MutableAddedFileRepository addedFileRepository; | |||
public FileMoveDetectionStep(AnalysisMetadataHolder analysisMetadataHolder, TreeRootHolder rootHolder, DbClient dbClient, | |||
FileSimilarity fileSimilarity, MutableMovedFilesRepository movedFilesRepository, SourceLinesHashRepository sourceLinesHash, | |||
ScoreMatrixDumper scoreMatrixDumper) { | |||
ScoreMatrixDumper scoreMatrixDumper, MutableAddedFileRepository addedFileRepository) { | |||
this.analysisMetadataHolder = analysisMetadataHolder; | |||
this.rootHolder = rootHolder; | |||
this.dbClient = dbClient; | |||
@@ -86,6 +86,7 @@ public class FileMoveDetectionStep implements ComputationStep { | |||
this.movedFilesRepository = movedFilesRepository; | |||
this.sourceLinesHash = sourceLinesHash; | |||
this.scoreMatrixDumper = scoreMatrixDumper; | |||
this.addedFileRepository = addedFileRepository; | |||
} | |||
@Override | |||
@@ -103,25 +104,30 @@ public class FileMoveDetectionStep implements ComputationStep { | |||
Profiler p = Profiler.createIfTrace(LOG); | |||
p.start(); | |||
Map<String, DbComponent> dbFilesByKey = getDbFilesByKey(); | |||
context.getStatistics().add("dbFiles", dbFilesByKey.size()); | |||
if (dbFilesByKey.isEmpty()) { | |||
LOG.debug("Previous snapshot has no file. Do nothing."); | |||
return; | |||
} | |||
Map<String, Component> reportFilesByKey = getReportFilesByKey(this.rootHolder.getRoot()); | |||
context.getStatistics().add("reportFiles", reportFilesByKey.size()); | |||
if (reportFilesByKey.isEmpty()) { | |||
LOG.debug("No files in report. Do nothing."); | |||
LOG.debug("No files in report. No file move detection."); | |||
return; | |||
} | |||
Set<String> addedFileKeys = ImmutableSet.copyOf(Sets.difference(reportFilesByKey.keySet(), dbFilesByKey.keySet())); | |||
Map<String, DbComponent> dbFilesByKey = getDbFilesByKey(); | |||
context.getStatistics().add("dbFiles", dbFilesByKey.size()); | |||
Set<String> addedFileKeys = difference(reportFilesByKey.keySet(), dbFilesByKey.keySet()); | |||
context.getStatistics().add("addedFiles", addedFileKeys.size()); | |||
Set<String> removedFileKeys = ImmutableSet.copyOf(Sets.difference(dbFilesByKey.keySet(), reportFilesByKey.keySet())); | |||
if (dbFilesByKey.isEmpty()) { | |||
registerAddedFiles(addedFileKeys, reportFilesByKey, null); | |||
LOG.debug("Previous snapshot has no file. No file move detection."); | |||
return; | |||
} | |||
Set<String> removedFileKeys = difference(dbFilesByKey.keySet(), reportFilesByKey.keySet()); | |||
// can find matches if at least one of the added or removed files groups is empty => abort | |||
if (addedFileKeys.isEmpty() || removedFileKeys.isEmpty()) { | |||
registerAddedFiles(addedFileKeys, reportFilesByKey, null); | |||
LOG.debug("Either no files added or no files removed. Do nothing."); | |||
return; | |||
} | |||
@@ -138,6 +144,8 @@ public class FileMoveDetectionStep implements ComputationStep { | |||
// not a single match with score higher than MIN_REQUIRED_SCORE => abort | |||
if (scoreMatrix.getMaxScore() < MIN_REQUIRED_SCORE) { | |||
context.getStatistics().add("movedFiles", 0); | |||
registerAddedFiles(addedFileKeys, reportFilesByKey, null); | |||
LOG.debug("max score in matrix is less than min required score ({}). Do nothing.", MIN_REQUIRED_SCORE); | |||
return; | |||
} | |||
@@ -148,7 +156,16 @@ public class FileMoveDetectionStep implements ComputationStep { | |||
ElectedMatches electedMatches = electMatches(removedFileKeys, reportFileSourcesByKey, matchesByScore); | |||
p.stopTrace("Matches elected"); | |||
context.getStatistics().add("movedFiles", electedMatches.size()); | |||
registerMatches(dbFilesByKey, reportFilesByKey, electedMatches); | |||
registerAddedFiles(addedFileKeys, reportFilesByKey, electedMatches); | |||
} | |||
public Set<String> difference(Set<String> set1, Set<String> set2) { | |||
if (set1.isEmpty() || set2.isEmpty()) { | |||
return set1; | |||
} | |||
return Sets.difference(set1, set2).immutableCopy(); | |||
} | |||
private void registerMatches(Map<String, DbComponent> dbFilesByKey, Map<String, Component> reportFilesByKey, ElectedMatches electedMatches) { | |||
@@ -161,6 +178,22 @@ public class FileMoveDetectionStep implements ComputationStep { | |||
} | |||
} | |||
private void registerAddedFiles(Set<String> addedFileKeys, Map<String, Component> reportFilesByKey, @Nullable ElectedMatches electedMatches) { | |||
if (electedMatches == null || electedMatches.isEmpty()) { | |||
addedFileKeys.stream() | |||
.map(reportFilesByKey::get) | |||
.forEach(addedFileRepository::register); | |||
} else { | |||
Set<String> reallyAddedFileKeys = new HashSet<>(addedFileKeys); | |||
for (Match electedMatch : electedMatches) { | |||
reallyAddedFileKeys.remove(electedMatch.getReportKey()); | |||
} | |||
reallyAddedFileKeys.stream() | |||
.map(reportFilesByKey::get) | |||
.forEach(addedFileRepository::register); | |||
} | |||
} | |||
private Map<String, DbComponent> getDbFilesByKey() { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
ImmutableList.Builder<DbComponent> builder = ImmutableList.builder(); | |||
@@ -402,5 +435,9 @@ public class FileMoveDetectionStep implements ComputationStep { | |||
public int size() { | |||
return matches.size(); | |||
} | |||
public boolean isEmpty() { | |||
return matches.isEmpty(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.ce.task.projectanalysis.filemove; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
public interface MutableAddedFileRepository extends AddedFileRepository { | |||
/** | |||
* @throws IllegalArgumentException if the specified component is not a {@link Component.Type#FILE File} | |||
* @throws IllegalStateException on first analysis as all components are added on first analysis, none should be | |||
* registered for performance reasons. | |||
*/ | |||
void register(Component file); | |||
} |
@@ -30,6 +30,7 @@ import org.sonar.ce.task.projectanalysis.analysis.Analysis; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; | |||
import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
import org.sonar.ce.task.projectanalysis.filemove.AddedFileRepository; | |||
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule; | |||
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder; | |||
import org.sonar.ce.task.projectanalysis.scm.Changeset; | |||
@@ -53,15 +54,18 @@ public class IssueCreationDateCalculator extends IssueVisitor { | |||
private final IssueChangeContext changeContext; | |||
private final ActiveRulesHolder activeRulesHolder; | |||
private final RuleRepository ruleRepository; | |||
private final AddedFileRepository addedFileRepository; | |||
public IssueCreationDateCalculator(AnalysisMetadataHolder analysisMetadataHolder, ScmInfoRepository scmInfoRepository, | |||
IssueFieldsSetter issueUpdater, ActiveRulesHolder activeRulesHolder, RuleRepository ruleRepository) { | |||
IssueFieldsSetter issueUpdater, ActiveRulesHolder activeRulesHolder, RuleRepository ruleRepository, | |||
AddedFileRepository addedFileRepository) { | |||
this.scmInfoRepository = scmInfoRepository; | |||
this.issueUpdater = issueUpdater; | |||
this.analysisMetadataHolder = analysisMetadataHolder; | |||
this.ruleRepository = ruleRepository; | |||
this.changeContext = createScan(new Date(analysisMetadataHolder.getAnalysisDate())); | |||
this.activeRulesHolder = activeRulesHolder; | |||
this.addedFileRepository = addedFileRepository; | |||
} | |||
@Override | |||
@@ -69,23 +73,36 @@ public class IssueCreationDateCalculator extends IssueVisitor { | |||
if (!issue.isNew()) { | |||
return; | |||
} | |||
Optional<Long> lastAnalysisOptional = lastAnalysis(); | |||
boolean firstAnalysis = !lastAnalysisOptional.isPresent(); | |||
if (firstAnalysis || isNewFile(component)) { | |||
backdateIssue(component, issue); | |||
return; | |||
} | |||
Rule rule = ruleRepository.findByKey(issue.getRuleKey()) | |||
.orElseThrow(illegalStateException("The rule with key '%s' raised an issue, but no rule with that key was found", issue.getRuleKey())); | |||
if (rule.isExternal()) { | |||
getDateOfLatestChange(component, issue).ifPresent(changeDate -> updateDate(issue, changeDate)); | |||
backdateIssue(component, issue); | |||
} else { | |||
// Rule can't be inactive (see contract of IssueVisitor) | |||
ActiveRule activeRule = activeRulesHolder.get(issue.getRuleKey()).get(); | |||
if (firstAnalysis || activeRuleIsNew(activeRule, lastAnalysisOptional.get()) | |||
if (activeRuleIsNew(activeRule, lastAnalysisOptional.get()) | |||
|| ruleImplementationChanged(activeRule.getRuleKey(), activeRule.getPluginKey(), lastAnalysisOptional.get())) { | |||
getDateOfLatestChange(component, issue).ifPresent(changeDate -> updateDate(issue, changeDate)); | |||
backdateIssue(component, issue); | |||
} | |||
} | |||
} | |||
private boolean isNewFile(Component component) { | |||
return component.getType() == Component.Type.FILE && addedFileRepository.isAdded(component); | |||
} | |||
private void backdateIssue(Component component, DefaultIssue issue) { | |||
getDateOfLatestChange(component, issue).ifPresent(changeDate -> updateDate(issue, changeDate)); | |||
} | |||
private boolean ruleImplementationChanged(RuleKey ruleKey, @Nullable String pluginKey, long lastAnalysisDate) { | |||
if (pluginKey == null) { | |||
return false; |
@@ -0,0 +1,129 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.ce.task.projectanalysis.filemove; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.util.Arrays; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.runner.RunWith; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
@RunWith(DataProviderRunner.class) | |||
public class AddedFileRepositoryImplTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class); | |||
private AddedFileRepositoryImpl underTest = new AddedFileRepositoryImpl(analysisMetadataHolder); | |||
@Test | |||
public void isAdded_fails_with_NPE_if_component_is_null() { | |||
expectedException.expect(NullPointerException.class); | |||
expectedException.expectMessage("component can't be null"); | |||
underTest.isAdded(null); | |||
} | |||
@Test | |||
public void isAdded_returns_true_for_any_component_type_on_first_analysis() { | |||
when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true); | |||
Arrays.stream(Component.Type.values()).forEach(type -> { | |||
Component component = newComponent(type); | |||
assertThat(underTest.isAdded(component)).isTrue(); | |||
}); | |||
} | |||
@Test | |||
public void isAdded_returns_false_for_unregistered_component_type_when_not_on_first_analysis() { | |||
when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false); | |||
Arrays.stream(Component.Type.values()).forEach(type -> { | |||
Component component = newComponent(type); | |||
assertThat(underTest.isAdded(component)).isFalse(); | |||
}); | |||
} | |||
@Test | |||
public void isAdded_returns_true_for_registered_file_when_not_on_first_analysis() { | |||
when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false); | |||
Component file1 = newComponent(Component.Type.FILE); | |||
Component file2 = newComponent(Component.Type.FILE); | |||
underTest.register(file1); | |||
assertThat(underTest.isAdded(file1)).isTrue(); | |||
assertThat(underTest.isAdded(file2)).isFalse(); | |||
} | |||
@Test | |||
public void register_fails_with_NPE_if_component_is_null() { | |||
expectedException.expect(NullPointerException.class); | |||
expectedException.expectMessage("component can't be null"); | |||
underTest.register(null); | |||
} | |||
@Test | |||
@UseDataProvider("anyTypeButFile") | |||
public void register_fails_with_IAE_if_component_is_not_a_file(Component.Type anyTypeButFile) { | |||
Component component = newComponent(anyTypeButFile); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("component must be a file"); | |||
underTest.register(component); | |||
} | |||
@DataProvider | |||
public static Object[][] anyTypeButFile() { | |||
return Arrays.stream(Component.Type.values()) | |||
.filter(t -> t != Component.Type.FILE) | |||
.map(t -> new Object[] {t}) | |||
.toArray(Object[][]::new); | |||
} | |||
@Test | |||
public void register_fails_with_ISE_if_called_on_first_analysis() { | |||
when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true); | |||
Component component = newComponent(Component.Type.FILE); | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("No file can be registered on first analysis"); | |||
underTest.register(component); | |||
} | |||
private static Component newComponent(Component.Type type) { | |||
Component component = mock(Component.class); | |||
when(component.getType()).thenReturn(type); | |||
return component; | |||
} | |||
} |
@@ -22,7 +22,9 @@ package org.sonar.ce.task.projectanalysis.filemove; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.function.Function; | |||
import java.util.stream.IntStream; | |||
@@ -228,9 +230,10 @@ public class FileMoveDetectionStepTest { | |||
private SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class); | |||
private FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl()); | |||
private CapturingScoreMatrixDumper scoreMatrixDumper = new CapturingScoreMatrixDumper(); | |||
private RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository(); | |||
private FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, | |||
fileSimilarity, movedFilesRepository, sourceLinesHash, scoreMatrixDumper); | |||
fileSimilarity, movedFilesRepository, sourceLinesHash, scoreMatrixDumper, addedFileRepository); | |||
@Before | |||
public void setUp() throws Exception { | |||
@@ -245,23 +248,26 @@ public class FileMoveDetectionStepTest { | |||
} | |||
@Test | |||
public void execute_detects_no_move_if_baseProjectSnapshot_is_null() { | |||
public void execute_detects_no_move_on_first_analysis() { | |||
analysisMetadataHolder.setBaseAnalysis(null); | |||
TestComputationStepContext context = new TestComputationStepContext(); | |||
underTest.execute(context); | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
verifyStatistics(context, null, null); | |||
verifyStatistics(context, null, null, null, null); | |||
} | |||
@Test | |||
public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() { | |||
analysisMetadataHolder.setBaseAnalysis(ANALYSIS); | |||
underTest.execute(new TestComputationStepContext()); | |||
TestComputationStepContext context = new TestComputationStepContext(); | |||
underTest.execute(context); | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(addedFileRepository.getComponents()).isEmpty(); | |||
verifyStatistics(context, 0, null, null, null); | |||
} | |||
@Test | |||
@@ -275,7 +281,8 @@ public class FileMoveDetectionStepTest { | |||
underTest.execute(context); | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
verifyStatistics(context, 0, null); | |||
assertThat(addedFileRepository.getComponents()).containsOnly(file1, file2); | |||
verifyStatistics(context, 2, 0, 2, null); | |||
} | |||
@Test | |||
@@ -288,7 +295,8 @@ public class FileMoveDetectionStepTest { | |||
underTest.execute(context); | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
verifyStatistics(context, 0, null); | |||
assertThat(addedFileRepository.getComponents()).isEmpty(); | |||
verifyStatistics(context, 0, null, null, null); | |||
} | |||
@Test | |||
@@ -297,13 +305,16 @@ public class FileMoveDetectionStepTest { | |||
Component file1 = fileComponent(FILE_1_REF, null); | |||
Component file2 = fileComponent(FILE_2_REF, null); | |||
insertFiles(file1.getDbKey(), file2.getDbKey()); | |||
insertContentOfFileInDb(file1.getDbKey(), CONTENT1); | |||
insertContentOfFileInDb(file2.getDbKey(), CONTENT2); | |||
setFilesInReport(file2, file1); | |||
TestComputationStepContext context = new TestComputationStepContext(); | |||
underTest.execute(context); | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
verifyStatistics(context, 0, null); | |||
assertThat(addedFileRepository.getComponents()).isEmpty(); | |||
verifyStatistics(context, 2, 2, 0, null); | |||
} | |||
@Test | |||
@@ -323,7 +334,8 @@ public class FileMoveDetectionStepTest { | |||
assertThat(originalFile.getId()).isEqualTo(dtos[0].getId()); | |||
assertThat(originalFile.getKey()).isEqualTo(dtos[0].getDbKey()); | |||
assertThat(originalFile.getUuid()).isEqualTo(dtos[0].uuid()); | |||
verifyStatistics(context, 1, 1); | |||
assertThat(addedFileRepository.getComponents()).isEmpty(); | |||
verifyStatistics(context, 1, 1, 1, 1); | |||
} | |||
@Test | |||
@@ -342,7 +354,8 @@ public class FileMoveDetectionStepTest { | |||
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()) | |||
.isGreaterThan(0) | |||
.isLessThan(MIN_REQUIRED_SCORE); | |||
verifyStatistics(context, 1, 1); | |||
assertThat(addedFileRepository.getComponents()).contains(file2); | |||
verifyStatistics(context, 1, 1, 1, 0); | |||
} | |||
@Test | |||
@@ -359,7 +372,8 @@ public class FileMoveDetectionStepTest { | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); | |||
verifyStatistics(context, 1, 1); | |||
assertThat(addedFileRepository.getComponents()).contains(file2); | |||
verifyStatistics(context, 1, 1, 1, 0); | |||
} | |||
@Test | |||
@@ -376,7 +390,8 @@ public class FileMoveDetectionStepTest { | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(scoreMatrixDumper.scoreMatrix).isNull(); | |||
verifyStatistics(context, 0, null); | |||
assertThat(addedFileRepository.getComponents()).containsOnly(file2); | |||
verifyStatistics(context, 1, 0, 1, null); | |||
} | |||
@Test | |||
@@ -393,7 +408,8 @@ public class FileMoveDetectionStepTest { | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); | |||
verifyStatistics(context, 1, 1); | |||
assertThat(addedFileRepository.getComponents()).contains(file2); | |||
verifyStatistics(context, 1, 1, 1, 0); | |||
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("max score in matrix is less than min required score (85). Do nothing."); | |||
} | |||
@@ -412,7 +428,8 @@ public class FileMoveDetectionStepTest { | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100); | |||
verifyStatistics(context, 1, 2); | |||
assertThat(addedFileRepository.getComponents()).containsOnly(file2, file3); | |||
verifyStatistics(context, 2, 1, 2, 0); | |||
} | |||
@Test | |||
@@ -431,24 +448,27 @@ public class FileMoveDetectionStepTest { | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100); | |||
verifyStatistics(context, 2, 1); | |||
assertThat(addedFileRepository.getComponents()).containsOnly(file3); | |||
verifyStatistics(context, 1, 2, 1, 0); | |||
} | |||
@Test | |||
public void execute_detects_no_move_if_two_files_are_empty() { | |||
public void execute_detects_no_move_if_two_files_are_empty_in_DB() { | |||
analysisMetadataHolder.setBaseAnalysis(ANALYSIS); | |||
Component file1 = fileComponent(FILE_1_REF, null); | |||
Component file2 = fileComponent(FILE_2_REF, null); | |||
insertFiles(file1.getDbKey(), file2.getDbKey()); | |||
insertContentOfFileInDb(file1.getDbKey(), null); | |||
insertContentOfFileInDb(file2.getDbKey(), null); | |||
setFilesInReport(file1, file2); | |||
TestComputationStepContext context = new TestComputationStepContext(); | |||
underTest.execute(context); | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(scoreMatrixDumper.scoreMatrix).isNull(); | |||
verifyStatistics(context, 2, null); | |||
assertThat(addedFileRepository.getComponents()).isEmpty(); | |||
verifyStatistics(context, 2, 2, 0, null); | |||
} | |||
@Test | |||
@@ -485,7 +505,8 @@ public class FileMoveDetectionStepTest { | |||
assertThat(originalFile5.getKey()).isEqualTo(dtos[3].getDbKey()); | |||
assertThat(originalFile5.getUuid()).isEqualTo(dtos[3].uuid()); | |||
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isGreaterThan(MIN_REQUIRED_SCORE); | |||
verifyStatistics(context, 4, 2); | |||
assertThat(addedFileRepository.getComponents()).isEmpty(); | |||
verifyStatistics(context, 3, 4, 2, 2); | |||
} | |||
@Test | |||
@@ -505,7 +526,7 @@ public class FileMoveDetectionStepTest { | |||
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); | |||
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); | |||
verifyStatistics(context, 2, 2); | |||
verifyStatistics(context, 2, 2, 2, 0); | |||
} | |||
/** | |||
@@ -559,7 +580,7 @@ public class FileMoveDetectionStepTest { | |||
.isEqualTo("1242_make_analysis_uuid_not_null_on_duplications_index.rb"); | |||
assertThat(movedFilesRepository.getOriginalFile(addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex).get().getKey()) | |||
.isEqualTo("AddComponentUuidColumnToDuplicationsIndex.java"); | |||
verifyStatistics(context, 12, 6); | |||
verifyStatistics(context, comps.values().size(), 12, 6, 3); | |||
} | |||
private String[] readLines(File filename) throws IOException { | |||
@@ -640,8 +661,30 @@ public class FileMoveDetectionStepTest { | |||
} | |||
} | |||
private static void verifyStatistics(TestComputationStepContext context, @Nullable Integer expectedDbFiles, @Nullable Integer expectedAddedFiles) { | |||
private static void verifyStatistics(TestComputationStepContext context, | |||
@Nullable Integer expectedReportFiles, @Nullable Integer expectedDbFiles, | |||
@Nullable Integer expectedAddedFiles, @Nullable Integer expectedMovedFiles) { | |||
context.getStatistics().assertValue("reportFiles", expectedReportFiles); | |||
context.getStatistics().assertValue("dbFiles", expectedDbFiles); | |||
context.getStatistics().assertValue("addedFiles", expectedAddedFiles); | |||
context.getStatistics().assertValue("movedFiles", expectedMovedFiles); | |||
} | |||
private static class RecordingMutableAddedFileRepository implements MutableAddedFileRepository { | |||
private final List<Component> components = new ArrayList<>(); | |||
@Override | |||
public void register(Component file) { | |||
components.add(file); | |||
} | |||
@Override | |||
public boolean isAdded(Component component) { | |||
throw new UnsupportedOperationException("isAdded should not be called"); | |||
} | |||
public List<Component> getComponents() { | |||
return components; | |||
} | |||
} | |||
} |
@@ -19,18 +19,25 @@ | |||
*/ | |||
package org.sonar.ce.task.projectanalysis.issue; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.function.BiConsumer; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.runner.RunWith; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.ce.task.projectanalysis.analysis.Analysis; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; | |||
import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
import org.sonar.ce.task.projectanalysis.filemove.AddedFileRepository; | |||
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule; | |||
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder; | |||
import org.sonar.ce.task.projectanalysis.scm.Changeset; | |||
@@ -54,12 +61,12 @@ import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyZeroInteractions; | |||
import static org.mockito.Mockito.when; | |||
@RunWith(DataProviderRunner.class) | |||
public class IssueCreationDateCalculatorTest { | |||
private static final String COMPONENT_UUID = "ab12"; | |||
@org.junit.Rule | |||
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); | |||
@org.junit.Rule | |||
public ExpectedException exception = ExpectedException.none(); | |||
@@ -70,10 +77,13 @@ public class IssueCreationDateCalculatorTest { | |||
private RuleKey ruleKey = RuleKey.of("reop", "rule"); | |||
private DefaultIssue issue = mock(DefaultIssue.class); | |||
private ActiveRule activeRule = mock(ActiveRule.class); | |||
private IssueCreationDateCalculator calculator; | |||
private IssueCreationDateCalculator underTest; | |||
private Analysis baseAnalysis = mock(Analysis.class); | |||
private Map<String, ScannerPlugin> scannerPlugins = new HashMap<>(); | |||
private RuleRepository ruleRepository = mock(RuleRepository.class); | |||
private AddedFileRepository addedFileRepository = mock(AddedFileRepository.class); | |||
private ScmInfo scmInfo; | |||
private Rule rule = mock(Rule.class); | |||
@@ -82,9 +92,9 @@ public class IssueCreationDateCalculatorTest { | |||
analysisMetadataHolder.setScannerPluginsByKey(scannerPlugins); | |||
analysisMetadataHolder.setAnalysisDate(new Date()); | |||
when(component.getUuid()).thenReturn(COMPONENT_UUID); | |||
calculator = new IssueCreationDateCalculator(analysisMetadataHolder, scmInfoRepository, issueUpdater, activeRulesHolder, ruleRepository); | |||
underTest = new IssueCreationDateCalculator(analysisMetadataHolder, scmInfoRepository, issueUpdater, activeRulesHolder, ruleRepository, addedFileRepository); | |||
when(ruleRepository.findByKey(ruleKey)).thenReturn(java.util.Optional.of(rule)); | |||
when(ruleRepository.findByKey(ruleKey)).thenReturn(Optional.of(rule)); | |||
when(activeRulesHolder.get(any(RuleKey.class))) | |||
.thenReturn(Optional.empty()); | |||
when(activeRulesHolder.get(ruleKey)) | |||
@@ -94,13 +104,13 @@ public class IssueCreationDateCalculatorTest { | |||
} | |||
@Test | |||
public void should_not_change_date_if_no_scm_available() { | |||
public void should_not_backdate_if_no_scm_available() { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
makeIssueNew(); | |||
noScm(); | |||
ruleCreatedAt(2800L); | |||
setRuleCreatedAt(2800L); | |||
run(); | |||
@@ -108,13 +118,14 @@ public class IssueCreationDateCalculatorTest { | |||
} | |||
@Test | |||
public void should_not_change_date_if_rule_and_plugin_and_base_plugin_are_old() { | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_not_backdate_if_rule_and_plugin_and_base_plugin_are_old(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
withScm(1200L); | |||
ruleCreatedAt(1500L); | |||
makeIssueNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
setRuleCreatedAt(1500L); | |||
rulePlugin("customjava"); | |||
pluginUpdatedAt("customjava", "java", 1700L); | |||
pluginUpdatedAt("java", 1700L); | |||
@@ -125,13 +136,14 @@ public class IssueCreationDateCalculatorTest { | |||
} | |||
@Test | |||
public void should_not_change_date_if_rule_and_plugin_are_old_and_no_base_plugin() { | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_not_backdate_if_rule_and_plugin_are_old_and_no_base_plugin(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
withScm(1200L); | |||
ruleCreatedAt(1500L); | |||
makeIssueNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
setRuleCreatedAt(1500L); | |||
rulePlugin("java"); | |||
pluginUpdatedAt("java", 1700L); | |||
@@ -141,13 +153,14 @@ public class IssueCreationDateCalculatorTest { | |||
} | |||
@Test | |||
public void should_not_change_date_if_issue_existed_before() { | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_not_backdate_if_issue_existed_before(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
existingIssue(); | |||
withScm(1200L); | |||
ruleCreatedAt(2800L); | |||
makeIssueNotNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
setRuleCreatedAt(2800L); | |||
run(); | |||
@@ -159,9 +172,8 @@ public class IssueCreationDateCalculatorTest { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
existingIssue(); | |||
when(issue.getRuleKey()) | |||
.thenReturn(RuleKey.of("repo", "disabled")); | |||
makeIssueNotNew(); | |||
setIssueBelongToNonExistingRule(); | |||
run(); | |||
@@ -173,8 +185,8 @@ public class IssueCreationDateCalculatorTest { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
when(ruleRepository.findByKey(ruleKey)).thenReturn(java.util.Optional.empty()); | |||
newIssue(); | |||
when(ruleRepository.findByKey(ruleKey)).thenReturn(Optional.empty()); | |||
makeIssueNew(); | |||
exception.expect(IllegalStateException.class); | |||
exception.expectMessage("The rule with key 'reop:rule' raised an issue, but no rule with that key was found"); | |||
@@ -182,155 +194,159 @@ public class IssueCreationDateCalculatorTest { | |||
} | |||
@Test | |||
public void should_change_date_if_scm_is_available_and_rule_is_new() { | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_backdate_date_if_scm_is_available_and_rule_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
withScm(1200L); | |||
ruleCreatedAt(2800L); | |||
makeIssueNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
setRuleCreatedAt(2800L); | |||
run(); | |||
assertChangeOfCreationDateTo(1200L); | |||
assertChangeOfCreationDateTo(expectedDate); | |||
} | |||
@Test | |||
public void should_change_date_if_scm_is_available_and_first_analysis() { | |||
analysisMetadataHolder.setBaseAnalysis(null); | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_backdate_date_if_scm_is_available_and_first_analysis(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
currentAnalysisIsFirstAnalysis(); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
withScm(1200L); | |||
makeIssueNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
run(); | |||
assertChangeOfCreationDateTo(1200L); | |||
assertChangeOfCreationDateTo(expectedDate); | |||
} | |||
@Test | |||
public void should_change_date_if_scm_is_available_and_plugin_is_new() { | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_backdate_date_if_scm_is_available_and_current_component_is_new_file(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
withScm(1200L); | |||
ruleCreatedAt(1500L); | |||
rulePlugin("java"); | |||
pluginUpdatedAt("java", 2500L); | |||
makeIssueNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
currentComponentIsNewFile(); | |||
run(); | |||
assertChangeOfCreationDateTo(1200L); | |||
assertChangeOfCreationDateTo(expectedDate); | |||
} | |||
@Test | |||
public void should_change_date_if_scm_is_available_and_base_plugin_is_new() { | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_backdate_if_scm_is_available_and_plugin_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
withScm(1200L); | |||
ruleCreatedAt(1500L); | |||
rulePlugin("customjava"); | |||
pluginUpdatedAt("customjava", "java", 1000L); | |||
makeIssueNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
setRuleCreatedAt(1500L); | |||
rulePlugin("java"); | |||
pluginUpdatedAt("java", 2500L); | |||
run(); | |||
assertChangeOfCreationDateTo(1200L); | |||
assertChangeOfCreationDateTo(expectedDate); | |||
} | |||
@Test | |||
public void should_backdate_external_issues() { | |||
analysisMetadataHolder.setBaseAnalysis(null); | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_backdate_if_scm_is_available_and_base_plugin_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
previousAnalysisWas(2000L); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
when(rule.isExternal()).thenReturn(true); | |||
when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder().setTextRange(range(2, 3)).build()); | |||
withScmAt(2, 1200L); | |||
withScmAt(3, 1300L); | |||
makeIssueNew(); | |||
configure.accept(issue, createMockScmInfo()); | |||
setRuleCreatedAt(1500L); | |||
rulePlugin("customjava"); | |||
pluginUpdatedAt("customjava", "java", 1000L); | |||
pluginUpdatedAt("java", 2500L); | |||
run(); | |||
assertChangeOfCreationDateTo(1300L); | |||
verifyZeroInteractions(activeRulesHolder); | |||
assertChangeOfCreationDateTo(expectedDate); | |||
} | |||
@Test | |||
public void should_use_primary_location_when_backdating() { | |||
analysisMetadataHolder.setBaseAnalysis(null); | |||
@UseDataProvider("backdatingDateCases") | |||
public void should_backdate_external_issues(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) { | |||
currentAnalysisIsFirstAnalysis(); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder().setTextRange(range(2, 3)).build()); | |||
withScmAt(2, 1200L); | |||
withScmAt(3, 1300L); | |||
makeIssueNew(); | |||
when(rule.isExternal()).thenReturn(true); | |||
configure.accept(issue, createMockScmInfo()); | |||
run(); | |||
assertChangeOfCreationDateTo(1300L); | |||
assertChangeOfCreationDateTo(expectedDate); | |||
verifyZeroInteractions(activeRulesHolder); | |||
} | |||
@Test | |||
public void should_use_flows_location_when_backdating() { | |||
analysisMetadataHolder.setBaseAnalysis(null); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
Builder builder = DbIssues.Locations.newBuilder() | |||
.setTextRange(range(2, 3)); | |||
Flow.Builder secondary = Flow.newBuilder().addLocation(Location.newBuilder().setTextRange(range(4, 5))); | |||
builder.addFlow(secondary).build(); | |||
Flow.Builder flow = Flow.newBuilder() | |||
.addLocation(Location.newBuilder().setTextRange(range(6, 7)).setComponentId(COMPONENT_UUID)) | |||
.addLocation(Location.newBuilder().setTextRange(range(8, 9)).setComponentId(COMPONENT_UUID)); | |||
builder.addFlow(flow).build(); | |||
when(issue.getLocations()).thenReturn(builder.build()); | |||
withScmAt(2, 1200L); | |||
withScmAt(3, 1300L); | |||
withScmAt(4, 1400L); | |||
withScmAt(5, 1500L); | |||
// some lines missing should be ok | |||
withScmAt(9, 1900L); | |||
run(); | |||
assertChangeOfCreationDateTo(1900L); | |||
@DataProvider | |||
public static Object[][] backdatingDateCases() { | |||
return new Object[][] { | |||
{new NoIssueLocation(), 1200L}, | |||
{new OnlyPrimaryLocation(), 1300L}, | |||
{new FlowOnCurrentFileOnly(), 1900L}, | |||
{new FlowOnMultipleFiles(), 1700L} | |||
}; | |||
} | |||
@Test | |||
public void should_ignore_flows_location_outside_current_file_when_backdating() { | |||
analysisMetadataHolder.setBaseAnalysis(null); | |||
currentAnalysisIs(3000L); | |||
newIssue(); | |||
Builder builder = DbIssues.Locations.newBuilder() | |||
.setTextRange(range(2, 3)); | |||
Flow.Builder secondary = Flow.newBuilder().addLocation(Location.newBuilder().setTextRange(range(4, 5))); | |||
builder.addFlow(secondary).build(); | |||
Flow.Builder flow = Flow.newBuilder() | |||
.addLocation(Location.newBuilder().setTextRange(range(6, 7)).setComponentId(COMPONENT_UUID)) | |||
.addLocation(Location.newBuilder().setTextRange(range(8, 9)).setComponentId("another")); | |||
builder.addFlow(flow).build(); | |||
when(issue.getLocations()).thenReturn(builder.build()); | |||
withScmAt(2, 1200L); | |||
withScmAt(3, 1300L); | |||
withScmAt(4, 1400L); | |||
withScmAt(5, 1500L); | |||
withScmAt(6, 1600L); | |||
withScmAt(7, 1700L); | |||
withScmAt(8, 1800L); | |||
withScmAt(9, 1900L); | |||
private static class NoIssueLocation implements BiConsumer<DefaultIssue, ScmInfo> { | |||
@Override | |||
public void accept(DefaultIssue issue, ScmInfo scmInfo) { | |||
setDateOfLatestScmChangeset(scmInfo, 1200L); | |||
} | |||
} | |||
run(); | |||
private static class OnlyPrimaryLocation implements BiConsumer<DefaultIssue, ScmInfo> { | |||
@Override | |||
public void accept(DefaultIssue issue, ScmInfo scmInfo) { | |||
when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder().setTextRange(range(2, 3)).build()); | |||
setDateOfChangetsetAtLine(scmInfo, 2, 1200L); | |||
setDateOfChangetsetAtLine(scmInfo, 3, 1300L); | |||
} | |||
} | |||
assertChangeOfCreationDateTo(1700L); | |||
private static class FlowOnCurrentFileOnly implements BiConsumer<DefaultIssue, ScmInfo> { | |||
@Override | |||
public void accept(DefaultIssue issue, ScmInfo scmInfo) { | |||
Builder locations = DbIssues.Locations.newBuilder() | |||
.setTextRange(range(2, 3)) | |||
.addFlow(newFlow(newLocation(4, 5))) | |||
.addFlow(newFlow(newLocation(6, 7, COMPONENT_UUID), newLocation(8, 9, COMPONENT_UUID))); | |||
when(issue.getLocations()).thenReturn(locations.build()); | |||
setDateOfChangetsetAtLine(scmInfo, 2, 1200L); | |||
setDateOfChangetsetAtLine(scmInfo, 3, 1300L); | |||
setDateOfChangetsetAtLine(scmInfo, 4, 1400L); | |||
setDateOfChangetsetAtLine(scmInfo, 5, 1500L); | |||
// some lines missing should be ok | |||
setDateOfChangetsetAtLine(scmInfo, 9, 1900L); | |||
} | |||
} | |||
private org.sonar.db.protobuf.DbCommons.TextRange.Builder range(int startLine, int endLine) { | |||
return TextRange.newBuilder().setStartLine(startLine).setEndLine(endLine); | |||
private static class FlowOnMultipleFiles implements BiConsumer<DefaultIssue, ScmInfo> { | |||
@Override | |||
public void accept(DefaultIssue issue, ScmInfo scmInfo) { | |||
Builder locations = DbIssues.Locations.newBuilder() | |||
.setTextRange(range(2, 3)) | |||
.addFlow(newFlow(newLocation(4, 5))) | |||
.addFlow(newFlow(newLocation(6, 7, COMPONENT_UUID), newLocation(8, 9, "another"))); | |||
when(issue.getLocations()).thenReturn(locations.build()); | |||
setDateOfChangetsetAtLine(scmInfo, 2, 1200L); | |||
setDateOfChangetsetAtLine(scmInfo, 3, 1300L); | |||
setDateOfChangetsetAtLine(scmInfo, 4, 1400L); | |||
setDateOfChangetsetAtLine(scmInfo, 5, 1500L); | |||
setDateOfChangetsetAtLine(scmInfo, 6, 1600L); | |||
setDateOfChangetsetAtLine(scmInfo, 7, 1700L); | |||
setDateOfChangetsetAtLine(scmInfo, 8, 1800L); | |||
setDateOfChangetsetAtLine(scmInfo, 9, 1900L); | |||
} | |||
} | |||
private void previousAnalysisWas(long analysisDate) { | |||
@@ -347,47 +363,60 @@ public class IssueCreationDateCalculatorTest { | |||
scannerPlugins.put(pluginKey, new ScannerPlugin(pluginKey, basePluginKey, updatedAt)); | |||
} | |||
private AnalysisMetadataHolderRule currentAnalysisIsFirstAnalysis() { | |||
return analysisMetadataHolder.setBaseAnalysis(null); | |||
} | |||
private void currentAnalysisIs(long analysisDate) { | |||
analysisMetadataHolder.setAnalysisDate(analysisDate); | |||
} | |||
private void newIssue() { | |||
private void currentComponentIsNewFile() { | |||
when(component.getType()).thenReturn(Component.Type.FILE); | |||
when(addedFileRepository.isAdded(component)).thenReturn(true); | |||
} | |||
private void makeIssueNew() { | |||
when(issue.isNew()) | |||
.thenReturn(true); | |||
} | |||
private void existingIssue() { | |||
private void makeIssueNotNew() { | |||
when(issue.isNew()) | |||
.thenReturn(false); | |||
} | |||
private void setIssueBelongToNonExistingRule() { | |||
when(issue.getRuleKey()) | |||
.thenReturn(RuleKey.of("repo", "disabled")); | |||
} | |||
private void noScm() { | |||
when(scmInfoRepository.getScmInfo(component)) | |||
.thenReturn(java.util.Optional.empty()); | |||
.thenReturn(Optional.empty()); | |||
} | |||
private void withScm(long blame) { | |||
createMockScmInfo(); | |||
Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build(); | |||
private static void setDateOfLatestScmChangeset(ScmInfo scmInfo, long date) { | |||
Changeset changeset = Changeset.newChangesetBuilder().setDate(date).setRevision("1").build(); | |||
when(scmInfo.getLatestChangeset()).thenReturn(changeset); | |||
} | |||
private void createMockScmInfo() { | |||
private static void setDateOfChangetsetAtLine(ScmInfo scmInfo, int line, long date) { | |||
Changeset changeset = Changeset.newChangesetBuilder().setDate(date).setRevision("1").build(); | |||
when(scmInfo.hasChangesetForLine(line)).thenReturn(true); | |||
when(scmInfo.getChangesetForLine(line)).thenReturn(changeset); | |||
} | |||
private ScmInfo createMockScmInfo() { | |||
if (scmInfo == null) { | |||
scmInfo = mock(ScmInfo.class); | |||
when(scmInfoRepository.getScmInfo(component)) | |||
.thenReturn(java.util.Optional.of(scmInfo)); | |||
.thenReturn(Optional.of(scmInfo)); | |||
} | |||
return scmInfo; | |||
} | |||
private void withScmAt(int line, long blame) { | |||
createMockScmInfo(); | |||
Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build(); | |||
when(scmInfo.hasChangesetForLine(line)).thenReturn(true); | |||
when(scmInfo.getChangesetForLine(line)).thenReturn(changeset); | |||
} | |||
private void ruleCreatedAt(long createdAt) { | |||
private void setRuleCreatedAt(long createdAt) { | |||
when(activeRule.getCreatedAt()).thenReturn(createdAt); | |||
} | |||
@@ -395,10 +424,28 @@ public class IssueCreationDateCalculatorTest { | |||
when(activeRule.getPluginKey()).thenReturn(pluginKey); | |||
} | |||
private static Location newLocation(int startLine, int endLine) { | |||
return Location.newBuilder().setTextRange(range(startLine, endLine)).build(); | |||
} | |||
private static Location newLocation(int startLine, int endLine, String componentUuid) { | |||
return Location.newBuilder().setTextRange(range(startLine, endLine)).setComponentId(componentUuid).build(); | |||
} | |||
private static org.sonar.db.protobuf.DbCommons.TextRange range(int startLine, int endLine) { | |||
return TextRange.newBuilder().setStartLine(startLine).setEndLine(endLine).build(); | |||
} | |||
private static Flow newFlow(Location... locations) { | |||
Flow.Builder builder = Flow.newBuilder(); | |||
Arrays.stream(locations).forEach(builder::addLocation); | |||
return builder.build(); | |||
} | |||
private void run() { | |||
calculator.beforeComponent(component); | |||
calculator.onIssue(component, issue); | |||
calculator.afterComponent(component); | |||
underTest.beforeComponent(component); | |||
underTest.onIssue(component, issue); | |||
underTest.afterComponent(component); | |||
} | |||
private void assertNoChangeOfCreationDate() { |