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