You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FileMoveDetectionStepTest.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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. import java.io.File;
  22. import java.io.IOException;
  23. import java.nio.charset.StandardCharsets;
  24. import java.util.ArrayList;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.function.Function;
  29. import java.util.stream.IntStream;
  30. import javax.annotation.CheckForNull;
  31. import javax.annotation.Nullable;
  32. import org.apache.commons.io.FileUtils;
  33. import org.junit.Before;
  34. import org.junit.Rule;
  35. import org.junit.Test;
  36. import org.sonar.api.utils.System2;
  37. import org.sonar.api.utils.log.LogTester;
  38. import org.sonar.api.utils.log.LoggerLevel;
  39. import org.sonar.ce.task.projectanalysis.analysis.Analysis;
  40. import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
  41. import org.sonar.ce.task.projectanalysis.component.Component;
  42. import org.sonar.ce.task.projectanalysis.component.FileAttributes;
  43. import org.sonar.ce.task.projectanalysis.component.ReportComponent;
  44. import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
  45. import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
  46. import org.sonar.ce.task.step.TestComputationStepContext;
  47. import org.sonar.core.hash.SourceLineHashesComputer;
  48. import org.sonar.core.util.Uuids;
  49. import org.sonar.db.DbClient;
  50. import org.sonar.db.DbTester;
  51. import org.sonar.db.component.ComponentDto;
  52. import org.sonar.db.component.ComponentTesting;
  53. import org.sonar.db.source.FileSourceDto;
  54. import static java.util.Arrays.stream;
  55. import static org.assertj.core.api.Assertions.assertThat;
  56. import static org.mockito.Mockito.mock;
  57. import static org.mockito.Mockito.when;
  58. import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
  59. import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStep.MIN_REQUIRED_SCORE;
  60. public class FileMoveDetectionStepTest {
  61. private static final String SNAPSHOT_UUID = "uuid_1";
  62. private static final Analysis ANALYSIS = new Analysis.Builder()
  63. .setUuid(SNAPSHOT_UUID)
  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 String[] CONTENT1 = {
  71. "package org.sonar.ce.task.projectanalysis.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.ce.task.projectanalysis.filemove;",
  81. "",
  82. "public class Foo {",
  83. " public String foo() {",
  84. " return \"Donut!\";",
  85. " }",
  86. "}"
  87. };
  88. private static final String[] CONTENT_EMPTY = {
  89. ""
  90. };
  91. private static final String[] CONTENT2 = {
  92. "package org.sonar.ce.queue;",
  93. "",
  94. "import com.google.common.base.MoreObjects;",
  95. "import javax.annotation.CheckForNull;",
  96. "import javax.annotation.Nullable;",
  97. "import javax.annotation.concurrent.Immutable;",
  98. "",
  99. "import static com.google.common.base.Strings.emptyToNull;",
  100. "import static java.util.Objects.requireNonNull;",
  101. "",
  102. "@Immutable",
  103. "public class CeTask {",
  104. "",
  105. ", private final String type;",
  106. ", private final String uuid;",
  107. ", private final String componentUuid;",
  108. ", private final String componentKey;",
  109. ", private final String componentName;",
  110. ", private final String submitterLogin;",
  111. "",
  112. ", private CeTask(Builder builder) {",
  113. ", this.uuid = requireNonNull(emptyToNull(builder.uuid));",
  114. ", this.type = requireNonNull(emptyToNull(builder.type));",
  115. ", this.componentUuid = emptyToNull(builder.componentUuid);",
  116. ", this.componentKey = emptyToNull(builder.componentKey);",
  117. ", this.componentName = emptyToNull(builder.componentName);",
  118. ", this.submitterLogin = emptyToNull(builder.submitterLogin);",
  119. ", }",
  120. "",
  121. ", public String getUuid() {",
  122. ", return uuid;",
  123. ", }",
  124. "",
  125. ", public String getType() {",
  126. ", return type;",
  127. ", }",
  128. "",
  129. ", @CheckForNull",
  130. ", public String getComponentUuid() {",
  131. ", return componentUuid;",
  132. ", }",
  133. "",
  134. ", @CheckForNull",
  135. ", public String getComponentKey() {",
  136. ", return componentKey;",
  137. ", }",
  138. "",
  139. ", @CheckForNull",
  140. ", public String getComponentName() {",
  141. ", return componentName;",
  142. ", }",
  143. "",
  144. ", @CheckForNull",
  145. ", public String getSubmitterLogin() {",
  146. ", return submitterLogin;",
  147. ", }",
  148. ",}",
  149. };
  150. // removed immutable annotation
  151. private static final String[] LESS_CONTENT2 = {
  152. "package org.sonar.ce.queue;",
  153. "",
  154. "import com.google.common.base.MoreObjects;",
  155. "import javax.annotation.CheckForNull;",
  156. "import javax.annotation.Nullable;",
  157. "",
  158. "import static com.google.common.base.Strings.emptyToNull;",
  159. "import static java.util.Objects.requireNonNull;",
  160. "",
  161. "public class CeTask {",
  162. "",
  163. ", private final String type;",
  164. ", private final String uuid;",
  165. ", private final String componentUuid;",
  166. ", private final String componentKey;",
  167. ", private final String componentName;",
  168. ", private final String submitterLogin;",
  169. "",
  170. ", private CeTask(Builder builder) {",
  171. ", this.uuid = requireNonNull(emptyToNull(builder.uuid));",
  172. ", this.type = requireNonNull(emptyToNull(builder.type));",
  173. ", this.componentUuid = emptyToNull(builder.componentUuid);",
  174. ", this.componentKey = emptyToNull(builder.componentKey);",
  175. ", this.componentName = emptyToNull(builder.componentName);",
  176. ", this.submitterLogin = emptyToNull(builder.submitterLogin);",
  177. ", }",
  178. "",
  179. ", public String getUuid() {",
  180. ", return uuid;",
  181. ", }",
  182. "",
  183. ", public String getType() {",
  184. ", return type;",
  185. ", }",
  186. "",
  187. ", @CheckForNull",
  188. ", public String getComponentUuid() {",
  189. ", return componentUuid;",
  190. ", }",
  191. "",
  192. ", @CheckForNull",
  193. ", public String getComponentKey() {",
  194. ", return componentKey;",
  195. ", }",
  196. "",
  197. ", @CheckForNull",
  198. ", public String getComponentName() {",
  199. ", return componentName;",
  200. ", }",
  201. "",
  202. ", @CheckForNull",
  203. ", public String getSubmitterLogin() {",
  204. ", return submitterLogin;",
  205. ", }",
  206. ",}",
  207. };
  208. @Rule
  209. public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
  210. @Rule
  211. public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
  212. @Rule
  213. public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
  214. @Rule
  215. public DbTester dbTester = DbTester.create(System2.INSTANCE);
  216. @Rule
  217. public LogTester logTester = new LogTester();
  218. private DbClient dbClient = dbTester.getDbClient();
  219. private ComponentDto project;
  220. private SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
  221. private FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl());
  222. private CapturingScoreMatrixDumper scoreMatrixDumper = new CapturingScoreMatrixDumper();
  223. private RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository();
  224. private FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient,
  225. fileSimilarity, movedFilesRepository, sourceLinesHash, scoreMatrixDumper, addedFileRepository);
  226. @Before
  227. public void setUp() throws Exception {
  228. project = dbTester.components().insertPrivateProject();
  229. treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF).setUuid(project.uuid()).build());
  230. }
  231. @Test
  232. public void getDescription_returns_description() {
  233. assertThat(underTest.getDescription()).isEqualTo("Detect file moves");
  234. }
  235. @Test
  236. public void execute_detects_no_move_on_first_analysis() {
  237. analysisMetadataHolder.setBaseAnalysis(null);
  238. TestComputationStepContext context = new TestComputationStepContext();
  239. underTest.execute(context);
  240. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  241. verifyStatistics(context, null, null, null, null);
  242. }
  243. @Test
  244. public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() {
  245. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  246. TestComputationStepContext context = new TestComputationStepContext();
  247. underTest.execute(context);
  248. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  249. assertThat(addedFileRepository.getComponents()).isEmpty();
  250. verifyStatistics(context, 0, null, null, null);
  251. }
  252. @Test
  253. public void execute_detects_no_move_if_baseSnapshot_has_no_file() {
  254. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  255. Component file1 = fileComponent(FILE_1_REF, null);
  256. Component file2 = fileComponent(FILE_2_REF, null);
  257. setFilesInReport(file1, file2);
  258. TestComputationStepContext context = new TestComputationStepContext();
  259. underTest.execute(context);
  260. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  261. assertThat(addedFileRepository.getComponents()).containsOnly(file1, file2);
  262. verifyStatistics(context, 2, 0, 2, null);
  263. }
  264. @Test
  265. public void execute_detects_no_move_if_there_is_no_file_in_report() {
  266. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  267. insertFiles( /* no components */);
  268. setFilesInReport();
  269. TestComputationStepContext context = new TestComputationStepContext();
  270. underTest.execute(context);
  271. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  272. assertThat(addedFileRepository.getComponents()).isEmpty();
  273. verifyStatistics(context, 0, null, null, null);
  274. }
  275. @Test
  276. public void execute_detects_no_move_if_file_key_exists_in_both_DB_and_report() {
  277. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  278. Component file1 = fileComponent(FILE_1_REF, null);
  279. Component file2 = fileComponent(FILE_2_REF, null);
  280. insertFiles(file1.getUuid(), file2.getUuid());
  281. insertContentOfFileInDb(file1.getUuid(), CONTENT1);
  282. insertContentOfFileInDb(file2.getUuid(), CONTENT2);
  283. setFilesInReport(file2, file1);
  284. TestComputationStepContext context = new TestComputationStepContext();
  285. underTest.execute(context);
  286. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  287. assertThat(addedFileRepository.getComponents()).isEmpty();
  288. verifyStatistics(context, 2, 2, 0, null);
  289. }
  290. @Test
  291. public void execute_detects_move_if_content_of_file_is_same_in_DB_and_report() {
  292. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  293. Component file1 = fileComponent(FILE_1_REF, null);
  294. Component file2 = fileComponent(FILE_2_REF, CONTENT1);
  295. ComponentDto[] dtos = insertFiles(file1.getUuid());
  296. insertContentOfFileInDb(file1.getUuid(), CONTENT1);
  297. setFilesInReport(file2);
  298. TestComputationStepContext context = new TestComputationStepContext();
  299. underTest.execute(context);
  300. assertThat(movedFilesRepository.getComponentsWithOriginal()).containsExactly(file2);
  301. MovedFilesRepository.OriginalFile originalFile = movedFilesRepository.getOriginalFile(file2).get();
  302. assertThat(originalFile.getKey()).isEqualTo(dtos[0].getDbKey());
  303. assertThat(originalFile.getUuid()).isEqualTo(dtos[0].uuid());
  304. assertThat(addedFileRepository.getComponents()).isEmpty();
  305. verifyStatistics(context, 1, 1, 1, 1);
  306. }
  307. @Test
  308. public void execute_detects_no_move_if_content_of_file_is_not_similar_enough() {
  309. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  310. Component file1 = fileComponent(FILE_1_REF, null);
  311. Component file2 = fileComponent(FILE_2_REF, LESS_CONTENT1);
  312. insertFiles(file1.getDbKey());
  313. insertContentOfFileInDb(file1.getDbKey(), CONTENT1);
  314. setFilesInReport(file2);
  315. TestComputationStepContext context = new TestComputationStepContext();
  316. underTest.execute(context);
  317. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  318. assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore())
  319. .isPositive()
  320. .isLessThan(MIN_REQUIRED_SCORE);
  321. assertThat(addedFileRepository.getComponents()).contains(file2);
  322. verifyStatistics(context, 1, 1, 1, 0);
  323. }
  324. @Test
  325. public void execute_detects_no_move_if_content_of_file_is_empty_in_DB() {
  326. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  327. Component file1 = fileComponent(FILE_1_REF, null);
  328. Component file2 = fileComponent(FILE_2_REF, CONTENT1);
  329. insertFiles(file1.getDbKey());
  330. insertContentOfFileInDb(file1.getDbKey(), CONTENT_EMPTY);
  331. setFilesInReport(file2);
  332. TestComputationStepContext context = new TestComputationStepContext();
  333. underTest.execute(context);
  334. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  335. assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
  336. assertThat(addedFileRepository.getComponents()).contains(file2);
  337. verifyStatistics(context, 1, 1, 1, 0);
  338. }
  339. @Test
  340. public void execute_detects_no_move_if_content_of_file_has_no_path_in_DB() {
  341. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  342. Component file1 = fileComponent(FILE_1_REF, null);
  343. Component file2 = fileComponent(FILE_2_REF, CONTENT1);
  344. insertFiles(key -> newComponentDto(key).setPath(null), file1.getDbKey());
  345. insertContentOfFileInDb(file1.getDbKey(), CONTENT1);
  346. setFilesInReport(file2);
  347. TestComputationStepContext context = new TestComputationStepContext();
  348. underTest.execute(context);
  349. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  350. assertThat(scoreMatrixDumper.scoreMatrix).isNull();
  351. assertThat(addedFileRepository.getComponents()).containsOnly(file2);
  352. verifyStatistics(context, 1, 0, 1, null);
  353. }
  354. @Test
  355. public void execute_detects_no_move_if_content_of_file_is_empty_in_report() {
  356. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  357. Component file1 = fileComponent(FILE_1_REF, null);
  358. Component file2 = fileComponent(FILE_2_REF, CONTENT_EMPTY);
  359. insertFiles(file1.getDbKey());
  360. insertContentOfFileInDb(file1.getDbKey(), CONTENT1);
  361. setFilesInReport(file2);
  362. TestComputationStepContext context = new TestComputationStepContext();
  363. underTest.execute(context);
  364. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  365. assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
  366. assertThat(addedFileRepository.getComponents()).contains(file2);
  367. verifyStatistics(context, 1, 1, 1, 0);
  368. assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("max score in matrix is less than min required score (85). Do nothing.");
  369. }
  370. @Test
  371. public void execute_detects_no_move_if_two_added_files_have_same_content_as_the_one_in_db() {
  372. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  373. Component file1 = fileComponent(FILE_1_REF, null);
  374. Component file2 = fileComponent(FILE_2_REF, CONTENT1);
  375. Component file3 = fileComponent(FILE_3_REF, CONTENT1);
  376. insertFiles(file1.getDbKey());
  377. insertContentOfFileInDb(file1.getDbKey(), CONTENT1);
  378. setFilesInReport(file2, file3);
  379. TestComputationStepContext context = new TestComputationStepContext();
  380. underTest.execute(context);
  381. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  382. assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100);
  383. assertThat(addedFileRepository.getComponents()).containsOnly(file2, file3);
  384. verifyStatistics(context, 2, 1, 2, 0);
  385. }
  386. @Test
  387. public void execute_detects_no_move_if_two_deleted_files_have_same_content_as_the_one_added() {
  388. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  389. Component file1 = fileComponent(FILE_1_REF, null);
  390. Component file2 = fileComponent(FILE_2_REF, null);
  391. Component file3 = fileComponent(FILE_3_REF, CONTENT1);
  392. insertFiles(file1.getUuid(), file2.getUuid());
  393. insertContentOfFileInDb(file1.getUuid(), CONTENT1);
  394. insertContentOfFileInDb(file2.getUuid(), CONTENT1);
  395. setFilesInReport(file3);
  396. TestComputationStepContext context = new TestComputationStepContext();
  397. underTest.execute(context);
  398. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  399. assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100);
  400. assertThat(addedFileRepository.getComponents()).containsOnly(file3);
  401. verifyStatistics(context, 1, 2, 1, 0);
  402. }
  403. @Test
  404. public void execute_detects_no_move_if_two_files_are_empty_in_DB() {
  405. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  406. Component file1 = fileComponent(FILE_1_REF, null);
  407. Component file2 = fileComponent(FILE_2_REF, null);
  408. insertFiles(file1.getUuid(), file2.getUuid());
  409. insertContentOfFileInDb(file1.getUuid(), null);
  410. insertContentOfFileInDb(file2.getUuid(), null);
  411. setFilesInReport(file1, file2);
  412. TestComputationStepContext context = new TestComputationStepContext();
  413. underTest.execute(context);
  414. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  415. assertThat(scoreMatrixDumper.scoreMatrix).isNull();
  416. assertThat(addedFileRepository.getComponents()).isEmpty();
  417. verifyStatistics(context, 2, 2, 0, null);
  418. }
  419. @Test
  420. public void execute_detects_several_moves() {
  421. // testing:
  422. // - file1 renamed to file3
  423. // - file2 deleted
  424. // - file4 untouched
  425. // - file5 renamed to file6 with a small change
  426. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  427. Component file1 = fileComponent(FILE_1_REF, null);
  428. Component file2 = fileComponent(FILE_2_REF, null);
  429. Component file3 = fileComponent(FILE_3_REF, CONTENT1);
  430. Component file4 = fileComponent(5, new String[] {"a", "b"});
  431. Component file5 = fileComponent(6, null);
  432. Component file6 = fileComponent(7, LESS_CONTENT2);
  433. ComponentDto[] dtos = insertFiles(file1.getUuid(), file2.getUuid(), file4.getUuid(), file5.getUuid());
  434. insertContentOfFileInDb(file1.getUuid(), CONTENT1);
  435. insertContentOfFileInDb(file2.getUuid(), LESS_CONTENT1);
  436. insertContentOfFileInDb(file4.getUuid(), new String[] {"e", "f", "g", "h", "i"});
  437. insertContentOfFileInDb(file5.getUuid(), CONTENT2);
  438. setFilesInReport(file3, file4, file6);
  439. TestComputationStepContext context = new TestComputationStepContext();
  440. underTest.execute(context);
  441. assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(file3, file6);
  442. MovedFilesRepository.OriginalFile originalFile2 = movedFilesRepository.getOriginalFile(file3).get();
  443. assertThat(originalFile2.getKey()).isEqualTo(dtos[0].getDbKey());
  444. assertThat(originalFile2.getUuid()).isEqualTo(dtos[0].uuid());
  445. MovedFilesRepository.OriginalFile originalFile5 = movedFilesRepository.getOriginalFile(file6).get();
  446. assertThat(originalFile5.getKey()).isEqualTo(dtos[3].getDbKey());
  447. assertThat(originalFile5.getUuid()).isEqualTo(dtos[3].uuid());
  448. assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isGreaterThan(MIN_REQUIRED_SCORE);
  449. assertThat(addedFileRepository.getComponents()).isEmpty();
  450. verifyStatistics(context, 3, 4, 2, 2);
  451. }
  452. @Test
  453. public void execute_does_not_compute_any_distance_if_all_files_sizes_are_all_too_different() {
  454. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  455. Component file1 = fileComponent(FILE_1_REF, null);
  456. Component file2 = fileComponent(FILE_2_REF, null);
  457. Component file3 = fileComponent(FILE_3_REF, arrayOf(118));
  458. Component file4 = fileComponent(5, arrayOf(25));
  459. insertFiles(file1.getDbKey(), file2.getDbKey());
  460. insertContentOfFileInDb(file1.getDbKey(), arrayOf(100));
  461. insertContentOfFileInDb(file2.getDbKey(), arrayOf(30));
  462. setFilesInReport(file3, file4);
  463. TestComputationStepContext context = new TestComputationStepContext();
  464. underTest.execute(context);
  465. assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
  466. assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
  467. verifyStatistics(context, 2, 2, 2, 0);
  468. }
  469. /**
  470. * Creates an array of {@code numberOfElements} int values as String, starting with zero.
  471. */
  472. private static String[] arrayOf(int numberOfElements) {
  473. return IntStream.range(0, numberOfElements).mapToObj(String::valueOf).toArray(String[]::new);
  474. }
  475. /**
  476. * JH: A bug was encountered in the algorithm and I didn't manage to forge a simpler test case.
  477. */
  478. @Test
  479. public void real_life_use_case() throws Exception {
  480. analysisMetadataHolder.setBaseAnalysis(ANALYSIS);
  481. for (File f : FileUtils.listFiles(new File("src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1"), null, false)) {
  482. insertFiles("uuid_" + f.getName().hashCode());
  483. insertContentOfFileInDb("uuid_" + f.getName().hashCode(), readLines(f));
  484. }
  485. Map<String, Component> comps = new HashMap<>();
  486. int i = 1;
  487. for (File f : FileUtils.listFiles(new File("src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2"), null, false)) {
  488. String[] lines = readLines(f);
  489. Component c = builder(Component.Type.FILE, i++)
  490. .setUuid("uuid_" + f.getName().hashCode())
  491. .setKey(f.getName())
  492. .setName(f.getName())
  493. .setFileAttributes(new FileAttributes(false, null, lines.length))
  494. .build();
  495. comps.put(f.getName(), c);
  496. setFileLineHashesInReport(c, lines);
  497. }
  498. setFilesInReport(comps.values().toArray(new Component[0]));
  499. TestComputationStepContext context = new TestComputationStepContext();
  500. underTest.execute(context);
  501. Component makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex = comps.get("MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java");
  502. Component migrationRb1238 = comps.get("1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb");
  503. Component addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex = comps.get("AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java");
  504. assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(
  505. makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex,
  506. migrationRb1238,
  507. addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex);
  508. assertThat(movedFilesRepository.getOriginalFile(makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex).get().getUuid())
  509. .isEqualTo("uuid_" + "MakeComponentUuidNotNullOnDuplicationsIndex.java".hashCode());
  510. assertThat(movedFilesRepository.getOriginalFile(migrationRb1238).get().getUuid())
  511. .isEqualTo("uuid_" + "1242_make_analysis_uuid_not_null_on_duplications_index.rb".hashCode());
  512. assertThat(movedFilesRepository.getOriginalFile(addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex).get().getUuid())
  513. .isEqualTo("uuid_" + "AddComponentUuidColumnToDuplicationsIndex.java".hashCode());
  514. verifyStatistics(context, comps.values().size(), 12, 6, 3);
  515. }
  516. private String[] readLines(File filename) throws IOException {
  517. return FileUtils
  518. .readLines(filename, StandardCharsets.UTF_8)
  519. .toArray(new String[0]);
  520. }
  521. @CheckForNull
  522. private FileSourceDto insertContentOfFileInDb(String uuid, @Nullable String[] content) {
  523. return dbTester.getDbClient().componentDao().selectByUuid(dbTester.getSession(), uuid)
  524. .map(file -> {
  525. SourceLineHashesComputer linesHashesComputer = new SourceLineHashesComputer();
  526. if (content != null) {
  527. stream(content).forEach(linesHashesComputer::addLine);
  528. }
  529. FileSourceDto fileSourceDto = new FileSourceDto()
  530. .setUuid(Uuids.createFast())
  531. .setFileUuid(file.uuid())
  532. .setProjectUuid(file.projectUuid())
  533. .setLineHashes(linesHashesComputer.getLineHashes());
  534. dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto);
  535. dbTester.commit();
  536. return fileSourceDto;
  537. }).orElse(null);
  538. }
  539. private void setFilesInReport(Component... files) {
  540. treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF)
  541. .setUuid(project.uuid())
  542. .addChildren(files)
  543. .build());
  544. }
  545. private ComponentDto[] insertFiles(String... uuids) {
  546. return insertFiles(this::newComponentDto, uuids);
  547. }
  548. private ComponentDto[] insertFiles(Function<String, ComponentDto> newComponentDto, String... uuids) {
  549. return stream(uuids)
  550. .map(newComponentDto)
  551. .map(dto -> dbTester.components().insertComponent(dto))
  552. .toArray(ComponentDto[]::new);
  553. }
  554. private ComponentDto newComponentDto(String uuid) {
  555. return ComponentTesting.newFileDto(project)
  556. .setDbKey("key_" + uuid)
  557. .setUuid(uuid)
  558. .setPath("path_" + uuid);
  559. }
  560. private Component fileComponent(int ref, @Nullable String[] content) {
  561. ReportComponent component = builder(Component.Type.FILE, ref)
  562. .setName("report_path" + ref)
  563. .setFileAttributes(new FileAttributes(false, null, content == null ? 1 : content.length))
  564. .build();
  565. if (content != null) {
  566. setFileLineHashesInReport(component, content);
  567. }
  568. return component;
  569. }
  570. private void setFileLineHashesInReport(Component file, String[] content) {
  571. SourceLineHashesComputer computer = new SourceLineHashesComputer();
  572. for (String line : content) {
  573. computer.addLine(line);
  574. }
  575. when(sourceLinesHash.getLineHashesMatchingDBVersion(file)).thenReturn(computer.getLineHashes());
  576. }
  577. private static class CapturingScoreMatrixDumper implements ScoreMatrixDumper {
  578. private ScoreMatrix scoreMatrix;
  579. @Override
  580. public void dumpAsCsv(ScoreMatrix scoreMatrix) {
  581. this.scoreMatrix = scoreMatrix;
  582. }
  583. }
  584. private static void verifyStatistics(TestComputationStepContext context,
  585. @Nullable Integer expectedReportFiles, @Nullable Integer expectedDbFiles,
  586. @Nullable Integer expectedAddedFiles, @Nullable Integer expectedMovedFiles) {
  587. context.getStatistics().assertValue("reportFiles", expectedReportFiles);
  588. context.getStatistics().assertValue("dbFiles", expectedDbFiles);
  589. context.getStatistics().assertValue("addedFiles", expectedAddedFiles);
  590. context.getStatistics().assertValue("movedFiles", expectedMovedFiles);
  591. }
  592. private static class RecordingMutableAddedFileRepository implements MutableAddedFileRepository {
  593. private final List<Component> components = new ArrayList<>();
  594. @Override
  595. public void register(Component file) {
  596. components.add(file);
  597. }
  598. @Override
  599. public boolean isAdded(Component component) {
  600. throw new UnsupportedOperationException("isAdded should not be called");
  601. }
  602. public List<Component> getComponents() {
  603. return components;
  604. }
  605. }
  606. }