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;
SourceSimilarityImpl.class,
FileSimilarityImpl.class,
MutableMovedFilesRepositoryImpl.class,
+ AddedFileRepositoryImpl.class,
// duplication
IntegrateCrossProjectDuplications.class,
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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");
+ }
+}
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;
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;
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;
this.movedFilesRepository = movedFilesRepository;
this.sourceLinesHash = sourceLinesHash;
this.scoreMatrixDumper = scoreMatrixDumper;
+ this.addedFileRepository = addedFileRepository;
}
@Override
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;
}
// 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;
}
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) {
}
}
+ 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();
public int size() {
return matches.size();
}
+
+ public boolean isEmpty() {
+ return matches.isEmpty();
+ }
}
}
--- /dev/null
+/*
+ * 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);
+}
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;
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
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;
--- /dev/null
+/*
+ * 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;
+ }
+}
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;
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 {
}
@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
underTest.execute(context);
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
- verifyStatistics(context, 0, null);
+ assertThat(addedFileRepository.getComponents()).containsOnly(file1, file2);
+ verifyStatistics(context, 2, 0, 2, null);
}
@Test
underTest.execute(context);
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
- verifyStatistics(context, 0, null);
+ assertThat(addedFileRepository.getComponents()).isEmpty();
+ verifyStatistics(context, 0, null, null, null);
}
@Test
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
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
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
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
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
assertThat(scoreMatrixDumper.scoreMatrix).isNull();
- verifyStatistics(context, 0, null);
+ assertThat(addedFileRepository.getComponents()).containsOnly(file2);
+ verifyStatistics(context, 1, 0, 1, null);
}
@Test
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.");
}
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
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
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
assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
- verifyStatistics(context, 2, 2);
+ verifyStatistics(context, 2, 2, 2, 0);
}
/**
.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 {
}
}
- 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;
+ }
}
}
*/
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;
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();
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);
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))
}
@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();
}
@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);
}
@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);
}
@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();
previousAnalysisWas(2000L);
currentAnalysisIs(3000L);
- existingIssue();
- when(issue.getRuleKey())
- .thenReturn(RuleKey.of("repo", "disabled"));
+ makeIssueNotNew();
+ setIssueBelongToNonExistingRule();
run();
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");
}
@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) {
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);
}
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() {