Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

FileMoveDetectionStepIT.java 28KB

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