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