3 * Copyright (C) 2009-2016 SonarSource SA
4 * mailto:contact 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.server.computation.filemove;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.List;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.mockito.ArgumentCaptor;
30 import org.sonar.core.hash.SourceHashComputer;
31 import org.sonar.core.hash.SourceLinesHashesComputer;
32 import org.sonar.db.DbClient;
33 import org.sonar.db.DbSession;
34 import org.sonar.db.component.ComponentDao;
35 import org.sonar.db.component.ComponentDto;
36 import org.sonar.db.component.ComponentTreeQuery;
37 import org.sonar.db.source.FileSourceDao;
38 import org.sonar.db.source.FileSourceDto;
39 import org.sonar.server.computation.analysis.AnalysisMetadataHolderRule;
40 import org.sonar.server.computation.batch.TreeRootHolderRule;
41 import org.sonar.server.computation.component.Component;
42 import org.sonar.server.computation.component.ReportComponent;
43 import org.sonar.server.computation.snapshot.Snapshot;
44 import org.sonar.server.computation.source.SourceLinesRepositoryRule;
46 import static com.google.common.base.Joiner.on;
47 import static java.util.Arrays.stream;
48 import static java.util.stream.Collectors.toList;
49 import static org.assertj.core.api.Java6Assertions.assertThat;
50 import static org.mockito.Matchers.any;
51 import static org.mockito.Matchers.eq;
52 import static org.mockito.Mockito.mock;
53 import static org.mockito.Mockito.when;
54 import static org.sonar.api.resources.Qualifiers.FILE;
55 import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
56 import static org.sonar.server.computation.component.ReportComponent.builder;
58 public class FileMoveDetectionStepTest {
60 private static final long SNAPSHOT_ID = 98765;
61 private static final Snapshot SNAPSHOT = new Snapshot.Builder()
66 private static final int ROOT_REF = 1;
67 private static final int FILE_1_REF = 2;
68 private static final int FILE_2_REF = 3;
69 private static final int FILE_3_REF = 4;
70 private static final ReportComponent PROJECT = builder(Component.Type.PROJECT, ROOT_REF).build();
71 private static final Component FILE_1 = fileComponent(FILE_1_REF);
72 private static final Component FILE_2 = fileComponent(FILE_2_REF);
73 private static final Component FILE_3 = fileComponent(FILE_3_REF);
74 private static final String[] CONTENT1 = {
75 "package org.sonar.server.computation.filemove;",
78 " public String bar() {",
83 private static final String[] LESS_CONTENT1 = {
84 "package org.sonar.server.computation.filemove;",
89 private static final String[] CONTENT_EMPTY = {
92 private static final String[] CONTENT2 = {
93 "package org.sonar.ce.queue;",
95 "import com.google.common.base.MoreObjects;",
96 "import javax.annotation.CheckForNull;",
97 "import javax.annotation.Nullable;",
98 "import javax.annotation.concurrent.Immutable;",
100 "import static com.google.common.base.Strings.emptyToNull;",
101 "import static java.util.Objects.requireNonNull;",
104 "public class CeTask {",
106 ", private final String type;",
107 ", private final String uuid;",
108 ", private final String componentUuid;",
109 ", private final String componentKey;",
110 ", private final String componentName;",
111 ", private final String submitterLogin;",
113 ", private CeTask(Builder builder) {",
114 ", this.uuid = requireNonNull(emptyToNull(builder.uuid));",
115 ", this.type = requireNonNull(emptyToNull(builder.type));",
116 ", this.componentUuid = emptyToNull(builder.componentUuid);",
117 ", this.componentKey = emptyToNull(builder.componentKey);",
118 ", this.componentName = emptyToNull(builder.componentName);",
119 ", this.submitterLogin = emptyToNull(builder.submitterLogin);",
122 ", public String getUuid() {",
126 ", public String getType() {",
131 ", public String getComponentUuid() {",
132 ", return componentUuid;",
136 ", public String getComponentKey() {",
137 ", return componentKey;",
141 ", public String getComponentName() {",
142 ", return componentName;",
146 ", public String getSubmitterLogin() {",
147 ", return submitterLogin;",
151 // removed immutable annotation
152 private static final String[] LESS_CONTENT2 = {
153 "package org.sonar.ce.queue;",
155 "import com.google.common.base.MoreObjects;",
156 "import javax.annotation.CheckForNull;",
157 "import javax.annotation.Nullable;",
159 "import static com.google.common.base.Strings.emptyToNull;",
160 "import static java.util.Objects.requireNonNull;",
162 "public class CeTask {",
164 ", private final String type;",
165 ", private final String uuid;",
166 ", private final String componentUuid;",
167 ", private final String componentKey;",
168 ", private final String componentName;",
169 ", private final String submitterLogin;",
171 ", private CeTask(Builder builder) {",
172 ", this.uuid = requireNonNull(emptyToNull(builder.uuid));",
173 ", this.type = requireNonNull(emptyToNull(builder.type));",
174 ", this.componentUuid = emptyToNull(builder.componentUuid);",
175 ", this.componentKey = emptyToNull(builder.componentKey);",
176 ", this.componentName = emptyToNull(builder.componentName);",
177 ", this.submitterLogin = emptyToNull(builder.submitterLogin);",
180 ", public String getUuid() {",
184 ", public String getType() {",
189 ", public String getComponentUuid() {",
190 ", return componentUuid;",
194 ", public String getComponentKey() {",
195 ", return componentKey;",
199 ", public String getComponentName() {",
200 ", return componentName;",
204 ", public String getSubmitterLogin() {",
205 ", return submitterLogin;",
211 public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
213 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
215 public SourceLinesRepositoryRule sourceLinesRepository = new SourceLinesRepositoryRule();
217 public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
219 private DbClient dbClient = mock(DbClient.class);
220 private DbSession dbSession = mock(DbSession.class);
221 private ComponentDao componentDao = mock(ComponentDao.class);
222 private FileSourceDao fileSourceDao = mock(FileSourceDao.class);
223 private FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl());
224 private long dbIdGenerator = 0;
226 private FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient,
227 sourceLinesRepository, fileSimilarity, movedFilesRepository);
230 public void setUp() throws Exception {
231 when(dbClient.openSession(false)).thenReturn(dbSession);
232 when(dbClient.componentDao()).thenReturn(componentDao);
233 when(dbClient.fileSourceDao()).thenReturn(fileSourceDao);
234 treeRootHolder.setRoot(PROJECT);
238 public void getDescription_returns_description() {
239 assertThat(underTest.getDescription()).isEqualTo("Detect file moves");
243 public void execute_detects_no_move_if_baseProjectSnapshot_is_null() {
244 analysisMetadataHolder.setBaseProjectSnapshot(null);
248 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
252 public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() {
253 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
257 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
261 public void execute_detects_no_move_if_baseSnapshot_has_no_file() {
262 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
263 setFilesInReport(FILE_1, FILE_2);
267 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
271 public void execute_retrieves_only_file_and_unit_tests_from_last_snapshot() {
272 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
273 ArgumentCaptor<ComponentTreeQuery> captor = ArgumentCaptor.forClass(ComponentTreeQuery.class);
274 when(componentDao.selectDescendants(eq(dbSession), captor.capture()))
275 .thenReturn(Collections.emptyList());
279 ComponentTreeQuery query = captor.getValue();
280 assertThat(query.getBaseUuid()).isEqualTo(PROJECT.getUuid());
281 assertThat(query.getPage()).isEqualTo(1);
282 assertThat(query.getPageSize()).isEqualTo(Integer.MAX_VALUE);
283 assertThat(query.getSqlSort()).isEqualTo("LOWER(p.name) ASC, p.name ASC");
284 assertThat(query.getQualifiers()).containsOnly(FILE, UNIT_TEST_FILE);
288 public void execute_detects_no_move_if_there_is_no_file_in_report() {
289 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
290 mockComponents( /* no components */);
295 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
299 public void execute_detects_no_move_if_file_key_exists_in_both_DB_and_report() {
300 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
301 mockComponents(FILE_1.getKey(), FILE_2.getKey());
302 setFilesInReport(FILE_2, FILE_1);
306 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
310 public void execute_detects_move_if_content_of_file_is_same_in_DB_and_report() {
311 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
312 ComponentDto[] dtos = mockComponents(FILE_1.getKey());
313 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
314 setFilesInReport(FILE_2);
315 setFileContentInReport(FILE_2_REF, CONTENT1);
319 assertThat(movedFilesRepository.getComponentsWithOriginal()).containsExactly(FILE_2);
320 MovedFilesRepository.OriginalFile originalFile = movedFilesRepository.getOriginalFile(FILE_2).get();
321 assertThat(originalFile.getId()).isEqualTo(dtos[0].getId());
322 assertThat(originalFile.getKey()).isEqualTo(dtos[0].getKey());
323 assertThat(originalFile.getUuid()).isEqualTo(dtos[0].uuid());
327 public void execute_detects_no_move_if_content_of_file_is_not_similar_enough() {
328 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
329 mockComponents(FILE_1.getKey());
330 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
331 setFilesInReport(FILE_2);
332 setFileContentInReport(FILE_2_REF, LESS_CONTENT1);
336 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
340 public void execute_detects_no_move_if_content_of_file_is_empty_in_DB() {
341 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
342 mockComponents(FILE_1.getKey());
343 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT_EMPTY);
344 setFilesInReport(FILE_2);
345 setFileContentInReport(FILE_2_REF, CONTENT1);
349 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
353 public void execute_detects_no_move_if_content_of_file_is_empty_in_report() {
354 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
355 mockComponents(FILE_1.getKey());
356 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
357 setFilesInReport(FILE_2);
358 setFileContentInReport(FILE_2_REF, CONTENT_EMPTY);
362 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
366 public void execute_detects_no_move_if_two_added_files_have_same_content_as_the_one_in_db() {
367 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
368 mockComponents(FILE_1.getKey());
369 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
370 setFilesInReport(FILE_2, FILE_3);
371 setFileContentInReport(FILE_2_REF, CONTENT1);
372 setFileContentInReport(FILE_3_REF, CONTENT1);
376 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
380 public void execute_detects_no_move_if_two_deleted_files_have_same_content_as_the_one_added() {
381 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
382 mockComponents(FILE_1.getKey(), FILE_2.getKey());
383 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
384 mockContentOfFileIdDb(FILE_2.getKey(), CONTENT1);
385 setFilesInReport(FILE_3);
386 setFileContentInReport(FILE_3_REF, CONTENT1);
390 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
394 public void execute_detects_several_moves() {
396 // - file1 renamed to file3
399 // - file5 renamed to file6 with a small change
400 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
401 Component file4 = fileComponent(5);
402 Component file5 = fileComponent(6);
403 Component file6 = fileComponent(7);
404 ComponentDto[] dtos = mockComponents(FILE_1.getKey(), FILE_2.getKey(), file4.getKey(), file5.getKey());
405 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
406 mockContentOfFileIdDb(FILE_2.getKey(), LESS_CONTENT1);
407 mockContentOfFileIdDb(file4.getKey(), new String[] {"e", "f", "g", "h", "i"});
408 mockContentOfFileIdDb(file5.getKey(), CONTENT2);
409 setFilesInReport(FILE_3, file4, file6);
410 setFileContentInReport(FILE_3_REF, CONTENT1);
411 setFileContentInReport(file4.getReportAttributes().getRef(), new String[] {"a", "b"});
412 setFileContentInReport(file6.getReportAttributes().getRef(), LESS_CONTENT2);
416 assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(FILE_3, file6);
417 MovedFilesRepository.OriginalFile originalFile2 = movedFilesRepository.getOriginalFile(FILE_3).get();
418 assertThat(originalFile2.getId()).isEqualTo(dtos[0].getId());
419 assertThat(originalFile2.getKey()).isEqualTo(dtos[0].getKey());
420 assertThat(originalFile2.getUuid()).isEqualTo(dtos[0].uuid());
421 MovedFilesRepository.OriginalFile originalFile5 = movedFilesRepository.getOriginalFile(file6).get();
422 assertThat(originalFile5.getId()).isEqualTo(dtos[3].getId());
423 assertThat(originalFile5.getKey()).isEqualTo(dtos[3].getKey());
424 assertThat(originalFile5.getUuid()).isEqualTo(dtos[3].uuid());
427 private void setFileContentInReport(int ref, String[] content) {
428 sourceLinesRepository.addLines(ref, content);
431 private void mockContentOfFileIdDb(String key, String[] content) {
432 SourceLinesHashesComputer linesHashesComputer = new SourceLinesHashesComputer();
433 SourceHashComputer sourceHashComputer = new SourceHashComputer();
434 Iterator<String> lineIterator = Arrays.asList(content).iterator();
435 while (lineIterator.hasNext()) {
436 String line = lineIterator.next();
437 linesHashesComputer.addLine(line);
438 sourceHashComputer.addLine(line, lineIterator.hasNext());
441 when(fileSourceDao.selectSourceByFileUuid(dbSession, componentUuidOf(key)))
442 .thenReturn(new FileSourceDto()
443 .setLineHashes(on('\n').join(linesHashesComputer.getLineHashes()))
444 .setSrcHash(sourceHashComputer.getHash()));
447 private void setFilesInReport(Component... files) {
448 treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF)
453 private ComponentDto[] mockComponents(String... componentKeys) {
454 List<ComponentDto> componentDtos = stream(componentKeys)
455 .map(key -> newComponentDto(key))
457 when(componentDao.selectDescendants(eq(dbSession), any(ComponentTreeQuery.class)))
458 .thenReturn(componentDtos);
459 return componentDtos.toArray(new ComponentDto[componentDtos.size()]);
462 private ComponentDto newComponentDto(String key) {
463 ComponentDto res = new ComponentDto();
465 .setId(dbIdGenerator)
467 .setUuid(componentUuidOf(key))
468 .setPath("path_" + key);
473 private static String componentUuidOf(String key) {
474 return "uuid_" + key;
477 private static Component fileComponent(int ref) {
478 return builder(Component.Type.FILE, ref)
479 .setPath("report_path" + ref)