]> source.dussan.org Git - sonarqube.git/blob
0cf21b266ae8c5e18de5c4bb11ff2a8f049d2036
[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.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;
45
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;
57
58 public class FileMoveDetectionStepTest {
59
60   private static final long SNAPSHOT_ID = 98765;
61   private static final Snapshot SNAPSHOT = new Snapshot.Builder()
62     .setId(SNAPSHOT_ID)
63     .setUuid("uuid_1")
64     .setCreatedAt(86521)
65     .build();
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;",
76     "",
77     "public class Foo {",
78     "  public String bar() {",
79     "    return \"Doh!\";",
80     "  }",
81     "}"
82   };
83   private static final String[] LESS_CONTENT1 = {
84     "package org.sonar.server.computation.filemove;",
85     "",
86     "public class Foo {",
87     "}"
88   };
89   private static final String[] CONTENT_EMPTY = {
90     ""
91   };
92   private static final String[] CONTENT2 = {
93     "package org.sonar.ce.queue;",
94     "",
95     "import com.google.common.base.MoreObjects;",
96     "import javax.annotation.CheckForNull;",
97     "import javax.annotation.Nullable;",
98     "import javax.annotation.concurrent.Immutable;",
99     "",
100     "import static com.google.common.base.Strings.emptyToNull;",
101     "import static java.util.Objects.requireNonNull;",
102     "",
103     "@Immutable",
104     "public class CeTask {",
105     "",
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;",
112     "",
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);",
120     ",  }",
121     "",
122     ",  public String getUuid() {",
123     ",    return uuid;",
124     ",  }",
125     "",
126     ",  public String getType() {",
127     ",    return type;",
128     ",  }",
129     "",
130     ",  @CheckForNull",
131     ",  public String getComponentUuid() {",
132     ",    return componentUuid;",
133     ",  }",
134     "",
135     ",  @CheckForNull",
136     ",  public String getComponentKey() {",
137     ",    return componentKey;",
138     ",  }",
139     "",
140     ",  @CheckForNull",
141     ",  public String getComponentName() {",
142     ",    return componentName;",
143     ",  }",
144     "",
145     ",  @CheckForNull",
146     ",  public String getSubmitterLogin() {",
147     ",    return submitterLogin;",
148     ",  }",
149     ",}",
150   };
151   // removed immutable annotation
152   private static final String[] LESS_CONTENT2 = {
153     "package org.sonar.ce.queue;",
154     "",
155     "import com.google.common.base.MoreObjects;",
156     "import javax.annotation.CheckForNull;",
157     "import javax.annotation.Nullable;",
158     "",
159     "import static com.google.common.base.Strings.emptyToNull;",
160     "import static java.util.Objects.requireNonNull;",
161     "",
162     "public class CeTask {",
163     "",
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;",
170     "",
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);",
178     ",  }",
179     "",
180     ",  public String getUuid() {",
181     ",    return uuid;",
182     ",  }",
183     "",
184     ",  public String getType() {",
185     ",    return type;",
186     ",  }",
187     "",
188     ",  @CheckForNull",
189     ",  public String getComponentUuid() {",
190     ",    return componentUuid;",
191     ",  }",
192     "",
193     ",  @CheckForNull",
194     ",  public String getComponentKey() {",
195     ",    return componentKey;",
196     ",  }",
197     "",
198     ",  @CheckForNull",
199     ",  public String getComponentName() {",
200     ",    return componentName;",
201     ",  }",
202     "",
203     ",  @CheckForNull",
204     ",  public String getSubmitterLogin() {",
205     ",    return submitterLogin;",
206     ",  }",
207     ",}",
208   };
209
210   @Rule
211   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
212   @Rule
213   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
214   @Rule
215   public SourceLinesRepositoryRule sourceLinesRepository = new SourceLinesRepositoryRule();
216   @Rule
217   public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
218
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;
225
226   private FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient,
227     sourceLinesRepository, fileSimilarity, movedFilesRepository);
228
229   @Before
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);
235   }
236
237   @Test
238   public void getDescription_returns_description() {
239     assertThat(underTest.getDescription()).isEqualTo("Detect file moves");
240   }
241
242   @Test
243   public void execute_detects_no_move_if_baseProjectSnapshot_is_null() {
244     analysisMetadataHolder.setBaseProjectSnapshot(null);
245
246     underTest.execute();
247
248     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
249   }
250
251   @Test
252   public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() {
253     analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
254
255     underTest.execute();
256
257     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
258   }
259
260   @Test
261   public void execute_detects_no_move_if_baseSnapshot_has_no_file() {
262     analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
263     setFilesInReport(FILE_1, FILE_2);
264
265     underTest.execute();
266
267     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
268   }
269
270   @Test
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());
276
277     underTest.execute();
278
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);
285   }
286
287   @Test
288   public void execute_detects_no_move_if_there_is_no_file_in_report() {
289     analysisMetadataHolder.setBaseProjectSnapshot(SNAPSHOT);
290     mockComponents( /* no components */);
291     setFilesInReport();
292
293     underTest.execute();
294
295     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
296   }
297
298   @Test
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);
303
304     underTest.execute();
305
306     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
307   }
308
309   @Test
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);
316
317     underTest.execute();
318
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());
324   }
325
326   @Test
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);
333
334     underTest.execute();
335
336     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
337   }
338
339   @Test
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);
346
347     underTest.execute();
348
349     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
350   }
351
352   @Test
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);
359
360     underTest.execute();
361
362     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
363   }
364
365   @Test
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);
373
374     underTest.execute();
375
376     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
377   }
378
379   @Test
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);
387
388     underTest.execute();
389
390     assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
391   }
392
393   @Test
394   public void execute_detects_several_moves() {
395     // testing:
396     // - file1 renamed to file3
397     // - file2 deleted
398     // - file4 untouched
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);
413
414     underTest.execute();
415
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());
425   }
426
427   private void setFileContentInReport(int ref, String[] content) {
428     sourceLinesRepository.addLines(ref, content);
429   }
430
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());
439     }
440
441     when(fileSourceDao.selectSourceByFileUuid(dbSession, componentUuidOf(key)))
442       .thenReturn(new FileSourceDto()
443         .setLineHashes(on('\n').join(linesHashesComputer.getLineHashes()))
444         .setSrcHash(sourceHashComputer.getHash()));
445   }
446
447   private void setFilesInReport(Component... files) {
448     treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF)
449       .addChildren(files)
450       .build());
451   }
452
453   private ComponentDto[] mockComponents(String... componentKeys) {
454     List<ComponentDto> componentDtos = stream(componentKeys)
455       .map(key -> newComponentDto(key))
456       .collect(toList());
457     when(componentDao.selectDescendants(eq(dbSession), any(ComponentTreeQuery.class)))
458       .thenReturn(componentDtos);
459     return componentDtos.toArray(new ComponentDto[componentDtos.size()]);
460   }
461
462   private ComponentDto newComponentDto(String key) {
463     ComponentDto res = new ComponentDto();
464     res
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 }