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