]> source.dussan.org Git - sonarqube.git/blob
03955c55068f99f3810e537ac8fc8c50f39c3f65
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.duplication;
21
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;
37
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.lang3.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;
48
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))
55     .build();
56   private static final String OTHER_FILE_KEY = "OTHER_FILE_KEY";
57
58   @Rule
59   public LogTester logTester = new LogTester();
60   @Rule
61   public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create();
62
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);
69
70   @Test
71   public void add_duplications_from_two_blocks() {
72     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
73
74     Collection<Block> originBlocks = asList(
75       new Block.Builder()
76         .setResourceId(ORIGIN_FILE_KEY)
77         .setBlockHash(new ByteArray("a8998353e96320ec"))
78         .setIndexInFile(0)
79         .setLines(30, 43)
80         .setUnit(0, 5)
81         .build(),
82       new Block.Builder()
83         .setResourceId(ORIGIN_FILE_KEY)
84         .setBlockHash(new ByteArray("2b5747f0e4c59124"))
85         .setIndexInFile(1)
86         .setLines(32, 45)
87         .setUnit(5, 20)
88         .build());
89
90     Collection<Block> duplicatedBlocks = asList(
91       new Block.Builder()
92         .setResourceId(OTHER_FILE_KEY)
93         .setBlockHash(new ByteArray("a8998353e96320ec"))
94         .setIndexInFile(0)
95         .setLines(40, 53)
96         .build(),
97       new Block.Builder()
98         .setResourceId(OTHER_FILE_KEY)
99         .setBlockHash(new ByteArray("2b5747f0e4c59124"))
100         .setIndexInFile(1)
101         .setLines(42, 55)
102         .build());
103
104     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
105
106     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
107       .containsExactly(
108         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
109   }
110
111   @Test
112   public void add_duplications_from_a_single_block() {
113     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
114
115     Collection<Block> originBlocks = singletonList(
116       // This block contains 11 tokens -> a duplication will be created
117       new Block.Builder()
118         .setResourceId(ORIGIN_FILE_KEY)
119         .setBlockHash(new ByteArray("a8998353e96320ec"))
120         .setIndexInFile(0)
121         .setLines(30, 45)
122         .setUnit(0, 10)
123         .build());
124
125     Collection<Block> duplicatedBlocks = singletonList(
126       new Block.Builder()
127         .setResourceId(OTHER_FILE_KEY)
128         .setBlockHash(new ByteArray("a8998353e96320ec"))
129         .setIndexInFile(0)
130         .setLines(40, 55)
131         .build());
132
133     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
134
135     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
136       .containsExactly(
137         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
138   }
139
140   @Test
141   public void add_no_duplication_from_current_file() {
142     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
143
144     Collection<Block> originBlocks = asList(
145       new Block.Builder()
146         .setResourceId(ORIGIN_FILE_KEY)
147         .setBlockHash(new ByteArray("a8998353e96320ec"))
148         .setIndexInFile(0)
149         .setLines(30, 45)
150         .setUnit(0, 10)
151         .build(),
152       // Duplication is on the same file
153       new Block.Builder()
154         .setResourceId(ORIGIN_FILE_KEY)
155         .setBlockHash(new ByteArray("a8998353e96320ec"))
156         .setIndexInFile(0)
157         .setLines(46, 60)
158         .setUnit(0, 10)
159         .build());
160
161     Collection<Block> duplicatedBlocks = singletonList(
162       new Block.Builder()
163         .setResourceId(OTHER_FILE_KEY)
164         .setBlockHash(new ByteArray("a8998353e96320ed"))
165         .setIndexInFile(0)
166         .setLines(40, 55)
167         .build());
168
169     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
170
171     assertNoDuplicationAdded(ORIGIN_FILE);
172   }
173
174   @Test
175   public void add_no_duplication_when_not_enough_tokens() {
176     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
177
178     Collection<Block> originBlocks = singletonList(
179       // This block contains 5 tokens -> not enough to consider it as a duplication
180       new Block.Builder()
181         .setResourceId(ORIGIN_FILE_KEY)
182         .setBlockHash(new ByteArray("a8998353e96320ec"))
183         .setIndexInFile(0)
184         .setLines(30, 45)
185         .setUnit(0, 4)
186         .build());
187
188     Collection<Block> duplicatedBlocks = singletonList(
189       new Block.Builder()
190         .setResourceId(OTHER_FILE_KEY)
191         .setBlockHash(new ByteArray("a8998353e96320ec"))
192         .setIndexInFile(0)
193         .setLines(40, 55)
194         .build());
195
196     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
197
198     assertNoDuplicationAdded(ORIGIN_FILE);
199   }
200
201   @Test
202   public void add_no_duplication_when_no_duplicated_blocks() {
203     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
204
205     Collection<Block> originBlocks = singletonList(
206       new Block.Builder()
207         .setResourceId(ORIGIN_FILE_KEY)
208         .setBlockHash(new ByteArray("a8998353e96320ec"))
209         .setIndexInFile(0)
210         .setLines(30, 45)
211         .setUnit(0, 10)
212         .build());
213
214     underTest.computeCpd(ORIGIN_FILE, originBlocks, Collections.emptyList());
215
216     assertNoDuplicationAdded(ORIGIN_FILE);
217   }
218
219   @Test
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))
224       .build();
225
226     Collection<Block> originBlocks = singletonList(
227       // This block contains 0 token
228       new Block.Builder()
229         .setResourceId(ORIGIN_FILE_KEY)
230         .setBlockHash(new ByteArray("a8998353e96320ec"))
231         .setIndexInFile(0)
232         .setLines(30, 45)
233         .setUnit(0, 0)
234         .build());
235
236     Collection<Block> duplicatedBlocks = singletonList(
237       new Block.Builder()
238         .setResourceId(OTHER_FILE_KEY)
239         .setBlockHash(new ByteArray("a8998353e96320ec"))
240         .setIndexInFile(0)
241         .setLines(40, 55)
242         .build());
243
244     underTest.computeCpd(javaFile, originBlocks, duplicatedBlocks);
245
246     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
247       .containsExactly(
248         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
249   }
250
251   @Test
252   public void default_minimum_tokens_is_one_hundred() {
253     settings.setProperty("sonar.cpd.xoo.minimumTokens", (Integer) null);
254
255     Collection<Block> originBlocks = singletonList(
256       new Block.Builder()
257         .setResourceId(ORIGIN_FILE_KEY)
258         .setBlockHash(new ByteArray("a8998353e96320ec"))
259         .setIndexInFile(0)
260         .setLines(30, 45)
261         .setUnit(0, 100)
262         .build());
263
264     Collection<Block> duplicatedBlocks = singletonList(
265       new Block.Builder()
266         .setResourceId(OTHER_FILE_KEY)
267         .setBlockHash(new ByteArray("a8998353e96320ec"))
268         .setIndexInFile(0)
269         .setLines(40, 55)
270         .build());
271
272     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
273
274     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
275       .containsExactly(
276         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
277   }
278
279   @Test
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<>();
283
284     Block.Builder blockBuilder = new Block.Builder()
285       .setResourceId(ORIGIN_FILE_KEY)
286       .setBlockHash(new ByteArray("a8998353e96320ec"))
287       .setIndexInFile(0)
288       .setLines(30, 45)
289       .setUnit(0, 100);
290     originBlocks.add(blockBuilder.build());
291
292     // Generate more than 100 duplications of the same block
293     for (int i = 0; i < 110; i++) {
294       duplicatedBlocks.add(
295         blockBuilder
296           .setResourceId(randomAlphanumeric(16))
297           .build());
298     }
299
300     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
301
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);
307   }
308
309   @Test
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<>();
313
314     Block.Builder blockBuilder = new Block.Builder()
315       .setIndexInFile(0)
316       .setLines(30, 45)
317       .setUnit(0, 100);
318
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');
322       originBlocks.add(
323         blockBuilder
324           .setResourceId(ORIGIN_FILE_KEY)
325           .setBlockHash(new ByteArray(hash))
326           .build());
327       duplicatedBlocks.add(
328         blockBuilder
329           .setResourceId("resource" + i)
330           .setBlockHash(new ByteArray(hash))
331           .build());
332     }
333
334     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
335
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.");
338   }
339
340   @Test
341   public void log_warning_if_this_deprecated_feature_is_enabled() {
342     when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
343     system.setNow(1000L);
344
345     new IntegrateCrossProjectDuplications(crossProjectDuplicationStatusHolder, settings.asConfig(), duplicationRepository, ceTaskMessages, system);
346
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));
349   }
350
351   private static Duplication crossProjectDuplication(TextBlock original, String otherFileKey, TextBlock duplicate) {
352     return new Duplication(original, Arrays.asList(new CrossProjectDuplicate(otherFileKey, duplicate)));
353   }
354
355   private void assertNoDuplicationAdded(Component file) {
356     assertThat(duplicationRepository.getDuplications(file)).isEmpty();
357   }
358
359 }