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.

CpdExecutorTest.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 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.scanner.cpd;
  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.Collections;
  26. import java.util.List;
  27. import java.util.concurrent.Callable;
  28. import java.util.concurrent.CompletableFuture;
  29. import java.util.concurrent.ExecutorService;
  30. import java.util.function.Consumer;
  31. import javax.annotation.Nullable;
  32. import org.junit.Before;
  33. import org.junit.Rule;
  34. import org.junit.Test;
  35. import org.junit.rules.ExpectedException;
  36. import org.junit.rules.TemporaryFolder;
  37. import org.mockito.ArgumentMatchers;
  38. import org.sonar.api.batch.fs.internal.DefaultInputFile;
  39. import org.sonar.api.batch.fs.internal.DefaultInputProject;
  40. import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
  41. import org.sonar.api.utils.log.LogTester;
  42. import org.sonar.api.utils.log.LoggerLevel;
  43. import org.sonar.core.util.CloseableIterator;
  44. import org.sonar.duplications.block.Block;
  45. import org.sonar.duplications.block.ByteArray;
  46. import org.sonar.duplications.index.CloneGroup;
  47. import org.sonar.duplications.index.ClonePart;
  48. import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
  49. import org.sonar.scanner.protocol.output.ScannerReport.Duplicate;
  50. import org.sonar.scanner.protocol.output.ScannerReport.Duplication;
  51. import org.sonar.scanner.protocol.output.ScannerReportReader;
  52. import org.sonar.scanner.protocol.output.ScannerReportWriter;
  53. import org.sonar.scanner.report.ReportPublisher;
  54. import org.sonar.scanner.scan.branch.BranchConfiguration;
  55. import org.sonar.scanner.scan.filesystem.InputComponentStore;
  56. import static org.assertj.core.api.Assertions.assertThat;
  57. import static org.mockito.Mockito.mock;
  58. import static org.mockito.Mockito.verify;
  59. import static org.mockito.Mockito.verifyNoMoreInteractions;
  60. import static org.mockito.Mockito.when;
  61. public class CpdExecutorTest {
  62. @Rule
  63. public LogTester logTester = new LogTester();
  64. @Rule
  65. public TemporaryFolder temp = new TemporaryFolder();
  66. @Rule
  67. public ExpectedException thrown = ExpectedException.none();
  68. private CpdExecutor executor;
  69. private ExecutorService executorService = mock(ExecutorService.class);
  70. private CpdSettings settings = mock(CpdSettings.class);
  71. private ReportPublisher publisher = mock(ReportPublisher.class);
  72. private SonarCpdBlockIndex index = new SonarCpdBlockIndex(publisher, settings);
  73. private ScannerReportReader reader;
  74. private DefaultInputFile batchComponent1;
  75. private DefaultInputFile batchComponent2;
  76. private DefaultInputFile batchComponent3;
  77. private File baseDir;
  78. private InputComponentStore componentStore;
  79. @Before
  80. public void setUp() throws IOException {
  81. File outputDir = temp.newFolder();
  82. baseDir = temp.newFolder();
  83. when(publisher.getWriter()).thenReturn(new ScannerReportWriter(outputDir));
  84. DefaultInputProject project = TestInputFileBuilder.newDefaultInputProject("foo", baseDir);
  85. componentStore = new InputComponentStore(mock(BranchConfiguration.class));
  86. executor = new CpdExecutor(settings, index, publisher, componentStore, executorService);
  87. reader = new ScannerReportReader(outputDir);
  88. batchComponent1 = createComponent("src/Foo.php", 5);
  89. batchComponent2 = createComponent("src/Foo2.php", 5);
  90. batchComponent3 = createComponent("src/Foo3.php", 5);
  91. }
  92. @Test
  93. public void dont_fail_if_nothing_to_save() {
  94. executor.saveDuplications(batchComponent1, Collections.<CloneGroup>emptyList());
  95. assertThat(reader.readComponentDuplications(batchComponent1.scannerId())).isExhausted();
  96. }
  97. @Test
  98. public void should_save_single_duplication() {
  99. List<CloneGroup> groups = Collections.singletonList(newCloneGroup(
  100. new ClonePart(batchComponent1.key(), 0, 2, 4),
  101. new ClonePart(batchComponent2.key(), 0, 15, 17)));
  102. executor.saveDuplications(batchComponent1, groups);
  103. Duplication[] dups = readDuplications(1);
  104. assertDuplication(dups[0], 2, 4, batchComponent2.scannerId(), 15, 17);
  105. }
  106. @Test
  107. public void should_save_duplication_on_same_file() {
  108. List<CloneGroup> groups = Collections.singletonList(newCloneGroup(
  109. new ClonePart(batchComponent1.key(), 0, 5, 204),
  110. new ClonePart(batchComponent1.key(), 0, 215, 414)));
  111. executor.saveDuplications(batchComponent1, groups);
  112. Duplication[] dups = readDuplications(1);
  113. assertDuplication(dups[0], 5, 204, null, 215, 414);
  114. }
  115. @Test
  116. public void should_limit_number_of_references() {
  117. // 1 origin part + 101 duplicates = 102
  118. List<ClonePart> parts = new ArrayList<>(CpdExecutor.MAX_CLONE_PART_PER_GROUP + 2);
  119. for (int i = 0; i < CpdExecutor.MAX_CLONE_PART_PER_GROUP + 2; i++) {
  120. parts.add(new ClonePart(batchComponent1.key(), i, i, i + 1));
  121. }
  122. List<CloneGroup> groups = Collections.singletonList(CloneGroup.builder().setLength(0).setOrigin(parts.get(0)).setParts(parts).build());
  123. executor.saveDuplications(batchComponent1, groups);
  124. Duplication[] dups = readDuplications(1);
  125. assertThat(dups[0].getDuplicateList()).hasSize(CpdExecutor.MAX_CLONE_PART_PER_GROUP);
  126. assertThat(logTester.logs(LoggerLevel.WARN))
  127. .contains("Too many duplication references on file " + batchComponent1 + " for block at line 0. Keep only the first "
  128. + CpdExecutor.MAX_CLONE_PART_PER_GROUP + " references.");
  129. }
  130. @Test
  131. public void should_limit_number_of_clones() {
  132. // 1 origin part + 101 duplicates = 102
  133. List<CloneGroup> dups = new ArrayList<>(CpdExecutor.MAX_CLONE_GROUP_PER_FILE + 1);
  134. for (int i = 0; i < CpdExecutor.MAX_CLONE_GROUP_PER_FILE + 1; i++) {
  135. ClonePart clonePart = new ClonePart(batchComponent1.key(), i, i, i + 1);
  136. ClonePart dupPart = new ClonePart(batchComponent1.key(), i + 1, i + 1, i + 2);
  137. dups.add(newCloneGroup(clonePart, dupPart));
  138. }
  139. executor.saveDuplications(batchComponent1, dups);
  140. assertThat(reader.readComponentDuplications(batchComponent1.scannerId())).toIterable().hasSize(CpdExecutor.MAX_CLONE_GROUP_PER_FILE);
  141. assertThat(logTester.logs(LoggerLevel.WARN))
  142. .contains("Too many duplication groups on file " + batchComponent1 + ". Keep only the first " + CpdExecutor.MAX_CLONE_GROUP_PER_FILE + " groups.");
  143. }
  144. @Test
  145. public void should_save_duplication_involving_three_files() {
  146. List<CloneGroup> groups = Collections.singletonList(newCloneGroup(
  147. new ClonePart(batchComponent1.key(), 0, 5, 204),
  148. new ClonePart(batchComponent2.key(), 0, 15, 214),
  149. new ClonePart(batchComponent3.key(), 0, 25, 224)));
  150. executor.saveDuplications(batchComponent1, groups);
  151. Duplication[] dups = readDuplications(1);
  152. assertDuplication(dups[0], 5, 204, 2);
  153. assertDuplicate(dups[0].getDuplicate(0), batchComponent2.scannerId(), 15, 214);
  154. assertDuplicate(dups[0].getDuplicate(1), batchComponent3.scannerId(), 25, 224);
  155. }
  156. @Test
  157. public void should_save_two_duplicated_groups_involving_three_files() {
  158. List<CloneGroup> groups = Arrays.asList(
  159. newCloneGroup(new ClonePart(batchComponent1.key(), 0, 5, 204),
  160. new ClonePart(batchComponent2.key(), 0, 15, 214)),
  161. newCloneGroup(new ClonePart(batchComponent1.key(), 0, 15, 214),
  162. new ClonePart(batchComponent3.key(), 0, 15, 214)));
  163. executor.saveDuplications(batchComponent1, groups);
  164. Duplication[] dups = readDuplications(2);
  165. assertDuplication(dups[0], 5, 204, batchComponent2.scannerId(), 15, 214);
  166. assertDuplication(dups[1], 15, 214, batchComponent3.scannerId(), 15, 214);
  167. }
  168. @Test
  169. public void should_ignore_missing_component() {
  170. Block block = Block.builder()
  171. .setBlockHash(new ByteArray("AAAABBBBCCCC"))
  172. .setResourceId("unknown")
  173. .build();
  174. index.insert(batchComponent1, Collections.singletonList(block));
  175. executor.execute();
  176. verify(executorService).shutdown();
  177. verifyNoMoreInteractions(executorService);
  178. readDuplications(batchComponent1, 0);
  179. assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Resource not found in component store: unknown. Skipping CPD computation for it");
  180. }
  181. @Test
  182. public void should_timeout() {
  183. Block block = Block.builder()
  184. .setBlockHash(new ByteArray("AAAABBBBCCCC"))
  185. .setResourceId(batchComponent1.key())
  186. .build();
  187. index.insert(batchComponent1, Collections.singletonList(block));
  188. when(executorService.submit(ArgumentMatchers.any(Callable.class))).thenReturn(new CompletableFuture());
  189. executor.execute(1);
  190. readDuplications(0);
  191. assertThat(logTester.logs(LoggerLevel.WARN))
  192. .usingElementComparator((l, r) -> l.matches(r) ? 0 : 1)
  193. .containsOnly(
  194. "Timeout during detection of duplications for .*Foo.php");
  195. }
  196. private DefaultInputFile createComponent(String relativePath, int lines) {
  197. return createComponent(relativePath, lines, f -> {
  198. });
  199. }
  200. private DefaultInputFile createComponent(String relativePath, int lines, Consumer<TestInputFileBuilder> config) {
  201. TestInputFileBuilder fileBuilder = new TestInputFileBuilder("foo", relativePath)
  202. .setModuleBaseDir(baseDir.toPath())
  203. .setLines(lines);
  204. config.accept(fileBuilder);
  205. DefaultInputFile file = fileBuilder.build();
  206. componentStore.put("foo", file);
  207. return file;
  208. }
  209. private Duplication[] readDuplications(int expected) {
  210. return readDuplications(batchComponent1, expected);
  211. }
  212. private Duplication[] readDuplications(DefaultInputFile file, int expected) {
  213. assertThat(reader.readComponentDuplications(file.scannerId())).toIterable().hasSize(expected);
  214. Duplication[] duplications = new Duplication[expected];
  215. CloseableIterator<Duplication> dups = reader.readComponentDuplications(file.scannerId());
  216. for (int i = 0; i < expected; i++) {
  217. duplications[i] = dups.next();
  218. }
  219. dups.close();
  220. return duplications;
  221. }
  222. private void assertDuplicate(Duplicate d, int otherFileRef, int rangeStartLine, int rangeEndLine) {
  223. assertThat(d.getOtherFileRef()).isEqualTo(otherFileRef);
  224. assertThat(d.getRange().getStartLine()).isEqualTo(rangeStartLine);
  225. assertThat(d.getRange().getEndLine()).isEqualTo(rangeEndLine);
  226. }
  227. private void assertDuplication(Duplication d, int originStartLine, int originEndLine, int numDuplicates) {
  228. assertThat(d.getOriginPosition().getStartLine()).isEqualTo(originStartLine);
  229. assertThat(d.getOriginPosition().getEndLine()).isEqualTo(originEndLine);
  230. assertThat(d.getDuplicateList()).hasSize(numDuplicates);
  231. }
  232. private void assertDuplication(Duplication d, int originStartLine, int originEndLine, @Nullable Integer otherFileRef, int rangeStartLine, int rangeEndLine) {
  233. assertThat(d.getOriginPosition().getStartLine()).isEqualTo(originStartLine);
  234. assertThat(d.getOriginPosition().getEndLine()).isEqualTo(originEndLine);
  235. assertThat(d.getDuplicateList()).hasSize(1);
  236. if (otherFileRef != null) {
  237. assertThat(d.getDuplicate(0).getOtherFileRef()).isEqualTo(otherFileRef);
  238. } else {
  239. assertThat(d.getDuplicate(0).getOtherFileRef()).isEqualTo(0);
  240. }
  241. assertThat(d.getDuplicate(0).getRange().getStartLine()).isEqualTo(rangeStartLine);
  242. assertThat(d.getDuplicate(0).getRange().getEndLine()).isEqualTo(rangeEndLine);
  243. }
  244. private CloneGroup newCloneGroup(ClonePart... parts) {
  245. return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build();
  246. }
  247. }