View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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.nio.file.Path;
32  import java.text.MessageFormat;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Locale;
39  import java.util.Map;
40  import java.util.ResourceBundle;
41  import java.util.stream.Collectors;
42  
43  import com.google.common.collect.ImmutableMap;
44  import com.google.common.collect.Maps;
45  import com.puppycrawl.tools.checkstyle.LocalizedMessage.Utf8Control;
46  import com.puppycrawl.tools.checkstyle.api.AuditListener;
47  import com.puppycrawl.tools.checkstyle.api.Configuration;
48  import com.puppycrawl.tools.checkstyle.api.DetailAST;
49  import com.puppycrawl.tools.checkstyle.bdd.InlineConfigParser;
50  import com.puppycrawl.tools.checkstyle.bdd.TestInputConfiguration;
51  import com.puppycrawl.tools.checkstyle.bdd.TestInputViolation;
52  import com.puppycrawl.tools.checkstyle.internal.utils.BriefUtLogger;
53  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
54  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
55  import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
56  import com.puppycrawl.tools.checkstyle.xpath.RootNode;
57  
58  public abstract class AbstractModuleTestSupport extends AbstractPathTestSupport {
59  
60      protected static final String ROOT_MODULE_NAME = Checker.class.getSimpleName();
61  
62      private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
63  
64      /**
65       * Returns log stream.
66       *
67       * @return stream with log
68       */
69      protected final ByteArrayOutputStream getStream() {
70          return stream;
71      }
72  
73      /**
74       * Returns test logger.
75       *
76       * @return logger for tests
77       */
78      protected final BriefUtLogger getBriefUtLogger() {
79          return new BriefUtLogger(stream);
80      }
81  
82      /**
83       * Creates a default module configuration {@link DefaultConfiguration} for a given object
84       * of type {@link Class}.
85       *
86       * @param clazz a {@link Class} type object.
87       * @return default module configuration for the given {@link Class} instance.
88       */
89      protected static DefaultConfiguration createModuleConfig(Class<?> clazz) {
90          return new DefaultConfiguration(clazz.getName());
91      }
92  
93      /**
94       * Creates {@link Checker} instance based on the given {@link Configuration} instance.
95       *
96       * @param moduleConfig {@link Configuration} instance.
97       * @return {@link Checker} instance based on the given {@link Configuration} instance.
98       * @throws Exception if an exception occurs during checker configuration.
99       */
100     protected final Checker createChecker(Configuration moduleConfig)
101             throws Exception {
102         final String moduleName = moduleConfig.getName();
103         final Checker checker = new Checker();
104         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
105 
106         if (ROOT_MODULE_NAME.equals(moduleName)) {
107             checker.configure(moduleConfig);
108         }
109         else {
110             configureChecker(checker, moduleConfig);
111         }
112 
113         checker.addListener(getBriefUtLogger());
114         return checker;
115     }
116 
117     /**
118      * Configures the {@code checker} instance with {@code moduleConfig}.
119      *
120      * @param checker {@link Checker} instance.
121      * @param moduleConfig {@link Configuration} instance.
122      * @throws Exception if an exception occurs during configuration.
123      */
124     protected void configureChecker(Checker checker, Configuration moduleConfig) throws Exception {
125         final Class<?> moduleClass = Class.forName(moduleConfig.getName());
126 
127         if (ModuleReflectionUtil.isCheckstyleTreeWalkerCheck(moduleClass)
128                 || ModuleReflectionUtil.isTreeWalkerFilterModule(moduleClass)) {
129             final Configuration config = createTreeWalkerConfig(moduleConfig);
130             checker.configure(config);
131         }
132         else {
133             final Configuration config = createRootConfig(moduleConfig);
134             checker.configure(config);
135         }
136     }
137 
138     /**
139      * Creates {@link DefaultConfiguration} for the {@link TreeWalker}
140      * based on the given {@link Configuration} instance.
141      *
142      * @param config {@link Configuration} instance.
143      * @return {@link DefaultConfiguration} for the {@link TreeWalker}
144      *     based on the given {@link Configuration} instance.
145      */
146     protected static DefaultConfiguration createTreeWalkerConfig(Configuration config) {
147         final DefaultConfiguration rootConfig =
148                 new DefaultConfiguration(ROOT_MODULE_NAME);
149         final DefaultConfiguration twConf = createModuleConfig(TreeWalker.class);
150         // make sure that the tests always run with this charset
151         rootConfig.addProperty("charset", StandardCharsets.UTF_8.name());
152         rootConfig.addChild(twConf);
153         twConf.addChild(config);
154         return rootConfig;
155     }
156 
157     /**
158      * Creates {@link DefaultConfiguration} for the given {@link Configuration} instance.
159      *
160      * @param config {@link Configuration} instance.
161      * @return {@link DefaultConfiguration} for the given {@link Configuration} instance.
162      */
163     protected static DefaultConfiguration createRootConfig(Configuration config) {
164         final DefaultConfiguration rootConfig = new DefaultConfiguration(ROOT_MODULE_NAME);
165         if (config != null) {
166             rootConfig.addChild(config);
167         }
168         return rootConfig;
169     }
170 
171     /**
172      * Returns canonical path for the file with the given file name.
173      * The path is formed base on the non-compilable resources location.
174      *
175      * @param filename file name.
176      * @return canonical path for the file with the given file name.
177      * @throws IOException if I/O exception occurs while forming the path.
178      */
179     protected final String getNonCompilablePath(String filename) throws IOException {
180         return new File("src/" + getResourceLocation()
181                 + "/resources-noncompilable/" + getPackageLocation() + "/"
182                 + filename).getCanonicalPath();
183     }
184 
185     /**
186      * Creates a RootNode for non-compilable test files.
187      *
188      * @param fileName name of the test file
189      * @return RootNode for the parsed AST
190      * @throws Exception if file parsing fails
191      */
192     protected RootNode getRootNodeForNonCompilable(String fileName) throws Exception {
193         final File file = new File(getNonCompilablePath(fileName));
194         final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
195         return new RootNode(rootAst);
196     }
197 
198     /**
199      * Returns URI-representation of the path for the given file name.
200      * The path is formed base on the root location.
201      *
202      * @param filename file name.
203      * @return URI-representation of the path for the file with the given file name.
204      */
205     protected final String getUriString(String filename) {
206         return new File("src/test/resources/" + getPackageLocation() + "/" + filename).toURI()
207                 .toString();
208     }
209 
210     /**
211      * Performs verification of the file with the given file path using specified configuration
212      * and the array of expected messages. Also performs verification of the config with filters
213      * specified in the input file.
214      *
215      * @param filePath file path to verify.
216      * @param expectedUnfiltered an array of expected unfiltered config.
217      * @param expectedFiltered an array of expected config with filters.
218      * @throws Exception if exception occurs during verification process.
219      */
220     protected final void verifyFilterWithInlineConfigParser(String filePath,
221                                                             String[] expectedUnfiltered,
222                                                             String... expectedFiltered)
223             throws Exception {
224         final TestInputConfiguration testInputConfiguration =
225                 InlineConfigParser.parseWithFilteredViolations(filePath);
226         final DefaultConfiguration configWithoutFilters =
227                 testInputConfiguration.createConfigurationWithoutFilters();
228         final List<TestInputViolation> violationsWithoutFilters =
229                 new ArrayList<>(testInputConfiguration.getViolations());
230         violationsWithoutFilters.addAll(testInputConfiguration.getFilteredViolations());
231         Collections.sort(violationsWithoutFilters);
232         verifyViolations(configWithoutFilters, filePath, violationsWithoutFilters);
233         verify(configWithoutFilters, filePath, expectedUnfiltered);
234         final DefaultConfiguration configWithFilters =
235                 testInputConfiguration.createConfiguration();
236         verifyViolations(configWithFilters, filePath, testInputConfiguration.getViolations());
237         verify(configWithFilters, filePath, expectedFiltered);
238     }
239 
240     /**
241      * Performs verification of the file with given file path using configurations parsed from
242      * xml header of the file and the array expected messages. Also performs verification of
243      * the config specified in input file.
244      *
245      * @param filePath file path to verify
246      * @param expected an array of expected messages
247      * @throws Exception if exception occurs
248      */
249     protected final void verifyWithInlineXmlConfig(String filePath, String... expected)
250             throws Exception {
251         final TestInputConfiguration testInputConfiguration =
252                 InlineConfigParser.parseWithXmlHeader(filePath);
253         final Configuration xmlConfig =
254                 testInputConfiguration.getXmlConfiguration();
255         verifyViolations(xmlConfig, filePath, testInputConfiguration.getViolations());
256         verify(xmlConfig, filePath, expected);
257     }
258 
259     /**
260      * Performs verification of the file with the given file path using specified configuration
261      * and the array expected messages. Also performs verification of the config specified in
262      * input file.
263      *
264      * @param filePath file path to verify.
265      * @param expected an array of expected messages.
266      * @throws Exception if exception occurs during verification process.
267      */
268     protected final void verifyWithInlineConfigParser(String filePath, String... expected)
269             throws Exception {
270         final TestInputConfiguration testInputConfiguration =
271                 InlineConfigParser.parse(filePath);
272         final DefaultConfiguration parsedConfig =
273                 testInputConfiguration.createConfiguration();
274         final List<String> actualViolations = getActualViolationsForFile(parsedConfig, filePath);
275         verifyViolations(filePath, testInputConfiguration.getViolations(), actualViolations);
276         assertWithMessage("Violations for %s differ.", filePath)
277             .that(actualViolations)
278             .containsExactlyElementsIn(expected);
279     }
280 
281     /**
282      * Performs verification of two files with their given file paths using specified
283      * configuration of one file only. Also performs verification of the config specified
284      * in the input file. This method needs to be implemented when two given files need to be
285      * checked through a single check only.
286      *
287      * @param filePath1 file path of first file to verify
288      * @param filePath2 file path of second file to verify
289      * @param expected an array of expected messages
290      * @throws Exception if exception occurs during verification process
291      */
292     protected final void verifyWithInlineConfigParser(String filePath1,
293                                                       String filePath2,
294                                                       String... expected)
295             throws Exception {
296         final TestInputConfiguration testInputConfiguration1 =
297                 InlineConfigParser.parse(filePath1);
298         final DefaultConfiguration parsedConfig =
299                 testInputConfiguration1.createConfiguration();
300         final TestInputConfiguration testInputConfiguration2 =
301                 InlineConfigParser.parse(filePath2);
302         verifyViolations(parsedConfig, filePath1, testInputConfiguration1.getViolations());
303         verifyViolations(parsedConfig, filePath2, testInputConfiguration2.getViolations());
304         verify(createChecker(parsedConfig),
305                 new File[] {new File(filePath1), new File(filePath2)},
306                 filePath1,
307                 expected);
308     }
309 
310     /**
311      * Performs verification of two files with their given file paths.
312      * using specified configuration of one file only. Also performs
313      * verification of the config specified in the input file. This method
314      * needs to be implemented when two given files need to be
315      * checked through a single check only.
316      *
317      * @param filePath1 file path of first file to verify
318      * @param filePath2 file path of first file to verify
319      * @param expectedFromFile1 list of expected message
320      * @param expectedFromFile2 list of expected message
321      * @throws Exception if exception occurs during verification process
322      */
323     protected final void verifyWithInlineConfigParser(String filePath1,
324                                                       String filePath2,
325                                                       List<String> expectedFromFile1,
326                                                       List<String> expectedFromFile2)
327             throws Exception {
328         final TestInputConfiguration testInputConfiguration = InlineConfigParser.parse(filePath1);
329         final DefaultConfiguration parsedConfig = testInputConfiguration.createConfiguration();
330         final TestInputConfiguration testInputConfiguration2 = InlineConfigParser.parse(filePath2);
331         final DefaultConfiguration parsedConfig2 = testInputConfiguration.createConfiguration();
332         final File[] inputs = {new File(filePath1), new File(filePath2)};
333         verifyViolations(parsedConfig, filePath1, testInputConfiguration.getViolations());
334         verifyViolations(parsedConfig2, filePath2, testInputConfiguration2.getViolations());
335         verify(createChecker(parsedConfig), inputs, ImmutableMap.of(
336             filePath1, expectedFromFile1,
337             filePath2, expectedFromFile2));
338     }
339 
340     /**
341      * Verifies the target file against the configuration specified in a separate configuration
342      * file.
343      * This method is intended for use cases when the configuration is stored in one file and the
344      * content to verify is stored in another file.
345      *
346      * @param fileWithConfig file path of the configuration file
347      * @param targetFile file path of the target file to be verified
348      * @param expected an array of expected messages
349      * @throws Exception if an exception occurs during verification process
350      */
351     protected final void verifyWithInlineConfigParserSeparateConfigAndTarget(String fileWithConfig,
352                                                                              String targetFile,
353                                                                              String... expected)
354             throws Exception {
355         final TestInputConfiguration testInputConfiguration1 =
356                 InlineConfigParser.parse(fileWithConfig);
357         final DefaultConfiguration parsedConfig =
358                 testInputConfiguration1.createConfiguration();
359         final List<TestInputViolation> inputViolations =
360                 InlineConfigParser.getViolationsFromInputFile(targetFile);
361         final List<String> actualViolations = getActualViolationsForFile(parsedConfig, targetFile);
362         verifyViolations(targetFile, inputViolations, actualViolations);
363         assertWithMessage("Violations for %s differ.", targetFile)
364                 .that(actualViolations)
365                 .containsExactlyElementsIn(expected);
366     }
367 
368     /**
369      * Performs verification of the file with the given file path using specified configuration
370      * and the array expected messages. Also performs verification of the config specified in
371      * input file
372      *
373      * @param filePath file path to verify.
374      * @param expected an array of expected messages.
375      * @throws Exception if exception occurs during verification process.
376      */
377     protected void verifyWithInlineConfigParserTwice(String filePath, String... expected)
378             throws Exception {
379         final TestInputConfiguration testInputConfiguration =
380                 InlineConfigParser.parse(filePath);
381         final DefaultConfiguration parsedConfig =
382                 testInputConfiguration.createConfiguration();
383         verifyViolations(parsedConfig, filePath, testInputConfiguration.getViolations());
384         verify(parsedConfig, filePath, expected);
385     }
386 
387     /**
388      * Verifies logger output using the inline configuration parser.
389      * Expects an input file with configuration and violations, and a report file with expected
390      * output.
391      *
392      * @param inputFile path to the file with configuration and violations
393      * @param expectedReportFile path to the expected logger report file
394      * @param logger logger to test
395      * @param outputStream output stream where the logger writes its actual output
396      * @throws Exception if an exception occurs during verification
397      */
398     protected void verifyWithInlineConfigParserAndLogger(String inputFile,
399                                                          String expectedReportFile,
400                                                          AuditListener logger,
401                                                          ByteArrayOutputStream outputStream)
402             throws Exception {
403         final TestInputConfiguration testInputConfiguration =
404                 InlineConfigParser.parse(inputFile);
405         final DefaultConfiguration parsedConfig =
406                 testInputConfiguration.createConfiguration();
407         final List<File> filesToCheck = Collections.singletonList(new File(inputFile));
408         final String basePath = Path.of("").toAbsolutePath().toString();
409 
410         final Checker checker = createChecker(parsedConfig);
411         checker.setBasedir(basePath);
412         checker.addListener(logger);
413         checker.process(filesToCheck);
414 
415         verifyContent(expectedReportFile, outputStream);
416     }
417 
418     /**
419      * Performs verification of the file with the given file name. Uses specified configuration.
420      * Expected messages are represented by the array of strings.
421      * This implementation uses overloaded
422      * {@link AbstractModuleTestSupport#verify(Checker, File[], String, String...)} method inside.
423      *
424      * @param config configuration.
425      * @param fileName file name to verify.
426      * @param expected an array of expected messages.
427      * @throws Exception if exception occurs during verification process.
428      */
429     protected final void verify(Configuration config, String fileName, String... expected)
430             throws Exception {
431         verify(createChecker(config), fileName, fileName, expected);
432     }
433 
434     /**
435      * Performs verification of the file with the given file name.
436      * Uses provided {@link Checker} instance.
437      * Expected messages are represented by the array of strings.
438      * This implementation uses overloaded
439      * {@link AbstractModuleTestSupport#verify(Checker, String, String, String...)} method inside.
440      *
441      * @param checker {@link Checker} instance.
442      * @param fileName file name to verify.
443      * @param expected an array of expected messages.
444      * @throws Exception if exception occurs during verification process.
445      */
446     protected void verify(Checker checker, String fileName, String... expected)
447             throws Exception {
448         verify(checker, fileName, fileName, expected);
449     }
450 
451     /**
452      * Performs verification of the file with the given file name.
453      * Uses provided {@link Checker} instance.
454      * Expected messages are represented by the array of strings.
455      * This implementation uses overloaded
456      * {@link AbstractModuleTestSupport#verify(Checker, File[], String, String...)} method inside.
457      *
458      * @param checker {@link Checker} instance.
459      * @param processedFilename file name to verify.
460      * @param messageFileName message file name.
461      * @param expected an array of expected messages.
462      * @throws Exception if exception occurs during verification process.
463      */
464     protected final void verify(Checker checker,
465                           String processedFilename,
466                           String messageFileName,
467                           String... expected)
468             throws Exception {
469         verify(checker,
470                 new File[] {new File(processedFilename)},
471                 messageFileName, expected);
472     }
473 
474     /**
475      *  Performs verification of the given files against the array of
476      *  expected messages using the provided {@link Checker} instance.
477      *
478      *  @param checker {@link Checker} instance.
479      *  @param processedFiles list of files to verify.
480      *  @param messageFileName message file name.
481      *  @param expected an array of expected messages.
482      *  @throws Exception if exception occurs during verification process.
483      */
484     protected void verify(Checker checker,
485                           File[] processedFiles,
486                           String messageFileName,
487                           String... expected)
488             throws Exception {
489         final Map<String, List<String>> expectedViolations = new HashMap<>();
490         expectedViolations.put(messageFileName, Arrays.asList(expected));
491         verify(checker, processedFiles, expectedViolations);
492     }
493 
494     /**
495      * Performs verification of the given files.
496      *
497      * @param checker {@link Checker} instance
498      * @param processedFiles files to process.
499      * @param expectedViolations a map of expected violations per files.
500      * @throws Exception if exception occurs during verification process.
501      */
502     protected final void verify(Checker checker,
503                           File[] processedFiles,
504                           Map<String, List<String>> expectedViolations)
505             throws Exception {
506         stream.flush();
507         stream.reset();
508         final List<File> theFiles = new ArrayList<>();
509         Collections.addAll(theFiles, processedFiles);
510         final int errs = checker.process(theFiles);
511 
512         // process each of the lines
513         final Map<String, List<String>> actualViolations = getActualViolations(errs);
514         final Map<String, List<String>> realExpectedViolations =
515                 Maps.filterValues(expectedViolations, input -> !input.isEmpty());
516 
517         assertWithMessage("Files with expected violations and actual violations differ.")
518             .that(actualViolations.keySet())
519             .isEqualTo(realExpectedViolations.keySet());
520 
521         realExpectedViolations.forEach((fileName, violationList) -> {
522             assertWithMessage("Violations for %s differ.", fileName)
523                 .that(actualViolations.get(fileName))
524                 .containsExactlyElementsIn(violationList);
525         });
526 
527         checker.destroy();
528     }
529 
530     /**
531      * Runs 'verifyWithInlineConfigParser' with limited stack size and time duration.
532      *
533      * @param fileName file name to verify.
534      * @param expected an array of expected messages.
535      * @throws Exception if exception occurs during verification process.
536      */
537     protected final void verifyWithLimitedResources(String fileName, String... expected)
538             throws Exception {
539         // We return null here, which gives us a result to make an assertion about
540         final Void result = TestUtil.getResultWithLimitedResources(() -> {
541             verifyWithInlineConfigParser(fileName, expected);
542             return null;
543         });
544         assertWithMessage("Verify should complete successfully.")
545                 .that(result)
546                 .isNull();
547     }
548 
549     /**
550      * Executes given config on a list of files only. Does not verify violations.
551      *
552      * @param config check configuration
553      * @param filenames names of files to process
554      * @throws Exception if there is a problem during checker configuration
555      */
556     protected final void execute(Configuration config, String... filenames) throws Exception {
557         final Checker checker = createChecker(config);
558         final List<File> files = Arrays.stream(filenames)
559                 .map(File::new)
560                 .collect(Collectors.toUnmodifiableList());
561         checker.process(files);
562         checker.destroy();
563     }
564 
565     /**
566      * Executes given config on a list of files only. Does not verify violations.
567      *
568      * @param checker check configuration
569      * @param filenames names of files to process
570      * @throws Exception if there is a problem during checker configuration
571      */
572     protected static void execute(Checker checker, String... filenames) throws Exception {
573         final List<File> files = Arrays.stream(filenames)
574                 .map(File::new)
575                 .collect(Collectors.toUnmodifiableList());
576         checker.process(files);
577         checker.destroy();
578     }
579 
580     /**
581      * Performs verification of violation lines.
582      *
583      * @param config parsed config.
584      * @param file file path.
585      * @param testInputViolations List of TestInputViolation objects.
586      * @throws Exception if exception occurs during verification process.
587      */
588     private void verifyViolations(Configuration config,
589                                   String file,
590                                   List<TestInputViolation> testInputViolations)
591             throws Exception {
592         final List<String> actualViolations = getActualViolationsForFile(config, file);
593         final List<Integer> actualViolationLines = actualViolations.stream()
594                 .map(violation -> violation.substring(0, violation.indexOf(':')))
595                 .map(Integer::valueOf)
596                 .collect(Collectors.toUnmodifiableList());
597         final List<Integer> expectedViolationLines = testInputViolations.stream()
598                 .map(TestInputViolation::getLineNo)
599                 .collect(Collectors.toUnmodifiableList());
600         assertWithMessage("Violation lines for %s differ.", file)
601                 .that(actualViolationLines)
602                 .isEqualTo(expectedViolationLines);
603         for (int index = 0; index < actualViolations.size(); index++) {
604             assertWithMessage("Actual and expected violations differ.")
605                     .that(actualViolations.get(index))
606                     .matches(testInputViolations.get(index).toRegex());
607         }
608     }
609 
610     /**
611      * Performs verification of violation lines.
612      *
613      * @param file file path.
614      * @param testInputViolations List of TestInputViolation objects.
615      * @param actualViolations for a file
616      */
617     private static void verifyViolations(String file,
618                                   List<TestInputViolation> testInputViolations,
619                                   List<String> actualViolations) {
620         final List<Integer> actualViolationLines = actualViolations.stream()
621                 .map(violation -> violation.substring(0, violation.indexOf(':')))
622                 .map(Integer::valueOf)
623                 .collect(Collectors.toUnmodifiableList());
624         final List<Integer> expectedViolationLines = testInputViolations.stream()
625                 .map(TestInputViolation::getLineNo)
626                 .collect(Collectors.toUnmodifiableList());
627         assertWithMessage("Violation lines for %s differ.", file)
628                 .that(actualViolationLines)
629                 .isEqualTo(expectedViolationLines);
630         for (int index = 0; index < actualViolations.size(); index++) {
631             assertWithMessage("Actual and expected violations differ.")
632                     .that(actualViolations.get(index))
633                     .matches(testInputViolations.get(index).toRegex());
634         }
635     }
636 
637     /**
638      * Verifies that the logger's actual output matches the expected report file.
639      *
640      * @param expectedOutputFile path to the expected logger report file
641      * @param outputStream output stream containing the actual logger output
642      * @throws IOException if an exception occurs while reading the file
643      */
644     private static void verifyContent(
645             String expectedOutputFile,
646             ByteArrayOutputStream outputStream) throws IOException {
647         final String expectedContent = readFile(expectedOutputFile);
648         final String actualContent =
649                 toLfLineEnding(outputStream.toString(StandardCharsets.UTF_8));
650         assertWithMessage("Content should match")
651                 .that(actualContent)
652                 .isEqualTo(expectedContent);
653     }
654 
655     /**
656      * Tests the file with the check config.
657      *
658      * @param config check configuration.
659      * @param file input file path.
660      * @return list of actual violations.
661      * @throws Exception if exception occurs during verification process.
662      */
663     private List<String> getActualViolationsForFile(Configuration config,
664                                                     String file) throws Exception {
665         stream.flush();
666         stream.reset();
667         final List<File> files = Collections.singletonList(new File(file));
668         final Checker checker = createChecker(config);
669         final Map<String, List<String>> actualViolations =
670                 getActualViolations(checker.process(files));
671         checker.destroy();
672         return actualViolations.getOrDefault(file, new ArrayList<>());
673     }
674 
675     /**
676      * Returns the actual violations for each file that has been checked against {@link Checker}.
677      * Each file is mapped to their corresponding violation messages. Reads input stream for these
678      * messages using instance of {@link InputStreamReader}.
679      *
680      * @param errorCount count of errors after checking set of files against {@link Checker}.
681      * @return a {@link Map} object containing file names and the corresponding violation messages.
682      * @throws IOException exception can occur when reading input stream.
683      */
684     private Map<String, List<String>> getActualViolations(int errorCount) throws IOException {
685         // process each of the lines
686         try (ByteArrayInputStream inputStream =
687                 new ByteArrayInputStream(stream.toByteArray());
688             LineNumberReader lnr = new LineNumberReader(
689                 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
690             final Map<String, List<String>> actualViolations = new HashMap<>();
691             for (String line = lnr.readLine(); line != null && lnr.getLineNumber() <= errorCount;
692                  line = lnr.readLine()) {
693                 // have at least 2 characters before the splitting colon,
694                 // to not split after the drive letter on Windows
695                 final String[] actualViolation = line.split("(?<=.{2}):", 2);
696                 final String actualViolationFileName = actualViolation[0];
697                 final String actualViolationMessage = actualViolation[1];
698 
699                 actualViolations
700                         .computeIfAbsent(actualViolationFileName, key -> new ArrayList<>())
701                         .add(actualViolationMessage);
702             }
703 
704             return actualViolations;
705         }
706     }
707 
708     /**
709      * Gets the check message 'as is' from appropriate 'messages.properties'
710      * file.
711      *
712      * @param messageKey the key of message in 'messages.properties' file.
713      * @param arguments  the arguments of message in 'messages.properties' file.
714      * @return The message of the check with the arguments applied.
715      */
716     protected final String getCheckMessage(String messageKey, Object... arguments) {
717         return internalGetCheckMessage(getMessageBundle(), messageKey, arguments);
718     }
719 
720     /**
721      * Gets the check message 'as is' from appropriate 'messages.properties'
722      * file.
723      *
724      * @param clazz the related check class.
725      * @param messageKey the key of message in 'messages.properties' file.
726      * @param arguments the arguments of message in 'messages.properties' file.
727      * @return The message of the check with the arguments applied.
728      */
729     protected static String getCheckMessage(
730             Class<?> clazz, String messageKey, Object... arguments) {
731         return internalGetCheckMessage(getMessageBundle(clazz.getName()), messageKey, arguments);
732     }
733 
734     /**
735      * Gets the check message 'as is' from appropriate 'messages.properties'
736      * file.
737      *
738      * @param messageBundle the bundle name.
739      * @param messageKey the key of message in 'messages.properties' file.
740      * @param arguments the arguments of message in 'messages.properties' file.
741      * @return The message of the check with the arguments applied.
742      */
743     private static String internalGetCheckMessage(
744             String messageBundle, String messageKey, Object... arguments) {
745         final ResourceBundle resourceBundle = ResourceBundle.getBundle(
746                 messageBundle,
747                 Locale.ROOT,
748                 Thread.currentThread().getContextClassLoader(),
749                 new Utf8Control());
750         final String pattern = resourceBundle.getString(messageKey);
751         final MessageFormat formatter = new MessageFormat(pattern, Locale.ROOT);
752         return formatter.format(arguments);
753     }
754 
755     /**
756      * Returns message bundle for a class specified by its class name.
757      *
758      * @return a string of message bundles for the class using class name.
759      */
760     private String getMessageBundle() {
761         final String className = getClass().getName();
762         return getMessageBundle(className);
763     }
764 
765     /**
766      * Returns message bundles for a class by providing class name.
767      *
768      * @param className name of the class.
769      * @return message bundles containing package name.
770      */
771     private static String getMessageBundle(String className) {
772         final String messageBundle;
773         final String messages = "messages";
774         final int endIndex = className.lastIndexOf('.');
775         final Map<String, String> messageBundleMappings = new HashMap<>();
776         messageBundleMappings.put("SeverityMatchFilterExamplesTest",
777                 "com.puppycrawl.tools.checkstyle.checks.naming.messages");
778 
779         if (endIndex < 0) {
780             messageBundle = messages;
781         }
782         else {
783             final String packageName = className.substring(0, endIndex);
784             if ("com.puppycrawl.tools.checkstyle.filters".equals(packageName)) {
785                 messageBundle = messageBundleMappings.get(className.substring(endIndex + 1));
786             }
787             else {
788                 messageBundle = packageName + "." + messages;
789             }
790         }
791         return messageBundle;
792     }
793 
794     /**
795      * Remove suppressed violation messages from actual violation messages.
796      *
797      * @param actualViolations actual violation messages
798      * @param suppressedViolations suppressed violation messages
799      * @return an array of actual violation messages minus suppressed violation messages
800      */
801     protected static String[] removeSuppressed(String[] actualViolations,
802                                                String... suppressedViolations) {
803         final List<String> actualViolationsList =
804             Arrays.stream(actualViolations).collect(Collectors.toCollection(ArrayList::new));
805         actualViolationsList.removeAll(Arrays.asList(suppressedViolations));
806         return actualViolationsList.toArray(CommonUtil.EMPTY_STRING_ARRAY);
807     }
808 
809 }