3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.duplication;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.slf4j.event.Level;
29 import org.sonar.api.config.internal.MapSettings;
30 import org.sonar.api.impl.utils.TestSystem2;
31 import org.sonar.api.testfixtures.log.LogTester;
32 import org.sonar.ce.task.log.CeTaskMessages;
33 import org.sonar.ce.task.projectanalysis.component.Component;
34 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
35 import org.sonar.duplications.block.Block;
36 import org.sonar.duplications.block.ByteArray;
38 import static com.google.common.base.Strings.padStart;
39 import static java.util.Arrays.asList;
40 import static java.util.Collections.singletonList;
41 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
42 import static org.assertj.core.api.Assertions.assertThat;
43 import static org.mockito.Mockito.mock;
44 import static org.mockito.Mockito.verify;
45 import static org.mockito.Mockito.when;
46 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
47 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
49 public class IntegrateCrossProjectDuplicationsTest {
50 private static final String XOO_LANGUAGE = "xoo";
51 private static final String ORIGIN_FILE_KEY = "ORIGIN_FILE_KEY";
52 private static final Component ORIGIN_FILE = builder(FILE, 1)
53 .setKey(ORIGIN_FILE_KEY)
54 .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
56 private static final String OTHER_FILE_KEY = "OTHER_FILE_KEY";
59 public LogTester logTester = new LogTester();
61 public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create();
63 private final TestSystem2 system = new TestSystem2();
64 private final MapSettings settings = new MapSettings();
65 private final CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class);
66 private final CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class);
67 private final IntegrateCrossProjectDuplications underTest = new IntegrateCrossProjectDuplications(crossProjectDuplicationStatusHolder, settings.asConfig(),
68 duplicationRepository, ceTaskMessages, system);
71 public void add_duplications_from_two_blocks() {
72 settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
74 Collection<Block> originBlocks = asList(
76 .setResourceId(ORIGIN_FILE_KEY)
77 .setBlockHash(new ByteArray("a8998353e96320ec"))
83 .setResourceId(ORIGIN_FILE_KEY)
84 .setBlockHash(new ByteArray("2b5747f0e4c59124"))
90 Collection<Block> duplicatedBlocks = asList(
92 .setResourceId(OTHER_FILE_KEY)
93 .setBlockHash(new ByteArray("a8998353e96320ec"))
98 .setResourceId(OTHER_FILE_KEY)
99 .setBlockHash(new ByteArray("2b5747f0e4c59124"))
104 underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
106 assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
108 crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
112 public void add_duplications_from_a_single_block() {
113 settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
115 Collection<Block> originBlocks = singletonList(
116 // This block contains 11 tokens -> a duplication will be created
118 .setResourceId(ORIGIN_FILE_KEY)
119 .setBlockHash(new ByteArray("a8998353e96320ec"))
125 Collection<Block> duplicatedBlocks = singletonList(
127 .setResourceId(OTHER_FILE_KEY)
128 .setBlockHash(new ByteArray("a8998353e96320ec"))
133 underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
135 assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
137 crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
141 public void add_no_duplication_from_current_file() {
142 settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
144 Collection<Block> originBlocks = asList(
146 .setResourceId(ORIGIN_FILE_KEY)
147 .setBlockHash(new ByteArray("a8998353e96320ec"))
152 // Duplication is on the same file
154 .setResourceId(ORIGIN_FILE_KEY)
155 .setBlockHash(new ByteArray("a8998353e96320ec"))
161 Collection<Block> duplicatedBlocks = singletonList(
163 .setResourceId(OTHER_FILE_KEY)
164 .setBlockHash(new ByteArray("a8998353e96320ed"))
169 underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
171 assertNoDuplicationAdded(ORIGIN_FILE);
175 public void add_no_duplication_when_not_enough_tokens() {
176 settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
178 Collection<Block> originBlocks = singletonList(
179 // This block contains 5 tokens -> not enough to consider it as a duplication
181 .setResourceId(ORIGIN_FILE_KEY)
182 .setBlockHash(new ByteArray("a8998353e96320ec"))
188 Collection<Block> duplicatedBlocks = singletonList(
190 .setResourceId(OTHER_FILE_KEY)
191 .setBlockHash(new ByteArray("a8998353e96320ec"))
196 underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
198 assertNoDuplicationAdded(ORIGIN_FILE);
202 public void add_no_duplication_when_no_duplicated_blocks() {
203 settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
205 Collection<Block> originBlocks = singletonList(
207 .setResourceId(ORIGIN_FILE_KEY)
208 .setBlockHash(new ByteArray("a8998353e96320ec"))
214 underTest.computeCpd(ORIGIN_FILE, originBlocks, Collections.emptyList());
216 assertNoDuplicationAdded(ORIGIN_FILE);
220 public void add_duplication_for_java_even_when_no_token() {
221 Component javaFile = builder(FILE, 1)
222 .setKey(ORIGIN_FILE_KEY)
223 .setFileAttributes(new FileAttributes(false, "java", 10))
226 Collection<Block> originBlocks = singletonList(
227 // This block contains 0 token
229 .setResourceId(ORIGIN_FILE_KEY)
230 .setBlockHash(new ByteArray("a8998353e96320ec"))
236 Collection<Block> duplicatedBlocks = singletonList(
238 .setResourceId(OTHER_FILE_KEY)
239 .setBlockHash(new ByteArray("a8998353e96320ec"))
244 underTest.computeCpd(javaFile, originBlocks, duplicatedBlocks);
246 assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
248 crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
252 public void default_minimum_tokens_is_one_hundred() {
253 settings.setProperty("sonar.cpd.xoo.minimumTokens", (Integer) null);
255 Collection<Block> originBlocks = singletonList(
257 .setResourceId(ORIGIN_FILE_KEY)
258 .setBlockHash(new ByteArray("a8998353e96320ec"))
264 Collection<Block> duplicatedBlocks = singletonList(
266 .setResourceId(OTHER_FILE_KEY)
267 .setBlockHash(new ByteArray("a8998353e96320ec"))
272 underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
274 assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
276 crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
280 public void do_not_compute_more_than_one_hundred_duplications_when_too_many_duplicated_references() {
281 Collection<Block> originBlocks = new ArrayList<>();
282 Collection<Block> duplicatedBlocks = new ArrayList<>();
284 Block.Builder blockBuilder = new Block.Builder()
285 .setResourceId(ORIGIN_FILE_KEY)
286 .setBlockHash(new ByteArray("a8998353e96320ec"))
290 originBlocks.add(blockBuilder.build());
292 // Generate more than 100 duplications of the same block
293 for (int i = 0; i < 110; i++) {
294 duplicatedBlocks.add(
296 .setResourceId(randomAlphanumeric(16))
300 underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
302 assertThat(logTester.logs(Level.WARN)).containsOnly(
303 "Too many duplication references on file " + ORIGIN_FILE_KEY + " for block at line 30. Keeping only the first 100 references.");
304 Iterable<Duplication> duplications = duplicationRepository.getDuplications(ORIGIN_FILE);
305 assertThat(duplications).hasSize(1);
306 assertThat(duplications.iterator().next().getDuplicates()).hasSize(100);
310 public void do_not_compute_more_than_one_hundred_duplications_when_too_many_duplications() {
311 Collection<Block> originBlocks = new ArrayList<>();
312 Collection<Block> duplicatedBlocks = new ArrayList<>();
314 Block.Builder blockBuilder = new Block.Builder()
319 // Generate more than 100 duplication on different files
320 for (int i = 0; i < 110; i++) {
321 String hash = padStart("hash" + i, 16, 'a');
324 .setResourceId(ORIGIN_FILE_KEY)
325 .setBlockHash(new ByteArray(hash))
327 duplicatedBlocks.add(
329 .setResourceId("resource" + i)
330 .setBlockHash(new ByteArray(hash))
334 underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
336 assertThat(duplicationRepository.getDuplications(ORIGIN_FILE)).hasSize(100);
337 assertThat(logTester.logs(Level.WARN)).containsOnly("Too many duplication groups on file " + ORIGIN_FILE_KEY + ". Keeping only the first 100 groups.");
341 public void log_warning_if_this_deprecated_feature_is_enabled() {
342 when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
343 system.setNow(1000L);
345 new IntegrateCrossProjectDuplications(crossProjectDuplicationStatusHolder, settings.asConfig(), duplicationRepository, ceTaskMessages, system);
347 assertThat(logTester.logs()).containsExactly("This analysis uses the deprecated cross-project duplication feature.");
348 verify(ceTaskMessages).add(new CeTaskMessages.Message("This project uses the deprecated cross-project duplication feature.", 1000L));
351 private static Duplication crossProjectDuplication(TextBlock original, String otherFileKey, TextBlock duplicate) {
352 return new Duplication(original, Arrays.asList(new CrossProjectDuplicate(otherFileKey, duplicate)));
355 private void assertNoDuplicationAdded(Component file) {
356 assertThat(duplicationRepository.getDuplications(file)).isEmpty();