]> source.dussan.org Git - sonarqube.git/blob
3effd77ae17b273102a056ac7cb68bd20b673e2f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.task.projectanalysis.filemove;
21
22 import java.util.Map;
23 import java.util.Optional;
24 import java.util.Set;
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;
50
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;
63
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";
76
77   private static final Analysis ANALYSIS = new Analysis.Builder()
78     .setUuid(SNAPSHOT_UUID)
79     .setCreatedAt(86521)
80     .build();
81
82   @Rule
83   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
84   @Rule
85   public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
86   @Rule
87   public DbTester dbTester = DbTester.create(System2.INSTANCE);
88   @Rule
89   public LogTester logTester = new LogTester();
90
91   private ComponentDto branch;
92   private ComponentDto project;
93
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);
98
99   @Before
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());
104   }
105
106   @Test
107   public void getDescription_returns_description() {
108     assertThat(underTest.getDescription()).isEqualTo("Detect file moves in Pull Request scope");
109   }
110
111   @Test
112   public void execute_does_not_detect_any_files_if_not_in_pull_request_scope() {
113     prepareAnalysis(BRANCH, ANALYSIS);
114
115     TestComputationStepContext context = new TestComputationStepContext();
116     underTest.execute(context);
117
118     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
119     verifyStatistics(context, null, null, null, null);
120   }
121
122   @Test
123   public void execute_detects_no_move_if_report_has_no_file() {
124     preparePullRequestAnalysis(ANALYSIS);
125
126     TestComputationStepContext context = new TestComputationStepContext();
127     underTest.execute(context);
128
129     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
130     assertThat(addedFileRepository.getComponents()).isEmpty();
131     verifyStatistics(context, 0, null, null, null);
132   }
133
134   @Test
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);
139
140     TestComputationStepContext context = new TestComputationStepContext();
141     underTest.execute(context);
142
143     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
144     assertThat(addedFileRepository.getComponents()).containsOnlyOnceElementsOf(reportFilesByUuid.values());
145     verifyStatistics(context, 2, 0, 2, null);
146   }
147
148   @Test
149   public void execute_detects_no_move_if_there_are_no_files_in_report() {
150     preparePullRequestAnalysis(ANALYSIS);
151     initializeAnalysisReportComponents(Set.of());
152
153     TestComputationStepContext context = new TestComputationStepContext();
154     underTest.execute(context);
155
156     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
157     assertThat(addedFileRepository.getComponents()).isEmpty();
158     verifyStatistics(context, 0, null, null, null);
159   }
160
161   @Test
162   public void execute_detects_no_move_if_file_key_exists_in_both_database_and_report() {
163     preparePullRequestAnalysis(ANALYSIS);
164
165     Set<FileReference> fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF));
166     initializeAnalysisReportComponents(fileReferences);
167     initializeTargetBranchDatabaseComponents(fileReferences);
168
169     TestComputationStepContext context = new TestComputationStepContext();
170     underTest.execute(context);
171
172     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
173     assertThat(addedFileRepository.getComponents()).isEmpty();
174     verifyStatistics(context, 2, 2, 0, 0);
175   }
176
177   @Test
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);
181
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));
184
185     Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
186     Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
187
188     TestComputationStepContext context = new TestComputationStepContext();
189     underTest.execute(context);
190
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);
195   }
196
197   @Test
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);
205
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));
211
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));
217
218     Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
219     Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
220
221     TestComputationStepContext context = new TestComputationStepContext();
222     underTest.execute(context);
223
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);
230   }
231
232   private void assertThatFileAdditionHasBeenDetected(Map<String, Component> reportFilesByUuid, String fileInReportReference) {
233     Component fileInReport = reportFilesByUuid.get(fileInReportReference);
234
235     assertThat(addedFileRepository.getComponents()).contains(fileInReport);
236     assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isEmpty();
237   }
238
239
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);
243
244     assertThat(movedFilesRepository.getComponentsWithOriginal()).contains(fileInReport);
245     assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isPresent();
246
247     OriginalFile detectedOriginalFile = movedFilesRepository.getOriginalPullRequestFile(fileInReport).get();
248     assertThat(detectedOriginalFile.key()).isEqualTo(originalFileInDatabase.getKey());
249     assertThat(detectedOriginalFile.uuid()).isEqualTo(originalFileInDatabase.getUuid());
250   }
251
252   private Map<String, Component> initializeTargetBranchDatabaseComponents(Set<FileReference> references) {
253     Set<Component> fileComponents = createFileComponents(references);
254     insertFileComponentsInDatabase(fileComponents);
255     return toFileComponentsByUuidMap(fileComponents);
256   }
257
258   private Map<String, Component> initializeAnalysisReportComponents(Set<FileReference> refs) {
259     Set<Component> fileComponents = createFileComponents(refs);
260     insertFileComponentsInReport(fileComponents);
261     return toFileComponentsByUuidMap(fileComponents);
262   }
263
264   private Map<String, Component> toFileComponentsByUuidMap(Set<Component> fileComponents) {
265     return fileComponents
266       .stream()
267       .collect(toMap(Component::getUuid, identity()));
268   }
269
270   private static Set<Component> createFileComponents(Set<FileReference> references) {
271     return references
272       .stream()
273       .map(PullRequestFileMoveDetectionStepIT::createReportFileComponent)
274       .collect(toSet());
275   }
276
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())))
282       .build();
283   }
284
285   private void insertFileComponentsInReport(Set<Component> files) {
286     treeRootHolder
287       .setRoot(builder(PROJECT, Integer.parseInt(ROOT_REF))
288       .setUuid(project.uuid())
289       .addChildren(files.toArray(Component[]::new))
290       .build());
291   }
292
293   private Set<ComponentDto> insertFileComponentsInDatabase(Set<Component> files) {
294     return files
295       .stream()
296       .map(Component::getUuid)
297       .map(this::composeComponentDto)
298       .peek(this::insertComponentDto)
299       .peek(this::insertContentOfFileInDatabase)
300       .collect(toSet());
301   }
302
303   private void insertComponentDto(ComponentDto component) {
304     dbTester.components().insertComponent(component);
305   }
306
307   private ComponentDto composeComponentDto(String uuid) {
308     return ComponentTesting
309       .newFileDto(project)
310       .setBranchUuid(branch.uuid())
311       .setKey("key_" + uuid)
312       .setUuid(uuid)
313       .setPath(composeComponentPath(uuid));
314   }
315
316   @CheckForNull
317   private static String composeComponentPath(@Nullable String reference) {
318     return Optional.ofNullable(reference)
319       .map(r -> String.join("_", "path", r))
320       .orElse(null);
321   }
322
323   private FileSourceDto insertContentOfFileInDatabase(ComponentDto file) {
324     FileSourceDto fileSourceDto = composeFileSourceDto(file);
325     persistFileSourceDto(fileSourceDto);
326     return fileSourceDto;
327   }
328
329   private static FileSourceDto composeFileSourceDto(ComponentDto file) {
330     return new FileSourceDto()
331       .setUuid(Uuids.createFast())
332       .setFileUuid(file.uuid())
333       .setProjectUuid(file.branchUuid());
334   }
335
336   private void persistFileSourceDto(FileSourceDto fileSourceDto) {
337     dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto);
338     dbTester.commit();
339   }
340
341   private void preparePullRequestAnalysis(Analysis analysis) {
342     prepareAnalysis(PULL_REQUEST, analysis);
343   }
344
345   private void prepareAnalysis(BranchType branch, Analysis analysis) {
346     mockBranchType(branch);
347     analysisMetadataHolder.setBaseAnalysis(analysis);
348   }
349
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));
356   }
357
358   @Immutable
359   private static class FileReference {
360     private final String reference;
361     private final String pastReference;
362
363     private FileReference(String reference, @Nullable String pastReference) {
364       this.reference = reference;
365       this.pastReference = pastReference;
366     }
367
368     public String getReference() {
369       return reference;
370     }
371
372     @CheckForNull
373     public String getPastReference() {
374       return pastReference;
375     }
376
377     public static FileReference of(String reference, String pastReference) {
378       return new FileReference(reference, pastReference);
379     }
380
381     public static FileReference of(String reference) {
382       return new FileReference(reference, null);
383     }
384   }
385 }