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.ComponentDtoWithSnapshotId;
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.snapshot.Snapshot;
43 import org.sonar.server.computation.source.SourceLinesRepositoryRule;
45 import static com.google.common.base.Joiner.on;
46 import static java.util.Arrays.stream;
47 import static java.util.stream.Collectors.toList;
48 import static org.assertj.core.api.Java6Assertions.assertThat;
49 import static org.mockito.Matchers.any;
50 import static org.mockito.Matchers.eq;
51 import static org.mockito.Mockito.mock;
52 import static org.mockito.Mockito.when;
53 import static org.sonar.api.resources.Qualifiers.FILE;
54 import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
55 import static org.sonar.server.computation.component.ReportComponent.builder;
57 public class FileMoveDetectionStepTest {
58 private static final long SNAPSHOT_ID = 98765;
59 private static final Snapshot SNAPSHOT = new Snapshot.Builder()
63 private static final int ROOT_REF = 1;
64 private static final int FILE_1_REF = 2;
65 private static final int FILE_2_REF = 3;
66 private static final int FILE_3_REF = 4;
67 private static final Component FILE_1 = fileComponent(FILE_1_REF);
68 private static final Component FILE_2 = fileComponent(FILE_2_REF);
69 private static final Component FILE_3 = fileComponent(FILE_3_REF);
70 private static final String[] CONTENT1 = {
71 "package org.sonar.server.computation.filemove;",
74 " public String bar() {",
79 private static final String[] LESS_CONTENT1 = {
80 "package org.sonar.server.computation.filemove;",
85 public static final String[] CONTENT_EMPTY = {
88 private static final String[] CONTENT2 = {
89 "package org.sonar.ce.queue;",
91 "import com.google.common.base.MoreObjects;",
92 "import javax.annotation.CheckForNull;",
93 "import javax.annotation.Nullable;",
94 "import javax.annotation.concurrent.Immutable;",
96 "import static com.google.common.base.Strings.emptyToNull;",
97 "import static java.util.Objects.requireNonNull;",
100 "public class CeTask {",
102 ", private final String type;",
103 ", private final String uuid;",
104 ", private final String componentUuid;",
105 ", private final String componentKey;",
106 ", private final String componentName;",
107 ", private final String submitterLogin;",
109 ", private CeTask(Builder builder) {",
110 ", this.uuid = requireNonNull(emptyToNull(builder.uuid));",
111 ", this.type = requireNonNull(emptyToNull(builder.type));",
112 ", this.componentUuid = emptyToNull(builder.componentUuid);",
113 ", this.componentKey = emptyToNull(builder.componentKey);",
114 ", this.componentName = emptyToNull(builder.componentName);",
115 ", this.submitterLogin = emptyToNull(builder.submitterLogin);",
118 ", public String getUuid() {",
122 ", public String getType() {",
127 ", public String getComponentUuid() {",
128 ", return componentUuid;",
132 ", public String getComponentKey() {",
133 ", return componentKey;",
137 ", public String getComponentName() {",
138 ", return componentName;",
142 ", public String getSubmitterLogin() {",
143 ", return submitterLogin;",
147 // removed immutable annotation
148 private static final String[] LESS_CONTENT2 = {
149 "package org.sonar.ce.queue;",
151 "import com.google.common.base.MoreObjects;",
152 "import javax.annotation.CheckForNull;",
153 "import javax.annotation.Nullable;",
155 "import static com.google.common.base.Strings.emptyToNull;",
156 "import static java.util.Objects.requireNonNull;",
158 "public class CeTask {",
160 ", private final String type;",
161 ", private final String uuid;",
162 ", private final String componentUuid;",
163 ", private final String componentKey;",
164 ", private final String componentName;",
165 ", private final String submitterLogin;",
167 ", private CeTask(Builder builder) {",
168 ", this.uuid = requireNonNull(emptyToNull(builder.uuid));",
169 ", this.type = requireNonNull(emptyToNull(builder.type));",
170 ", this.componentUuid = emptyToNull(builder.componentUuid);",
171 ", this.componentKey = emptyToNull(builder.componentKey);",
172 ", this.componentName = emptyToNull(builder.componentName);",
173 ", this.submitterLogin = emptyToNull(builder.submitterLogin);",
176 ", public String getUuid() {",
180 ", public String getType() {",
185 ", public String getComponentUuid() {",
186 ", return componentUuid;",
190 ", public String getComponentKey() {",
191 ", return componentKey;",
195 ", public String getComponentName() {",
196 ", return componentName;",
200 ", public String getSubmitterLogin() {",
201 ", return submitterLogin;",
207 public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
209 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
211 public SourceLinesRepositoryRule sourceLinesRepository = new SourceLinesRepositoryRule();
213 public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
215 private DbClient dbClient = mock(DbClient.class);
216 private DbSession dbSession = mock(DbSession.class);
217 private ComponentDao componentDao = mock(ComponentDao.class);
218 private FileSourceDao fileSourceDao = mock(FileSourceDao.class);
219 private FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl());
220 private long dbIdGenerator = 0;
222 private FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient,
223 sourceLinesRepository, fileSimilarity, movedFilesRepository);
226 public void setUp() throws Exception {
227 when(dbClient.openSession(false)).thenReturn(dbSession);
228 when(dbClient.componentDao()).thenReturn(componentDao);
229 when(dbClient.fileSourceDao()).thenReturn(fileSourceDao);
233 public void getDescription_returns_description() {
234 assertThat(underTest.getDescription()).isEqualTo("Detect file moves");
238 public void execute_detects_no_move_if_baseProjectSnaphost_is_null() {
239 analysisMetadataHolder.setBaseProjectSnapshot(null);
243 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
247 public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() {
248 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
252 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
256 public void execute_detects_no_move_if_baseSnapshot_has_no_file() {
257 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
258 setFilesInReport(FILE_1, FILE_2);
262 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
266 public void execute_retrieves_only_file_and_unit_tests_from_last_snapshot() {
267 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
268 ArgumentCaptor<ComponentTreeQuery> captor = ArgumentCaptor.forClass(ComponentTreeQuery.class);
269 when(componentDao.selectAllChildren(eq(dbSession), captor.capture()))
270 .thenReturn(Collections.<ComponentDtoWithSnapshotId>emptyList());
274 ComponentTreeQuery query = captor.getValue();
275 assertThat(query.getBaseSnapshot().getId()).isEqualTo(SNAPSHOT_ID);
276 assertThat(query.getBaseSnapshot().getRootId()).isEqualTo(SNAPSHOT_ID);
277 assertThat(query.getPage()).isEqualTo(1);
278 assertThat(query.getPageSize()).isEqualTo(Integer.MAX_VALUE);
279 assertThat(query.getSqlSort()).isEqualTo("LOWER(p.name) ASC, p.name ASC");
280 assertThat(query.getQualifiers()).containsOnly(FILE, UNIT_TEST_FILE);
284 public void execute_detects_no_move_if_there_is_no_file_in_report() {
285 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
286 mockComponentsForSnapshot(1);
291 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
295 public void execute_detects_no_move_if_file_key_exists_in_both_DB_and_report() {
296 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
297 mockComponentsForSnapshot(FILE_1.getKey(), FILE_2.getKey());
298 setFilesInReport(FILE_2, FILE_1);
302 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
306 public void execute_detects_move_if_content_of_file_is_same_in_DB_and_report() {
307 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
308 ComponentDtoWithSnapshotId[] dtos = mockComponentsForSnapshot(FILE_1.getKey());
309 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
310 setFilesInReport(FILE_2);
311 setFileContentInReport(FILE_2_REF, CONTENT1);
315 assertThat(movedFilesRepository.getComponentsWithOriginal()).containsExactly(FILE_2);
316 MovedFilesRepository.OriginalFile originalFile = movedFilesRepository.getOriginalFile(FILE_2).get();
317 assertThat(originalFile.getId()).isEqualTo(dtos[0].getId());
318 assertThat(originalFile.getKey()).isEqualTo(dtos[0].getKey());
319 assertThat(originalFile.getUuid()).isEqualTo(dtos[0].uuid());
323 public void execute_detects_no_move_if_content_of_file_is_not_similar_enough() {
324 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
325 mockComponentsForSnapshot(FILE_1.getKey());
326 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
327 setFilesInReport(FILE_2);
328 setFileContentInReport(FILE_2_REF, LESS_CONTENT1);
332 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
336 public void execute_detects_no_move_if_content_of_file_is_empty_in_DB() {
337 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
338 mockComponentsForSnapshot(FILE_1.getKey());
339 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT_EMPTY);
340 setFilesInReport(FILE_2);
341 setFileContentInReport(FILE_2_REF, CONTENT1);
345 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
349 public void execute_detects_no_move_if_content_of_file_is_empty_in_report() {
350 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
351 mockComponentsForSnapshot(FILE_1.getKey());
352 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
353 setFilesInReport(FILE_2);
354 setFileContentInReport(FILE_2_REF, CONTENT_EMPTY);
358 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
362 public void execute_detects_no_move_if_two_added_files_have_same_content_as_the_one_in_db() {
363 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
364 mockComponentsForSnapshot(FILE_1.getKey());
365 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
366 setFilesInReport(FILE_2, FILE_3);
367 setFileContentInReport(FILE_2_REF, CONTENT1);
368 setFileContentInReport(FILE_3_REF, CONTENT1);
372 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
376 public void execute_detects_no_move_if_two_deleted_files_have_same_content_as_the_one_added() {
377 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
378 mockComponentsForSnapshot(FILE_1.getKey(), FILE_2.getKey());
379 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
380 mockContentOfFileIdDb(FILE_2.getKey(), CONTENT1);
381 setFilesInReport(FILE_3);
382 setFileContentInReport(FILE_3_REF, CONTENT1);
386 assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
390 public void execute_detects_several_moves() {
392 // - file1 renamed to file3
395 // - file5 renamed to file6 with a small change
396 analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
397 Component file4 = fileComponent(5);
398 Component file5 = fileComponent(6);
399 Component file6 = fileComponent(7);
400 ComponentDtoWithSnapshotId[] dtos = mockComponentsForSnapshot(FILE_1.getKey(), FILE_2.getKey(), file4.getKey(), file5.getKey());
401 mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1);
402 mockContentOfFileIdDb(FILE_2.getKey(), LESS_CONTENT1);
403 mockContentOfFileIdDb(file4.getKey(), new String[]{"e","f","g","h","i"});
404 mockContentOfFileIdDb(file5.getKey(), CONTENT2);
405 setFilesInReport(FILE_3, file4, file6);
406 setFileContentInReport(FILE_3_REF, CONTENT1);
407 setFileContentInReport(file4.getReportAttributes().getRef(), new String[]{"a","b"});
408 setFileContentInReport(file6.getReportAttributes().getRef(), LESS_CONTENT2);
412 assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(FILE_3, file6);
413 MovedFilesRepository.OriginalFile originalFile2 = movedFilesRepository.getOriginalFile(FILE_3).get();
414 assertThat(originalFile2.getId()).isEqualTo(dtos[0].getId());
415 assertThat(originalFile2.getKey()).isEqualTo(dtos[0].getKey());
416 assertThat(originalFile2.getUuid()).isEqualTo(dtos[0].uuid());
417 MovedFilesRepository.OriginalFile originalFile5 = movedFilesRepository.getOriginalFile(file6).get();
418 assertThat(originalFile5.getId()).isEqualTo(dtos[3].getId());
419 assertThat(originalFile5.getKey()).isEqualTo(dtos[3].getKey());
420 assertThat(originalFile5.getUuid()).isEqualTo(dtos[3].uuid());
423 private void setFileContentInReport(int ref, String[] content) {
424 sourceLinesRepository.addLines(ref, content);
427 private void mockContentOfFileIdDb(String key, String[] content) {
428 SourceLinesHashesComputer linesHashesComputer = new SourceLinesHashesComputer();
429 SourceHashComputer sourceHashComputer = new SourceHashComputer();
430 Iterator<String> lineIterator = Arrays.asList(content).iterator();
431 while (lineIterator.hasNext()) {
432 String line = lineIterator.next();
433 linesHashesComputer.addLine(line);
434 sourceHashComputer.addLine(line, lineIterator.hasNext());
437 when(fileSourceDao.selectSourceByFileUuid(dbSession, componentUuidOf(key)))
438 .thenReturn(new FileSourceDto()
439 .setLineHashes(on('\n').join(linesHashesComputer.getLineHashes()))
440 .setSrcHash(sourceHashComputer.getHash()));
443 private void setFilesInReport(Component... files) {
444 treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF)
449 private ComponentDtoWithSnapshotId[] mockComponentsForSnapshot(String... componentKeys) {
450 return mockComponentsForSnapshot(SNAPSHOT_ID, componentKeys);
453 private ComponentDtoWithSnapshotId[] mockComponentsForSnapshot(long snapshotId, String... componentKeys) {
454 List<ComponentDtoWithSnapshotId> componentDtoWithSnapshotIds = stream(componentKeys)
455 .map(key -> newComponentDto(snapshotId, key))
457 when(componentDao.selectAllChildren(eq(dbSession), any(ComponentTreeQuery.class)))
458 .thenReturn(componentDtoWithSnapshotIds);
459 return componentDtoWithSnapshotIds.toArray(new ComponentDtoWithSnapshotId[componentDtoWithSnapshotIds.size()]);
462 private ComponentDtoWithSnapshotId newComponentDto(long snapshotId, String key) {
463 ComponentDtoWithSnapshotId res = new ComponentDtoWithSnapshotId();
464 res.setSnapshotId(snapshotId)
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)