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