View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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.api.AuditListener;
46  import com.puppycrawl.tools.checkstyle.api.Configuration;
47  import com.puppycrawl.tools.checkstyle.api.DetailAST;
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  import com.puppycrawl.tools.checkstyle.xpath.RootNode;
56  
57  public abstract class AbstractModuleTestSupport extends AbstractPathTestSupport {
58  
59      protected static final String ROOT_MODULE_NAME = Checker.class.getSimpleName();
60  
61      private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
62  
63      /**
64       * Returns log stream.
65       *
66       * @return stream with log
67       */
68      protected final ByteArrayOutputStream getStream() {
69          return stream;
70      }
71  
72      /**
73       * Returns test logger.
74       *
75       * @return logger for tests
76       */
77      protected final DefaultLogger getBriefUtLogger() {
78          return new BriefUtLogger(stream);
79      }
80  
81      /**
82       * Creates a default module configuration {@link DefaultConfiguration} for a given object
83       * of type {@link Class}.
84       *
85       * @param clazz a {@code Class} type object.
86       * @return default module configuration for the given {@code Class} instance.
87       */
88      protected static DefaultConfiguration createModuleConfig(Class<?> clazz) {
89          return new DefaultConfiguration(clazz.getName());
90      }
91  
92      /**
93       * Creates {@link Checker} instance based on the given {@link Configuration} instance.
94       *
95       * @param moduleConfig {@code Configuration} instance.
96       * @return {@code Checker} instance based on the given {@code Configuration} instance.
97       * @throws Exception if an exception occurs during checker configuration.
98       */
99      protected final Checker createChecker(Configuration moduleConfig)
100             throws Exception {
101         final String moduleName = moduleConfig.getName();
102         final Checker checker = new Checker();
103         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
104 
105         if (ROOT_MODULE_NAME.equals(moduleName)) {
106             checker.configure(moduleConfig);
107         }
108         else {
109             configureChecker(checker, moduleConfig);
110         }
111 
112         checker.addListener(getBriefUtLogger());
113         return checker;
114     }
115 
116     /**
117      * Configures the {@code checker} instance with {@code moduleConfig}.
118      *
119      * @param checker {@link Checker} instance.
120      * @param moduleConfig {@link Configuration} instance.
121      * @throws Exception if an exception occurs during configuration.
122      */
123     protected void configureChecker(Checker checker, Configuration moduleConfig) throws Exception {
124         final Class<?> moduleClass = Class.forName(moduleConfig.getName());
125 
126         final Configuration config;
127         if (ModuleReflectionUtil.isCheckstyleTreeWalkerCheck(moduleClass)
128                 || ModuleReflectionUtil.isTreeWalkerFilterModule(moduleClass)) {
129             config = createTreeWalkerConfig(moduleConfig);
130         }
131         else {
132             config = createRootConfig(moduleConfig);
133         }
134         checker.configure(config);
135     }
136 
137     /**
138      * Creates {@link DefaultConfiguration} for the {@link TreeWalker}
139      * based on the given {@link Configuration} instance.
140      *
141      * @param config {@code Configuration} instance.
142      * @return {@code DefaultConfiguration} for the {@code TreeWalker}
143      *     based on the given {@code Configuration} instance.
144      */
145     protected static DefaultConfiguration createTreeWalkerConfig(Configuration config) {
146         final DefaultConfiguration rootConfig =
147                 new DefaultConfiguration(ROOT_MODULE_NAME);
148         final DefaultConfiguration twConf = createModuleConfig(TreeWalker.class);
149         // make sure that the tests always run with this charset
150         rootConfig.addProperty("charset", StandardCharsets.UTF_8.name());
151         rootConfig.addChild(twConf);
152         twConf.addChild(config);
153         return rootConfig;
154     }
155 
156     /**
157      * Creates {@link DefaultConfiguration} for the given {@link Configuration} instance.
158      *
159      * @param config {@code Configuration} instance.
160      * @return {@code DefaultConfiguration} for the given {@code Configuration} instance.
161      */
162     protected static DefaultConfiguration createRootConfig(Configuration config) {
163         final DefaultConfiguration rootConfig = new DefaultConfiguration(ROOT_MODULE_NAME);
164         if (config != null) {
165             rootConfig.addChild(config);
166         }
167         return rootConfig;
168     }
169 
170     /**
171      * Returns canonical path for the file with the given file name.
172      * The path is formed base on the non-compilable resources location.
173      *
174      * @param filename file name.
175      * @return canonical path for the file with the given file name.
176      * @throws IOException if I/O exception occurs while forming the path.
177      */
178     protected final String getNonCompilablePath(String filename) throws IOException {
179         return new File("src/" + getResourceLocation()
180                 + "/resources-noncompilable/" + getPackageLocation() + "/"
181                 + filename).getCanonicalPath();
182     }
183 
184     /**
185      * Returns canonical path for the Javadoc file that intentionally contains errors.
186      *
187      * @param filename file name.
188      * @return canonical path for the file with Javadoc errors.
189      * @throws IOException if I/O exception occurs while forming the path.
190      */
191     protected final String getJavadocWithErrorPath(String filename) throws IOException {
192         return new File("src/" + getResourceLocation()
193                 + "/resources-with-javadoc-error/" + getPackageLocation() + "/"
194                 + filename).getCanonicalPath();
195     }
196 
197     /**
198      * Creates a RootNode for non-compilable test files.
199      *
200      * @param fileName name of the test file
201      * @return RootNode for the parsed AST
202      * @throws Exception if file parsing fails
203      */
204     protected RootNode getRootNodeForNonCompilable(String fileName) throws Exception {
205         final File file = new File(getNonCompilablePath(fileName));
206         final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
207         return new RootNode(rootAst);
208     }
209 
210     /**
211      * Returns URI-representation of the path for the given file name.
212      * The path is formed base on the root location.
213      *
214      * @param filename file name.
215      * @return URI-representation of the path for the file with the given file name.
216      */
217     protected final String getUriString(String filename) {
218         return new File("src/test/resources/" + getPackageLocation() + "/" + filename).toURI()
219                 .toString();
220     }
221 
222     /**
223      * Performs verification of the file with the given file path using specified configuration
224      * and the array of expected messages. Also performs verification of the config with filters
225      * specified in the input file.
226      *
227      * @param filePath file path to verify.
228      * @param expectedUnfiltered an array of expected unfiltered config.
229      * @param expectedFiltered an array of expected config with filters.
230      * @throws Exception if exception occurs during verification process.
231      */
232     protected final void verifyFilterWithInlineConfigParser(String filePath,
233                                                             String[] expectedUnfiltered,
234                                                             String... expectedFiltered)
235             throws Exception {
236         final TestInputConfiguration testInputConfiguration =
237                 InlineConfigParser.parseWithFilteredViolations(filePath);
238         final DefaultConfiguration configWithoutFilters =
239                 testInputConfiguration.createConfigurationWithoutFilters();
240         final List<TestInputViolation> violationsWithoutFilters =
241                 new ArrayList<>(testInputConfiguration.violations());
242         violationsWithoutFilters.addAll(testInputConfiguration.filteredViolations());
243         Collections.sort(violationsWithoutFilters);
244         verifyViolations(configWithoutFilters, filePath, violationsWithoutFilters);
245         verify(configWithoutFilters, filePath, expectedUnfiltered);
246         final DefaultConfiguration configWithFilters =
247                 testInputConfiguration.createConfiguration();
248         verifyViolations(configWithFilters, filePath, testInputConfiguration.violations());
249         verify(configWithFilters, filePath, expectedFiltered);
250     }
251 
252     /**
253      * Performs verification of the file with given file path using configurations parsed from
254      * xml header of the file and the array expected messages. Also performs verification of
255      * the config specified in input file.
256      *
257      * @param filePath file path to verify
258      * @param expected an array of expected messages
259      * @throws Exception if exception occurs
260      */
261     protected final void verifyWithInlineXmlConfig(String filePath, String... expected)
262             throws Exception {
263         final TestInputConfiguration testInputConfiguration =
264                 InlineConfigParser.parseWithXmlHeader(filePath);
265         final Configuration xmlConfig =
266                 testInputConfiguration.xmlConfiguration();
267         verifyViolations(xmlConfig, filePath, testInputConfiguration.violations());
268         verify(xmlConfig, filePath, expected);
269     }
270 
271     /**
272      * Performs verification of the file with the given file path using configuration,
273      * loaded from an external XML resource and the array of expected messages.
274      *
275      * @param configPath path to the XML configuration resource.
276      * @param filePath file path to verify.
277      * @param expected an array of expected messages.
278      * @throws Exception if exception occurs during verification process.
279      */
280     protected void verifyWithExternalXmlConfig(
281             String configPath,
282             String filePath,
283             String... expected)
284                     throws Exception {
285         final Configuration config =
286                 ConfigurationLoader.loadConfiguration(
287                         configPath,
288                         new PropertiesExpander(System.getProperties()),
289                         ConfigurationLoader.IgnoredModulesOptions.EXECUTE);
290         verify(config, filePath, expected);
291     }
292 
293     /**
294      * Performs verification of the file with the given file path using specified configuration
295      * and the array expected messages. Also performs verification of the config specified in
296      * input file.
297      *
298      * @param filePath file path to verify.
299      * @param expected an array of expected messages.
300      * @throws Exception if exception occurs during verification process.
301      */
302     protected final void verifyWithInlineConfigParser(String filePath, String... expected)
303             throws Exception {
304         final TestInputConfiguration testInputConfiguration =
305                 InlineConfigParser.parse(filePath);
306         final DefaultConfiguration parsedConfig =
307                 testInputConfiguration.createConfiguration();
308         final List<String> actualViolations = getActualViolationsForFile(parsedConfig, filePath);
309         verifyViolations(filePath, testInputConfiguration.violations(), actualViolations);
310         assertWithMessage("Violations for %s differ.", filePath)
311             .that(actualViolations)
312             .containsExactlyElementsIn(expected);
313     }
314 
315     /**
316      * Performs verification of two files with their given file paths using specified
317      * configuration of one file only. Also performs verification of the config specified
318      * in the input file. This method needs to be implemented when two given files need to be
319      * checked through a single check only.
320      *
321      * @param filePath1 file path of first file to verify
322      * @param filePath2 file path of second file to verify
323      * @param expected an array of expected messages
324      * @throws Exception if exception occurs during verification process
325      */
326     protected final void verifyWithInlineConfigParser(String filePath1,
327                                                       String filePath2,
328                                                       String... expected)
329             throws Exception {
330         final TestInputConfiguration testInputConfiguration1 =
331                 InlineConfigParser.parse(filePath1);
332         final DefaultConfiguration parsedConfig =
333                 testInputConfiguration1.createConfiguration();
334         final TestInputConfiguration testInputConfiguration2 =
335                 InlineConfigParser.parse(filePath2);
336         verifyViolations(parsedConfig, filePath1, testInputConfiguration1.violations());
337         verifyViolations(parsedConfig, filePath2, testInputConfiguration2.violations());
338         verify(createChecker(parsedConfig),
339                 new File[] {new File(filePath1), new File(filePath2)},
340                 filePath1,
341                 expected);
342     }
343 
344     /**
345      * Performs verification of two files with their given file paths.
346      * using specified configuration of one file only. Also performs
347      * verification of the config specified in the input file. This method
348      * needs to be implemented when two given files need to be
349      * checked through a single check only.
350      *
351      * @param filePath1 file path of first file to verify
352      * @param filePath2 file path of first file to verify
353      * @param expectedFromFile1 list of expected message
354      * @param expectedFromFile2 list of expected message
355      * @throws Exception if exception occurs during verification process
356      */
357     protected final void verifyWithInlineConfigParser(String filePath1,
358                                                       String filePath2,
359                                                       List<String> expectedFromFile1,
360                                                       List<String> expectedFromFile2)
361             throws Exception {
362         final TestInputConfiguration testInputConfiguration = InlineConfigParser.parse(filePath1);
363         final DefaultConfiguration parsedConfig = testInputConfiguration.createConfiguration();
364         final TestInputConfiguration testInputConfiguration2 = InlineConfigParser.parse(filePath2);
365         final DefaultConfiguration parsedConfig2 = testInputConfiguration.createConfiguration();
366         final File[] inputs = {new File(filePath1), new File(filePath2)};
367         verifyViolations(parsedConfig, filePath1, testInputConfiguration.violations());
368         verifyViolations(parsedConfig2, filePath2, testInputConfiguration2.violations());
369         verify(createChecker(parsedConfig), inputs, ImmutableMap.of(
370             filePath1, expectedFromFile1,
371             filePath2, expectedFromFile2));
372     }
373 
374     /**
375      * Verifies the target file against the configuration specified in a separate configuration
376      * file.
377      * This method is intended for use cases when the configuration is stored in one file and the
378      * content to verify is stored in another file.
379      *
380      * @param fileWithConfig file path of the configuration file
381      * @param targetFile file path of the target file to be verified
382      * @param expected an array of expected messages
383      * @throws Exception if an exception occurs during verification process
384      */
385     protected final void verifyWithInlineConfigParserSeparateConfigAndTarget(String fileWithConfig,
386                                                                              String targetFile,
387                                                                              String... expected)
388             throws Exception {
389         final TestInputConfiguration testInputConfiguration1 =
390                 InlineConfigParser.parse(fileWithConfig);
391         final DefaultConfiguration parsedConfig =
392                 testInputConfiguration1.createConfiguration();
393         final List<TestInputViolation> inputViolations =
394                 InlineConfigParser.getViolationsFromInputFile(targetFile);
395         final List<String> actualViolations = getActualViolationsForFile(parsedConfig, targetFile);
396         verifyViolations(targetFile, inputViolations, actualViolations);
397         assertWithMessage("Violations for %s differ.", targetFile)
398                 .that(actualViolations)
399                 .containsExactlyElementsIn(expected);
400     }
401 
402     /**
403      * Performs verification of the file with the given file path using specified configuration
404      * and the array expected messages. Also performs verification of the config specified in
405      * input file
406      *
407      * @param filePath file path to verify.
408      * @param expected an array of expected messages.
409      * @throws Exception if exception occurs during verification process.
410      */
411     protected void verifyWithInlineConfigParserTwice(String filePath, String... expected)
412             throws Exception {
413         final TestInputConfiguration testInputConfiguration =
414                 InlineConfigParser.parse(filePath);
415         final DefaultConfiguration parsedConfig =
416                 testInputConfiguration.createConfiguration();
417         verifyViolations(parsedConfig, filePath, testInputConfiguration.violations());
418         verify(parsedConfig, filePath, expected);
419     }
420 
421     /**
422      * Verifies logger output using the inline configuration parser.
423      * Expects an input file with configuration and violations, and a report file with expected
424      * output.
425      *
426      * @param inputFile path to the file with configuration and violations
427      * @param expectedReportFile path to the expected logger report file
428      * @param logger logger to test
429      * @param outputStream output stream where the logger writes its actual output
430      * @throws Exception if an exception occurs during verification
431      */
432     protected void verifyWithInlineConfigParserAndLogger(String inputFile,
433                                                          String expectedReportFile,
434                                                          AuditListener logger,
435                                                          ByteArrayOutputStream outputStream)
436             throws Exception {
437         final TestInputConfiguration testInputConfiguration =
438                 InlineConfigParser.parse(inputFile);
439         final DefaultConfiguration parsedConfig =
440                 testInputConfiguration.createConfiguration();
441         final List<File> filesToCheck = Collections.singletonList(new File(inputFile));
442         final String basePath = Path.of("").toAbsolutePath().toString();
443 
444         final Checker checker = createChecker(parsedConfig);
445         checker.setBasedir(basePath);
446         checker.addListener(logger);
447         checker.process(filesToCheck);
448 
449         verifyContent(expectedReportFile, outputStream);
450     }
451 
452     /**
453      * Verifies logger output using the inline configuration parser for default logger.
454      * Expects an input file with configuration and violations, and expected output file.
455      * Uses full Checker configuration.
456      *
457      * @param inputFile path to the file with configuration and violations
458      * @param expectedOutputFile path to the expected info stream output file
459      * @param logger logger to test
460      * @param outputStream where the logger writes its actual info stream output
461      * @throws Exception if an exception occurs during verification
462      */
463     protected final void verifyWithInlineConfigParserAndDefaultLogger(String inputFile,
464                                                               String expectedOutputFile,
465                                                               AuditListener logger,
466                                                               ByteArrayOutputStream outputStream)
467             throws Exception {
468         final TestInputConfiguration testInputConfiguration =
469                 InlineConfigParser.parseWithXmlHeader(inputFile);
470         final Configuration parsedConfig =
471                 testInputConfiguration.xmlConfiguration();
472         final List<File> filesToCheck = Collections.singletonList(new File(inputFile));
473         final String basePath = Path.of("").toAbsolutePath().toString();
474 
475         final Checker checker = createChecker(parsedConfig);
476         checker.setBasedir(basePath);
477         checker.addListener(logger);
478         checker.process(filesToCheck);
479 
480         verifyCleanedMessageContent(expectedOutputFile, outputStream, basePath);
481     }
482 
483     /**
484      * Verifies logger output using the inline configuration parser for default logger.
485      * Expects an input file with configuration and violations, and separate expected output files
486      * for info and error streams.
487      * Uses full Checker configuration.
488      *
489      * @param inputFile path to the file with configuration and violations
490      * @param expectedInfoFile path to the expected info stream output file
491      * @param expectedErrorFile path to the expected error stream output file
492      * @param logger logger to test
493      * @param infoStream where the logger writes its actual info stream output
494      * @param errorStream where the logger writes its actual error stream output
495      * @throws Exception if an exception occurs during verification
496      * @noinspection MethodWithTooManyParameters
497      * @noinspectionreason MethodWithTooManyParameters - Method requires a lot of parameters to
498      *                     verify the default logger output.
499      */
500     protected final void verifyWithInlineConfigParserAndDefaultLogger(String inputFile,
501                                                          String expectedInfoFile,
502                                                          String expectedErrorFile,
503                                                          AuditListener logger,
504                                                          ByteArrayOutputStream infoStream,
505                                                          ByteArrayOutputStream errorStream)
506             throws Exception {
507         final TestInputConfiguration testInputConfiguration =
508                 InlineConfigParser.parseWithXmlHeader(inputFile);
509         final Configuration parsedConfig =
510                 testInputConfiguration.xmlConfiguration();
511         final List<File> filesToCheck = Collections.singletonList(new File(inputFile));
512         final String basePath = Path.of("").toAbsolutePath().toString();
513 
514         final Checker checker = createChecker(parsedConfig);
515         checker.setBasedir(basePath);
516         checker.addListener(logger);
517         checker.process(filesToCheck);
518 
519         verifyContent(expectedInfoFile, infoStream);
520         verifyCleanedMessageContent(expectedErrorFile, errorStream, basePath);
521     }
522 
523     /**
524      * Performs verification of the file with the given file name. Uses specified configuration.
525      * Expected messages are represented by the array of strings.
526      * This implementation uses overloaded
527      * {@link AbstractModuleTestSupport#verify(Checker, File[], String, String...)} method inside.
528      *
529      * @param config configuration.
530      * @param fileName file name to verify.
531      * @param expected an array of expected messages.
532      * @throws Exception if exception occurs during verification process.
533      */
534     protected final void verify(Configuration config, String fileName, String... expected)
535             throws Exception {
536         verify(createChecker(config), fileName, fileName, expected);
537     }
538 
539     /**
540      * Performs verification of the file with the given file name.
541      * Uses provided {@link Checker} instance.
542      * Expected messages are represented by the array of strings.
543      * This implementation uses overloaded
544      * {@link AbstractModuleTestSupport#verify(Checker, String, String, String...)} method inside.
545      *
546      * @param checker {@code Checker} instance.
547      * @param fileName file name to verify.
548      * @param expected an array of expected messages.
549      * @throws Exception if exception occurs during verification process.
550      */
551     protected void verify(Checker checker, String fileName, String... expected)
552             throws Exception {
553         verify(checker, fileName, fileName, expected);
554     }
555 
556     /**
557      * Performs verification of the given files.
558      *
559      * @param checker {@link Checker} instance
560      * @param processedFiles files to process.
561      * @param expectedViolations a map of expected violations per files.
562      * @throws Exception if exception occurs during verification process.
563      */
564     protected final void verify(Checker checker,
565                           File[] processedFiles,
566                           Map<String, List<String>> expectedViolations)
567             throws Exception {
568         stream.flush();
569         stream.reset();
570         final List<File> theFiles = new ArrayList<>();
571         Collections.addAll(theFiles, processedFiles);
572         checker.process(theFiles);
573 
574         // process each of the lines
575         final Map<String, List<String>> actualViolations = getActualViolations();
576         final Map<String, List<String>> realExpectedViolations =
577                 Maps.filterValues(expectedViolations, input -> !input.isEmpty());
578 
579         assertWithMessage("Files with expected violations and actual violations differ.")
580             .that(actualViolations.keySet())
581             .isEqualTo(realExpectedViolations.keySet());
582 
583         realExpectedViolations.forEach((fileName, violationList) -> {
584             assertWithMessage("Violations for %s differ.", fileName)
585                 .that(actualViolations.get(fileName))
586                 .containsExactlyElementsIn(violationList);
587         });
588 
589         checker.destroy();
590     }
591 
592     /**
593      * Performs verification of the file with the given file name.
594      * Uses provided {@link Checker} instance.
595      * Expected messages are represented by the array of strings.
596      * This implementation uses overloaded
597      * {@link AbstractModuleTestSupport#verify(Checker, File[], String, String...)} method inside.
598      *
599      * @param checker {@code Checker} instance.
600      * @param processedFilename file name to verify.
601      * @param messageFileName message file name.
602      * @param expected an array of expected messages.
603      * @throws Exception if exception occurs during verification process.
604      */
605     protected final void verify(Checker checker,
606                           String processedFilename,
607                           String messageFileName,
608                           String... expected)
609             throws Exception {
610         verify(checker,
611                 new File[] {new File(processedFilename)},
612                 messageFileName, expected);
613     }
614 
615     /**
616      *  Performs verification of the given files against the array of
617      *  expected messages using the provided {@link Checker} instance.
618      *
619      *  @param checker {@code Checker} instance.
620      *  @param processedFiles list of files to verify.
621      *  @param messageFileName message file name.
622      *  @param expected an array of expected messages.
623      *  @throws Exception if exception occurs during verification process.
624      */
625     protected void verify(Checker checker,
626                           File[] processedFiles,
627                           String messageFileName,
628                           String... expected)
629             throws Exception {
630         final Map<String, List<String>> expectedViolations = new HashMap<>();
631         expectedViolations.put(messageFileName, Arrays.asList(expected));
632         verify(checker, processedFiles, expectedViolations);
633     }
634 
635     /**
636      * Runs 'verifyWithInlineConfigParser' with limited stack size and time duration.
637      *
638      * @param fileName file name to verify.
639      * @param expected an array of expected messages.
640      * @throws Exception if exception occurs during verification process.
641      */
642     protected final void verifyWithLimitedResources(String fileName, String... expected)
643             throws Exception {
644         TestUtil.getResultWithLimitedResources(() -> {
645             verifyWithInlineConfigParser(fileName, expected);
646             return null;
647         });
648     }
649 
650     /**
651      * Runs 'verifyWithInlineConfigParser' with limited stack size suitable for XPath-based
652      * checks, allowing Saxon's XPath engine to initialize while still detecting stack
653      * overflows caused by deep AST traversal.
654      *
655      * @param fileName file name to verify.
656      * @param expected an array of expected messages.
657      * @throws Exception if exception occurs during verification process.
658      */
659     protected final void verifyWithLimitedXpathResources(String fileName, String... expected)
660             throws Exception {
661         TestUtil.runWithLimitedXpathResources(() -> {
662             verifyWithInlineConfigParser(fileName, expected);
663             return null;
664         });
665     }
666 
667     /**
668      * Executes given config on a list of files only. Does not verify violations.
669      *
670      * @param config check configuration
671      * @param filenames names of files to process
672      * @throws Exception if there is a problem during checker configuration
673      */
674     protected final void execute(Configuration config, String... filenames) throws Exception {
675         final Checker checker = createChecker(config);
676         final List<File> files = Arrays.stream(filenames)
677                 .map(File::new)
678                 .toList();
679         checker.process(files);
680         checker.destroy();
681     }
682 
683     /**
684      * Executes given config on a list of files only. Does not verify violations.
685      *
686      * @param checker check configuration
687      * @param filenames names of files to process
688      * @throws Exception if there is a problem during checker configuration
689      */
690     protected static void execute(Checker checker, String... filenames) throws Exception {
691         final List<File> files = Arrays.stream(filenames)
692                 .map(File::new)
693                 .toList();
694         checker.process(files);
695         checker.destroy();
696     }
697 
698     /**
699      * Performs verification of violation lines.
700      *
701      * @param config parsed config.
702      * @param file file path.
703      * @param testInputViolations List of TestInputViolation objects.
704      * @throws Exception if exception occurs during verification process.
705      */
706     private void verifyViolations(Configuration config,
707                                   String file,
708                                   List<TestInputViolation> testInputViolations)
709             throws Exception {
710         final List<String> actualViolations = getActualViolationsForFile(config, file);
711         final List<Integer> actualViolationLines = actualViolations.stream()
712                 .map(violation -> violation.substring(0, violation.indexOf(':')))
713                 .map(Integer::valueOf)
714                 .toList();
715         final List<Integer> expectedViolationLines = testInputViolations.stream()
716                 .map(TestInputViolation::getLineNo)
717                 .toList();
718         assertWithMessage("Violation lines for %s differ.", file)
719                 .that(actualViolationLines)
720                 .isEqualTo(expectedViolationLines);
721         for (int index = 0; index < actualViolations.size(); index++) {
722             assertWithMessage("Actual and expected violations differ.")
723                     .that(actualViolations.get(index))
724                     .matches(testInputViolations.get(index).toRegex());
725         }
726     }
727 
728     /**
729      * Performs verification of violation lines.
730      *
731      * @param file file path.
732      * @param testInputViolations List of TestInputViolation objects.
733      * @param actualViolations for a file
734      */
735     private static void verifyViolations(String file,
736                                   List<TestInputViolation> testInputViolations,
737                                   List<String> actualViolations) {
738         final List<Integer> actualViolationLines = actualViolations.stream()
739                 .map(violation -> violation.substring(0, violation.indexOf(':')))
740                 .map(Integer::valueOf)
741                 .toList();
742         final List<Integer> expectedViolationLines = testInputViolations.stream()
743                 .map(TestInputViolation::getLineNo)
744                 .toList();
745         assertWithMessage("Violation lines for %s differ.", file)
746                 .that(actualViolationLines)
747                 .isEqualTo(expectedViolationLines);
748         for (int index = 0; index < actualViolations.size(); index++) {
749             assertWithMessage("Actual and expected violations differ.")
750                     .that(actualViolations.get(index))
751                     .matches(testInputViolations.get(index).toRegex());
752         }
753     }
754 
755     /**
756      * Verifies that the logger's actual output matches the expected report file.
757      *
758      * @param expectedOutputFile path to the expected logger report file
759      * @param outputStream output stream containing the actual logger output
760      * @throws IOException if an exception occurs while reading the file
761      */
762     private static void verifyContent(
763             String expectedOutputFile,
764             ByteArrayOutputStream outputStream)
765                     throws IOException {
766         final String expectedContent = readFile(expectedOutputFile);
767         final String actualContent =
768                 toLfLineEnding(outputStream.toString(StandardCharsets.UTF_8));
769         assertWithMessage("Content should match")
770                 .that(actualContent)
771                 .isEqualTo(expectedContent);
772     }
773 
774     /**
775      * Verifies that the logger output matches the expected report file content,
776      * keeping only severity-tagged lines (e.g. [ERROR], [WARN], [INFO]) or lines containing
777      * "Starting audit..." or "Audit done".
778      *
779      * <p>
780      * This method strips:
781      * <ul>
782      *   <li>any stack trace lines from exception outputs (i.e. lines not starting with a severity
783      *   tag),</li>
784      *   <li>any absolute {@code basePath} prefixes in the message content.</li>
785      * </ul>
786      * The result is compared with expected output that includes only severity-tagged lines.
787      *
788      * @param expectedOutputFile path to a file that contains the expected first line
789      * @param outputStream output stream containing the actual logger output
790      * @param basePath absolute path prefix to strip before comparison
791      * @throws IOException if an exception occurs while reading the file
792      */
793     private static void verifyCleanedMessageContent(
794             String expectedOutputFile,
795             ByteArrayOutputStream outputStream,
796             String basePath)
797                     throws IOException {
798         final String expectedContent = readFile(expectedOutputFile);
799         final String rawActualContent =
800                 toLfLineEnding(outputStream.toString(StandardCharsets.UTF_8));
801 
802         final String cleanedActualContent = rawActualContent.lines()
803                 .filter(line -> {
804                     return line.startsWith("[")
805                             || line.contains("Starting audit...")
806                             || line.contains("Audit done.");
807                 })
808                 .map(line -> line.replace(basePath, ""))
809                 .map(line -> line.replace('\\', '/'))
810                 .collect(Collectors.joining("\n", "", "\n"));
811 
812         assertWithMessage("Content should match")
813                 .that(cleanedActualContent)
814                 .isEqualTo(expectedContent);
815     }
816 
817     /**
818      * Tests the file with the check config.
819      *
820      * @param config check configuration.
821      * @param file input file path.
822      * @return list of actual violations.
823      * @throws Exception if exception occurs during verification process.
824      */
825     private List<String> getActualViolationsForFile(Configuration config,
826                                                     String file)
827             throws Exception {
828         stream.flush();
829         stream.reset();
830         final List<File> files = Collections.singletonList(new File(file));
831         final Checker checker = createChecker(config);
832         checker.process(files);
833         final Map<String, List<String>> actualViolations =
834                 getActualViolations();
835         checker.destroy();
836         return actualViolations.getOrDefault(file, new ArrayList<>());
837     }
838 
839     /**
840      * Returns the actual violations for each file that has been checked against {@link Checker}.
841      * Each file is mapped to their corresponding violation messages. Reads input stream for these
842      * messages using instance of {@link InputStreamReader}.
843      *
844      * @return a {@link Map} object containing file names and the corresponding violation messages.
845      * @throws IOException exception can occur when reading input stream.
846      */
847     private Map<String, List<String>> getActualViolations() throws IOException {
848         // process each of the lines
849         try (ByteArrayInputStream inputStream =
850                 new ByteArrayInputStream(stream.toByteArray());
851             LineNumberReader lnr = new LineNumberReader(
852                 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
853             final Map<String, List<String>> actualViolations = new HashMap<>();
854             for (String line = lnr.readLine(); line != null;
855                  line = lnr.readLine()) {
856                 if ("Audit done.".equals(line) || line.contains("at com")) {
857                     break;
858                 }
859                 // have at least 2 characters before the splitting colon,
860                 // to not split after the drive letter on Windows
861                 final String[] actualViolation = line.split("(?<=.{2}):", 2);
862                 final String actualViolationFileName = actualViolation[0];
863                 final String actualViolationMessage = actualViolation[1];
864 
865                 actualViolations
866                         .computeIfAbsent(actualViolationFileName, key -> new ArrayList<>())
867                         .add(actualViolationMessage);
868             }
869 
870             return actualViolations;
871         }
872     }
873 
874     /**
875      * Gets the check message 'as is' from appropriate 'messages.properties'
876      * file.
877      *
878      * @param messageKey the key of message in 'messages.properties' file.
879      * @param arguments  the arguments of message in 'messages.properties' file.
880      * @return The message of the check with the arguments applied.
881      */
882     protected final String getCheckMessage(String messageKey, Object... arguments) {
883         return internalGetCheckMessage(getMessageBundle(), messageKey, arguments);
884     }
885 
886     /**
887      * Gets the check message 'as is' from appropriate 'messages.properties'
888      * file.
889      *
890      * @param clazz the related check class.
891      * @param messageKey the key of message in 'messages.properties' file.
892      * @param arguments the arguments of message in 'messages.properties' file.
893      * @return The message of the check with the arguments applied.
894      */
895     protected static String getCheckMessage(
896             Class<?> clazz, String messageKey, Object... arguments) {
897         return internalGetCheckMessage(getMessageBundle(clazz.getName()), messageKey, arguments);
898     }
899 
900     /**
901      * Gets the check message 'as is' from appropriate 'messages.properties'
902      * file.
903      *
904      * @param messageBundle the bundle name.
905      * @param messageKey the key of message in 'messages.properties' file.
906      * @param arguments the arguments of message in 'messages.properties' file.
907      * @return The message of the check with the arguments applied.
908      */
909     private static String internalGetCheckMessage(
910             String messageBundle, String messageKey, Object... arguments) {
911         final ResourceBundle resourceBundle = ResourceBundle.getBundle(
912                 messageBundle,
913                 Locale.ROOT,
914                 Thread.currentThread().getContextClassLoader());
915         final String pattern = resourceBundle.getString(messageKey);
916         final MessageFormat formatter = new MessageFormat(pattern, Locale.ROOT);
917         return formatter.format(arguments);
918     }
919 
920     /**
921      * Returns message bundle for a class specified by its class name.
922      *
923      * @return a string of message bundles for the class using class name.
924      */
925     private String getMessageBundle() {
926         final String className = getClass().getName();
927         return getMessageBundle(className);
928     }
929 
930     /**
931      * Returns message bundles for a class by providing class name.
932      *
933      * @param className name of the class.
934      * @return message bundles containing package name.
935      */
936     private static String getMessageBundle(String className) {
937         final String messageBundle;
938         final String messages = "messages";
939         final int endIndex = className.lastIndexOf('.');
940         final Map<String, String> messageBundleMappings = new HashMap<>();
941         messageBundleMappings.put("SeverityMatchFilterExamplesTest",
942                 "com.puppycrawl.tools.checkstyle.checks.naming.messages");
943 
944         if (endIndex < 0) {
945             messageBundle = messages;
946         }
947         else {
948             final String packageName = className.substring(0, endIndex);
949             if ("com.puppycrawl.tools.checkstyle.filters".equals(packageName)) {
950                 messageBundle = messageBundleMappings.get(className.substring(endIndex + 1));
951             }
952             else {
953                 messageBundle = packageName + "." + messages;
954             }
955         }
956         return messageBundle;
957     }
958 
959     /**
960      * Remove suppressed violation messages from actual violation messages.
961      *
962      * @param actualViolations actual violation messages
963      * @param suppressedViolations suppressed violation messages
964      * @return an array of actual violation messages minus suppressed violation messages
965      */
966     protected static String[] removeSuppressed(String[] actualViolations,
967                                                String... suppressedViolations) {
968         final List<String> actualViolationsList =
969             Arrays.stream(actualViolations).collect(Collectors.toCollection(ArrayList::new));
970         actualViolationsList.removeAll(Arrays.asList(suppressedViolations));
971         return actualViolationsList.toArray(CommonUtil.EMPTY_STRING_ARRAY);
972     }
973 
974 }