1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle;
21
22 import static com.google.common.truth.Truth.assertWithMessage;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.io.LineNumberReader;
30 import java.nio.charset.StandardCharsets;
31 import java.text.MessageFormat;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.ResourceBundle;
40 import java.util.stream.Collectors;
41
42 import com.google.common.collect.ImmutableMap;
43 import com.google.common.collect.Maps;
44 import com.puppycrawl.tools.checkstyle.LocalizedMessage.Utf8Control;
45 import com.puppycrawl.tools.checkstyle.api.Configuration;
46 import com.puppycrawl.tools.checkstyle.bdd.InlineConfigParser;
47 import com.puppycrawl.tools.checkstyle.bdd.TestInputConfiguration;
48 import com.puppycrawl.tools.checkstyle.bdd.TestInputViolation;
49 import com.puppycrawl.tools.checkstyle.internal.utils.BriefUtLogger;
50 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
51 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
52 import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
53
54 public abstract class AbstractModuleTestSupport extends AbstractPathTestSupport {
55
56 protected static final String ROOT_MODULE_NAME = Checker.class.getSimpleName();
57
58 private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
59
60
61
62
63
64
65 protected final ByteArrayOutputStream getStream() {
66 return stream;
67 }
68
69
70
71
72
73
74 protected final BriefUtLogger getBriefUtLogger() {
75 return new BriefUtLogger(stream);
76 }
77
78
79
80
81
82
83
84
85 protected static DefaultConfiguration createModuleConfig(Class<?> clazz) {
86 return new DefaultConfiguration(clazz.getName());
87 }
88
89
90
91
92
93
94
95
96 protected final Checker createChecker(Configuration moduleConfig)
97 throws Exception {
98 final String moduleName = moduleConfig.getName();
99 final Checker checker = new Checker();
100 checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
101
102 if (ROOT_MODULE_NAME.equals(moduleName)) {
103 checker.configure(moduleConfig);
104 }
105 else {
106 configureChecker(checker, moduleConfig);
107 }
108
109 checker.addListener(getBriefUtLogger());
110 return checker;
111 }
112
113
114
115
116
117
118
119
120 protected void configureChecker(Checker checker, Configuration moduleConfig) throws Exception {
121 final Class<?> moduleClass = Class.forName(moduleConfig.getName());
122
123 if (ModuleReflectionUtil.isCheckstyleTreeWalkerCheck(moduleClass)
124 || ModuleReflectionUtil.isTreeWalkerFilterModule(moduleClass)) {
125 final Configuration config = createTreeWalkerConfig(moduleConfig);
126 checker.configure(config);
127 }
128 else {
129 final Configuration config = createRootConfig(moduleConfig);
130 checker.configure(config);
131 }
132 }
133
134
135
136
137
138
139
140
141
142 protected static DefaultConfiguration createTreeWalkerConfig(Configuration config) {
143 final DefaultConfiguration rootConfig =
144 new DefaultConfiguration(ROOT_MODULE_NAME);
145 final DefaultConfiguration twConf = createModuleConfig(TreeWalker.class);
146
147 rootConfig.addProperty("charset", StandardCharsets.UTF_8.name());
148 rootConfig.addChild(twConf);
149 twConf.addChild(config);
150 return rootConfig;
151 }
152
153
154
155
156
157
158
159 protected static DefaultConfiguration createRootConfig(Configuration config) {
160 final DefaultConfiguration rootConfig = new DefaultConfiguration(ROOT_MODULE_NAME);
161 if (config != null) {
162 rootConfig.addChild(config);
163 }
164 return rootConfig;
165 }
166
167
168
169
170
171
172
173
174
175 protected final String getNonCompilablePath(String filename) throws IOException {
176 return new File("src/" + getResourceLocation()
177 + "/resources-noncompilable/" + getPackageLocation() + "/"
178 + filename).getCanonicalPath();
179 }
180
181
182
183
184
185
186
187
188 protected final String getUriString(String filename) {
189 return new File("src/test/resources/" + getPackageLocation() + "/" + filename).toURI()
190 .toString();
191 }
192
193
194
195
196
197
198
199
200
201
202
203 protected final void verifyFilterWithInlineConfigParser(String filePath,
204 String[] expectedUnfiltered,
205 String... expectedFiltered)
206 throws Exception {
207 final TestInputConfiguration testInputConfiguration =
208 InlineConfigParser.parseWithFilteredViolations(filePath);
209 final DefaultConfiguration configWithoutFilters =
210 testInputConfiguration.createConfigurationWithoutFilters();
211 final List<TestInputViolation> violationsWithoutFilters =
212 new ArrayList<>(testInputConfiguration.getViolations());
213 violationsWithoutFilters.addAll(testInputConfiguration.getFilteredViolations());
214 Collections.sort(violationsWithoutFilters);
215 verifyViolations(configWithoutFilters, filePath, violationsWithoutFilters);
216 verify(configWithoutFilters, filePath, expectedUnfiltered);
217 final DefaultConfiguration configWithFilters =
218 testInputConfiguration.createConfiguration();
219 verifyViolations(configWithFilters, filePath, testInputConfiguration.getViolations());
220 verify(configWithFilters, filePath, expectedFiltered);
221 }
222
223
224
225
226
227
228
229
230
231
232 protected final void verifyWithInlineXmlConfig(String filePath, String... expected)
233 throws Exception {
234 final TestInputConfiguration testInputConfiguration =
235 InlineConfigParser.parseWithXmlHeader(filePath);
236 final Configuration xmlConfig =
237 testInputConfiguration.getXmlConfiguration();
238 verifyViolations(xmlConfig, filePath, testInputConfiguration.getViolations());
239 verify(xmlConfig, filePath, expected);
240 }
241
242
243
244
245
246
247
248
249
250
251 protected final void verifyWithInlineConfigParser(String filePath, String... expected)
252 throws Exception {
253 final TestInputConfiguration testInputConfiguration =
254 InlineConfigParser.parse(filePath);
255 final DefaultConfiguration parsedConfig =
256 testInputConfiguration.createConfiguration();
257 verifyViolations(parsedConfig, filePath, testInputConfiguration.getViolations());
258 verify(parsedConfig, filePath, expected);
259 }
260
261
262
263
264
265
266
267
268
269
270
271
272 protected final void verifyWithInlineConfigParser(String filePath1,
273 String filePath2,
274 String... expected)
275 throws Exception {
276 final TestInputConfiguration testInputConfiguration1 =
277 InlineConfigParser.parse(filePath1);
278 final DefaultConfiguration parsedConfig =
279 testInputConfiguration1.createConfiguration();
280 final TestInputConfiguration testInputConfiguration2 =
281 InlineConfigParser.parse(filePath2);
282 verifyViolations(parsedConfig, filePath1, testInputConfiguration1.getViolations());
283 verifyViolations(parsedConfig, filePath2, testInputConfiguration2.getViolations());
284 verify(createChecker(parsedConfig),
285 new File[] {new File(filePath1), new File(filePath2)},
286 filePath1,
287 expected);
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303 protected final void verifyWithInlineConfigParser(String filePath1,
304 String filePath2,
305 List<String> expectedFromFile1,
306 List<String> expectedFromFile2)
307 throws Exception {
308 final TestInputConfiguration testInputConfiguration = InlineConfigParser.parse(filePath1);
309 final DefaultConfiguration parsedConfig = testInputConfiguration.createConfiguration();
310 final TestInputConfiguration testInputConfiguration2 = InlineConfigParser.parse(filePath2);
311 final DefaultConfiguration parsedConfig2 = testInputConfiguration.createConfiguration();
312 final File[] inputs = {new File(filePath1), new File(filePath2)};
313 verifyViolations(parsedConfig, filePath1, testInputConfiguration.getViolations());
314 verifyViolations(parsedConfig2, filePath2, testInputConfiguration2.getViolations());
315 verify(createChecker(parsedConfig), inputs, ImmutableMap.of(
316 filePath1, expectedFromFile1,
317 filePath2, expectedFromFile2));
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331 protected final void verify(Configuration config, String fileName, String... expected)
332 throws Exception {
333 verify(createChecker(config), fileName, fileName, expected);
334 }
335
336
337
338
339
340
341
342
343
344
345
346
347
348 protected void verify(Checker checker, String fileName, String... expected)
349 throws Exception {
350 verify(checker, fileName, fileName, expected);
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366 protected final void verify(Checker checker,
367 String processedFilename,
368 String messageFileName,
369 String... expected)
370 throws Exception {
371 verify(checker,
372 new File[] {new File(processedFilename)},
373 messageFileName, expected);
374 }
375
376
377
378
379
380
381
382
383
384
385
386 protected void verify(Checker checker,
387 File[] processedFiles,
388 String messageFileName,
389 String... expected)
390 throws Exception {
391 final Map<String, List<String>> expectedViolations = new HashMap<>();
392 expectedViolations.put(messageFileName, Arrays.asList(expected));
393 verify(checker, processedFiles, expectedViolations);
394 }
395
396
397
398
399
400
401
402
403
404 protected final void verify(Checker checker,
405 File[] processedFiles,
406 Map<String, List<String>> expectedViolations)
407 throws Exception {
408 stream.flush();
409 stream.reset();
410 final List<File> theFiles = new ArrayList<>();
411 Collections.addAll(theFiles, processedFiles);
412 final int errs = checker.process(theFiles);
413
414
415 final Map<String, List<String>> actualViolations = getActualViolations(errs);
416 final Map<String, List<String>> realExpectedViolations =
417 Maps.filterValues(expectedViolations, input -> !input.isEmpty());
418
419 assertWithMessage("Files with expected violations and actual violations differ.")
420 .that(actualViolations.keySet())
421 .isEqualTo(realExpectedViolations.keySet());
422
423 realExpectedViolations.forEach((fileName, violationList) -> {
424 assertWithMessage("Violations for %s differ.", fileName)
425 .that(actualViolations.get(fileName))
426 .containsExactlyElementsIn(violationList);
427 });
428
429 checker.destroy();
430 }
431
432
433
434
435
436
437
438
439 protected final void verifyWithLimitedResources(String fileName, String... expected)
440 throws Exception {
441
442 final Void result = TestUtil.getResultWithLimitedResources(() -> {
443 verifyWithInlineConfigParser(fileName, expected);
444 return null;
445 });
446 assertWithMessage("Verify should complete successfully.")
447 .that(result)
448 .isNull();
449 }
450
451
452
453
454
455
456
457
458 protected final void execute(Configuration config, String... filenames) throws Exception {
459 final Checker checker = createChecker(config);
460 final List<File> files = Arrays.stream(filenames)
461 .map(File::new)
462 .collect(Collectors.toUnmodifiableList());
463 checker.process(files);
464 checker.destroy();
465 }
466
467
468
469
470
471
472
473
474 protected static void execute(Checker checker, String... filenames) throws Exception {
475 final List<File> files = Arrays.stream(filenames)
476 .map(File::new)
477 .collect(Collectors.toUnmodifiableList());
478 checker.process(files);
479 checker.destroy();
480 }
481
482
483
484
485
486
487
488
489
490 private void verifyViolations(Configuration config,
491 String file,
492 List<TestInputViolation> testInputViolations)
493 throws Exception {
494 final List<String> actualViolations = getActualViolationsForFile(config, file);
495 final List<Integer> actualViolationLines = actualViolations.stream()
496 .map(violation -> violation.substring(0, violation.indexOf(':')))
497 .map(Integer::valueOf)
498 .collect(Collectors.toUnmodifiableList());
499 final List<Integer> expectedViolationLines = testInputViolations.stream()
500 .map(TestInputViolation::getLineNo)
501 .collect(Collectors.toUnmodifiableList());
502 assertWithMessage("Violation lines for %s differ.", file)
503 .that(actualViolationLines)
504 .isEqualTo(expectedViolationLines);
505 for (int index = 0; index < actualViolations.size(); index++) {
506 assertWithMessage("Actual and expected violations differ.")
507 .that(actualViolations.get(index))
508 .matches(testInputViolations.get(index).toRegex());
509 }
510 }
511
512
513
514
515
516
517
518
519
520 private List<String> getActualViolationsForFile(Configuration config,
521 String file) throws Exception {
522 stream.flush();
523 stream.reset();
524 final List<File> files = Collections.singletonList(new File(file));
525 final Checker checker = createChecker(config);
526 final Map<String, List<String>> actualViolations =
527 getActualViolations(checker.process(files));
528 checker.destroy();
529 return actualViolations.getOrDefault(file, new ArrayList<>());
530 }
531
532
533
534
535
536
537
538
539
540
541 private Map<String, List<String>> getActualViolations(int errorCount) throws IOException {
542
543 try (ByteArrayInputStream inputStream =
544 new ByteArrayInputStream(stream.toByteArray());
545 LineNumberReader lnr = new LineNumberReader(
546 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
547 final Map<String, List<String>> actualViolations = new HashMap<>();
548 for (String line = lnr.readLine(); line != null && lnr.getLineNumber() <= errorCount;
549 line = lnr.readLine()) {
550
551
552 final String[] actualViolation = line.split("(?<=.{2}):", 2);
553 final String actualViolationFileName = actualViolation[0];
554 final String actualViolationMessage = actualViolation[1];
555
556 actualViolations
557 .computeIfAbsent(actualViolationFileName, key -> new ArrayList<>())
558 .add(actualViolationMessage);
559 }
560
561 return actualViolations;
562 }
563 }
564
565
566
567
568
569
570
571
572
573 protected final String getCheckMessage(String messageKey, Object... arguments) {
574 return internalGetCheckMessage(getMessageBundle(), messageKey, arguments);
575 }
576
577
578
579
580
581
582
583
584
585
586 protected static String getCheckMessage(
587 Class<?> clazz, String messageKey, Object... arguments) {
588 return internalGetCheckMessage(getMessageBundle(clazz.getName()), messageKey, arguments);
589 }
590
591
592
593
594
595
596
597
598
599
600 private static String internalGetCheckMessage(
601 String messageBundle, String messageKey, Object... arguments) {
602 final ResourceBundle resourceBundle = ResourceBundle.getBundle(
603 messageBundle,
604 Locale.ROOT,
605 Thread.currentThread().getContextClassLoader(),
606 new Utf8Control());
607 final String pattern = resourceBundle.getString(messageKey);
608 final MessageFormat formatter = new MessageFormat(pattern, Locale.ROOT);
609 return formatter.format(arguments);
610 }
611
612
613
614
615
616
617 private String getMessageBundle() {
618 final String className = getClass().getName();
619 return getMessageBundle(className);
620 }
621
622
623
624
625
626
627
628 private static String getMessageBundle(String className) {
629 final String messageBundle;
630 final String messages = "messages";
631 final int endIndex = className.lastIndexOf('.');
632 if (endIndex < 0) {
633 messageBundle = messages;
634 }
635 else {
636 final String packageName = className.substring(0, endIndex);
637 messageBundle = packageName + "." + messages;
638 }
639 return messageBundle;
640 }
641
642
643
644
645
646
647
648
649 protected static String[] removeSuppressed(String[] actualViolations,
650 String... suppressedViolations) {
651 final List<String> actualViolationsList =
652 Arrays.stream(actualViolations).collect(Collectors.toCollection(ArrayList::new));
653 actualViolationsList.removeAll(Arrays.asList(suppressedViolations));
654 return actualViolationsList.toArray(CommonUtil.EMPTY_STRING_ARRAY);
655 }
656
657 }