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 final List<String> actualViolations = getActualViolationsForFile(parsedConfig, filePath);
258 verifyViolations(filePath, testInputConfiguration.getViolations(), actualViolations);
259 assertWithMessage("Violations for %s differ.", filePath)
260 .that(actualViolations)
261 .containsExactlyElementsIn(expected);
262 }
263
264
265
266
267
268
269
270
271
272
273
274
275 protected final void verifyWithInlineConfigParser(String filePath1,
276 String filePath2,
277 String... expected)
278 throws Exception {
279 final TestInputConfiguration testInputConfiguration1 =
280 InlineConfigParser.parse(filePath1);
281 final DefaultConfiguration parsedConfig =
282 testInputConfiguration1.createConfiguration();
283 final TestInputConfiguration testInputConfiguration2 =
284 InlineConfigParser.parse(filePath2);
285 verifyViolations(parsedConfig, filePath1, testInputConfiguration1.getViolations());
286 verifyViolations(parsedConfig, filePath2, testInputConfiguration2.getViolations());
287 verify(createChecker(parsedConfig),
288 new File[] {new File(filePath1), new File(filePath2)},
289 filePath1,
290 expected);
291 }
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306 protected final void verifyWithInlineConfigParser(String filePath1,
307 String filePath2,
308 List<String> expectedFromFile1,
309 List<String> expectedFromFile2)
310 throws Exception {
311 final TestInputConfiguration testInputConfiguration = InlineConfigParser.parse(filePath1);
312 final DefaultConfiguration parsedConfig = testInputConfiguration.createConfiguration();
313 final TestInputConfiguration testInputConfiguration2 = InlineConfigParser.parse(filePath2);
314 final DefaultConfiguration parsedConfig2 = testInputConfiguration.createConfiguration();
315 final File[] inputs = {new File(filePath1), new File(filePath2)};
316 verifyViolations(parsedConfig, filePath1, testInputConfiguration.getViolations());
317 verifyViolations(parsedConfig2, filePath2, testInputConfiguration2.getViolations());
318 verify(createChecker(parsedConfig), inputs, ImmutableMap.of(
319 filePath1, expectedFromFile1,
320 filePath2, expectedFromFile2));
321 }
322
323
324
325
326
327
328
329
330
331
332 protected void verifyWithInlineConfigParserTwice(String filePath, String... expected)
333 throws Exception {
334 final TestInputConfiguration testInputConfiguration =
335 InlineConfigParser.parse(filePath);
336 final DefaultConfiguration parsedConfig =
337 testInputConfiguration.createConfiguration();
338 verifyViolations(parsedConfig, filePath, testInputConfiguration.getViolations());
339 verify(parsedConfig, filePath, expected);
340 }
341
342
343
344
345
346
347
348
349
350
351
352
353 protected final void verify(Configuration config, String fileName, String... expected)
354 throws Exception {
355 verify(createChecker(config), fileName, fileName, expected);
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369
370 protected void verify(Checker checker, String fileName, String... expected)
371 throws Exception {
372 verify(checker, fileName, fileName, expected);
373 }
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388 protected final void verify(Checker checker,
389 String processedFilename,
390 String messageFileName,
391 String... expected)
392 throws Exception {
393 verify(checker,
394 new File[] {new File(processedFilename)},
395 messageFileName, expected);
396 }
397
398
399
400
401
402
403
404
405
406
407
408 protected void verify(Checker checker,
409 File[] processedFiles,
410 String messageFileName,
411 String... expected)
412 throws Exception {
413 final Map<String, List<String>> expectedViolations = new HashMap<>();
414 expectedViolations.put(messageFileName, Arrays.asList(expected));
415 verify(checker, processedFiles, expectedViolations);
416 }
417
418
419
420
421
422
423
424
425
426 protected final void verify(Checker checker,
427 File[] processedFiles,
428 Map<String, List<String>> expectedViolations)
429 throws Exception {
430 stream.flush();
431 stream.reset();
432 final List<File> theFiles = new ArrayList<>();
433 Collections.addAll(theFiles, processedFiles);
434 final int errs = checker.process(theFiles);
435
436
437 final Map<String, List<String>> actualViolations = getActualViolations(errs);
438 final Map<String, List<String>> realExpectedViolations =
439 Maps.filterValues(expectedViolations, input -> !input.isEmpty());
440
441 assertWithMessage("Files with expected violations and actual violations differ.")
442 .that(actualViolations.keySet())
443 .isEqualTo(realExpectedViolations.keySet());
444
445 realExpectedViolations.forEach((fileName, violationList) -> {
446 assertWithMessage("Violations for %s differ.", fileName)
447 .that(actualViolations.get(fileName))
448 .containsExactlyElementsIn(violationList);
449 });
450
451 checker.destroy();
452 }
453
454
455
456
457
458
459
460
461 protected final void verifyWithLimitedResources(String fileName, String... expected)
462 throws Exception {
463
464 final Void result = TestUtil.getResultWithLimitedResources(() -> {
465 verifyWithInlineConfigParser(fileName, expected);
466 return null;
467 });
468 assertWithMessage("Verify should complete successfully.")
469 .that(result)
470 .isNull();
471 }
472
473
474
475
476
477
478
479
480 protected final void execute(Configuration config, String... filenames) throws Exception {
481 final Checker checker = createChecker(config);
482 final List<File> files = Arrays.stream(filenames)
483 .map(File::new)
484 .collect(Collectors.toUnmodifiableList());
485 checker.process(files);
486 checker.destroy();
487 }
488
489
490
491
492
493
494
495
496 protected static void execute(Checker checker, String... filenames) throws Exception {
497 final List<File> files = Arrays.stream(filenames)
498 .map(File::new)
499 .collect(Collectors.toUnmodifiableList());
500 checker.process(files);
501 checker.destroy();
502 }
503
504
505
506
507
508
509
510
511
512 private void verifyViolations(Configuration config,
513 String file,
514 List<TestInputViolation> testInputViolations)
515 throws Exception {
516 final List<String> actualViolations = getActualViolationsForFile(config, file);
517 final List<Integer> actualViolationLines = actualViolations.stream()
518 .map(violation -> violation.substring(0, violation.indexOf(':')))
519 .map(Integer::valueOf)
520 .collect(Collectors.toUnmodifiableList());
521 final List<Integer> expectedViolationLines = testInputViolations.stream()
522 .map(TestInputViolation::getLineNo)
523 .collect(Collectors.toUnmodifiableList());
524 assertWithMessage("Violation lines for %s differ.", file)
525 .that(actualViolationLines)
526 .isEqualTo(expectedViolationLines);
527 for (int index = 0; index < actualViolations.size(); index++) {
528 assertWithMessage("Actual and expected violations differ.")
529 .that(actualViolations.get(index))
530 .matches(testInputViolations.get(index).toRegex());
531 }
532 }
533
534
535
536
537
538
539
540
541 private static void verifyViolations(String file,
542 List<TestInputViolation> testInputViolations,
543 List<String> actualViolations) {
544 final List<Integer> actualViolationLines = actualViolations.stream()
545 .map(violation -> violation.substring(0, violation.indexOf(':')))
546 .map(Integer::valueOf)
547 .collect(Collectors.toUnmodifiableList());
548 final List<Integer> expectedViolationLines = testInputViolations.stream()
549 .map(TestInputViolation::getLineNo)
550 .collect(Collectors.toUnmodifiableList());
551 assertWithMessage("Violation lines for %s differ.", file)
552 .that(actualViolationLines)
553 .isEqualTo(expectedViolationLines);
554 for (int index = 0; index < actualViolations.size(); index++) {
555 assertWithMessage("Actual and expected violations differ.")
556 .that(actualViolations.get(index))
557 .matches(testInputViolations.get(index).toRegex());
558 }
559 }
560
561
562
563
564
565
566
567
568
569 private List<String> getActualViolationsForFile(Configuration config,
570 String file) throws Exception {
571 stream.flush();
572 stream.reset();
573 final List<File> files = Collections.singletonList(new File(file));
574 final Checker checker = createChecker(config);
575 final Map<String, List<String>> actualViolations =
576 getActualViolations(checker.process(files));
577 checker.destroy();
578 return actualViolations.getOrDefault(file, new ArrayList<>());
579 }
580
581
582
583
584
585
586
587
588
589
590 private Map<String, List<String>> getActualViolations(int errorCount) throws IOException {
591
592 try (ByteArrayInputStream inputStream =
593 new ByteArrayInputStream(stream.toByteArray());
594 LineNumberReader lnr = new LineNumberReader(
595 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
596 final Map<String, List<String>> actualViolations = new HashMap<>();
597 for (String line = lnr.readLine(); line != null && lnr.getLineNumber() <= errorCount;
598 line = lnr.readLine()) {
599
600
601 final String[] actualViolation = line.split("(?<=.{2}):", 2);
602 final String actualViolationFileName = actualViolation[0];
603 final String actualViolationMessage = actualViolation[1];
604
605 actualViolations
606 .computeIfAbsent(actualViolationFileName, key -> new ArrayList<>())
607 .add(actualViolationMessage);
608 }
609
610 return actualViolations;
611 }
612 }
613
614
615
616
617
618
619
620
621
622 protected final String getCheckMessage(String messageKey, Object... arguments) {
623 return internalGetCheckMessage(getMessageBundle(), messageKey, arguments);
624 }
625
626
627
628
629
630
631
632
633
634
635 protected static String getCheckMessage(
636 Class<?> clazz, String messageKey, Object... arguments) {
637 return internalGetCheckMessage(getMessageBundle(clazz.getName()), messageKey, arguments);
638 }
639
640
641
642
643
644
645
646
647
648
649 private static String internalGetCheckMessage(
650 String messageBundle, String messageKey, Object... arguments) {
651 final ResourceBundle resourceBundle = ResourceBundle.getBundle(
652 messageBundle,
653 Locale.ROOT,
654 Thread.currentThread().getContextClassLoader(),
655 new Utf8Control());
656 final String pattern = resourceBundle.getString(messageKey);
657 final MessageFormat formatter = new MessageFormat(pattern, Locale.ROOT);
658 return formatter.format(arguments);
659 }
660
661
662
663
664
665
666 private String getMessageBundle() {
667 final String className = getClass().getName();
668 return getMessageBundle(className);
669 }
670
671
672
673
674
675
676
677 private static String getMessageBundle(String className) {
678 final String messageBundle;
679 final String messages = "messages";
680 final int endIndex = className.lastIndexOf('.');
681 final Map<String, String> messageBundleMappings = new HashMap<>();
682 messageBundleMappings.put("SeverityMatchFilterExamplesTest",
683 "com.puppycrawl.tools.checkstyle.checks.naming.messages");
684
685 if (endIndex < 0) {
686 messageBundle = messages;
687 }
688 else {
689 final String packageName = className.substring(0, endIndex);
690 if ("com.puppycrawl.tools.checkstyle.filters".equals(packageName)) {
691 messageBundle = messageBundleMappings.get(className.substring(endIndex + 1));
692 }
693 else {
694 messageBundle = packageName + "." + messages;
695 }
696 }
697 return messageBundle;
698 }
699
700
701
702
703
704
705
706
707 protected static String[] removeSuppressed(String[] actualViolations,
708 String... suppressedViolations) {
709 final List<String> actualViolationsList =
710 Arrays.stream(actualViolations).collect(Collectors.toCollection(ArrayList::new));
711 actualViolationsList.removeAll(Arrays.asList(suppressedViolations));
712 return actualViolationsList.toArray(CommonUtil.EMPTY_STRING_ARRAY);
713 }
714
715 }