1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.checkstyle.base;
21
22 import static com.google.common.truth.Truth.assertWithMessage;
23
24 import java.io.BufferedReader;
25 import java.io.ByteArrayInputStream;
26 import java.io.ByteArrayOutputStream;
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.io.LineNumberReader;
31 import java.nio.charset.StandardCharsets;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.text.MessageFormat;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.Properties;
43 import java.util.regex.Pattern;
44
45 import com.puppycrawl.tools.checkstyle.AbstractPathTestSupport;
46 import com.puppycrawl.tools.checkstyle.Checker;
47 import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
48 import com.puppycrawl.tools.checkstyle.TreeWalker;
49 import com.puppycrawl.tools.checkstyle.api.AbstractViolationReporter;
50 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
51 import com.puppycrawl.tools.checkstyle.api.Configuration;
52 import com.puppycrawl.tools.checkstyle.bdd.InlineConfigParser;
53 import com.puppycrawl.tools.checkstyle.bdd.TestInputViolation;
54 import com.puppycrawl.tools.checkstyle.internal.utils.BriefUtLogger;
55 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
56
57 public abstract class AbstractItModuleTestSupport extends AbstractPathTestSupport {
58
59
60
61
62 public enum ModuleCreationOption {
63
64
65
66
67
68 IN_TREEWALKER,
69
70
71
72
73 IN_CHECKER,
74
75 }
76
77 protected static final String ROOT_MODULE_NAME = "root";
78
79 private static final Pattern WARN_PATTERN = CommonUtil
80 .createPattern(".* *// *warn *|/[*]\\*?\\s?warn\\s?[*]/");
81
82 private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
83
84
85
86
87
88
89
90 protected abstract ModuleCreationOption findModuleCreationOption(String moduleName);
91
92
93
94
95
96
97 protected final BriefUtLogger getBriefUtLogger() {
98 return new BriefUtLogger(stream);
99 }
100
101
102
103
104
105
106
107
108 protected static DefaultConfiguration createModuleConfig(Class<?> clazz) {
109 return new DefaultConfiguration(clazz.getName());
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123 protected static Configuration getModuleConfig(Configuration masterConfig, String moduleName,
124 String moduleId) {
125 final Configuration result;
126 final List<Configuration> configs = getModuleConfigs(masterConfig, moduleName);
127 if (configs.size() == 1) {
128 result = configs.getFirst();
129 }
130 else if (configs.isEmpty()) {
131 throw new IllegalStateException("no instances of the Module was found: " + moduleName);
132 }
133 else if (moduleId == null) {
134 throw new IllegalStateException("multiple instances of the same Module are detected");
135 }
136 else {
137 result = configs.stream().filter(conf -> isSameModuleId(conf, moduleId))
138 .findFirst()
139 .orElseThrow(() -> new IllegalStateException("problem with module config"));
140 }
141
142 return result;
143 }
144
145
146
147
148
149
150
151
152
153 private static boolean isSameModuleId(Configuration conf, String moduleId) {
154 try {
155 return conf.getProperty("id").equals(moduleId);
156 }
157 catch (CheckstyleException exc) {
158 throw new IllegalStateException("problem to get ID attribute from " + conf, exc);
159 }
160 }
161
162
163
164
165
166
167
168
169
170
171 protected static List<Configuration> getModuleConfigsByIds(Configuration masterConfig,
172 String... moduleIds)
173 throws CheckstyleException {
174 final List<Configuration> result = new ArrayList<>();
175 for (Configuration currentConfig : masterConfig.getChildren()) {
176 if ("TreeWalker".equals(currentConfig.getName())) {
177 for (Configuration moduleConfig : currentConfig.getChildren()) {
178 final String id = getProperty(moduleConfig, "id");
179 if (id != null && isIn(id, moduleIds)) {
180 result.add(moduleConfig);
181 }
182 }
183 }
184 else {
185 final String id = getProperty(currentConfig, "id");
186 if (id != null && isIn(id, moduleIds)) {
187 result.add(currentConfig);
188 }
189 }
190 }
191 return result;
192 }
193
194
195
196
197
198
199
200
201
202 private static String getProperty(Configuration config, String name)
203 throws CheckstyleException {
204 String result = null;
205
206 if (isIn(name, config.getPropertyNames())) {
207 result = config.getProperty(name);
208 }
209
210 return result;
211 }
212
213
214
215
216
217
218
219
220 private static boolean isIn(String find, String... list) {
221 boolean found = false;
222
223 for (String item : list) {
224 if (find.equals(item)) {
225 found = true;
226 break;
227 }
228 }
229
230 return found;
231 }
232
233
234
235
236
237
238
239
240
241 private static List<Configuration> getModuleConfigs(Configuration masterConfig,
242 String moduleName) {
243 final List<Configuration> result = new ArrayList<>();
244 for (Configuration currentConfig : masterConfig.getChildren()) {
245 if ("TreeWalker".equals(currentConfig.getName())) {
246 for (Configuration moduleConfig : currentConfig.getChildren()) {
247 if (moduleName.equals(moduleConfig.getName())) {
248 result.add(moduleConfig);
249 }
250 }
251 }
252 else if (moduleName.equals(currentConfig.getName())) {
253 result.add(currentConfig);
254 }
255 }
256 return result;
257 }
258
259
260
261
262
263
264
265
266 protected final Checker createChecker(Configuration moduleConfig)
267 throws Exception {
268 final String name = moduleConfig.getName();
269
270 return createChecker(moduleConfig, findModuleCreationOption(name));
271 }
272
273
274
275
276
277
278
279
280
281
282 protected final Checker createChecker(Configuration moduleConfig,
283 ModuleCreationOption moduleCreationOption)
284 throws Exception {
285 final Checker checker = new Checker();
286 checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
287
288
289 final Locale locale = Locale.ENGLISH;
290 checker.setLocaleCountry(locale.getCountry());
291 checker.setLocaleLanguage(locale.getLanguage());
292
293 if (moduleCreationOption == ModuleCreationOption.IN_TREEWALKER) {
294 final Configuration config = createTreeWalkerConfig(moduleConfig);
295 checker.configure(config);
296 }
297 else if (ROOT_MODULE_NAME.equals(moduleConfig.getName())
298 || "Checker".equals(moduleConfig.getName())) {
299 checker.configure(moduleConfig);
300 }
301 else {
302 final Configuration config = createRootConfig(moduleConfig);
303 checker.configure(config);
304 }
305 checker.addListener(getBriefUtLogger());
306 return checker;
307 }
308
309
310
311
312
313
314
315
316
317 protected static DefaultConfiguration createTreeWalkerConfig(Configuration config) {
318 final DefaultConfiguration rootConfig =
319 new DefaultConfiguration(ROOT_MODULE_NAME);
320 final DefaultConfiguration twConf = createModuleConfig(TreeWalker.class);
321
322 rootConfig.addProperty("charset", StandardCharsets.UTF_8.name());
323 rootConfig.addChild(twConf);
324 twConf.addChild(config);
325 return rootConfig;
326 }
327
328
329
330
331
332
333
334
335 protected static DefaultConfiguration createTreeWalkerConfig(
336 List<Configuration> configs) {
337 DefaultConfiguration result = null;
338
339 for (Configuration config : configs) {
340 if (result == null) {
341 result = (DefaultConfiguration) createTreeWalkerConfig(config).getChildren()[0];
342 }
343 else {
344 result.addChild(config);
345 }
346 }
347
348 return result;
349 }
350
351
352
353
354
355
356
357 protected static DefaultConfiguration createRootConfig(Configuration config) {
358 final DefaultConfiguration rootConfig = new DefaultConfiguration(ROOT_MODULE_NAME);
359 rootConfig.addChild(config);
360 return rootConfig;
361 }
362
363
364
365
366
367
368
369
370
371 protected final String getNonCompilablePath(String filename) throws IOException {
372 return new File("src/" + getResourceLocation() + "/resources-noncompilable/"
373 + getPackageLocation() + "/" + filename).getCanonicalPath();
374 }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390 protected final void verify(Configuration config, String fileName, String[] expected,
391 Integer... warnsExpected)
392 throws Exception {
393 verify(createChecker(config),
394 new File[] {new File(fileName)},
395 fileName, expected, warnsExpected);
396 }
397
398
399
400
401
402
403
404
405
406
407
408
409 protected final void verify(Checker checker,
410 File[] processedFiles,
411 String messageFileName,
412 String[] expected,
413 Integer... warnsExpected)
414 throws Exception {
415 stream.flush();
416 stream.reset();
417 final List<File> theFiles = new ArrayList<>();
418 Collections.addAll(theFiles, processedFiles);
419 final List<Integer> expectedWarnings = Arrays.asList(warnsExpected);
420 final List<Integer> actualWarnings = new ArrayList<>();
421 final int errs = checker.process(theFiles);
422
423
424 try (ByteArrayInputStream inputStream =
425 new ByteArrayInputStream(stream.toByteArray());
426 LineNumberReader lnr = new LineNumberReader(
427 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
428 Integer previousLineNumber = 0;
429 for (int index = 0; index < expected.length; index++) {
430 final String expectedResult = messageFileName + ":" + expected[index];
431 final String actual = lnr.readLine();
432 assertWithMessage("Error message at position %s of 'expected' does "
433 + "not match actual message", index)
434 .that(actual)
435 .isEqualTo(expectedResult);
436
437 String parseInt = removeDeviceFromPathOnWindows(actual);
438 parseInt = parseInt.substring(parseInt.indexOf(':') + 1);
439 parseInt = parseInt.substring(0, parseInt.indexOf(':'));
440 final Integer lineNumber = Integer.parseInt(parseInt);
441 if (!previousLineNumber.equals(lineNumber)) {
442 assertWithMessage(
443 "input file is expected to have a warning comment on line number %s",
444 lineNumber)
445 .that(expectedWarnings.contains(lineNumber))
446 .isTrue();
447
448 actualWarnings.add(lineNumber);
449 }
450 previousLineNumber = lineNumber;
451 }
452
453 assertWithMessage("unexpected output: %s", lnr.readLine())
454 .that(errs)
455 .isEqualTo(expected.length);
456 assertWithMessage("warning line numbers should match expected")
457 .that(actualWarnings)
458 .containsExactlyElementsIn(expectedWarnings)
459 .inOrder();
460 }
461
462 checker.destroy();
463 }
464
465
466
467
468
469
470
471
472 protected void verifyWithItConfig(Configuration config, String filePath) throws Exception {
473 final List<TestInputViolation> violations =
474 InlineConfigParser.getViolationsFromInputFile(filePath);
475 final List<String> actualViolations = getActualViolationsForFile(config, filePath);
476
477 verifyViolations(filePath, violations, actualViolations);
478 }
479
480
481
482
483
484
485
486
487
488 private List<String> getActualViolationsForFile(Configuration config,
489 String file)
490 throws Exception {
491 stream.flush();
492 stream.reset();
493 final List<File> files = Collections.singletonList(new File(file));
494 final Checker checker = createChecker(config);
495 final Map<String, List<String>> actualViolations =
496 getActualViolations(checker.process(files));
497 checker.destroy();
498 return actualViolations.getOrDefault(file, new ArrayList<>());
499 }
500
501
502
503
504
505
506
507
508
509
510 private Map<String, List<String>> getActualViolations(int errorCount) throws IOException {
511
512 try (ByteArrayInputStream inputStream =
513 new ByteArrayInputStream(stream.toByteArray());
514 LineNumberReader lnr = new LineNumberReader(
515 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
516 final Map<String, List<String>> actualViolations = new HashMap<>();
517 for (String line = lnr.readLine(); line != null && lnr.getLineNumber() <= errorCount;
518 line = lnr.readLine()) {
519
520
521 final String[] actualViolation = line.split("(?<=.{2}):", 2);
522 final String actualViolationFileName = actualViolation[0];
523 final String actualViolationMessage = actualViolation[1];
524
525 actualViolations
526 .computeIfAbsent(actualViolationFileName, key -> new ArrayList<>())
527 .add(actualViolationMessage);
528 }
529
530 return actualViolations;
531 }
532 }
533
534
535
536
537
538
539
540
541 private static void verifyViolations(String file, List<TestInputViolation> testInputViolations,
542 List<String> actualViolations) {
543 final List<Integer> actualViolationLines = actualViolations.stream()
544 .map(violation -> violation.substring(0, violation.indexOf(':')))
545 .map(Integer::valueOf)
546 .toList();
547 final List<Integer> expectedViolationLines = testInputViolations.stream()
548 .map(TestInputViolation::getLineNo)
549 .toList();
550 assertWithMessage("Violation lines for %s differ.", file)
551 .that(actualViolationLines)
552 .isEqualTo(expectedViolationLines);
553 for (int index = 0; index < actualViolations.size(); index++) {
554 assertWithMessage("Actual and expected violations differ.")
555 .that(actualViolations.get(index))
556 .matches(testInputViolations.get(index).toRegex());
557 }
558 }
559
560
561
562
563
564
565
566
567
568
569
570 protected static String getCheckMessage(
571 Class<? extends AbstractViolationReporter> reporterClass, String messageKey,
572 Object... arguments)
573 throws IOException {
574 final Properties pr = new Properties();
575 pr.load(reporterClass.getResourceAsStream("messages.properties"));
576 final MessageFormat formatter = new MessageFormat(pr.getProperty(messageKey),
577 Locale.ROOT);
578 return formatter.format(arguments);
579 }
580
581
582
583
584
585
586
587
588
589 protected static String getCheckMessage(Map<String, String> messages, String messageKey,
590 Object... arguments) {
591 String checkMessage = null;
592 for (Map.Entry<String, String> entry : messages.entrySet()) {
593 if (messageKey.equals(entry.getKey())) {
594 final MessageFormat formatter = new MessageFormat(entry.getValue(), Locale.ROOT);
595 checkMessage = formatter.format(arguments);
596 break;
597 }
598 }
599 return checkMessage;
600 }
601
602
603
604
605
606
607
608 private static String removeDeviceFromPathOnWindows(String path) {
609 String fixedPath = path;
610 final String os = System.getProperty("os.name", "Unix");
611 if (os.startsWith("Windows")) {
612 fixedPath = path.substring(path.indexOf(':') + 1);
613 }
614 return fixedPath;
615 }
616
617
618
619
620
621
622
623
624
625 protected Integer[] getLinesWithWarn(String fileName) throws IOException {
626 final List<Integer> result = new ArrayList<>();
627 try (BufferedReader br = Files.newBufferedReader(Path.of(fileName))) {
628 int lineNumber = 1;
629 while (true) {
630 final String line = br.readLine();
631 if (line == null) {
632 break;
633 }
634 if (WARN_PATTERN.matcher(line).find()) {
635 result.add(lineNumber);
636 }
637 lineNumber++;
638 }
639 }
640 return result.toArray(new Integer[0]);
641 }
642
643 @Override
644 protected String getResourceLocation() {
645 return "it";
646 }
647
648 }