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