]> source.dussan.org Git - sonarqube.git/blob
a2dd17509b93edc028f1f6ee24e959db941a1220
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2016 SonarSource SA
4  * mailto:contact 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.server.computation.filemove;
21
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;
44
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;
56
57 public class FileMoveDetectionStepTest {
58   private static final long SNAPSHOT_ID = 98765;
59   private static final Snapshot SNAPSHOT = new Snapshot.Builder()
60     .setId(SNAPSHOT_ID)
61     .setCreatedAt(86521)
62     .build();
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;",
72     "",
73     "public class Foo {",
74     "  public String bar() {",
75     "    return \"Doh!\";",
76     "  }",
77     "}"
78   };
79   private static final String[] LESS_CONTENT1 = {
80     "package org.sonar.server.computation.filemove;",
81     "",
82     "public class Foo {",
83     "}"
84   };
85   public static final String[] CONTENT_EMPTY = {
86     ""
87   };
88   private static final String[] CONTENT2 = {
89     "package org.sonar.ce.queue;",
90     "",
91     "import com.google.common.base.MoreObjects;",
92     "import javax.annotation.CheckForNull;",
93     "import javax.annotation.Nullable;",
94     "import javax.annotation.concurrent.Immutable;",
95     "",
96     "import static com.google.common.base.Strings.emptyToNull;",
97     "import static java.util.Objects.requireNonNull;",
98     "",
99     "@Immutable",
100     "public class CeTask {",
101     "",
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;",
108     "",
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);",
116     ",  }",
117     "",
118     ",  public String getUuid() {",
119     ",    return uuid;",
120     ",  }",
121     "",
122     ",  public String getType() {",
123     ",    return type;",
124     ",  }",
125     "",
126     ",  @CheckForNull",
127     ",  public String getComponentUuid() {",
128     ",    return componentUuid;",
129     ",  }",
130     "",
131     ",  @CheckForNull",
132     ",  public String getComponentKey() {",
133     ",    return componentKey;",
134     ",  }",
135     "",
136     ",  @CheckForNull",
137     ",  public String getComponentName() {",
138     ",    return componentName;",
139     ",  }",
140     "",
141     ",  @CheckForNull",
142     ",  public String getSubmitterLogin() {",
143     ",    return submitterLogin;",
144     ",  }",
145     ",}",
146   };
147   // removed immutable annotation
148   private static final String[] LESS_CONTENT2 = {
149     "package org.sonar.ce.queue;",
150     "",
151     "import com.google.common.base.MoreObjects;",
152     "import javax.annotation.CheckForNull;",
153     "import javax.annotation.Nullable;",
154     "",
155     "import static com.google.common.base.Strings.emptyToNull;",
156     "import static java.util.Objects.requireNonNull;",
157     "",
158     "public class CeTask {",
159     "",
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;",
166     "",
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);",
174     ",  }",
175     "",
176     ",  public String getUuid() {",
177     ",    return uuid;",
178     ",  }",
179     "",
180     ",  public String getType() {",
181     ",    return type;",
182     ",  }",
183     "",
184     ",  @CheckForNull",
185     ",  public String getComponentUuid() {",
186     ",    return componentUuid;",
187     ",  }",
188     "",
189     ",  @CheckForNull",
190     ",  public String getComponentKey() {",
191     ",    return componentKey;",
192     ",  }",
193     "",
194     ",  @CheckForNull",
195     ",  public String getComponentName() {",
196     ",    return componentName;",
197     ",  }",
198     "",
199     ",  @CheckForNull",
200     ",  public String getSubmitterLogin() {",
201     ",    return submitterLogin;",
202     ",  }",
203     ",}",
204   };
205
206   @Rule
207   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
208   @Rule
209   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
210   @Rule
211   public SourceLinesRepositoryRule sourceLinesRepository = new SourceLinesRepositoryRule();
212   @Rule
213   public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
214
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;
221
222   private FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient,
223     sourceLinesRepository, fileSimilarity, movedFilesRepository);
224
225   @Before
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);
230   }
231
232   @Test
233   public void getDescription_returns_description() {
234     assertThat(underTest.getDescription()).isEqualTo("Detect file moves");
235   }
236
237   @Test
238   public void execute_detects_no_move_if_baseProjectSnaphost_is_null() {
239     analysisMetadataHolder.setBaseProjectSnapshot(null);
240
241     underTest.execute();
242
243     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
244   }
245
246   @Test
247   public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() {
248     analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
249
250     underTest.execute();
251
252     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
253   }
254
255   @Test
256   public void execute_detects_no_move_if_baseSnapshot_has_no_file() {
257     analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
258     setFilesInReport(FILE_1, FILE_2);
259
260     underTest.execute();
261
262     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
263   }
264
265   @Test
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());
271
272     underTest.execute();
273
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);
281   }
282
283   @Test
284   public void execute_detects_no_move_if_there_is_no_file_in_report() {
285     analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
286     mockComponentsForSnapshot(1);
287     setFilesInReport();
288
289     underTest.execute();
290
291     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
292   }
293
294   @Test
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);
299
300     underTest.execute();
301
302     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
303   }
304
305   @Test
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);
312
313     underTest.execute();
314
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());
320   }
321
322   @Test
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);
329
330     underTest.execute();
331
332     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
333   }
334
335   @Test
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);
342
343     underTest.execute();
344
345     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
346   }
347
348   @Test
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);
355
356     underTest.execute();
357
358     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
359   }
360
361   @Test
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);
369
370     underTest.execute();
371
372     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
373   }
374
375   @Test
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);
383
384     underTest.execute();
385
386     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
387   }
388
389   @Test
390   public void execute_detects_several_moves() {
391     // testing:
392     // - file1 renamed to file3
393     // - file2 deleted
394     // - file4 untouched
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);
409
410     underTest.execute();
411
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());
421   }
422
423   private void setFileContentInReport(int ref, String[] content) {
424     sourceLinesRepository.addLines(ref, content);
425   }
426
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());
435     }
436
437     when(fileSourceDao.selectSourceByFileUuid(dbSession, componentUuidOf(key)))
438       .thenReturn(new FileSourceDto()
439         .setLineHashes(on('\n').join(linesHashesComputer.getLineHashes()))
440         .setSrcHash(sourceHashComputer.getHash()));
441   }
442
443   private void setFilesInReport(Component... files) {
444     treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF)
445       .addChildren(files)
446       .build());
447   }
448
449   private ComponentDtoWithSnapshotId[] mockComponentsForSnapshot(String... componentKeys) {
450     return mockComponentsForSnapshot(SNAPSHOT_ID, componentKeys);
451   }
452
453   private ComponentDtoWithSnapshotId[] mockComponentsForSnapshot(long snapshotId, String... componentKeys) {
454     List<ComponentDtoWithSnapshotId> componentDtoWithSnapshotIds = stream(componentKeys)
455       .map(key -> newComponentDto(snapshotId, key))
456       .collect(toList());
457     when(componentDao.selectAllChildren(eq(dbSession), any(ComponentTreeQuery.class)))
458       .thenReturn(componentDtoWithSnapshotIds);
459     return componentDtoWithSnapshotIds.toArray(new ComponentDtoWithSnapshotId[componentDtoWithSnapshotIds.size()]);
460   }
461
462   private ComponentDtoWithSnapshotId newComponentDto(long snapshotId, String key) {
463     ComponentDtoWithSnapshotId res = new ComponentDtoWithSnapshotId();
464     res.setSnapshotId(snapshotId)
465       .setId(dbIdGenerator)
466       .setKey(key)
467       .setUuid(componentUuidOf(key))
468       .setPath("path_" + key);
469     dbIdGenerator++;
470     return res;
471   }
472
473   private static String componentUuidOf(String key) {
474     return "uuid_" + key;
475   }
476
477   private static Component fileComponent(int ref) {
478     return builder(Component.Type.FILE, ref)
479       .setPath("report_path" + ref)
480       .build();
481   }
482
483 }