3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.ce.task.projectanalysis.filemove;
23 import java.util.Optional;
25 import javax.annotation.CheckForNull;
26 import javax.annotation.Nullable;
27 import javax.annotation.concurrent.Immutable;
28 import org.junit.Before;
29 import org.junit.Rule;
30 import org.junit.Test;
31 import org.sonar.api.utils.System2;
32 import org.sonar.api.testfixtures.log.LogTester;
33 import org.sonar.ce.task.projectanalysis.analysis.Analysis;
34 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
35 import org.sonar.ce.task.projectanalysis.analysis.Branch;
36 import org.sonar.ce.task.projectanalysis.component.Component;
37 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
38 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
39 import org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepIT.RecordingMutableAddedFileRepository;
40 import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository.OriginalFile;
41 import org.sonar.ce.task.step.TestComputationStepContext;
42 import org.sonar.core.util.Uuids;
43 import org.sonar.db.DbClient;
44 import org.sonar.db.DbTester;
45 import org.sonar.db.component.BranchType;
46 import org.sonar.db.component.ComponentDto;
47 import org.sonar.db.component.ComponentTesting;
48 import org.sonar.db.source.FileSourceDto;
49 import org.sonar.server.project.Project;
51 import static java.util.function.Function.identity;
52 import static java.util.stream.Collectors.toMap;
53 import static java.util.stream.Collectors.toSet;
54 import static org.assertj.core.api.Assertions.assertThat;
55 import static org.mockito.Mockito.mock;
56 import static org.mockito.Mockito.when;
57 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
58 import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
59 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
60 import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepIT.verifyStatistics;
61 import static org.sonar.db.component.BranchType.BRANCH;
62 import static org.sonar.db.component.BranchType.PULL_REQUEST;
64 public class PullRequestFileMoveDetectionStepIT {
65 private static final String ROOT_REF = "0";
66 private static final String FILE_1_REF = "1";
67 private static final String FILE_2_REF = "2";
68 private static final String FILE_3_REF = "3";
69 private static final String FILE_4_REF = "4";
70 private static final String FILE_5_REF = "5";
71 private static final String FILE_6_REF = "6";
72 private static final String FILE_7_REF = "7";
73 private static final String TARGET_BRANCH = "target_branch";
74 private static final String BRANCH_UUID = "branch_uuid";
75 private static final String SNAPSHOT_UUID = "uuid_1";
77 private static final Analysis ANALYSIS = new Analysis.Builder()
78 .setUuid(SNAPSHOT_UUID)
83 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
85 public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
87 public DbTester dbTester = DbTester.create(System2.INSTANCE);
89 public LogTester logTester = new LogTester();
91 private ComponentDto branch;
92 private ComponentDto project;
94 private final DbClient dbClient = dbTester.getDbClient();
95 private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class);
96 private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository();
97 private final PullRequestFileMoveDetectionStep underTest = new PullRequestFileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, movedFilesRepository, addedFileRepository);
100 public void setUp() throws Exception {
101 project = dbTester.components().insertPrivateProject().getMainBranchComponent();
102 branch = dbTester.components().insertProjectBranch(project, branchDto -> branchDto.setUuid(BRANCH_UUID).setKey(TARGET_BRANCH));
103 treeRootHolder.setRoot(builder(Component.Type.PROJECT, Integer.parseInt(ROOT_REF)).setUuid(project.uuid()).build());
107 public void getDescription_returns_description() {
108 assertThat(underTest.getDescription()).isEqualTo("Detect file moves in Pull Request scope");
112 public void execute_does_not_detect_any_files_if_not_in_pull_request_scope() {
113 prepareAnalysis(BRANCH, ANALYSIS);
115 TestComputationStepContext context = new TestComputationStepContext();
116 underTest.execute(context);
118 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
119 verifyStatistics(context, null, null, null, null);
123 public void execute_detects_no_move_if_report_has_no_file() {
124 preparePullRequestAnalysis(ANALYSIS);
126 TestComputationStepContext context = new TestComputationStepContext();
127 underTest.execute(context);
129 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
130 assertThat(addedFileRepository.getComponents()).isEmpty();
131 verifyStatistics(context, 0, null, null, null);
135 public void execute_detects_no_move_if_target_branch_has_no_files() {
136 preparePullRequestAnalysis(ANALYSIS);
137 Set<FileReference> fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF));
138 Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(fileReferences);
140 TestComputationStepContext context = new TestComputationStepContext();
141 underTest.execute(context);
143 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
144 assertThat(addedFileRepository.getComponents()).containsOnlyOnceElementsOf(reportFilesByUuid.values());
145 verifyStatistics(context, 2, 0, 2, null);
149 public void execute_detects_no_move_if_there_are_no_files_in_report() {
150 preparePullRequestAnalysis(ANALYSIS);
151 initializeAnalysisReportComponents(Set.of());
153 TestComputationStepContext context = new TestComputationStepContext();
154 underTest.execute(context);
156 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
157 assertThat(addedFileRepository.getComponents()).isEmpty();
158 verifyStatistics(context, 0, null, null, null);
162 public void execute_detects_no_move_if_file_key_exists_in_both_database_and_report() {
163 preparePullRequestAnalysis(ANALYSIS);
165 Set<FileReference> fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF));
166 initializeAnalysisReportComponents(fileReferences);
167 initializeTargetBranchDatabaseComponents(fileReferences);
169 TestComputationStepContext context = new TestComputationStepContext();
170 underTest.execute(context);
172 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
173 assertThat(addedFileRepository.getComponents()).isEmpty();
174 verifyStatistics(context, 2, 2, 0, 0);
178 public void execute_detects_renamed_file() {
179 // - FILE_1_REF on target branch is renamed to FILE_2_REF on Pull Request
180 preparePullRequestAnalysis(ANALYSIS);
182 Set<FileReference> reportFileReferences = Set.of(FileReference.of(FILE_2_REF, FILE_1_REF));
183 Set<FileReference> databaseFileReferences = Set.of(FileReference.of(FILE_1_REF));
185 Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
186 Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
188 TestComputationStepContext context = new TestComputationStepContext();
189 underTest.execute(context);
191 assertThat(addedFileRepository.getComponents()).isEmpty();
192 assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(1);
193 assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_2_REF, FILE_1_REF);
194 verifyStatistics(context, 1, 1, 0, 1);
198 public void execute_detects_several_renamed_file() {
199 // - FILE_1_REF has been renamed to FILE_3_REF on Pull Request
200 // - FILE_2_REF has been deleted on Pull Request
201 // - FILE_4_REF has been left untouched
202 // - FILE_5_REF has been renamed to FILE_6_REF on Pull Request
203 // - FILE_7_REF has been added on Pull Request
204 preparePullRequestAnalysis(ANALYSIS);
206 Set<FileReference> reportFileReferences = Set.of(
207 FileReference.of(FILE_3_REF, FILE_1_REF),
208 FileReference.of(FILE_4_REF),
209 FileReference.of(FILE_6_REF, FILE_5_REF),
210 FileReference.of(FILE_7_REF));
212 Set<FileReference> databaseFileReferences = Set.of(
213 FileReference.of(FILE_1_REF),
214 FileReference.of(FILE_2_REF),
215 FileReference.of(FILE_4_REF),
216 FileReference.of(FILE_5_REF));
218 Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
219 Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
221 TestComputationStepContext context = new TestComputationStepContext();
222 underTest.execute(context);
224 assertThat(addedFileRepository.getComponents()).hasSize(1);
225 assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(2);
226 assertThatFileAdditionHasBeenDetected(reportFilesByUuid, FILE_7_REF);
227 assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_3_REF, FILE_1_REF);
228 assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_6_REF, FILE_5_REF);
229 verifyStatistics(context, 4, 4, 1, 2);
232 private void assertThatFileAdditionHasBeenDetected(Map<String, Component> reportFilesByUuid, String fileInReportReference) {
233 Component fileInReport = reportFilesByUuid.get(fileInReportReference);
235 assertThat(addedFileRepository.getComponents()).contains(fileInReport);
236 assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isEmpty();
240 private void assertThatFileRenameHasBeenDetected(Map<String, Component> reportFilesByUuid, Map<String, Component> databaseFilesByUuid, String fileInReportReference, String originalFileInDatabaseReference) {
241 Component fileInReport = reportFilesByUuid.get(fileInReportReference);
242 Component originalFileInDatabase = databaseFilesByUuid.get(originalFileInDatabaseReference);
244 assertThat(movedFilesRepository.getComponentsWithOriginal()).contains(fileInReport);
245 assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isPresent();
247 OriginalFile detectedOriginalFile = movedFilesRepository.getOriginalPullRequestFile(fileInReport).get();
248 assertThat(detectedOriginalFile.key()).isEqualTo(originalFileInDatabase.getKey());
249 assertThat(detectedOriginalFile.uuid()).isEqualTo(originalFileInDatabase.getUuid());
252 private Map<String, Component> initializeTargetBranchDatabaseComponents(Set<FileReference> references) {
253 Set<Component> fileComponents = createFileComponents(references);
254 insertFileComponentsInDatabase(fileComponents);
255 return toFileComponentsByUuidMap(fileComponents);
258 private Map<String, Component> initializeAnalysisReportComponents(Set<FileReference> refs) {
259 Set<Component> fileComponents = createFileComponents(refs);
260 insertFileComponentsInReport(fileComponents);
261 return toFileComponentsByUuidMap(fileComponents);
264 private Map<String, Component> toFileComponentsByUuidMap(Set<Component> fileComponents) {
265 return fileComponents
267 .collect(toMap(Component::getUuid, identity()));
270 private static Set<Component> createFileComponents(Set<FileReference> references) {
273 .map(PullRequestFileMoveDetectionStepIT::createReportFileComponent)
277 private static Component createReportFileComponent(FileReference fileReference) {
278 return builder(FILE, Integer.parseInt(fileReference.getReference()))
279 .setUuid(fileReference.getReference())
280 .setName("report_path" + fileReference.getReference())
281 .setFileAttributes(new FileAttributes(false, null, 1, false, composeComponentPath(fileReference.getPastReference())))
285 private void insertFileComponentsInReport(Set<Component> files) {
287 .setRoot(builder(PROJECT, Integer.parseInt(ROOT_REF))
288 .setUuid(project.uuid())
289 .addChildren(files.toArray(Component[]::new))
293 private Set<ComponentDto> insertFileComponentsInDatabase(Set<Component> files) {
296 .map(Component::getUuid)
297 .map(this::composeComponentDto)
298 .peek(this::insertComponentDto)
299 .peek(this::insertContentOfFileInDatabase)
303 private void insertComponentDto(ComponentDto component) {
304 dbTester.components().insertComponent(component);
307 private ComponentDto composeComponentDto(String uuid) {
308 return ComponentTesting
310 .setBranchUuid(branch.uuid())
311 .setKey("key_" + uuid)
313 .setPath(composeComponentPath(uuid));
317 private static String composeComponentPath(@Nullable String reference) {
318 return Optional.ofNullable(reference)
319 .map(r -> String.join("_", "path", r))
323 private FileSourceDto insertContentOfFileInDatabase(ComponentDto file) {
324 FileSourceDto fileSourceDto = composeFileSourceDto(file);
325 persistFileSourceDto(fileSourceDto);
326 return fileSourceDto;
329 private static FileSourceDto composeFileSourceDto(ComponentDto file) {
330 return new FileSourceDto()
331 .setUuid(Uuids.createFast())
332 .setFileUuid(file.uuid())
333 .setProjectUuid(file.branchUuid());
336 private void persistFileSourceDto(FileSourceDto fileSourceDto) {
337 dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto);
341 private void preparePullRequestAnalysis(Analysis analysis) {
342 prepareAnalysis(PULL_REQUEST, analysis);
345 private void prepareAnalysis(BranchType branch, Analysis analysis) {
346 mockBranchType(branch);
347 analysisMetadataHolder.setBaseAnalysis(analysis);
350 private void mockBranchType(BranchType branchType) {
351 Branch branch = mock(Branch.class);
352 when(analysisMetadataHolder.getBranch()).thenReturn(branch);
353 when(analysisMetadataHolder.getBranch().getTargetBranchName()).thenReturn(TARGET_BRANCH);
354 when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST);
355 when(analysisMetadataHolder.getProject()).thenReturn(Project.from(project));
359 private static class FileReference {
360 private final String reference;
361 private final String pastReference;
363 private FileReference(String reference, @Nullable String pastReference) {
364 this.reference = reference;
365 this.pastReference = pastReference;
368 public String getReference() {
373 public String getPastReference() {
374 return pastReference;
377 public static FileReference of(String reference, String pastReference) {
378 return new FileReference(reference, pastReference);
381 public static FileReference of(String reference) {
382 return new FileReference(reference, null);