]> source.dussan.org Git - sonarqube.git/blob
4f1b01ebfa26098b136fdce72abd33131b15a3d3
[sonarqube.git] /
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.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.sonar.api.config.internal.MapSettings;
29 import org.sonar.api.utils.log.LogTester;
30 import org.sonar.api.utils.log.LoggerLevel;
31 import org.sonar.ce.task.projectanalysis.component.Component;
32 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
33 import org.sonar.duplications.block.Block;
34 import org.sonar.duplications.block.ByteArray;
35
36 import static com.google.common.base.Strings.padStart;
37 import static java.util.Arrays.asList;
38 import static java.util.Collections.singletonList;
39 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
40 import static org.assertj.core.api.Assertions.assertThat;
41 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
42 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
43
44 public class IntegrateCrossProjectDuplicationsTest {
45
46   @Rule
47   public LogTester logTester = new LogTester();
48   @Rule
49   public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create();
50
51   static final String XOO_LANGUAGE = "xoo";
52
53   static final String ORIGIN_FILE_KEY = "ORIGIN_FILE_KEY";
54   static final Component ORIGIN_FILE = builder(FILE, 1)
55     .setKey(ORIGIN_FILE_KEY)
56     .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
57     .build();
58
59   static final String OTHER_FILE_KEY = "OTHER_FILE_KEY";
60
61   MapSettings settings = new MapSettings();
62
63   IntegrateCrossProjectDuplications underTest = new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository);
64
65   @Test
66   public void add_duplications_from_two_blocks() {
67     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
68
69     Collection<Block> originBlocks = asList(
70       new Block.Builder()
71         .setResourceId(ORIGIN_FILE_KEY)
72         .setBlockHash(new ByteArray("a8998353e96320ec"))
73         .setIndexInFile(0)
74         .setLines(30, 43)
75         .setUnit(0, 5)
76         .build(),
77       new Block.Builder()
78         .setResourceId(ORIGIN_FILE_KEY)
79         .setBlockHash(new ByteArray("2b5747f0e4c59124"))
80         .setIndexInFile(1)
81         .setLines(32, 45)
82         .setUnit(5, 20)
83         .build());
84
85     Collection<Block> duplicatedBlocks = asList(
86       new Block.Builder()
87         .setResourceId(OTHER_FILE_KEY)
88         .setBlockHash(new ByteArray("a8998353e96320ec"))
89         .setIndexInFile(0)
90         .setLines(40, 53)
91         .build(),
92       new Block.Builder()
93         .setResourceId(OTHER_FILE_KEY)
94         .setBlockHash(new ByteArray("2b5747f0e4c59124"))
95         .setIndexInFile(1)
96         .setLines(42, 55)
97         .build());
98
99     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
100
101     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
102       .containsExactly(
103         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
104   }
105
106   @Test
107   public void add_duplications_from_a_single_block() {
108     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
109
110     Collection<Block> originBlocks = singletonList(
111       // This block contains 11 tokens -> a duplication will be created
112       new Block.Builder()
113         .setResourceId(ORIGIN_FILE_KEY)
114         .setBlockHash(new ByteArray("a8998353e96320ec"))
115         .setIndexInFile(0)
116         .setLines(30, 45)
117         .setUnit(0, 10)
118         .build());
119
120     Collection<Block> duplicatedBlocks = singletonList(
121       new Block.Builder()
122         .setResourceId(OTHER_FILE_KEY)
123         .setBlockHash(new ByteArray("a8998353e96320ec"))
124         .setIndexInFile(0)
125         .setLines(40, 55)
126         .build());
127
128     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
129
130     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
131       .containsExactly(
132         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
133   }
134
135   @Test
136   public void add_no_duplication_from_current_file() {
137     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
138
139     Collection<Block> originBlocks = asList(
140       new Block.Builder()
141         .setResourceId(ORIGIN_FILE_KEY)
142         .setBlockHash(new ByteArray("a8998353e96320ec"))
143         .setIndexInFile(0)
144         .setLines(30, 45)
145         .setUnit(0, 10)
146         .build(),
147       // Duplication is on the same file
148       new Block.Builder()
149         .setResourceId(ORIGIN_FILE_KEY)
150         .setBlockHash(new ByteArray("a8998353e96320ec"))
151         .setIndexInFile(0)
152         .setLines(46, 60)
153         .setUnit(0, 10)
154         .build());
155
156     Collection<Block> duplicatedBlocks = singletonList(
157       new Block.Builder()
158         .setResourceId(OTHER_FILE_KEY)
159         .setBlockHash(new ByteArray("a8998353e96320ed"))
160         .setIndexInFile(0)
161         .setLines(40, 55)
162         .build());
163
164     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
165
166     assertNoDuplicationAdded(ORIGIN_FILE);
167   }
168
169   @Test
170   public void add_no_duplication_when_not_enough_tokens() {
171     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
172
173     Collection<Block> originBlocks = singletonList(
174       // This block contains 5 tokens -> not enough to consider it as a duplication
175       new Block.Builder()
176         .setResourceId(ORIGIN_FILE_KEY)
177         .setBlockHash(new ByteArray("a8998353e96320ec"))
178         .setIndexInFile(0)
179         .setLines(30, 45)
180         .setUnit(0, 4)
181         .build());
182
183     Collection<Block> duplicatedBlocks = singletonList(
184       new Block.Builder()
185         .setResourceId(OTHER_FILE_KEY)
186         .setBlockHash(new ByteArray("a8998353e96320ec"))
187         .setIndexInFile(0)
188         .setLines(40, 55)
189         .build());
190
191     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
192
193     assertNoDuplicationAdded(ORIGIN_FILE);
194   }
195
196   @Test
197   public void add_no_duplication_when_no_duplicated_blocks() {
198     settings.setProperty("sonar.cpd.xoo.minimumTokens", 10);
199
200     Collection<Block> originBlocks = singletonList(
201       new Block.Builder()
202         .setResourceId(ORIGIN_FILE_KEY)
203         .setBlockHash(new ByteArray("a8998353e96320ec"))
204         .setIndexInFile(0)
205         .setLines(30, 45)
206         .setUnit(0, 10)
207         .build());
208
209     underTest.computeCpd(ORIGIN_FILE, originBlocks, Collections.emptyList());
210
211     assertNoDuplicationAdded(ORIGIN_FILE);
212   }
213
214   @Test
215   public void add_duplication_for_java_even_when_no_token() {
216     Component javaFile = builder(FILE, 1)
217       .setKey(ORIGIN_FILE_KEY)
218       .setFileAttributes(new FileAttributes(false, "java", 10))
219       .build();
220
221     Collection<Block> originBlocks = singletonList(
222       // This block contains 0 token
223       new Block.Builder()
224         .setResourceId(ORIGIN_FILE_KEY)
225         .setBlockHash(new ByteArray("a8998353e96320ec"))
226         .setIndexInFile(0)
227         .setLines(30, 45)
228         .setUnit(0, 0)
229         .build());
230
231     Collection<Block> duplicatedBlocks = singletonList(
232       new Block.Builder()
233         .setResourceId(OTHER_FILE_KEY)
234         .setBlockHash(new ByteArray("a8998353e96320ec"))
235         .setIndexInFile(0)
236         .setLines(40, 55)
237         .build());
238
239     underTest.computeCpd(javaFile, originBlocks, duplicatedBlocks);
240
241     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
242       .containsExactly(
243         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
244   }
245
246   @Test
247   public void default_minimum_tokens_is_one_hundred() {
248     settings.setProperty("sonar.cpd.xoo.minimumTokens", (Integer) null);
249
250     Collection<Block> originBlocks = singletonList(
251       new Block.Builder()
252         .setResourceId(ORIGIN_FILE_KEY)
253         .setBlockHash(new ByteArray("a8998353e96320ec"))
254         .setIndexInFile(0)
255         .setLines(30, 45)
256         .setUnit(0, 100)
257         .build());
258
259     Collection<Block> duplicatedBlocks = singletonList(
260       new Block.Builder()
261         .setResourceId(OTHER_FILE_KEY)
262         .setBlockHash(new ByteArray("a8998353e96320ec"))
263         .setIndexInFile(0)
264         .setLines(40, 55)
265         .build());
266
267     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
268
269     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE))
270       .containsExactly(
271         crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)));
272   }
273
274   @Test
275   public void do_not_compute_more_than_one_hundred_duplications_when_too_many_duplicated_references() {
276     Collection<Block> originBlocks = new ArrayList<>();
277     Collection<Block> duplicatedBlocks = new ArrayList<>();
278
279     Block.Builder blockBuilder = new Block.Builder()
280       .setResourceId(ORIGIN_FILE_KEY)
281       .setBlockHash(new ByteArray("a8998353e96320ec"))
282       .setIndexInFile(0)
283       .setLines(30, 45)
284       .setUnit(0, 100);
285     originBlocks.add(blockBuilder.build());
286
287     // Generate more than 100 duplications of the same block
288     for (int i = 0; i < 110; i++) {
289       duplicatedBlocks.add(
290         blockBuilder
291           .setResourceId(randomAlphanumeric(16))
292           .build());
293     }
294
295     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
296
297     assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly(
298       "Too many duplication references on file " + ORIGIN_FILE_KEY + " for block at line 30. Keeping only the first 100 references.");
299     Iterable<Duplication> duplications = duplicationRepository.getDuplications(ORIGIN_FILE);
300     assertThat(duplications).hasSize(1);
301     assertThat(duplications.iterator().next().getDuplicates()).hasSize(100);
302   }
303
304   @Test
305   public void do_not_compute_more_than_one_hundred_duplications_when_too_many_duplications() {
306     Collection<Block> originBlocks = new ArrayList<>();
307     Collection<Block> duplicatedBlocks = new ArrayList<>();
308
309     Block.Builder blockBuilder = new Block.Builder()
310       .setIndexInFile(0)
311       .setLines(30, 45)
312       .setUnit(0, 100);
313
314     // Generate more than 100 duplication on different files
315     for (int i = 0; i < 110; i++) {
316       String hash = padStart("hash" + i, 16, 'a');
317       originBlocks.add(
318         blockBuilder
319           .setResourceId(ORIGIN_FILE_KEY)
320           .setBlockHash(new ByteArray(hash))
321           .build());
322       duplicatedBlocks.add(
323         blockBuilder
324           .setResourceId("resource" + i)
325           .setBlockHash(new ByteArray(hash))
326           .build());
327     }
328
329     underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks);
330
331     assertThat(duplicationRepository.getDuplications(ORIGIN_FILE)).hasSize(100);
332     assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Too many duplication groups on file " + ORIGIN_FILE_KEY + ". Keeping only the first 100 groups.");
333   }
334
335   @Test
336   public void log_warning_if_this_deprecated_feature_is_enabled() {
337     settings.setProperty("sonar.cpd.cross_project", "true");
338
339     new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository);
340
341     assertThat(logTester.logs()).containsExactly("This analysis uses the deprecated cross-project duplication feature.");
342   }
343
344   private static Duplication crossProjectDuplication(TextBlock original, String otherFileKey, TextBlock duplicate) {
345     return new Duplication(original, Arrays.asList(new CrossProjectDuplicate(otherFileKey, duplicate)));
346   }
347
348   private void assertNoDuplicationAdded(Component file) {
349     assertThat(duplicationRepository.getDuplications(file)).isEmpty();
350   }
351
352 }